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