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 
newInstance(PackageInfo packageInfo)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
onCreate(Bundle savedInstanceState)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
onResume()116     public void onResume() {
117         super.onResume();
118         mAppPermissions.refresh();
119         loadPreferences();
120     }
121 
loadPreferences()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 
isPackageUpdated()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
onPreferenceChange(Preference preference, Object newValue)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 
showWarnRevokeDialog(final SwitchPreference preference)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 
confirmPermissionsReview()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 
addTitlePreferenceToScreen(PreferenceScreen screen)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 
addActionPreferencesToScreen(PreferenceScreen screen)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 
executeCallback(boolean success)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