• 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 
17 package com.android.packageinstaller.permission.ui.handheld;
18 
19 import android.app.ActionBar;
20 import android.app.AlertDialog;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageItemInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.PermissionGroupInfo;
29 import android.content.pm.PermissionInfo;
30 import android.graphics.drawable.Drawable;
31 import android.net.Uri;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.view.MenuItem;
39 import android.widget.Switch;
40 
41 import androidx.annotation.NonNull;
42 import androidx.preference.Preference;
43 import androidx.preference.PreferenceCategory;
44 import androidx.preference.PreferenceGroup;
45 
46 import com.android.packageinstaller.permission.model.AppPermissionGroup;
47 import com.android.packageinstaller.permission.model.Permission;
48 import com.android.packageinstaller.permission.utils.ArrayUtils;
49 import com.android.packageinstaller.permission.utils.Utils;
50 import com.android.permissioncontroller.R;
51 
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.List;
56 
57 /**
58  * Show and manage individual permissions for an app.
59  *
60  * <p>Shows the list of individual runtime and non-runtime permissions the app has requested.
61  */
62 public final class AllAppPermissionsFragment extends SettingsWithLargeHeader {
63 
64     private static final String LOG_TAG = "AllAppPermissionsFragment";
65 
66     private static final String KEY_OTHER = "other_perms";
67 
68     private List<AppPermissionGroup> mGroups;
69 
newInstance(@onNull String packageName, @NonNull UserHandle userHandle)70     public static AllAppPermissionsFragment newInstance(@NonNull String packageName,
71             @NonNull UserHandle userHandle) {
72         return newInstance(packageName, null, userHandle);
73     }
74 
newInstance(@onNull String packageName, @NonNull String filterGroup, @NonNull UserHandle userHandle)75     public static AllAppPermissionsFragment newInstance(@NonNull String packageName,
76             @NonNull String filterGroup, @NonNull UserHandle userHandle) {
77         AllAppPermissionsFragment instance = new AllAppPermissionsFragment();
78         Bundle arguments = new Bundle();
79         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
80         arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, filterGroup);
81         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
82         instance.setArguments(arguments);
83         return instance;
84     }
85 
86     @Override
onStart()87     public void onStart() {
88         super.onStart();
89 
90         final ActionBar ab = getActivity().getActionBar();
91         if (ab != null) {
92             // If we target a group make this look like app permissions.
93             if (getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null) {
94                 ab.setTitle(R.string.all_permissions);
95             } else {
96                 ab.setTitle(R.string.app_permissions);
97             }
98             ab.setDisplayHomeAsUpEnabled(true);
99         }
100 
101         setHasOptionsMenu(true);
102 
103         updateUi();
104     }
105 
106     @Override
onOptionsItemSelected(MenuItem item)107     public boolean onOptionsItemSelected(MenuItem item) {
108         switch (item.getItemId()) {
109             case android.R.id.home: {
110                 getFragmentManager().popBackStack();
111                 return true;
112             }
113         }
114         return super.onOptionsItemSelected(item);
115     }
116 
updateUi()117     private void updateUi() {
118         if (getPreferenceScreen() != null) {
119             getPreferenceScreen().removeAll();
120         }
121         addPreferencesFromResource(R.xml.all_permissions);
122         PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER);
123         ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting.
124         prefs.add(otherGroup);
125         String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
126         String filterGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
127         UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
128         otherGroup.removeAll();
129         PackageManager pm = getContext().getPackageManager();
130 
131         try {
132             PackageInfo info = getActivity().createPackageContextAsUser(pkg, 0, userHandle)
133                     .getPackageManager().getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
134 
135             ApplicationInfo appInfo = info.applicationInfo;
136             final Drawable icon = Utils.getBadgedIcon(getContext(), appInfo);
137             final CharSequence label = appInfo.loadLabel(pm);
138             Intent infoIntent = null;
139             if (!getActivity().getIntent().getBooleanExtra(
140                     AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) {
141                 infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
142                         .setData(Uri.fromParts("package", pkg, null));
143             }
144             setHeader(icon, label, infoIntent, userHandle, false);
145 
146             if (info.requestedPermissions != null) {
147                 for (int i = 0; i < info.requestedPermissions.length; i++) {
148                     PermissionInfo perm;
149                     try {
150                         perm = pm.getPermissionInfo(info.requestedPermissions[i], 0);
151                     } catch (NameNotFoundException e) {
152                         Log.e(LOG_TAG,
153                                 "Can't get permission info for " + info.requestedPermissions[i], e);
154                         continue;
155                     }
156 
157                     if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
158                             || (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
159                         continue;
160                     }
161 
162                     if (appInfo.isInstantApp()
163                             && (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT)
164                                 == 0) {
165                         continue;
166                     }
167                     if (appInfo.targetSdkVersion < Build.VERSION_CODES.M
168                             && (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
169                                 != 0) {
170                         continue;
171                     }
172 
173                     if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
174                             == PermissionInfo.PROTECTION_DANGEROUS) {
175                         PackageItemInfo group = getGroup(Utils.getGroupOfPermission(perm), pm);
176                         if (group == null) {
177                             group = perm;
178                         }
179                         // If we show a targeted group, then ignore everything else.
180                         if (filterGroup != null && !group.name.equals(filterGroup)) {
181                             continue;
182                         }
183                         PreferenceGroup pref = findOrCreate(group, pm, prefs);
184                         pref.addPreference(getPreference(info, perm, group, pm));
185                     } else if (filterGroup == null) {
186                         if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
187                                 == PermissionInfo.PROTECTION_NORMAL) {
188                             PermissionGroupInfo group = getGroup(perm.group, pm);
189                             otherGroup.addPreference(getPreference(info,
190                                     perm, group, pm));
191                         }
192                     }
193 
194                     // If we show a targeted group, then don't show 'other' permissions.
195                     if (filterGroup != null) {
196                         getPreferenceScreen().removePreference(otherGroup);
197                     }
198                 }
199             }
200         } catch (NameNotFoundException e) {
201             Log.e(LOG_TAG, "Problem getting package info for " + pkg, e);
202         }
203         // Sort an ArrayList of the groups and then set the order from the sorting.
204         Collections.sort(prefs, new Comparator<Preference>() {
205             @Override
206             public int compare(Preference lhs, Preference rhs) {
207                 String lKey = lhs.getKey();
208                 String rKey = rhs.getKey();
209                 if (lKey.equals(KEY_OTHER)) {
210                     return 1;
211                 } else if (rKey.equals(KEY_OTHER)) {
212                     return -1;
213                 } else if (Utils.isModernPermissionGroup(lKey)
214                         != Utils.isModernPermissionGroup(rKey)) {
215                     return Utils.isModernPermissionGroup(lKey) ? -1 : 1;
216                 }
217                 return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
218             }
219         });
220         for (int i = 0; i < prefs.size(); i++) {
221             prefs.get(i).setOrder(i);
222         }
223     }
224 
getGroup(String group, PackageManager pm)225     private PermissionGroupInfo getGroup(String group, PackageManager pm) {
226         try {
227             return pm.getPermissionGroupInfo(group, 0);
228         } catch (NameNotFoundException e) {
229             return null;
230         }
231     }
232 
findOrCreate(PackageItemInfo group, PackageManager pm, ArrayList<Preference> prefs)233     private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm,
234             ArrayList<Preference> prefs) {
235         PreferenceGroup pref = (PreferenceGroup) findPreference(group.name);
236         if (pref == null) {
237             pref = new PreferenceCategory(getPreferenceManager().getContext());
238             pref.setKey(group.name);
239             pref.setTitle(group.loadLabel(pm));
240             prefs.add(pref);
241             getPreferenceScreen().addPreference(pref);
242         }
243         return pref;
244     }
245 
getPreference(PackageInfo packageInfo, PermissionInfo perm, PackageItemInfo group, PackageManager pm)246     private Preference getPreference(PackageInfo packageInfo, PermissionInfo perm,
247             PackageItemInfo group, PackageManager pm) {
248         final Preference pref;
249         Context context = getPreferenceManager().getContext();
250 
251         // We allow individual permission control for some permissions if review enabled
252         final boolean mutable = Utils.isPermissionIndividuallyControlled(getContext(), perm.name);
253         if (mutable) {
254             pref = new MyMultiTargetSwitchPreference(context, perm.name,
255                     getPermissionForegroundGroup(packageInfo, perm.name));
256         } else {
257             pref = new Preference(context);
258         }
259 
260         Drawable icon = null;
261         if (perm.icon != 0) {
262             icon = perm.loadUnbadgedIcon(pm);
263         } else if (group != null && group.icon != 0) {
264             icon = group.loadUnbadgedIcon(pm);
265         } else {
266             icon = context.getDrawable(R.drawable.ic_perm_device_info);
267         }
268         pref.setIcon(Utils.applyTint(context, icon, android.R.attr.colorControlNormal));
269         pref.setTitle(perm.loadSafeLabel(pm, 20000, TextUtils.SAFE_STRING_FLAG_TRIM));
270         pref.setSingleLineTitle(false);
271         final CharSequence desc = perm.loadDescription(pm);
272 
273         pref.setOnPreferenceClickListener((Preference preference) -> {
274             new AlertDialog.Builder(getContext())
275                     .setMessage(desc)
276                     .setPositiveButton(android.R.string.ok, null)
277                     .show();
278             return mutable;
279         });
280 
281         return pref;
282     }
283 
284     /**
285      * Return the (foreground-) {@link AppPermissionGroup group} a permission belongs to.
286      *
287      * <p>For foreground or non background-foreground permissions this returns the group
288      * {@link AppPermissionGroup} the permission is in. For background permisisons this returns
289      * the group the matching foreground
290      *
291      * @param packageInfo Package information about the app
292      * @param permission The permission that belongs to a group
293      *
294      * @return the group the permissions belongs to
295      */
getPermissionForegroundGroup(PackageInfo packageInfo, String permission)296     private AppPermissionGroup getPermissionForegroundGroup(PackageInfo packageInfo,
297             String permission) {
298         AppPermissionGroup appPermissionGroup = null;
299         if (mGroups != null) {
300             final int groupCount = mGroups.size();
301             for (int i = 0; i < groupCount; i++) {
302                 AppPermissionGroup currentPermissionGroup = mGroups.get(i);
303                 if (currentPermissionGroup.hasPermission(permission)) {
304                     appPermissionGroup = currentPermissionGroup;
305                     break;
306                 }
307                 if (currentPermissionGroup.getBackgroundPermissions() != null
308                         && currentPermissionGroup.getBackgroundPermissions().hasPermission(
309                         permission)) {
310                     appPermissionGroup = currentPermissionGroup.getBackgroundPermissions();
311                     break;
312                 }
313             }
314         }
315         if (appPermissionGroup == null) {
316             appPermissionGroup = AppPermissionGroup.create(
317                     getContext(), packageInfo, permission, false);
318             if (mGroups == null) {
319                 mGroups = new ArrayList<>();
320             }
321             mGroups.add(appPermissionGroup);
322         }
323         return appPermissionGroup;
324     }
325 
326     private static final class MyMultiTargetSwitchPreference extends MultiTargetSwitchPreference {
MyMultiTargetSwitchPreference(Context context, String permission, AppPermissionGroup appPermissionGroup)327         MyMultiTargetSwitchPreference(Context context, String permission,
328                 AppPermissionGroup appPermissionGroup) {
329             super(context);
330 
331             setChecked(appPermissionGroup.areRuntimePermissionsGranted(
332                     new String[] {permission}));
333 
334             setSwitchOnClickListener(v -> {
335                 Switch switchView = (Switch) v;
336                 if (switchView.isChecked()) {
337                     appPermissionGroup.grantRuntimePermissions(false,
338                             new String[]{permission});
339                     // We are granting a permission from a group but since this is an
340                     // individual permission control other permissions in the group may
341                     // be revoked, hence we need to mark them user fixed to prevent the
342                     // app from requesting a non-granted permission and it being granted
343                     // because another permission in the group is granted. This applies
344                     // only to apps that support runtime permissions.
345                     if (appPermissionGroup.doesSupportRuntimePermissions()) {
346                         int grantedCount = 0;
347                         String[] revokedPermissionsToFix = null;
348                         final int permissionCount = appPermissionGroup.getPermissions().size();
349                         for (int i = 0; i < permissionCount; i++) {
350                             Permission current = appPermissionGroup.getPermissions().get(i);
351                             if (!current.isGrantedIncludingAppOp()) {
352                                 if (!current.isUserFixed()) {
353                                     revokedPermissionsToFix = ArrayUtils.appendString(
354                                             revokedPermissionsToFix, current.getName());
355                                 }
356                             } else {
357                                 grantedCount++;
358                             }
359                         }
360                         if (revokedPermissionsToFix != null) {
361                             // If some permissions were not granted then they should be fixed.
362                             appPermissionGroup.revokeRuntimePermissions(true,
363                                     revokedPermissionsToFix);
364                         } else if (appPermissionGroup.getPermissions().size() == grantedCount) {
365                             // If all permissions are granted then they should not be fixed.
366                             appPermissionGroup.grantRuntimePermissions(false);
367                         }
368                     }
369                 } else {
370                     appPermissionGroup.revokeRuntimePermissions(true,
371                             new String[]{permission});
372                     // If we just revoked the last permission we need to clear
373                     // the user fixed state as now the app should be able to
374                     // request them at runtime if supported.
375                     if (appPermissionGroup.doesSupportRuntimePermissions()
376                             && !appPermissionGroup.areRuntimePermissionsGranted()) {
377                         appPermissionGroup.revokeRuntimePermissions(false);
378                     }
379                 }
380             });
381         }
382     }
383 }
384