1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.packageinstaller.permission.ui.handheld;
17 
18 import static com.android.packageinstaller.Constants.EXTRA_SESSION_ID;
19 import static com.android.packageinstaller.Constants.INVALID_SESSION_ID;
20 import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED;
21 import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
22 import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
23 import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED;
24 import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED;
25 
26 import android.app.ActionBar;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.graphics.drawable.Drawable;
30 import android.os.Bundle;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 import android.view.Menu;
34 import android.view.MenuInflater;
35 import android.view.MenuItem;
36 import android.view.View;
37 
38 import androidx.annotation.NonNull;
39 import androidx.fragment.app.Fragment;
40 import androidx.preference.Preference;
41 import androidx.preference.PreferenceCategory;
42 import androidx.preference.PreferenceScreen;
43 import androidx.preference.SwitchPreferenceCompat;
44 
45 import com.android.packageinstaller.DeviceUtils;
46 import com.android.packageinstaller.PermissionControllerStatsLog;
47 import com.android.packageinstaller.permission.model.AppPermissionGroup;
48 import com.android.packageinstaller.permission.model.PermissionApps;
49 import com.android.packageinstaller.permission.model.PermissionApps.Callback;
50 import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
51 import com.android.packageinstaller.permission.utils.Utils;
52 import com.android.permissioncontroller.R;
53 import com.android.settingslib.HelpUtils;
54 
55 import java.text.Collator;
56 import java.util.ArrayList;
57 import java.util.Map;
58 import java.util.Random;
59 
60 /**
61  * Show and manage apps which request a single permission group.
62  *
63  * <p>Shows a list of apps which request at least on permission of this group.
64  */
65 public final class PermissionAppsFragment extends SettingsWithLargeHeader implements Callback {
66 
67     private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem";
68     private static final String CREATION_LOGGED_SYSTEM_PREFS = "_creationLogged";
69     private static final String KEY_FOOTER = "_footer";
70     private static final String LOG_TAG = "PermissionAppsFragment";
71 
72     private static final String SHOW_SYSTEM_KEY = PermissionAppsFragment.class.getName()
73             + KEY_SHOW_SYSTEM_PREFS;
74 
75     private static final String CREATION_LOGGED = PermissionAppsFragment.class.getName()
76             + CREATION_LOGGED_SYSTEM_PREFS;
77 
78     /**
79      * @return A new fragment
80      */
newInstance(String permissionName, long sessionId)81     public static PermissionAppsFragment newInstance(String permissionName, long sessionId) {
82         return setPermissionNameAndSessionId(
83                 new PermissionAppsFragment(), permissionName, sessionId);
84     }
85 
setPermissionNameAndSessionId( T fragment, String permissionName, long sessionId)86     private static <T extends Fragment> T setPermissionNameAndSessionId(
87             T fragment, String permissionName, long sessionId) {
88         Bundle arguments = new Bundle();
89         arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName);
90         arguments.putLong(EXTRA_SESSION_ID, sessionId);
91         fragment.setArguments(arguments);
92         return fragment;
93     }
94 
95     private PermissionApps mPermissionApps;
96 
97     private PreferenceScreen mExtraScreen;
98 
99     private boolean mShowSystem;
100     private boolean mCreationLogged;
101     private boolean mHasSystemApps;
102     private MenuItem mShowSystemMenu;
103     private MenuItem mHideSystemMenu;
104 
105     private Callback mOnPermissionsLoadedListener;
106 
107     private Collator mCollator;
108 
109     @Override
onCreate(Bundle savedInstanceState)110     public void onCreate(Bundle savedInstanceState) {
111         super.onCreate(savedInstanceState);
112 
113         if (savedInstanceState != null) {
114             mShowSystem = savedInstanceState.getBoolean(SHOW_SYSTEM_KEY);
115             mCreationLogged = savedInstanceState.getBoolean(CREATION_LOGGED);
116         }
117 
118         setLoading(true /* loading */, false /* animate */);
119         setHasOptionsMenu(true);
120         final ActionBar ab = getActivity().getActionBar();
121         if (ab != null) {
122             ab.setDisplayHomeAsUpEnabled(true);
123         }
124 
125         String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
126         mPermissionApps = new PermissionApps(getActivity(), groupName, this);
127         mPermissionApps.refresh(true);
128 
129         mCollator = Collator.getInstance(
130                 getContext().getResources().getConfiguration().getLocales().get(0));
131 
132         addPreferencesFromResource(R.xml.allowed_denied);
133     }
134 
135     @Override
onSaveInstanceState(Bundle outState)136     public void onSaveInstanceState(Bundle outState) {
137         super.onSaveInstanceState(outState);
138 
139         outState.putBoolean(SHOW_SYSTEM_KEY, mShowSystem);
140         outState.putBoolean(CREATION_LOGGED, mCreationLogged);
141     }
142 
143     @Override
onResume()144     public void onResume() {
145         super.onResume();
146         mPermissionApps.refresh(true);
147     }
148 
149     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)150     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
151         super.onCreateOptionsMenu(menu, inflater);
152 
153         if (mHasSystemApps) {
154             mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
155                     R.string.menu_show_system);
156             mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
157                     R.string.menu_hide_system);
158             updateMenu();
159         }
160 
161         HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
162                 getClass().getName());
163     }
164 
165     @Override
onOptionsItemSelected(MenuItem item)166     public boolean onOptionsItemSelected(MenuItem item) {
167         switch (item.getItemId()) {
168             case android.R.id.home:
169                 getActivity().finish();
170                 return true;
171             case MENU_SHOW_SYSTEM:
172             case MENU_HIDE_SYSTEM:
173                 mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM;
174                 if (mPermissionApps.getApps() != null) {
175                     onPermissionsLoaded(mPermissionApps);
176                 }
177                 updateMenu();
178                 break;
179         }
180         return super.onOptionsItemSelected(item);
181     }
182 
updateMenu()183     private void updateMenu() {
184         mShowSystemMenu.setVisible(!mShowSystem);
185         mHideSystemMenu.setVisible(mShowSystem);
186     }
187 
188     @Override
onViewCreated(View view, Bundle savedInstanceState)189     public void onViewCreated(View view, Bundle savedInstanceState) {
190         super.onViewCreated(view, savedInstanceState);
191         bindUi(this, mPermissionApps,
192                 getArguments().getString(Intent.EXTRA_PERMISSION_NAME));
193     }
194 
bindUi(SettingsWithLargeHeader fragment, PermissionApps permissionApps, @NonNull String groupName)195     private static void bindUi(SettingsWithLargeHeader fragment, PermissionApps permissionApps,
196             @NonNull String groupName) {
197         final Drawable icon = permissionApps.getIcon();
198         final CharSequence label = permissionApps.getFullLabel();
199 
200         fragment.setHeader(icon, label, null, null, true);
201         fragment.setSummary(Utils.getPermissionGroupDescriptionString(fragment.getActivity(),
202                 groupName, permissionApps.getDescription()), null);
203 
204         final ActionBar ab = fragment.getActivity().getActionBar();
205         if (ab != null) {
206             ab.setTitle(label);
207         }
208     }
209 
setOnPermissionsLoadedListener(Callback callback)210     private void setOnPermissionsLoadedListener(Callback callback) {
211         mOnPermissionsLoadedListener = callback;
212     }
213 
214     @Override
onPermissionsLoaded(PermissionApps permissionApps)215     public void onPermissionsLoaded(PermissionApps permissionApps) {
216         Context context = getPreferenceManager().getContext();
217 
218         if (context == null || getActivity() == null) {
219             return;
220         }
221 
222         boolean isTelevision = DeviceUtils.isTelevision(context);
223 
224         PreferenceCategory allowed = (PreferenceCategory) findPreference("allowed");
225         PreferenceCategory allowedForeground = findPreference("allowed_foreground");
226         PreferenceCategory denied = (PreferenceCategory) findPreference("denied");
227 
228         allowed.setOrderingAsAdded(true);
229         allowedForeground.setOrderingAsAdded(true);
230         denied.setOrderingAsAdded(true);
231 
232         Map<String, Preference> existingPrefs = new ArrayMap<>();
233         int numPreferences = allowed.getPreferenceCount();
234         for (int i = 0; i < numPreferences; i++) {
235             Preference preference = allowed.getPreference(i);
236             existingPrefs.put(preference.getKey(), preference);
237         }
238         allowed.removeAll();
239         numPreferences = allowedForeground.getPreferenceCount();
240         for (int i = 0; i < numPreferences; i++) {
241             Preference preference = allowedForeground.getPreference(i);
242             existingPrefs.put(preference.getKey(), preference);
243         }
244         allowedForeground.removeAll();
245         numPreferences = denied.getPreferenceCount();
246         for (int i = 0; i < numPreferences; i++) {
247             Preference preference = denied.getPreference(i);
248             existingPrefs.put(preference.getKey(), preference);
249         }
250         denied.removeAll();
251         if (mExtraScreen != null) {
252             for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
253                 Preference preference = mExtraScreen.getPreference(i);
254                 existingPrefs.put(preference.getKey(), preference);
255             }
256             mExtraScreen.removeAll();
257         }
258 
259         mHasSystemApps = false;
260         boolean menuOptionsInvalided = false;
261         boolean hasPermissionWithBackgroundMode = false;
262 
263         ArrayList<PermissionApp> sortedApps = new ArrayList<>(permissionApps.getApps());
264         sortedApps.sort((x, y) -> {
265             int result = mCollator.compare(x.getLabel(), y.getLabel());
266             if (result == 0) {
267                 result = x.getUid() - y.getUid();
268             }
269             return result;
270         });
271 
272         long viewIdForLogging = new Random().nextLong();
273         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
274 
275         for (int i = 0; i < sortedApps.size(); i++) {
276             PermissionApp app = sortedApps.get(i);
277             AppPermissionGroup group = app.getPermissionGroup();
278 
279             hasPermissionWithBackgroundMode =
280                     hasPermissionWithBackgroundMode || group.hasPermissionWithBackgroundMode();
281 
282             if (!Utils.shouldShowPermission(getContext(), group)) {
283                 continue;
284             }
285 
286             if (!app.getAppInfo().enabled) {
287                 continue;
288             }
289 
290             String key = app.getKey();
291             Preference existingPref = existingPrefs.get(key);
292             if (existingPref != null) {
293                 // Without this, existing preferences remember their old order.
294                 existingPref.setOrder(Preference.DEFAULT_ORDER);
295             }
296 
297             boolean isSystemApp = !Utils.isGroupOrBgGroupUserSensitive(group);
298 
299             if (isSystemApp && !menuOptionsInvalided) {
300                 mHasSystemApps = true;
301                 getActivity().invalidateOptionsMenu();
302                 menuOptionsInvalided = true;
303             }
304 
305             if (isSystemApp && !isTelevision && !mShowSystem) {
306                 continue;
307             }
308 
309             PreferenceCategory category = null;
310             if (group.areRuntimePermissionsGranted()) {
311                 if (!group.hasPermissionWithBackgroundMode()
312                         || (group.getBackgroundPermissions() != null
313                         && group.getBackgroundPermissions().areRuntimePermissionsGranted())) {
314                     category = allowed;
315                 } else {
316                     category = allowedForeground;
317                 }
318             } else {
319                 category = denied;
320             }
321 
322             if (existingPref != null) {
323                 category.addPreference(existingPref);
324                 continue;
325             }
326 
327             PermissionControlPreference pref = new PermissionControlPreference(context, group,
328                     PermissionAppsFragment.class.getName(), sessionId);
329             pref.setKey(key);
330             pref.setIcon(app.getIcon());
331             pref.setTitle(Utils.getFullAppLabel(app.getAppInfo(), context));
332             pref.setEllipsizeEnd();
333             pref.useSmallerIcon();
334 
335             if (isSystemApp && isTelevision) {
336                 if (mExtraScreen == null) {
337                     mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
338                 }
339                 mExtraScreen.addPreference(pref);
340             } else {
341                 category.addPreference(pref);
342                 if (!mCreationLogged) {
343                     logPermissionAppsFragmentCreated(app, viewIdForLogging, category == allowed,
344                             category == allowedForeground, category == denied);
345                 }
346             }
347         }
348         mCreationLogged = true;
349 
350         if (mExtraScreen != null) {
351             Preference pref = allowed.findPreference(KEY_SHOW_SYSTEM_PREFS);
352 
353             int grantedCount = 0;
354             for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
355                 if (((SwitchPreferenceCompat) mExtraScreen.getPreference(i)).isChecked()) {
356                     grantedCount++;
357                 }
358             }
359 
360             if (pref == null) {
361                 pref = new Preference(context);
362                 pref.setKey(KEY_SHOW_SYSTEM_PREFS);
363                 pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc,
364                         android.R.attr.colorControlNormal));
365                 pref.setTitle(R.string.preference_show_system_apps);
366                 pref.setOnPreferenceClickListener(preference -> {
367                     SystemAppsFragment frag = new SystemAppsFragment();
368                     setPermissionNameAndSessionId(frag,
369                             getArguments().getString(Intent.EXTRA_PERMISSION_NAME), sessionId);
370                     frag.setTargetFragment(PermissionAppsFragment.this, 0);
371                     getFragmentManager().beginTransaction()
372                         .replace(android.R.id.content, frag)
373                         .addToBackStack("SystemApps")
374                         .commit();
375                     return true;
376                 });
377                 PreferenceCategory category = grantedCount > 0 ? allowed : denied;
378                 category.addPreference(pref);
379             }
380 
381             pref.setSummary(getString(R.string.app_permissions_group_summary,
382                     grantedCount, mExtraScreen.getPreferenceCount()));
383         }
384 
385         if (hasPermissionWithBackgroundMode) {
386             allowed.setTitle(R.string.allowed_always_header);
387         }
388 
389         if (allowed.getPreferenceCount() == 0) {
390             Preference empty = new Preference(context);
391             empty.setTitle(getString(R.string.no_apps_allowed));
392             empty.setSelectable(false);
393             allowed.addPreference(empty);
394         }
395         if (allowedForeground.getPreferenceCount() == 0) {
396             findPreference("allowed_foreground").setVisible(false);
397         } else {
398             findPreference("allowed_foreground").setVisible(true);
399         }
400         if (denied.getPreferenceCount() == 0) {
401             Preference empty = new Preference(context);
402             empty.setTitle(getString(R.string.no_apps_denied));
403             empty.setSelectable(false);
404             denied.addPreference(empty);
405         }
406 
407         setLoading(false /* loading */, true /* animate */);
408 
409         if (mOnPermissionsLoadedListener != null) {
410             mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps);
411         }
412     }
413 
logPermissionAppsFragmentCreated(PermissionApp permissionApp, long viewId, boolean isAllowed, boolean isAllowedForeground, boolean isDenied)414     private void logPermissionAppsFragmentCreated(PermissionApp permissionApp, long viewId,
415             boolean isAllowed, boolean isAllowedForeground, boolean isDenied) {
416         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, 0);
417 
418         int category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED;
419         if (isAllowed) {
420             category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
421         } else if (isAllowedForeground) {
422             category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
423         } else if (isDenied) {
424             category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED;
425         }
426 
427         PermissionControllerStatsLog.write(PERMISSION_APPS_FRAGMENT_VIEWED, sessionId, viewId,
428                 mPermissionApps.getGroupName(), permissionApp.getUid(),
429                 permissionApp.getPackageName(), category);
430         Log.v(LOG_TAG, "PermissionAppsFragment created with sessionId=" + sessionId
431                 + " permissionGroupName=" + mPermissionApps.getGroupName() + " appUid="
432                 + permissionApp.getUid() + " packageName=" + permissionApp.getPackageName()
433                 + " category=" + category);
434     };
435 
436     public static class SystemAppsFragment extends SettingsWithLargeHeader implements Callback {
437         PermissionAppsFragment mOuterFragment;
438 
439         @Override
onCreate(Bundle savedInstanceState)440         public void onCreate(Bundle savedInstanceState) {
441             mOuterFragment = (PermissionAppsFragment) getTargetFragment();
442             setLoading(true /* loading */, false /* animate */);
443             super.onCreate(savedInstanceState);
444             setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, null, null, true);
445             if (mOuterFragment.mExtraScreen != null) {
446                 setPreferenceScreen();
447             } else {
448                 mOuterFragment.setOnPermissionsLoadedListener(this);
449             }
450         }
451 
452         @Override
onViewCreated(View view, Bundle savedInstanceState)453         public void onViewCreated(View view, Bundle savedInstanceState) {
454             super.onViewCreated(view, savedInstanceState);
455             String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
456             PermissionApps permissionApps = new PermissionApps(getActivity(),
457                     groupName, (Callback) null);
458             bindUi(this, permissionApps, groupName);
459         }
460 
461         @Override
onPermissionsLoaded(PermissionApps permissionApps)462         public void onPermissionsLoaded(PermissionApps permissionApps) {
463             setPreferenceScreen();
464             mOuterFragment.setOnPermissionsLoadedListener(null);
465         }
466 
setPreferenceScreen()467         private void setPreferenceScreen() {
468             setPreferenceScreen(mOuterFragment.mExtraScreen);
469             setLoading(false /* loading */, true /* animate */);
470         }
471     }
472 }
473