• 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.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 
newInstance(String packageName)60     public static AppPermissionsFragmentWear newInstance(String packageName) {
61         return setPackageName(new AppPermissionsFragmentWear(), packageName);
62     }
63 
setPackageName(T fragment, String packageName)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 
PermissionSwitchPreference(Activity activity)86         public PermissionSwitchPreference(Activity activity) {
87             super(activity);
88             this.mActivity = activity;
89         }
90 
91         @Override
performClick(PreferenceScreen preferenceScreen)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
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()155     public void onPause() {
156         super.onPause();
157         logAndClearToggledGroups();
158     }
159 
initializePermissionGroupList()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 
showOrAddToNonSystemPreferences(SwitchPreference pref, List<SwitchPreference> nonSystemPreferences, boolean isPlatform)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 
createSwitchPreferenceForPermission(AppPermissionGroup group, PermissionInfo perm)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 
showRevocationWarningDialog( DialogInterface.OnClickListener confirmListener, int warningMessageId)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 
getPermissionFromGroup(AppPermissionGroup group, String permName)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 
revokePermissionInGroup(AppPermissionGroup group, String permName)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 
createSwitchPreferenceForGroup(AppPermissionGroup group)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 
setPermission(AppPermissionGroup group, SwitchPreference pref, boolean grant)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 
addToggledGroup(AppPermissionGroup group)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 
logAndClearToggledGroups()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 
getPermissionInfosFromGroup(AppPermissionGroup group)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 
setPreferenceCheckedIfPresent(String preferenceKey, boolean checked)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