• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.permissioncontroller.permission.ui.handheld;
17 
18 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
19 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
20 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED;
21 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
22 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
23 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED;
24 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED;
25 import static com.android.permissioncontroller.permission.debug.UtilsKt.shouldShowPermissionsDashboard;
26 import static com.android.permissioncontroller.permission.ui.Category.ALLOWED;
27 import static com.android.permissioncontroller.permission.ui.Category.ALLOWED_FOREGROUND;
28 import static com.android.permissioncontroller.permission.ui.Category.ASK;
29 import static com.android.permissioncontroller.permission.ui.Category.DENIED;
30 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
31 
32 import android.Manifest;
33 import android.app.ActionBar;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.graphics.drawable.Drawable;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.UserHandle;
41 import android.util.ArrayMap;
42 import android.util.Log;
43 import android.view.Menu;
44 import android.view.MenuInflater;
45 import android.view.MenuItem;
46 import android.view.View;
47 
48 import androidx.annotation.NonNull;
49 import androidx.lifecycle.ViewModelProvider;
50 import androidx.preference.Preference;
51 import androidx.preference.PreferenceCategory;
52 
53 import com.android.permissioncontroller.PermissionControllerStatsLog;
54 import com.android.permissioncontroller.R;
55 import com.android.permissioncontroller.permission.ui.Category;
56 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
57 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel;
58 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModelFactory;
59 import com.android.permissioncontroller.permission.utils.KotlinUtils;
60 import com.android.permissioncontroller.permission.utils.Utils;
61 import com.android.settingslib.HelpUtils;
62 import com.android.settingslib.utils.applications.AppUtils;
63 
64 import java.text.Collator;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Random;
68 
69 import kotlin.Pair;
70 
71 /**
72  * Show and manage apps which request a single permission group.
73  *
74  * <p>Shows a list of apps which request at least on permission of this group.
75  */
76 public final class PermissionAppsFragment extends SettingsWithLargeHeader {
77 
78     private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem";
79     private static final String CREATION_LOGGED_SYSTEM_PREFS = "_creationLogged";
80     private static final String KEY_FOOTER = "_footer";
81     private static final String KEY_EMPTY = "_empty";
82     private static final String LOG_TAG = "PermissionAppsFragment";
83     private static final String STORAGE_ALLOWED_FULL = "allowed_storage_full";
84     private static final String STORAGE_ALLOWED_SCOPED = "allowed_storage_scoped";
85     private static final int SHOW_LOAD_DELAY_MS = 200;
86 
87     private static final int MENU_PERMISSION_USAGE = MENU_HIDE_SYSTEM + 1;
88 
89     /**
90      * Create a bundle with the arguments needed by this fragment
91      *
92      * @param permGroupName The name of the permission group
93      * @param sessionId     The current session ID
94      * @return A bundle with all of the args placed
95      */
createArgs(String permGroupName, long sessionId)96     public static Bundle createArgs(String permGroupName, long sessionId) {
97         Bundle arguments = new Bundle();
98         arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName);
99         arguments.putLong(EXTRA_SESSION_ID, sessionId);
100         return arguments;
101     }
102 
103     private MenuItem mShowSystemMenu;
104     private MenuItem mHideSystemMenu;
105     private String mPermGroupName;
106     private Collator mCollator;
107     private PermissionAppsViewModel mViewModel;
108 
109     @Override
onCreate(Bundle savedInstanceState)110     public void onCreate(Bundle savedInstanceState) {
111         super.onCreate(savedInstanceState);
112 
113         mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
114         if (mPermGroupName == null) {
115             mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
116         }
117 
118         mCollator = Collator.getInstance(
119                 getContext().getResources().getConfiguration().getLocales().get(0));
120 
121         PermissionAppsViewModelFactory factory =
122                 new PermissionAppsViewModelFactory(getActivity().getApplication(), mPermGroupName,
123                         this, new Bundle());
124         mViewModel = new ViewModelProvider(this, factory).get(PermissionAppsViewModel.class);
125 
126         mViewModel.getCategorizedAppsLiveData().observe(this, this::onPackagesLoaded);
127         mViewModel.getShouldShowSystemLiveData().observe(this, this::updateMenu);
128         mViewModel.getHasSystemAppsLiveData().observe(this, (Boolean hasSystem) ->
129                 getActivity().invalidateOptionsMenu());
130 
131         if (!mViewModel.arePackagesLoaded()) {
132             Handler handler = new Handler(Looper.getMainLooper());
133             handler.postDelayed(() -> {
134                 if (!mViewModel.arePackagesLoaded()) {
135                     setLoading(true /* loading */, false /* animate */);
136                 }
137             }, SHOW_LOAD_DELAY_MS);
138         }
139 
140         setHasOptionsMenu(true);
141         final ActionBar ab = getActivity().getActionBar();
142         if (ab != null) {
143             ab.setDisplayHomeAsUpEnabled(true);
144         }
145     }
146 
147     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)148     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
149         super.onCreateOptionsMenu(menu, inflater);
150 
151         if (mViewModel.getHasSystemAppsLiveData().getValue()) {
152             mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
153                     R.string.menu_show_system);
154             mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
155                     R.string.menu_hide_system);
156             updateMenu(mViewModel.getShouldShowSystemLiveData().getValue());
157         }
158 
159         if (shouldShowPermissionsDashboard()) {
160             menu.add(Menu.NONE, MENU_PERMISSION_USAGE, Menu.NONE, R.string.permission_usage_title);
161         }
162 
163         HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
164                 getClass().getName());
165     }
166 
167     @Override
onOptionsItemSelected(MenuItem item)168     public boolean onOptionsItemSelected(MenuItem item) {
169         switch (item.getItemId()) {
170             case android.R.id.home:
171                 mViewModel.updateShowSystem(false);
172                 pressBack(this);
173                 return true;
174             case MENU_SHOW_SYSTEM:
175             case MENU_HIDE_SYSTEM:
176                 mViewModel.updateShowSystem(item.getItemId() == MENU_SHOW_SYSTEM);
177                 break;
178             case MENU_PERMISSION_USAGE:
179                 getActivity().startActivity(new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
180                         .setClass(getContext(), ManagePermissionsActivity.class)
181                         .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName));
182                 return true;
183         }
184         return super.onOptionsItemSelected(item);
185     }
186 
updateMenu(Boolean showSystem)187     private void updateMenu(Boolean showSystem) {
188         if (showSystem == null) {
189             showSystem = false;
190         }
191         if (mShowSystemMenu != null && mHideSystemMenu != null) {
192             mShowSystemMenu.setVisible(!showSystem);
193             mHideSystemMenu.setVisible(showSystem);
194         }
195     }
196 
197     @Override
onViewCreated(View view, Bundle savedInstanceState)198     public void onViewCreated(View view, Bundle savedInstanceState) {
199         super.onViewCreated(view, savedInstanceState);
200         bindUi(this, mPermGroupName);
201     }
202 
bindUi(SettingsWithLargeHeader fragment, @NonNull String groupName)203     private static void bindUi(SettingsWithLargeHeader fragment, @NonNull String groupName) {
204         Context context = fragment.getContext();
205         if (context == null || fragment.getActivity() == null) {
206             return;
207         }
208         Drawable icon = KotlinUtils.INSTANCE.getPermGroupIcon(context, groupName);
209 
210         CharSequence label = KotlinUtils.INSTANCE.getPermGroupLabel(context, groupName);
211         CharSequence description = KotlinUtils.INSTANCE.getPermGroupDescription(context, groupName);
212 
213         fragment.setHeader(icon, label, null, null, true);
214         fragment.setSummary(Utils.getPermissionGroupDescriptionString(fragment.getActivity(),
215                 groupName, description), null);
216         fragment.getActivity().setTitle(label);
217     }
218 
onPackagesLoaded(Map<Category, List<Pair<String, UserHandle>>> categories)219     private void onPackagesLoaded(Map<Category, List<Pair<String, UserHandle>>> categories) {
220         boolean isStorage = mPermGroupName.equals(Manifest.permission_group.STORAGE);
221         if (getPreferenceScreen() == null) {
222             if (isStorage) {
223                 addPreferencesFromResource(R.xml.allowed_denied_storage);
224             } else {
225                 addPreferencesFromResource(R.xml.allowed_denied);
226             }
227             // Hide allowed foreground label by default, to avoid briefly showing it before updating
228             findPreference(ALLOWED_FOREGROUND.getCategoryName()).setVisible(false);
229         }
230         Context context = getPreferenceManager().getContext();
231 
232         if (context == null || getActivity() == null || categories == null) {
233             return;
234         }
235 
236         Map<String, Preference> existingPrefs = new ArrayMap<>();
237 
238         for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
239             PreferenceCategory category = (PreferenceCategory)
240                     getPreferenceScreen().getPreference(i);
241             category.setOrderingAsAdded(true);
242             int numPreferences = category.getPreferenceCount();
243             for (int j = 0; j < numPreferences; j++) {
244                 Preference preference = category.getPreference(j);
245                 existingPrefs.put(preference.getKey(), preference);
246             }
247             category.removeAll();
248         }
249 
250         long viewIdForLogging = new Random().nextLong();
251         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
252 
253         Boolean showAlways = mViewModel.getShowAllowAlwaysStringLiveData().getValue();
254         if (!isStorage) {
255             if (showAlways != null && showAlways) {
256                 findPreference(ALLOWED.getCategoryName()).setTitle(R.string.allowed_always_header);
257             } else {
258                 findPreference(ALLOWED.getCategoryName()).setTitle(R.string.allowed_header);
259             }
260         }
261 
262         for (Category grantCategory : categories.keySet()) {
263             List<Pair<String, UserHandle>> packages = categories.get(grantCategory);
264             PreferenceCategory category = findPreference(grantCategory.getCategoryName());
265 
266 
267             // If this category is empty, and this isn't the "allowed" category of the storage
268             // permission, set up the empty preference.
269             if (packages.size() == 0 && (!isStorage || !grantCategory.equals(ALLOWED))) {
270                 Preference empty = new Preference(context);
271                 empty.setSelectable(false);
272                 empty.setKey(category.getKey() + KEY_EMPTY);
273                 if (grantCategory.equals(ALLOWED)) {
274                     empty.setTitle(getString(R.string.no_apps_allowed));
275                 } else if (grantCategory.equals(ALLOWED_FOREGROUND)) {
276                     category.setVisible(false);
277                 } else if (grantCategory.equals(ASK)) {
278                     category.setVisible(false);
279                 } else {
280                     empty.setTitle(getString(R.string.no_apps_denied));
281                 }
282                 category.addPreference(empty);
283                 continue;
284             } else if (grantCategory.equals(ALLOWED_FOREGROUND)) {
285                 category.setVisible(true);
286             } else if (grantCategory.equals(ASK)) {
287                 category.setVisible(true);
288             }
289 
290             for (Pair<String, UserHandle> packageUserLabel : packages) {
291                 String packageName = packageUserLabel.getFirst();
292                 UserHandle user = packageUserLabel.getSecond();
293 
294                 String key = user + packageName;
295 
296                 if (isStorage && grantCategory.equals(ALLOWED)) {
297                     category = mViewModel.packageHasFullStorage(packageName, user)
298                             ? findPreference(STORAGE_ALLOWED_FULL)
299                             : findPreference(STORAGE_ALLOWED_SCOPED);
300                 }
301 
302                 Preference existingPref = existingPrefs.get(key);
303                 if (existingPref != null) {
304                     category.addPreference(existingPref);
305                     continue;
306                 }
307 
308                 SmartIconLoadPackagePermissionPreference pref =
309                         new SmartIconLoadPackagePermissionPreference(getActivity().getApplication(),
310                                 packageName, user, context);
311                 pref.setKey(key);
312                 pref.setTitle(KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(),
313                         packageName, user));
314                 pref.setOnPreferenceClickListener((Preference p) -> {
315                     mViewModel.navigateToAppPermission(this, packageName, user,
316                             AppPermissionFragment.createArgs(packageName, null, mPermGroupName,
317                                     user, getClass().getName(), sessionId,
318                                     grantCategory.getCategoryName()));
319                     return true;
320                 });
321                 pref.setTitleContentDescription(AppUtils.getAppContentDescription(context,
322                         packageName, user.getIdentifier()));
323 
324                 category.addPreference(pref);
325                 if (!mViewModel.getCreationLogged()) {
326                     logPermissionAppsFragmentCreated(packageName, user, viewIdForLogging,
327                             grantCategory.equals(ALLOWED), grantCategory.equals(ALLOWED_FOREGROUND),
328                             grantCategory.equals(DENIED));
329                 }
330             }
331 
332             if (isStorage && grantCategory.equals(ALLOWED)) {
333                 PreferenceCategory full = findPreference(STORAGE_ALLOWED_FULL);
334                 PreferenceCategory scoped = findPreference(STORAGE_ALLOWED_SCOPED);
335                 if (full.getPreferenceCount() == 0) {
336                     Preference empty = new Preference(context);
337                     empty.setSelectable(false);
338                     empty.setKey(STORAGE_ALLOWED_FULL + KEY_EMPTY);
339                     empty.setTitle(getString(R.string.no_apps_allowed_full));
340                     full.addPreference(empty);
341                 }
342 
343                 if (scoped.getPreferenceCount() == 0) {
344                     Preference empty = new Preference(context);
345                     empty.setSelectable(false);
346                     empty.setKey(STORAGE_ALLOWED_FULL + KEY_EMPTY);
347                     empty.setTitle(getString(R.string.no_apps_allowed_scoped));
348                     scoped.addPreference(empty);
349                 }
350                 KotlinUtils.INSTANCE.sortPreferenceGroup(full, this::comparePreference, false);
351                 KotlinUtils.INSTANCE.sortPreferenceGroup(scoped, this::comparePreference, false);
352             } else {
353                 KotlinUtils.INSTANCE.sortPreferenceGroup(category, this::comparePreference, false);
354             }
355         }
356 
357         mViewModel.setCreationLogged(true);
358 
359         setLoading(false /* loading */, true /* animate */);
360     }
361 
comparePreference(Preference lhs, Preference rhs)362     private int comparePreference(Preference lhs, Preference rhs) {
363         int result = mCollator.compare(lhs.getTitle().toString(),
364                 rhs.getTitle().toString());
365         if (result == 0) {
366             result = lhs.getKey().compareTo(rhs.getKey());
367         }
368         return result;
369     }
370 
logPermissionAppsFragmentCreated(String packageName, UserHandle user, long viewId, boolean isAllowed, boolean isAllowedForeground, boolean isDenied)371     private void logPermissionAppsFragmentCreated(String packageName, UserHandle user, long viewId,
372             boolean isAllowed, boolean isAllowedForeground, boolean isDenied) {
373         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, 0);
374 
375         int category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED;
376         if (isAllowed) {
377             category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
378         } else if (isAllowedForeground) {
379             category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
380         } else if (isDenied) {
381             category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED;
382         }
383 
384         Integer uid = KotlinUtils.INSTANCE.getPackageUid(getActivity().getApplication(),
385                 packageName, user);
386         if (uid == null) {
387             return;
388         }
389 
390         PermissionControllerStatsLog.write(PERMISSION_APPS_FRAGMENT_VIEWED, sessionId, viewId,
391                 mPermGroupName, uid, packageName, category);
392         Log.v(LOG_TAG, "PermissionAppsFragment created with sessionId=" + sessionId
393                 + " permissionGroupName=" + mPermGroupName + " appUid="
394                 + uid + " packageName=" + packageName
395                 + " category=" + category);
396     }
397 }
398