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 60 public static AppPermissionsFragmentWear newInstance(String packageName) { 61 return setPackageName(new AppPermissionsFragmentWear(), packageName); 62 } 63 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 86 public PermissionSwitchPreference(Activity activity) { 87 super(activity); 88 this.mActivity = activity; 89 } 90 91 @Override 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 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 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 155 public void onPause() { 156 super.onPause(); 157 logAndClearToggledGroups(); 158 } 159 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 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 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 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 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 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 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 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 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 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 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 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