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