• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Intent;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PermissionInfo;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.preference.Preference;
29 import android.preference.PreferenceFragment;
30 import android.preference.PreferenceScreen;
31 import android.preference.SwitchPreference;
32 import android.support.wearable.view.WearableDialogHelper;
33 import android.util.Log;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.widget.Toast;
38 
39 import com.android.packageinstaller.R;
40 import com.android.packageinstaller.permission.model.AppPermissionGroup;
41 import com.android.packageinstaller.permission.model.AppPermissions;
42 import com.android.packageinstaller.permission.model.Permission;
43 import com.android.packageinstaller.permission.utils.LocationUtils;
44 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
45 import com.android.packageinstaller.permission.utils.ArrayUtils;
46 import com.android.packageinstaller.permission.utils.Utils;
47 import com.android.settingslib.RestrictedLockUtils;
48 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 
53 public final class AppPermissionsFragmentWear extends PreferenceFragment {
54     private static final String LOG_TAG = "AppPermFragWear";
55 
56     private static final String KEY_NO_PERMISSIONS = "no_permissions";
57 
newInstance(String packageName)58     public static AppPermissionsFragmentWear newInstance(String packageName) {
59         return setPackageName(new AppPermissionsFragmentWear(), packageName);
60     }
61 
setPackageName(T fragment, String packageName)62     private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
63         Bundle arguments = new Bundle();
64         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
65         fragment.setArguments(arguments);
66         return fragment;
67     }
68 
69     private PackageManager mPackageManager;
70     private List<AppPermissionGroup> mToggledGroups;
71     private AppPermissions mAppPermissions;
72 
73     private boolean mHasConfirmedRevoke;
74 
75     /**
76      * Provides click behavior for disabled preferences.
77      * We can't use {@link PreferenceFragment#onPreferenceTreeClick}, as the base
78      * {@link SwitchPreference} doesn't delegate to that method if the preference is disabled.
79      */
80     private static class PermissionSwitchPreference extends SwitchPreference {
81 
82         private final Activity mActivity;
83 
PermissionSwitchPreference(Activity activity)84         public PermissionSwitchPreference(Activity activity) {
85             super(activity);
86             this.mActivity = activity;
87         }
88 
89         @Override
performClick(PreferenceScreen preferenceScreen)90         public void performClick(PreferenceScreen preferenceScreen) {
91             super.performClick(preferenceScreen);
92             if (!isEnabled()) {
93                 // If setting the permission is disabled, it must have been locked
94                 // by the device or profile owner. So get that info and pass it to
95                 // the support details dialog.
96                 EnforcedAdmin deviceOrProfileOwner = RestrictedLockUtils.getProfileOrDeviceOwner(
97                     mActivity, UserHandle.myUserId());
98                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
99                     mActivity, deviceOrProfileOwner);
100             }
101         }
102     }
103 
104     @Override
onCreate(Bundle savedInstanceState)105     public void onCreate(Bundle savedInstanceState) {
106         super.onCreate(savedInstanceState);
107 
108         String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
109         Activity activity = getActivity();
110         mPackageManager = activity.getPackageManager();
111 
112         PackageInfo packageInfo;
113 
114         try {
115             packageInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
116         } catch (PackageManager.NameNotFoundException e) {
117             Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
118             packageInfo = null;
119         }
120 
121         if (packageInfo == null) {
122             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
123             activity.finish();
124             return;
125         }
126 
127         mAppPermissions = new AppPermissions(
128                 activity, packageInfo, null, true, () -> getActivity().finish());
129 
130         addPreferencesFromResource(R.xml.watch_permissions);
131         initializePermissionGroupList();
132     }
133 
134     @Override
onResume()135     public void onResume() {
136         super.onResume();
137         mAppPermissions.refresh();
138 
139         // Also refresh the UI
140         for (final AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
141             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
142                 for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
143                     setPreferenceCheckedIfPresent(perm.name,
144                             group.areRuntimePermissionsGranted(new String[]{ perm.name }));
145                 }
146             } else {
147                 setPreferenceCheckedIfPresent(group.getName(), group.areRuntimePermissionsGranted());
148             }
149         }
150     }
151 
152     @Override
onPause()153     public void onPause() {
154         super.onPause();
155         logAndClearToggledGroups();
156     }
157 
initializePermissionGroupList()158     private void initializePermissionGroupList() {
159         final String packageName = mAppPermissions.getPackageInfo().packageName;
160         List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
161         List<SwitchPreference> nonSystemPreferences = new ArrayList<>();
162 
163         if (!groups.isEmpty()) {
164             getPreferenceScreen().removePreference(findPreference(KEY_NO_PERMISSIONS));
165         }
166 
167         for (final AppPermissionGroup group : groups) {
168             if (!Utils.shouldShowPermission(group, packageName)) {
169                 continue;
170             }
171 
172             boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
173 
174             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
175                 // If permission is controlled individually, we show all requested permission
176                 // inside this group.
177                 for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
178                     final SwitchPreference pref = createSwitchPreferenceForPermission(group, perm);
179                     showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
180                 }
181             } else {
182                 final SwitchPreference pref = createSwitchPreferenceForGroup(group);
183                 showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
184             }
185         }
186 
187         // Now add the non-system settings to the end of the list
188         for (SwitchPreference nonSystemPreference : nonSystemPreferences) {
189             getPreferenceScreen().addPreference(nonSystemPreference);
190         }
191     }
192 
showOrAddToNonSystemPreferences(SwitchPreference pref, List<SwitchPreference> nonSystemPreferences, boolean isPlatform)193     private void showOrAddToNonSystemPreferences(SwitchPreference pref,
194             List<SwitchPreference> nonSystemPreferences, // Mutate
195             boolean isPlatform) {
196         // The UI shows System settings first, then non-system settings
197         if (isPlatform) {
198             getPreferenceScreen().addPreference(pref);
199         } else {
200             nonSystemPreferences.add(pref);
201         }
202     }
203 
createSwitchPreferenceForPermission(AppPermissionGroup group, PermissionInfo perm)204     private SwitchPreference createSwitchPreferenceForPermission(AppPermissionGroup group,
205             PermissionInfo perm) {
206         final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
207         pref.setKey(perm.name);
208         pref.setTitle(perm.loadLabel(mPackageManager));
209         pref.setChecked(group.areRuntimePermissionsGranted(new String[]{ perm.name }));
210         pref.setOnPreferenceChangeListener((p, newVal) -> {
211             if((Boolean) newVal) {
212                 group.grantRuntimePermissions(false, new String[]{ perm.name });
213             } else {
214                 group.revokeRuntimePermissions(true, new String[]{ perm.name });
215             }
216 
217             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())
218                     && group.hasRuntimePermission()) {
219                 // As long as one permission is changed in individually controlled group
220                 // permissions, we will set user_fixed for non-granted permissions in that group.
221                 // This avoids the system to automatically grant runtime permissions based on the
222                 // fact that one of dangerous permission in that group is already granted.
223                 String[] revokedPermissionsToFix = null;
224                 final int permissionCount = group.getPermissions().size();
225 
226                 for (int i = 0; i < permissionCount; i++) {
227                     Permission current = group.getPermissions().get(i);
228                     if (!current.isGranted() && !current.isUserFixed()) {
229                         revokedPermissionsToFix = ArrayUtils.appendString(
230                                 revokedPermissionsToFix, current.getName());
231                     }
232                 }
233 
234                 if (revokedPermissionsToFix != null) {
235                     group.revokeRuntimePermissions(true, revokedPermissionsToFix);
236                 }
237             }
238             return true;
239         });
240         return pref;
241     }
242 
createSwitchPreferenceForGroup(AppPermissionGroup group)243     private SwitchPreference createSwitchPreferenceForGroup(AppPermissionGroup group) {
244         final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
245 
246         pref.setKey(group.getName());
247         pref.setTitle(group.getLabel());
248         pref.setChecked(group.areRuntimePermissionsGranted());
249 
250         if (group.isPolicyFixed()) {
251             pref.setEnabled(false);
252         } else {
253             pref.setOnPreferenceChangeListener((p, newVal) -> {
254                 if (LocationUtils.isLocationGroupAndProvider(
255                         group.getName(), group.getApp().packageName)) {
256                     LocationUtils.showLocationDialog(
257                             getContext(), mAppPermissions.getAppLabel());
258                     return false;
259                 }
260 
261                 if ((Boolean) newVal) {
262                     setPermission(group, pref, true);
263                 } else {
264                     final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
265                     if (grantedByDefault
266                             || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) {
267                         new WearableDialogHelper.DialogBuilder(getContext())
268                                 .setNegativeIcon(R.drawable.confirm_button)
269                                 .setPositiveIcon(R.drawable.cancel_button)
270                                 .setNegativeButton(R.string.grant_dialog_button_deny_anyway,
271                                         (dialog, which) -> {
272                                             setPermission(group, pref, false);
273                                             if (!group.hasGrantedByDefaultPermission()) {
274                                                 mHasConfirmedRevoke = true;
275                                             }
276                                         })
277                                 .setPositiveButton(R.string.cancel, (dialog, which) -> {})
278                                 .setMessage(grantedByDefault ?
279                                         R.string.system_warning : R.string.old_sdk_deny_warning)
280                                 .show();
281                         return false;
282                     } else {
283                         setPermission(group, pref, false);
284                     }
285                 }
286 
287                 return true;
288             });
289         }
290         return pref;
291     }
292 
setPermission(AppPermissionGroup group, SwitchPreference pref, boolean grant)293     private void setPermission(AppPermissionGroup group, SwitchPreference pref, boolean grant) {
294         if (grant) {
295             group.grantRuntimePermissions(false);
296         } else {
297             group.revokeRuntimePermissions(false);
298         }
299         addToggledGroup(group);
300         pref.setChecked(grant);
301     }
302 
addToggledGroup(AppPermissionGroup group)303     private void addToggledGroup(AppPermissionGroup group) {
304         if (mToggledGroups == null) {
305             mToggledGroups = new ArrayList<>();
306         }
307         // Double toggle is back to initial state.
308         if (mToggledGroups.contains(group)) {
309             mToggledGroups.remove(group);
310         } else {
311             mToggledGroups.add(group);
312         }
313     }
314 
logAndClearToggledGroups()315     private void logAndClearToggledGroups() {
316         if (mToggledGroups != null) {
317             String packageName = mAppPermissions.getPackageInfo().packageName;
318             SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
319             mToggledGroups = null;
320         }
321     }
322 
getPermissionInfosFromGroup(AppPermissionGroup group)323     private List<PermissionInfo> getPermissionInfosFromGroup(AppPermissionGroup group) {
324         ArrayList<PermissionInfo> permInfos = new ArrayList<>(group.getPermissions().size());
325         for(Permission perm : group.getPermissions()) {
326             try {
327                 permInfos.add(mPackageManager.getPermissionInfo(perm.getName(), 0));
328             } catch (PackageManager.NameNotFoundException e) {
329                 Log.w(LOG_TAG, "No permission:" + perm.getName());
330             }
331         }
332         return permInfos;
333     }
334 
setPreferenceCheckedIfPresent(String preferenceKey, boolean checked)335     private void setPreferenceCheckedIfPresent(String preferenceKey, boolean checked) {
336         Preference pref = findPreference(preferenceKey);
337         if (pref instanceof SwitchPreference) {
338             ((SwitchPreference) pref).setChecked(checked);
339         }
340     }
341 }
342