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.permissioncontroller.permission.ui.wear;
18 
19 import android.app.Activity;
20 import android.content.DialogInterface;
21 import android.content.Intent;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PermissionInfo;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.util.ArraySet;
29 import android.util.Log;
30 import android.view.View;
31 import android.widget.Toast;
32 
33 import androidx.fragment.app.Fragment;
34 import androidx.preference.Preference;
35 import androidx.preference.PreferenceFragmentCompat;
36 import androidx.preference.SwitchPreference;
37 import androidx.wear.ble.view.WearableDialogHelper;
38 
39 import com.android.permissioncontroller.R;
40 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
41 import com.android.permissioncontroller.permission.model.AppPermissions;
42 import com.android.permissioncontroller.permission.model.Permission;
43 import com.android.permissioncontroller.permission.utils.ArrayUtils;
44 import com.android.permissioncontroller.permission.utils.LocationUtils;
45 import com.android.permissioncontroller.permission.utils.SafetyNetLogger;
46 import com.android.permissioncontroller.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 PreferenceFragmentCompat {
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 ArraySet<AppPermissionGroup> mToggledGroups;
71     private AppPermissions mAppPermissions;
72 
73     private boolean mHasConfirmedRevoke;
74 
75     /**
76      * Provides click behavior for disabled preferences.
77      */
78     private static class PermissionSwitchPreference extends SwitchPreference {
79 
80         private final Activity mActivity;
81 
PermissionSwitchPreference(Activity activity)82         public PermissionSwitchPreference(Activity activity) {
83             super(activity);
84             this.mActivity = activity;
85         }
86 
87         @Override
performClick(View view)88         protected void performClick(View view) {
89             super.performClick(view);
90 
91             if (!isEnabled()) {
92                 // If setting the permission is disabled, it must have been locked
93                 // by the device or profile owner. So get that info and pass it to
94                 // the support details dialog.
95                 EnforcedAdmin deviceOrProfileOwner = RestrictedLockUtils.getProfileOrDeviceOwner(
96                         mActivity, UserHandle.of(UserHandle.myUserId()));
97                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
98                         mActivity, deviceOrProfileOwner);
99             }
100         }
101     }
102 
103     @Override
onCreatePreferences(Bundle bundle, String s)104     public void onCreatePreferences(Bundle bundle, String s) {
105         // empty
106     }
107 
108     @Override
onCreate(Bundle savedInstanceState)109     public void onCreate(Bundle savedInstanceState) {
110         super.onCreate(savedInstanceState);
111 
112         String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
113         Activity activity = getActivity();
114         mPackageManager = activity.getPackageManager();
115 
116         PackageInfo packageInfo;
117 
118         try {
119             packageInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
120         } catch (PackageManager.NameNotFoundException e) {
121             Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
122             packageInfo = null;
123         }
124 
125         if (packageInfo == null) {
126             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
127             activity.finish();
128             return;
129         }
130 
131         mAppPermissions = new AppPermissions(
132                 activity, packageInfo, true, () -> getActivity().finish());
133 
134         addPreferencesFromResource(R.xml.watch_permissions);
135         initializePermissionGroupList();
136     }
137 
138     @Override
onResume()139     public void onResume() {
140         super.onResume();
141         mAppPermissions.refresh();
142 
143         // Also refresh the UI
144         for (final AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
145             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
146                 for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
147                     setPreferenceCheckedIfPresent(perm.name,
148                             group.areRuntimePermissionsGranted(new String[]{ perm.name }));
149                 }
150             } else {
151                 setPreferenceCheckedIfPresent(group.getName(), group.areRuntimePermissionsGranted());
152             }
153         }
154     }
155 
156     @Override
onPause()157     public void onPause() {
158         super.onPause();
159         logAndClearToggledGroups();
160     }
161 
initializePermissionGroupList()162     private void initializePermissionGroupList() {
163         List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
164         List<SwitchPreference> nonSystemPreferences = new ArrayList<>();
165 
166         if (!groups.isEmpty()) {
167             getPreferenceScreen().removePreference(findPreference(KEY_NO_PERMISSIONS));
168         }
169 
170         for (final AppPermissionGroup group : groups) {
171             if (!Utils.shouldShowPermission(getContext(), group)) {
172                 continue;
173             }
174 
175             boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
176 
177             if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
178                 // If permission is controlled individually, we show all requested permission
179                 // inside this group.
180                 for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
181                     final SwitchPreference pref = createSwitchPreferenceForPermission(group, perm);
182                     showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
183                 }
184             } else {
185                 final SwitchPreference pref = createSwitchPreferenceForGroup(group);
186                 showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
187             }
188         }
189 
190         // Now add the non-system settings to the end of the list
191         for (SwitchPreference nonSystemPreference : nonSystemPreferences) {
192             getPreferenceScreen().addPreference(nonSystemPreference);
193         }
194     }
195 
showOrAddToNonSystemPreferences(SwitchPreference pref, List<SwitchPreference> nonSystemPreferences, boolean isPlatform)196     private void showOrAddToNonSystemPreferences(SwitchPreference pref,
197             List<SwitchPreference> nonSystemPreferences, // Mutate
198             boolean isPlatform) {
199         // The UI shows System settings first, then non-system settings
200         if (isPlatform) {
201             getPreferenceScreen().addPreference(pref);
202         } else {
203             nonSystemPreferences.add(pref);
204         }
205     }
206 
createSwitchPreferenceForPermission(AppPermissionGroup group, PermissionInfo perm)207     private SwitchPreference createSwitchPreferenceForPermission(AppPermissionGroup group,
208             PermissionInfo perm) {
209         final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
210         pref.setKey(perm.name);
211         pref.setTitle(perm.loadLabel(mPackageManager));
212         pref.setChecked(group.areRuntimePermissionsGranted(new String[]{ perm.name }));
213         pref.setOnPreferenceChangeListener((p, newVal) -> {
214             if((Boolean) newVal) {
215                 group.grantRuntimePermissions(true, false, new String[]{ perm.name });
216 
217                 if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())
218                         && group.doesSupportRuntimePermissions()) {
219                     // We are granting a permission from a group but since this is an
220                     // individual permission control other permissions in the group may
221                     // be revoked, hence we need to mark them user fixed to prevent the
222                     // app from requesting a non-granted permission and it being granted
223                     // because another permission in the group is granted. This applies
224                     // only to apps that support runtime permissions.
225                     String[] revokedPermissionsToFix = null;
226                     final int permissionCount = group.getPermissions().size();
227 
228                     for (int i = 0; i < permissionCount; i++) {
229                         Permission current = group.getPermissions().get(i);
230                         if (!current.isGranted() && !current.isUserFixed()) {
231                             revokedPermissionsToFix = ArrayUtils.appendString(
232                                     revokedPermissionsToFix, current.getName());
233                         }
234                     }
235 
236                     if (revokedPermissionsToFix != null) {
237                         // If some permissions were not granted then they should be fixed.
238                         group.revokeRuntimePermissions(true, revokedPermissionsToFix);
239                     }
240                 }
241             } else {
242                 final Permission appPerm = getPermissionFromGroup(group, perm.name);
243                 if (appPerm == null) {
244                     return false;
245                 }
246 
247                 final boolean grantedByDefault = appPerm.isGrantedByDefault();
248                 if (grantedByDefault
249                         || (!group.doesSupportRuntimePermissions() && !mHasConfirmedRevoke)) {
250                     showRevocationWarningDialog(
251                             (dialog, which) -> {
252                                 revokePermissionInGroup(group, perm.name);
253                                 pref.setChecked(false);
254                                 if (!appPerm.isGrantedByDefault()) {
255                                     mHasConfirmedRevoke = true;
256                                 }
257                             },
258                             grantedByDefault
259                                     ? R.string.system_warning
260                                     : R.string.old_sdk_deny_warning);
261                     return false;
262                 } else {
263                     revokePermissionInGroup(group, perm.name);
264                 }
265             }
266 
267             return true;
268         });
269         return pref;
270     }
271 
showRevocationWarningDialog( DialogInterface.OnClickListener confirmListener, int warningMessageId)272     private void showRevocationWarningDialog(
273             DialogInterface.OnClickListener confirmListener,
274             int warningMessageId) {
275         new WearableDialogHelper.DialogBuilder(getContext())
276                 .setNegativeIcon(R.drawable.confirm_button)
277                 .setPositiveIcon(R.drawable.cancel_button)
278                 .setNegativeButton(R.string.grant_dialog_button_deny_anyway, confirmListener)
279                 .setPositiveButton(R.string.cancel, null)
280                 .setMessage(warningMessageId)
281                 .show();
282     }
283 
getPermissionFromGroup(AppPermissionGroup group, String permName)284     private static Permission getPermissionFromGroup(AppPermissionGroup group, String permName) {
285         final int permissionCount = group.getPermissions().size();
286 
287         for (int i = 0; i < permissionCount; i++) {
288             Permission currentPerm = group.getPermissions().get(i);
289             if(currentPerm.getName().equals(permName)) {
290                 return currentPerm;
291             };
292         }
293 
294         if ("user".equals(Build.TYPE)) {
295             Log.e(LOG_TAG, String.format("The impossible happens, permission %s is not in group %s.",
296                     permName, group.getName()));
297             return null;
298         } else {
299             // This is impossible, throw a fatal error in non-user build.
300             throw new IllegalArgumentException(
301                     String.format("Permission %s is not in group %s", permName, group.getName()));
302         }
303     }
304 
revokePermissionInGroup(AppPermissionGroup group, String permName)305     private void revokePermissionInGroup(AppPermissionGroup group, String permName) {
306         group.revokeRuntimePermissions(true, new String[]{ permName });
307 
308         if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())
309                 && group.doesSupportRuntimePermissions()
310                 && !group.areRuntimePermissionsGranted()) {
311             // If we just revoked the last permission we need to clear
312             // the user fixed state as now the app should be able to
313             // request them at runtime if supported.
314             group.revokeRuntimePermissions(false);
315         }
316     }
317 
createSwitchPreferenceForGroup(AppPermissionGroup group)318     private SwitchPreference createSwitchPreferenceForGroup(AppPermissionGroup group) {
319         final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
320 
321         pref.setKey(group.getName());
322         pref.setTitle(group.getLabel());
323         pref.setChecked(group.areRuntimePermissionsGranted());
324 
325         if (group.isSystemFixed() || group.isPolicyFixed()) {
326             pref.setEnabled(false);
327         } else {
328             pref.setOnPreferenceChangeListener((p, newVal) -> {
329                 if (LocationUtils.isLocationGroupAndProvider(getContext(),
330                         group.getName(), group.getApp().packageName)) {
331                     LocationUtils.showLocationDialog(
332                             getContext(), mAppPermissions.getAppLabel());
333                     return false;
334                 }
335 
336                 if ((Boolean) newVal) {
337                     setPermission(group, pref, true);
338                 } else {
339                     final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
340                     if (grantedByDefault
341                             || (!group.doesSupportRuntimePermissions() && !mHasConfirmedRevoke)) {
342                         showRevocationWarningDialog(
343                                 (dialog, which) -> {
344                                     setPermission(group, pref, false);
345                                     if (!group.hasGrantedByDefaultPermission()) {
346                                         mHasConfirmedRevoke = true;
347                                     }
348                                 },
349                                 grantedByDefault
350                                         ? R.string.system_warning
351                                         : R.string.old_sdk_deny_warning);
352                         return false;
353                     } else {
354                         setPermission(group, pref, false);
355                     }
356                 }
357 
358                 return true;
359             });
360         }
361         return pref;
362     }
363 
setPermission(AppPermissionGroup group, SwitchPreference pref, boolean grant)364     private void setPermission(AppPermissionGroup group, SwitchPreference pref, boolean grant) {
365         if (grant) {
366             group.grantRuntimePermissions(true, false);
367         } else {
368             group.revokeRuntimePermissions(false);
369         }
370         addToggledGroup(group);
371         pref.setChecked(grant);
372     }
373 
addToggledGroup(AppPermissionGroup group)374     private void addToggledGroup(AppPermissionGroup group) {
375         if (mToggledGroups == null) {
376             mToggledGroups = new ArraySet<>();
377         }
378 
379         mToggledGroups.add(group);
380     }
381 
logAndClearToggledGroups()382     private void logAndClearToggledGroups() {
383         if (mToggledGroups != null) {
384             SafetyNetLogger.logPermissionsToggled(mToggledGroups);
385             mToggledGroups = null;
386         }
387     }
388 
getPermissionInfosFromGroup(AppPermissionGroup group)389     private List<PermissionInfo> getPermissionInfosFromGroup(AppPermissionGroup group) {
390         ArrayList<PermissionInfo> permInfos = new ArrayList<>(group.getPermissions().size());
391         for(Permission perm : group.getPermissions()) {
392             try {
393                 permInfos.add(mPackageManager.getPermissionInfo(perm.getName(), 0));
394             } catch (PackageManager.NameNotFoundException e) {
395                 Log.w(LOG_TAG, "No permission:" + perm.getName());
396             }
397         }
398         return permInfos;
399     }
400 
setPreferenceCheckedIfPresent(String preferenceKey, boolean checked)401     private void setPreferenceCheckedIfPresent(String preferenceKey, boolean checked) {
402         Preference pref = findPreference(preferenceKey);
403         if (pref instanceof SwitchPreference) {
404             ((SwitchPreference) pref).setChecked(checked);
405         }
406     }
407 }
408