Home | History | Annotate | Download | only in wear
      1 /*
      2  * Copyright (C) 2017 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.app.Activity;
     20 import android.content.Intent;
     21 import android.content.IntentSender;
     22 import android.content.pm.PackageInfo;
     23 import android.graphics.drawable.Drawable;
     24 import android.os.Bundle;
     25 import android.os.RemoteCallback;
     26 import android.preference.Preference;
     27 import android.preference.PreferenceCategory;
     28 import android.preference.PreferenceFragment;
     29 import android.preference.PreferenceGroup;
     30 import android.preference.PreferenceScreen;
     31 import android.preference.SwitchPreference;
     32 import android.preference.TwoStatePreference;
     33 import android.support.wearable.view.WearableDialogHelper;
     34 import android.text.SpannableString;
     35 import android.text.style.ForegroundColorSpan;
     36 import android.util.Log;
     37 import android.util.TypedValue;
     38 import com.android.packageinstaller.R;
     39 import com.android.packageinstaller.permission.model.AppPermissionGroup;
     40 import com.android.packageinstaller.permission.model.AppPermissions;
     41 import com.android.packageinstaller.permission.utils.Utils;
     42 
     43 import java.util.List;
     44 
     45 public class ReviewPermissionsWearFragment extends PreferenceFragment
     46         implements Preference.OnPreferenceChangeListener {
     47     private static final String TAG = "ReviewPermWear";
     48 
     49     private static final int ORDER_TITLE = 0;
     50     private static final int ORDER_NEW_PERMS = 1;
     51     private static final int ORDER_CURRENT_PERMS = 2;
     52     // Category for showing actions should be displayed last.
     53     private static final int ORDER_ACTION = 100000;
     54     private static final int ORDER_PERM_OFFSET_START = 100;
     55 
     56     private static final String EXTRA_PACKAGE_INFO =
     57         "com.android.packageinstaller.permission.ui.extra.PACKAGE_INFO";
     58 
     59     public static ReviewPermissionsWearFragment newInstance(PackageInfo packageInfo) {
     60         Bundle arguments = new Bundle();
     61         arguments.putParcelable(EXTRA_PACKAGE_INFO, packageInfo);
     62         ReviewPermissionsWearFragment instance = new ReviewPermissionsWearFragment();
     63         instance.setArguments(arguments);
     64         instance.setRetainInstance(true);
     65         return instance;
     66     }
     67 
     68     private AppPermissions mAppPermissions;
     69 
     70     private PreferenceCategory mNewPermissionsCategory;
     71 
     72     private boolean mHasConfirmedRevoke;
     73 
     74     @Override
     75     public void onCreate(Bundle savedInstanceState) {
     76         super.onCreate(savedInstanceState);
     77 
     78         Activity activity = getActivity();
     79         if (activity == null) {
     80             return;
     81         }
     82 
     83         PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO);
     84         if (packageInfo == null) {
     85             activity.finish();
     86             return;
     87         }
     88 
     89         mAppPermissions = new AppPermissions(activity, packageInfo, null, false,
     90                 new Runnable() {
     91                     @Override
     92                     public void run() {
     93                         getActivity().finish();
     94                     }
     95                 });
     96 
     97         if (mAppPermissions.getPermissionGroups().isEmpty()) {
     98             activity.finish();
     99             return;
    100         }
    101 
    102         boolean reviewRequired = false;
    103         for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
    104             if (group.isReviewRequired()) {
    105                 reviewRequired = true;
    106                 break;
    107             }
    108         }
    109 
    110         if (!reviewRequired) {
    111             activity.finish();
    112         }
    113     }
    114 
    115     @Override
    116     public void onResume() {
    117         super.onResume();
    118         mAppPermissions.refresh();
    119         loadPreferences();
    120     }
    121 
    122     private void loadPreferences() {
    123         Activity activity = getActivity();
    124         if (activity == null) {
    125             return;
    126         }
    127 
    128         PreferenceScreen screen = getPreferenceScreen();
    129         if (screen == null) {
    130             screen = getPreferenceManager().createPreferenceScreen(getActivity());
    131             setPreferenceScreen(screen);
    132         } else {
    133             screen.removeAll();
    134         }
    135 
    136         PreferenceGroup currentPermissionsCategory = null;
    137         PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory;
    138         mNewPermissionsCategory = null;
    139 
    140         final boolean isPackageUpdated = isPackageUpdated();
    141         int permOrder = ORDER_PERM_OFFSET_START;
    142 
    143         for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
    144             if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)
    145                     || !Utils.OS_PKG.equals(group.getDeclaringPackage())) {
    146                 continue;
    147             }
    148 
    149             final SwitchPreference preference;
    150             Preference cachedPreference = oldNewPermissionsCategory != null
    151                     ? oldNewPermissionsCategory.findPreference(group.getName()) : null;
    152             if (cachedPreference instanceof SwitchPreference) {
    153                 preference = (SwitchPreference) cachedPreference;
    154             } else {
    155                 preference = new SwitchPreference(getActivity());
    156 
    157                 preference.setKey(group.getName());
    158                 preference.setTitle(group.getLabel());
    159                 preference.setPersistent(false);
    160                 preference.setOrder(permOrder++);
    161 
    162                 preference.setOnPreferenceChangeListener(this);
    163             }
    164 
    165             preference.setChecked(group.areRuntimePermissionsGranted());
    166 
    167             // Mutable state
    168             if (group.isPolicyFixed()) {
    169                 preference.setEnabled(false);
    170             } else {
    171                 preference.setEnabled(true);
    172             }
    173 
    174             if (group.isReviewRequired()) {
    175                 if (!isPackageUpdated) {
    176                     // An app just being installed, which means all groups requiring reviews.
    177                     screen.addPreference(preference);
    178                 } else {
    179                     if (mNewPermissionsCategory == null) {
    180                         mNewPermissionsCategory = new PreferenceCategory(activity);
    181                         mNewPermissionsCategory.setTitle(R.string.new_permissions_category);
    182                         mNewPermissionsCategory.setOrder(ORDER_NEW_PERMS);
    183                         screen.addPreference(mNewPermissionsCategory);
    184                     }
    185                     mNewPermissionsCategory.addPreference(preference);
    186                 }
    187             } else {
    188                 if (currentPermissionsCategory == null) {
    189                     currentPermissionsCategory = new PreferenceCategory(activity);
    190                     currentPermissionsCategory.setTitle(R.string.current_permissions_category);
    191                     currentPermissionsCategory.setOrder(ORDER_CURRENT_PERMS);
    192                     screen.addPreference(currentPermissionsCategory);
    193                 }
    194                 currentPermissionsCategory.addPreference(preference);
    195             }
    196         }
    197 
    198         addTitlePreferenceToScreen(screen);
    199         addActionPreferencesToScreen(screen);
    200     }
    201 
    202     private boolean isPackageUpdated() {
    203         List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
    204         final int groupCount = groups.size();
    205         for (int i = 0; i < groupCount; i++) {
    206             AppPermissionGroup group = groups.get(i);
    207             if (!group.isReviewRequired()) {
    208                 return true;
    209             }
    210         }
    211         return false;
    212     }
    213 
    214     @Override
    215     public boolean onPreferenceChange(Preference preference, Object newValue) {
    216       Log.d(TAG, "onPreferenceChange " + preference.getTitle());
    217         if (mHasConfirmedRevoke) {
    218             return true;
    219         }
    220         if (preference instanceof SwitchPreference) {
    221             SwitchPreference switchPreference = (SwitchPreference) preference;
    222             if (switchPreference.isChecked()) {
    223                 showWarnRevokeDialog(switchPreference);
    224             } else {
    225                 return true;
    226             }
    227         }
    228         return false;
    229     }
    230 
    231     private void showWarnRevokeDialog(final SwitchPreference preference) {
    232         // When revoking, we set "confirm" as the negative icon to be shown at the bottom.
    233         new WearableDialogHelper.DialogBuilder(getContext())
    234                 .setPositiveIcon(R.drawable.cancel_button)
    235                 .setNegativeIcon(R.drawable.confirm_button)
    236                 .setPositiveButton(R.string.cancel, null)
    237                 .setNegativeButton(R.string.grant_dialog_button_deny_anyway,
    238                         (dialog, which) -> {
    239                             preference.setChecked(false);
    240                             mHasConfirmedRevoke = true;
    241                         })
    242                 .setMessage(R.string.old_sdk_deny_warning)
    243                 .show();
    244     }
    245 
    246     private void confirmPermissionsReview() {
    247         PreferenceGroup preferenceGroup = mNewPermissionsCategory != null
    248                 ? mNewPermissionsCategory : getPreferenceScreen();
    249 
    250         final int preferenceCount = preferenceGroup.getPreferenceCount();
    251         for (int i = 0; i < preferenceCount; i++) {
    252             Preference preference = preferenceGroup.getPreference(i);
    253             if (preference instanceof TwoStatePreference) {
    254                 TwoStatePreference twoStatePreference = (TwoStatePreference) preference;
    255                 String groupName = preference.getKey();
    256                 AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
    257                 if (twoStatePreference.isChecked()) {
    258                     group.grantRuntimePermissions(false);
    259                 } else {
    260                     group.revokeRuntimePermissions(false);
    261                 }
    262                 group.resetReviewRequired();
    263             }
    264         }
    265     }
    266 
    267     private void addTitlePreferenceToScreen(PreferenceScreen screen) {
    268         Activity activity = getActivity();
    269         Preference titlePref = new Preference(activity);
    270         screen.addPreference(titlePref);
    271 
    272         // Set icon
    273         Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon(
    274               activity.getPackageManager());
    275         titlePref.setIcon(icon);
    276 
    277         // Set message
    278         String appLabel = mAppPermissions.getAppLabel().toString();
    279         final int labelTemplateResId = isPackageUpdated()
    280                 ?  R.string.permission_review_title_template_update
    281                 :  R.string.permission_review_title_template_install;
    282         SpannableString message = new SpannableString(getString(labelTemplateResId, appLabel));
    283 
    284         // Color the app name.
    285         final int appLabelStart = message.toString().indexOf(appLabel, 0);
    286         final int appLabelLength = appLabel.length();
    287 
    288         TypedValue typedValue = new TypedValue();
    289         activity.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
    290         final int color = activity.getColor(typedValue.resourceId);
    291 
    292         message.setSpan(new ForegroundColorSpan(color), appLabelStart,
    293                 appLabelStart + appLabelLength, 0);
    294 
    295         titlePref.setTitle(message);
    296 
    297         titlePref.setSelectable(false);
    298         titlePref.setLayoutResource(R.layout.wear_review_permission_title_pref);
    299     }
    300 
    301     private void addActionPreferencesToScreen(PreferenceScreen screen) {
    302         final Activity activity = getActivity();
    303 
    304         Preference cancelPref = new Preference(activity);
    305         cancelPref.setTitle(R.string.review_button_cancel);
    306         cancelPref.setOrder(ORDER_ACTION);
    307         cancelPref.setEnabled(true);
    308         cancelPref.setLayoutResource(R.layout.wear_review_permission_action_pref);
    309         cancelPref.setOnPreferenceClickListener(p -> {
    310             executeCallback(false);
    311             activity.setResult(Activity.RESULT_CANCELED);
    312             activity.finish();
    313             return true;
    314         });
    315         screen.addPreference(cancelPref);
    316 
    317         Preference continuePref = new Preference(activity);
    318         continuePref.setTitle(R.string.review_button_continue);
    319         continuePref.setOrder(ORDER_ACTION + 1);
    320         continuePref.setEnabled(true);
    321         continuePref.setLayoutResource(R.layout.wear_review_permission_action_pref);
    322         continuePref.setOnPreferenceClickListener(p -> {
    323             confirmPermissionsReview();
    324             executeCallback(true);
    325             getActivity().finish();
    326             return true;
    327         });
    328         screen.addPreference(continuePref);
    329     }
    330 
    331     private void executeCallback(boolean success) {
    332         Activity activity = getActivity();
    333         if (activity == null) {
    334             return;
    335         }
    336         if (success) {
    337             IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
    338             if (intent != null) {
    339                 try {
    340                     int flagMask = 0;
    341                     int flagValues = 0;
    342                     if (activity.getIntent().getBooleanExtra(
    343                             Intent.EXTRA_RESULT_NEEDED, false)) {
    344                         flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
    345                         flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
    346                     }
    347                     activity.startIntentSenderForResult(intent, -1, null,
    348                             flagMask, flagValues, 0);
    349                 } catch (IntentSender.SendIntentException e) {
    350                     /* ignore */
    351                 }
    352                 return;
    353             }
    354         }
    355         RemoteCallback callback = activity.getIntent().getParcelableExtra(
    356                 Intent.EXTRA_REMOTE_CALLBACK);
    357         if (callback != null) {
    358             Bundle result = new Bundle();
    359             result.putBoolean(Intent.EXTRA_RETURN_RESULT, success);
    360             callback.sendResult(result);
    361         }
    362     }
    363 }
    364