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