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