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 package com.android.packageinstaller.permission.ui.handheld; 17 18 import android.app.ActionBar; 19 import android.app.AlertDialog; 20 import android.app.Fragment; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.DialogInterface.OnClickListener; 24 import android.content.Intent; 25 import android.graphics.drawable.Drawable; 26 import android.os.Bundle; 27 import android.preference.Preference; 28 import android.preference.Preference.OnPreferenceClickListener; 29 import android.preference.PreferenceScreen; 30 import android.preference.SwitchPreference; 31 import android.util.ArrayMap; 32 import android.util.ArraySet; 33 import android.view.Menu; 34 import android.view.MenuInflater; 35 import android.view.MenuItem; 36 import android.view.View; 37 import com.android.packageinstaller.DeviceUtils; 38 import com.android.packageinstaller.R; 39 import com.android.packageinstaller.permission.model.AppPermissionGroup; 40 import com.android.packageinstaller.permission.model.PermissionApps; 41 import com.android.packageinstaller.permission.model.PermissionApps.Callback; 42 import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; 43 import com.android.packageinstaller.permission.utils.LocationUtils; 44 import com.android.packageinstaller.permission.utils.SafetyNetLogger; 45 import com.android.packageinstaller.permission.utils.Utils; 46 import com.android.settingslib.HelpUtils; 47 import com.android.settingslib.RestrictedLockUtils; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 52 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 53 54 public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback, 55 Preference.OnPreferenceChangeListener { 56 57 private static final int MENU_SHOW_SYSTEM = Menu.FIRST; 58 private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 1; 59 private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem"; 60 newInstance(String permissionName)61 public static PermissionAppsFragment newInstance(String permissionName) { 62 return setPermissionName(new PermissionAppsFragment(), permissionName); 63 } 64 setPermissionName(T fragment, String permissionName)65 private static <T extends Fragment> T setPermissionName(T fragment, String permissionName) { 66 Bundle arguments = new Bundle(); 67 arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName); 68 fragment.setArguments(arguments); 69 return fragment; 70 } 71 72 private PermissionApps mPermissionApps; 73 74 private PreferenceScreen mExtraScreen; 75 76 private ArrayMap<String, AppPermissionGroup> mToggledGroups; 77 private ArraySet<String> mLauncherPkgs; 78 private boolean mHasConfirmedRevoke; 79 80 private boolean mShowSystem; 81 private boolean mHasSystemApps; 82 private MenuItem mShowSystemMenu; 83 private MenuItem mHideSystemMenu; 84 85 private Callback mOnPermissionsLoadedListener; 86 87 @Override onCreate(Bundle savedInstanceState)88 public void onCreate(Bundle savedInstanceState) { 89 super.onCreate(savedInstanceState); 90 setLoading(true /* loading */, false /* animate */); 91 setHasOptionsMenu(true); 92 final ActionBar ab = getActivity().getActionBar(); 93 if (ab != null) { 94 ab.setDisplayHomeAsUpEnabled(true); 95 } 96 mLauncherPkgs = Utils.getLauncherPackages(getContext()); 97 98 String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); 99 mPermissionApps = new PermissionApps(getActivity(), groupName, this); 100 mPermissionApps.refresh(true); 101 } 102 103 @Override onResume()104 public void onResume() { 105 super.onResume(); 106 mPermissionApps.refresh(true); 107 } 108 109 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)110 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 111 if (mHasSystemApps) { 112 mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, 113 R.string.menu_show_system); 114 mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, 115 R.string.menu_hide_system); 116 updateMenu(); 117 } 118 119 HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions, 120 getClass().getName()); 121 } 122 123 @Override onOptionsItemSelected(MenuItem item)124 public boolean onOptionsItemSelected(MenuItem item) { 125 switch (item.getItemId()) { 126 case android.R.id.home: 127 getActivity().finish(); 128 return true; 129 case MENU_SHOW_SYSTEM: 130 case MENU_HIDE_SYSTEM: 131 mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM; 132 if (mPermissionApps.getApps() != null) { 133 onPermissionsLoaded(mPermissionApps); 134 } 135 updateMenu(); 136 break; 137 } 138 return super.onOptionsItemSelected(item); 139 } 140 updateMenu()141 private void updateMenu() { 142 mShowSystemMenu.setVisible(!mShowSystem); 143 mHideSystemMenu.setVisible(mShowSystem); 144 } 145 146 @Override onViewCreated(View view, Bundle savedInstanceState)147 public void onViewCreated(View view, Bundle savedInstanceState) { 148 super.onViewCreated(view, savedInstanceState); 149 bindUi(this, mPermissionApps); 150 } 151 bindUi(Fragment fragment, PermissionApps permissionApps)152 private static void bindUi(Fragment fragment, PermissionApps permissionApps) { 153 final Drawable icon = permissionApps.getIcon(); 154 final CharSequence label = permissionApps.getLabel(); 155 final ActionBar ab = fragment.getActivity().getActionBar(); 156 if (ab != null) { 157 ab.setTitle(fragment.getString(R.string.permission_title, label)); 158 } 159 } 160 setOnPermissionsLoadedListener(Callback callback)161 private void setOnPermissionsLoadedListener(Callback callback) { 162 mOnPermissionsLoadedListener = callback; 163 } 164 165 @Override onPermissionsLoaded(PermissionApps permissionApps)166 public void onPermissionsLoaded(PermissionApps permissionApps) { 167 Context context = getActivity(); 168 169 if (context == null) { 170 return; 171 } 172 173 boolean isTelevision = DeviceUtils.isTelevision(context); 174 PreferenceScreen screen = getPreferenceScreen(); 175 if (screen == null) { 176 screen = getPreferenceManager().createPreferenceScreen(getActivity()); 177 setPreferenceScreen(screen); 178 } 179 180 ArraySet<String> preferencesToRemove = new ArraySet<>(); 181 for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) { 182 preferencesToRemove.add(screen.getPreference(i).getKey()); 183 } 184 if (mExtraScreen != null) { 185 for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { 186 preferencesToRemove.add(mExtraScreen.getPreference(i).getKey()); 187 } 188 } 189 190 mHasSystemApps = false; 191 boolean menuOptionsInvalided = false; 192 193 for (PermissionApp app : permissionApps.getApps()) { 194 if (!Utils.shouldShowPermission(app)) { 195 continue; 196 } 197 198 String key = app.getKey(); 199 preferencesToRemove.remove(key); 200 Preference existingPref = screen.findPreference(key); 201 if (existingPref == null && mExtraScreen != null) { 202 existingPref = mExtraScreen.findPreference(key); 203 } 204 205 boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs); 206 207 if (isSystemApp && !menuOptionsInvalided) { 208 mHasSystemApps = true; 209 getActivity().invalidateOptionsMenu(); 210 menuOptionsInvalided = true; 211 } 212 213 if (isSystemApp && !isTelevision && !mShowSystem) { 214 if (existingPref != null) { 215 screen.removePreference(existingPref); 216 } 217 continue; 218 } 219 220 if (existingPref != null) { 221 // If existing preference - only update its state. 222 final boolean isPolicyFixed = app.isPolicyFixed(); 223 EnforcedAdmin enforcedAdmin = RestrictedLockUtils.getProfileOrDeviceOwner( 224 getActivity(), app.getUserId()); 225 if (!isTelevision && (existingPref instanceof RestrictedSwitchPreference)) { 226 ((RestrictedSwitchPreference) existingPref).setDisabledByAdmin( 227 isPolicyFixed ? enforcedAdmin : null); 228 existingPref.setSummary(isPolicyFixed ? 229 getString(R.string.disabled_by_admin_summary_text) : null); 230 } else { 231 existingPref.setEnabled(!isPolicyFixed); 232 existingPref.setSummary(isPolicyFixed ? 233 getString(R.string.permission_summary_enforced_by_policy) : null); 234 } 235 existingPref.setPersistent(false); 236 if (existingPref instanceof SwitchPreference) { 237 ((SwitchPreference) existingPref) 238 .setChecked(app.areRuntimePermissionsGranted()); 239 } 240 continue; 241 } 242 243 RestrictedSwitchPreference pref = new RestrictedSwitchPreference(context); 244 pref.setOnPreferenceChangeListener(this); 245 pref.setKey(app.getKey()); 246 pref.setIcon(app.getIcon()); 247 pref.setTitle(app.getLabel()); 248 EnforcedAdmin enforcedAdmin = RestrictedLockUtils.getProfileOrDeviceOwner( 249 getActivity(), app.getUserId()); 250 if (app.isPolicyFixed()) { 251 if (!isTelevision && enforcedAdmin != null) { 252 pref.setDisabledByAdmin(enforcedAdmin); 253 pref.setSummary(R.string.disabled_by_admin_summary_text); 254 } else { 255 pref.setEnabled(false); 256 pref.setSummary(R.string.permission_summary_enforced_by_policy); 257 } 258 } 259 pref.setPersistent(false); 260 pref.setChecked(app.areRuntimePermissionsGranted()); 261 262 if (isSystemApp && isTelevision) { 263 if (mExtraScreen == null) { 264 mExtraScreen = getPreferenceManager().createPreferenceScreen(context); 265 } 266 mExtraScreen.addPreference(pref); 267 } else { 268 screen.addPreference(pref); 269 } 270 } 271 272 if (mExtraScreen != null) { 273 preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS); 274 Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS); 275 276 if (pref == null) { 277 pref = new Preference(context); 278 pref.setKey(KEY_SHOW_SYSTEM_PREFS); 279 pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc, 280 android.R.attr.colorControlNormal)); 281 pref.setTitle(R.string.preference_show_system_apps); 282 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 283 @Override 284 public boolean onPreferenceClick(Preference preference) { 285 SystemAppsFragment frag = new SystemAppsFragment(); 286 setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME)); 287 frag.setTargetFragment(PermissionAppsFragment.this, 0); 288 getFragmentManager().beginTransaction() 289 .replace(android.R.id.content, frag) 290 .addToBackStack("SystemApps") 291 .commit(); 292 return true; 293 } 294 }); 295 screen.addPreference(pref); 296 } 297 298 int grantedCount = 0; 299 for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { 300 if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) { 301 grantedCount++; 302 } 303 } 304 pref.setSummary(getString(R.string.app_permissions_group_summary, 305 grantedCount, mExtraScreen.getPreferenceCount())); 306 } 307 308 for (String key : preferencesToRemove) { 309 Preference pref = screen.findPreference(key); 310 if (pref != null) { 311 screen.removePreference(pref); 312 } else if (mExtraScreen != null) { 313 pref = mExtraScreen.findPreference(key); 314 if (pref != null) { 315 mExtraScreen.removePreference(pref); 316 } 317 } 318 } 319 320 setLoading(false /* loading */, true /* animate */); 321 322 if (mOnPermissionsLoadedListener != null) { 323 mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps); 324 } 325 } 326 327 @Override onPreferenceChange(final Preference preference, Object newValue)328 public boolean onPreferenceChange(final Preference preference, Object newValue) { 329 String pkg = preference.getKey(); 330 final PermissionApp app = mPermissionApps.getApp(pkg); 331 332 if (app == null) { 333 return false; 334 } 335 336 addToggledGroup(app.getPackageName(), app.getPermissionGroup()); 337 338 if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(), 339 app.getPackageName())) { 340 LocationUtils.showLocationDialog(getContext(), app.getLabel()); 341 return false; 342 } 343 if (newValue == Boolean.TRUE) { 344 app.grantRuntimePermissions(); 345 } else { 346 final boolean grantedByDefault = app.hasGrantedByDefaultPermissions(); 347 if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) { 348 new AlertDialog.Builder(getContext()) 349 .setMessage(grantedByDefault ? R.string.system_warning 350 : R.string.old_sdk_deny_warning) 351 .setNegativeButton(R.string.cancel, null) 352 .setPositiveButton(R.string.grant_dialog_button_deny_anyway, 353 new OnClickListener() { 354 @Override 355 public void onClick(DialogInterface dialog, int which) { 356 ((SwitchPreference) preference).setChecked(false); 357 app.revokeRuntimePermissions(); 358 if (!grantedByDefault) { 359 mHasConfirmedRevoke = true; 360 } 361 } 362 }) 363 .show(); 364 return false; 365 } else { 366 app.revokeRuntimePermissions(); 367 } 368 } 369 return true; 370 } 371 372 @Override onPause()373 public void onPause() { 374 super.onPause(); 375 logToggledGroups(); 376 } 377 addToggledGroup(String packageName, AppPermissionGroup group)378 private void addToggledGroup(String packageName, AppPermissionGroup group) { 379 if (mToggledGroups == null) { 380 mToggledGroups = new ArrayMap<>(); 381 } 382 // Double toggle is back to initial state. 383 if (mToggledGroups.containsKey(packageName)) { 384 mToggledGroups.remove(packageName); 385 } else { 386 mToggledGroups.put(packageName, group); 387 } 388 } 389 logToggledGroups()390 private void logToggledGroups() { 391 if (mToggledGroups != null) { 392 final int groupCount = mToggledGroups.size(); 393 for (int i = 0; i < groupCount; i++) { 394 String packageName = mToggledGroups.keyAt(i); 395 List<AppPermissionGroup> groups = new ArrayList<>(); 396 groups.add(mToggledGroups.valueAt(i)); 397 SafetyNetLogger.logPermissionsToggled(packageName, groups); 398 } 399 mToggledGroups = null; 400 } 401 } 402 403 public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback { 404 PermissionAppsFragment mOuterFragment; 405 406 @Override onCreate(Bundle savedInstanceState)407 public void onCreate(Bundle savedInstanceState) { 408 mOuterFragment = (PermissionAppsFragment) getTargetFragment(); 409 setLoading(true /* loading */, false /* animate */); 410 super.onCreate(savedInstanceState); 411 if (mOuterFragment.mExtraScreen != null) { 412 setPreferenceScreen(); 413 } else { 414 mOuterFragment.setOnPermissionsLoadedListener(this); 415 } 416 } 417 418 @Override onViewCreated(View view, Bundle savedInstanceState)419 public void onViewCreated(View view, Bundle savedInstanceState) { 420 super.onViewCreated(view, savedInstanceState); 421 String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); 422 PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null); 423 bindUi(this, permissionApps); 424 } 425 426 @Override onPermissionsLoaded(PermissionApps permissionApps)427 public void onPermissionsLoaded(PermissionApps permissionApps) { 428 setPreferenceScreen(); 429 mOuterFragment.setOnPermissionsLoadedListener(null); 430 } 431 setPreferenceScreen()432 private void setPreferenceScreen() { 433 setPreferenceScreen(mOuterFragment.mExtraScreen); 434 setLoading(false /* loading */, true /* animate */); 435 } 436 } 437 } 438