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