Home | History | Annotate | Download | only in wear
      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.wear;
     18 
     19 import android.Manifest;
     20 import android.app.Activity;
     21 import android.app.Fragment;
     22 import android.content.DialogInterface;
     23 import android.content.Intent;
     24 import android.content.pm.PackageInfo;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.PermissionInfo;
     27 import android.os.Build;
     28 import android.os.Bundle;
     29 import android.os.UserHandle;
     30 import android.preference.Preference;
     31 import android.preference.PreferenceFragment;
     32 import android.preference.PreferenceScreen;
     33 import android.preference.SwitchPreference;
     34 import android.support.wearable.view.WearableDialogHelper;
     35 import android.util.Log;
     36 import android.view.LayoutInflater;
     37 import android.view.View;
     38 import android.view.ViewGroup;
     39 import android.widget.Toast;
     40 
     41 import com.android.packageinstaller.R;
     42 import com.android.packageinstaller.permission.model.AppPermissionGroup;
     43 import com.android.packageinstaller.permission.model.AppPermissions;
     44 import com.android.packageinstaller.permission.model.Permission;
     45 import com.android.packageinstaller.permission.utils.ArrayUtils;
     46 import com.android.packageinstaller.permission.utils.LocationUtils;
     47 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
     48 import com.android.packageinstaller.permission.utils.Utils;
     49 import com.android.settingslib.RestrictedLockUtils;
     50 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
     51 
     52 import java.util.ArrayList;
     53 import java.util.List;
     54 
     55 public final class AppPermissionsFragmentWear extends PreferenceFragment {
     56     private static final String LOG_TAG = "AppPermFragWear";
     57 
     58     private static final String KEY_NO_PERMISSIONS = "no_permissions";
     59 
     60     public static AppPermissionsFragmentWear newInstance(String packageName) {
     61         return setPackageName(new AppPermissionsFragmentWear(), packageName);
     62     }
     63 
     64     private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
     65         Bundle arguments = new Bundle();
     66         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
     67         fragment.setArguments(arguments);
     68         return fragment;
     69     }
     70 
     71     private PackageManager mPackageManager;
     72     private List<AppPermissionGroup> mToggledGroups;
     73     private AppPermissions mAppPermissions;
     74 
     75     private boolean mHasConfirmedRevoke;
     76 
     77     /**
     78      * Provides click behavior for disabled preferences.
     79      * We can't use {@link PreferenceFragment#onPreferenceTreeClick}, as the base
     80      * {@link SwitchPreference} doesn't delegate to that method if the preference is disabled.
     81      */
     82     private static class PermissionSwitchPreference extends SwitchPreference {
     83 
     84         private final Activity mActivity;
     85 
     86         public PermissionSwitchPreference(Activity activity) {
     87             super(activity);
     88             this.mActivity = activity;
     89         }
     90 
     91         @Override
     92         public void performClick(PreferenceScreen preferenceScreen) {
     93             super.performClick(preferenceScreen);
     94             if (!isEnabled()) {
     95                 // If setting the permission is disabled, it must have been locked
     96                 // by the device or profile owner. So get that info and pass it to
     97                 // the support details dialog.
     98                 EnforcedAdmin deviceOrProfileOwner = RestrictedLockUtils.getProfileOrDeviceOwner(
     99                     mActivity, UserHandle.myUserId());
    100                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
    101                     mActivity, deviceOrProfileOwner);
    102             }
    103         }
    104     }
    105 
    106     @Override
    107     public void onCreate(Bundle savedInstanceState) {
    108         super.onCreate(savedInstanceState);
    109 
    110         String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
    111         Activity activity = getActivity();
    112         mPackageManager = activity.getPackageManager();
    113 
    114         PackageInfo packageInfo;
    115 
    116         try {
    117             packageInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
    118         } catch (PackageManager.NameNotFoundException e) {
    119             Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
    120             packageInfo = null;
    121         }
    122 
    123         if (packageInfo == null) {
    124             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
    125             activity.finish();
    126             return;
    127         }
    128 
    129         mAppPermissions = new AppPermissions(
    130                 activity, packageInfo, null, true, () -> getActivity().finish());
    131 
    132         addPreferencesFromResource(R.xml.watch_permissions);
    133         initializePermissionGroupList();
    134     }
    135 
    136     @Override
    137     public void onResume() {
    138         super.onResume();
    139         mAppPermissions.refresh();
    140 
    141         // Also refresh the UI
    142         for (final AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
    143             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
    144                 for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
    145                     setPreferenceCheckedIfPresent(perm.name,
    146                             group.areRuntimePermissionsGranted(new String[]{ perm.name }));
    147                 }
    148             } else {
    149                 setPreferenceCheckedIfPresent(group.getName(), group.areRuntimePermissionsGranted());
    150             }
    151         }
    152     }
    153 
    154     @Override
    155     public void onPause() {
    156         super.onPause();
    157         logAndClearToggledGroups();
    158     }
    159 
    160     private void initializePermissionGroupList() {
    161         final String packageName = mAppPermissions.getPackageInfo().packageName;
    162         List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
    163         List<SwitchPreference> nonSystemPreferences = new ArrayList<>();
    164 
    165         if (!groups.isEmpty()) {
    166             getPreferenceScreen().removePreference(findPreference(KEY_NO_PERMISSIONS));
    167         }
    168 
    169         for (final AppPermissionGroup group : groups) {
    170             if (!Utils.shouldShowPermission(group, packageName)) {
    171                 continue;
    172             }
    173 
    174             boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
    175 
    176             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
    177                 // If permission is controlled individually, we show all requested permission
    178                 // inside this group.
    179                 for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
    180                     final SwitchPreference pref = createSwitchPreferenceForPermission(group, perm);
    181                     showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
    182                 }
    183             } else {
    184                 final SwitchPreference pref = createSwitchPreferenceForGroup(group);
    185                 showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
    186             }
    187         }
    188 
    189         // Now add the non-system settings to the end of the list
    190         for (SwitchPreference nonSystemPreference : nonSystemPreferences) {
    191             getPreferenceScreen().addPreference(nonSystemPreference);
    192         }
    193     }
    194 
    195     private void showOrAddToNonSystemPreferences(SwitchPreference pref,
    196             List<SwitchPreference> nonSystemPreferences, // Mutate
    197             boolean isPlatform) {
    198         // The UI shows System settings first, then non-system settings
    199         if (isPlatform) {
    200             getPreferenceScreen().addPreference(pref);
    201         } else {
    202             nonSystemPreferences.add(pref);
    203         }
    204     }
    205 
    206     private SwitchPreference createSwitchPreferenceForPermission(AppPermissionGroup group,
    207             PermissionInfo perm) {
    208         final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
    209         pref.setKey(perm.name);
    210         pref.setTitle(perm.loadLabel(mPackageManager));
    211         pref.setChecked(group.areRuntimePermissionsGranted(new String[]{ perm.name }));
    212         pref.setOnPreferenceChangeListener((p, newVal) -> {
    213             if((Boolean) newVal) {
    214                 group.grantRuntimePermissions(false, new String[]{ perm.name });
    215 
    216                 if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())
    217                         && group.doesSupportRuntimePermissions()) {
    218                     // We are granting a permission from a group but since this is an
    219                     // individual permission control other permissions in the group may
    220                     // be revoked, hence we need to mark them user fixed to prevent the
    221                     // app from requesting a non-granted permission and it being granted
    222                     // because another permission in the group is granted. This applies
    223                     // only to apps that support runtime permissions.
    224                     String[] revokedPermissionsToFix = null;
    225                     final int permissionCount = group.getPermissions().size();
    226 
    227                     for (int i = 0; i < permissionCount; i++) {
    228                         Permission current = group.getPermissions().get(i);
    229                         if (!current.isGranted() && !current.isUserFixed()) {
    230                             revokedPermissionsToFix = ArrayUtils.appendString(
    231                                     revokedPermissionsToFix, current.getName());
    232                         }
    233                     }
    234 
    235                     if (revokedPermissionsToFix != null) {
    236                         // If some permissions were not granted then they should be fixed.
    237                         group.revokeRuntimePermissions(true, revokedPermissionsToFix);
    238                     }
    239                 }
    240             } else {
    241                 final Permission appPerm = getPermissionFromGroup(group, perm.name);
    242                 if (appPerm == null) {
    243                     return false;
    244                 }
    245 
    246                 final boolean grantedByDefault = appPerm.isGrantedByDefault();
    247                 if (grantedByDefault
    248                         || (!group.doesSupportRuntimePermissions() && !mHasConfirmedRevoke)) {
    249                     showRevocationWarningDialog(
    250                             (dialog, which) -> {
    251                                 revokePermissionInGroup(group, perm.name);
    252                                 pref.setChecked(false);
    253                                 if (!appPerm.isGrantedByDefault()) {
    254                                     mHasConfirmedRevoke = true;
    255                                 }
    256                             },
    257                             grantedByDefault
    258                                     ? R.string.system_warning
    259                                     : R.string.old_sdk_deny_warning);
    260                     return false;
    261                 } else {
    262                     revokePermissionInGroup(group, perm.name);
    263                 }
    264             }
    265 
    266             return true;
    267         });
    268         return pref;
    269     }
    270 
    271     private void showRevocationWarningDialog(
    272             DialogInterface.OnClickListener confirmListener,
    273             int warningMessageId) {
    274         new WearableDialogHelper.DialogBuilder(getContext())
    275                 .setNegativeIcon(R.drawable.confirm_button)
    276                 .setPositiveIcon(R.drawable.cancel_button)
    277                 .setNegativeButton(R.string.grant_dialog_button_deny_anyway, confirmListener)
    278                 .setPositiveButton(R.string.cancel, null)
    279                 .setMessage(warningMessageId)
    280                 .show();
    281     }
    282 
    283     private static Permission getPermissionFromGroup(AppPermissionGroup group, String permName) {
    284         final int permissionCount = group.getPermissions().size();
    285 
    286         for (int i = 0; i < permissionCount; i++) {
    287             Permission currentPerm = group.getPermissions().get(i);
    288             if(currentPerm.getName().equals(permName)) {
    289                 return currentPerm;
    290             };
    291         }
    292 
    293         if ("user".equals(Build.TYPE)) {
    294             Log.e(LOG_TAG, String.format("The impossible happens, permission %s is not in group %s.",
    295                     permName, group.getName()));
    296             return null;
    297         } else {
    298             // This is impossible, throw a fatal error in non-user build.
    299             throw new IllegalArgumentException(
    300                     String.format("Permission %s is not in group %s", permName, group.getName()));
    301         }
    302     }
    303 
    304     private void revokePermissionInGroup(AppPermissionGroup group, String permName) {
    305         group.revokeRuntimePermissions(true, new String[]{ permName });
    306 
    307         if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())
    308                 && group.doesSupportRuntimePermissions()
    309                 && !group.areRuntimePermissionsGranted()) {
    310             // If we just revoked the last permission we need to clear
    311             // the user fixed state as now the app should be able to
    312             // request them at runtime if supported.
    313             group.revokeRuntimePermissions(false);
    314         }
    315     }
    316 
    317     private SwitchPreference createSwitchPreferenceForGroup(AppPermissionGroup group) {
    318         final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
    319 
    320         pref.setKey(group.getName());
    321         pref.setTitle(group.getLabel());
    322         pref.setChecked(group.areRuntimePermissionsGranted());
    323 
    324         if (group.isPolicyFixed()) {
    325             pref.setEnabled(false);
    326         } else {
    327             pref.setOnPreferenceChangeListener((p, newVal) -> {
    328                 if (LocationUtils.isLocationGroupAndProvider(
    329                         group.getName(), group.getApp().packageName)) {
    330                     LocationUtils.showLocationDialog(
    331                             getContext(), mAppPermissions.getAppLabel());
    332                     return false;
    333                 }
    334 
    335                 if ((Boolean) newVal) {
    336                     setPermission(group, pref, true);
    337                 } else {
    338                     final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
    339                     if (grantedByDefault
    340                             || (!group.doesSupportRuntimePermissions() && !mHasConfirmedRevoke)) {
    341                         showRevocationWarningDialog(
    342                                 (dialog, which) -> {
    343                                     setPermission(group, pref, false);
    344                                     if (!group.hasGrantedByDefaultPermission()) {
    345                                         mHasConfirmedRevoke = true;
    346                                     }
    347                                 },
    348                                 grantedByDefault
    349                                         ? R.string.system_warning
    350                                         : R.string.old_sdk_deny_warning);
    351                         return false;
    352                     } else {
    353                         setPermission(group, pref, false);
    354                     }
    355                 }
    356 
    357                 return true;
    358             });
    359         }
    360         return pref;
    361     }
    362 
    363     private void setPermission(AppPermissionGroup group, SwitchPreference pref, boolean grant) {
    364         if (grant) {
    365             group.grantRuntimePermissions(false);
    366         } else {
    367             group.revokeRuntimePermissions(false);
    368         }
    369         addToggledGroup(group);
    370         pref.setChecked(grant);
    371     }
    372 
    373     private void addToggledGroup(AppPermissionGroup group) {
    374         if (mToggledGroups == null) {
    375             mToggledGroups = new ArrayList<>();
    376         }
    377         // Double toggle is back to initial state.
    378         if (mToggledGroups.contains(group)) {
    379             mToggledGroups.remove(group);
    380         } else {
    381             mToggledGroups.add(group);
    382         }
    383     }
    384 
    385     private void logAndClearToggledGroups() {
    386         if (mToggledGroups != null) {
    387             String packageName = mAppPermissions.getPackageInfo().packageName;
    388             SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
    389             mToggledGroups = null;
    390         }
    391     }
    392 
    393     private List<PermissionInfo> getPermissionInfosFromGroup(AppPermissionGroup group) {
    394         ArrayList<PermissionInfo> permInfos = new ArrayList<>(group.getPermissions().size());
    395         for(Permission perm : group.getPermissions()) {
    396             try {
    397                 permInfos.add(mPackageManager.getPermissionInfo(perm.getName(), 0));
    398             } catch (PackageManager.NameNotFoundException e) {
    399                 Log.w(LOG_TAG, "No permission:" + perm.getName());
    400             }
    401         }
    402         return permInfos;
    403     }
    404 
    405     private void setPreferenceCheckedIfPresent(String preferenceKey, boolean checked) {
    406         Preference pref = findPreference(preferenceKey);
    407         if (pref instanceof SwitchPreference) {
    408             ((SwitchPreference) pref).setChecked(checked);
    409         }
    410     }
    411 }
    412