AppRestrictionsFragment.java revision 9a23adf69dc53126c9858b19760eab5b67c23b97
1/* 2 * Copyright (C) 2013 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 17package com.android.settings.users; 18 19import android.app.Activity; 20import android.appwidget.AppWidgetManager; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.RestrictionEntry; 26import android.content.pm.ActivityInfo; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.IPackageManager; 29import android.content.pm.PackageInfo; 30import android.content.pm.PackageManager; 31import android.content.pm.PackageManager.NameNotFoundException; 32import android.content.pm.ResolveInfo; 33import android.content.res.Resources; 34import android.graphics.drawable.Drawable; 35import android.os.AsyncTask; 36import android.os.Bundle; 37import android.os.RemoteException; 38import android.os.ServiceManager; 39import android.os.UserHandle; 40import android.os.UserManager; 41import android.preference.ListPreference; 42import android.preference.MultiSelectListPreference; 43import android.preference.Preference; 44import android.preference.Preference.OnPreferenceChangeListener; 45import android.preference.Preference.OnPreferenceClickListener; 46import android.preference.PreferenceGroup; 47import android.preference.SwitchPreference; 48import android.text.TextUtils; 49import android.util.Log; 50import android.view.View; 51import android.view.View.OnClickListener; 52import android.view.ViewGroup; 53import android.view.inputmethod.InputMethodInfo; 54import android.view.inputmethod.InputMethodManager; 55import android.widget.CompoundButton; 56import android.widget.CompoundButton.OnCheckedChangeListener; 57import android.widget.Switch; 58 59import com.android.settings.R; 60import com.android.settings.SettingsPreferenceFragment; 61import com.android.settings.Utils; 62import java.util.ArrayList; 63import java.util.Collections; 64import java.util.Comparator; 65import java.util.HashMap; 66import java.util.HashSet; 67import java.util.List; 68import java.util.Map; 69import java.util.Set; 70import java.util.StringTokenizer; 71 72public class AppRestrictionsFragment extends SettingsPreferenceFragment implements 73 OnPreferenceChangeListener, OnClickListener, OnPreferenceClickListener { 74 75 private static final String TAG = AppRestrictionsFragment.class.getSimpleName(); 76 77 private static final boolean DEBUG = false; 78 79 private static final String PKG_PREFIX = "pkg_"; 80 81 protected PackageManager mPackageManager; 82 protected UserManager mUserManager; 83 protected IPackageManager mIPm; 84 protected UserHandle mUser; 85 private PackageInfo mSysPackageInfo; 86 87 private PreferenceGroup mAppList; 88 89 private static final int MAX_APP_RESTRICTIONS = 100; 90 91 private static final String DELIMITER = ";"; 92 93 /** Key for extra passed in from calling fragment for the userId of the user being edited */ 94 public static final String EXTRA_USER_ID = "user_id"; 95 96 /** Key for extra passed in from calling fragment to indicate if this is a newly created user */ 97 public static final String EXTRA_NEW_USER = "new_user"; 98 99 HashMap<String,Boolean> mSelectedPackages = new HashMap<String,Boolean>(); 100 private boolean mFirstTime = true; 101 private boolean mNewUser; 102 private boolean mAppListChanged; 103 protected boolean mRestrictedProfile; 104 105 private static final int CUSTOM_REQUEST_CODE_START = 1000; 106 private int mCustomRequestCode = CUSTOM_REQUEST_CODE_START; 107 108 private HashMap<Integer, AppRestrictionsPreference> mCustomRequestMap = 109 new HashMap<Integer,AppRestrictionsPreference>(); 110 111 private List<SelectableAppInfo> mVisibleApps; 112 private List<ApplicationInfo> mUserApps; 113 private AsyncTask mAppLoadingTask; 114 115 private BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() { 116 @Override 117 public void onReceive(Context context, Intent intent) { 118 // Update the user's app selection right away without waiting for a pause 119 // onPause() might come in too late, causing apps to disappear after broadcasts 120 // have been scheduled during user startup. 121 if (mAppListChanged) { 122 if (DEBUG) Log.d(TAG, "User backgrounding, update app list"); 123 applyUserAppsStates(); 124 if (DEBUG) Log.d(TAG, "User backgrounding, done updating app list"); 125 } 126 } 127 }; 128 129 private BroadcastReceiver mPackageObserver = new BroadcastReceiver() { 130 @Override 131 public void onReceive(Context context, Intent intent) { 132 onPackageChanged(intent); 133 } 134 }; 135 136 static class SelectableAppInfo { 137 String packageName; 138 CharSequence appName; 139 CharSequence activityName; 140 Drawable icon; 141 SelectableAppInfo masterEntry; 142 143 @Override 144 public String toString() { 145 return packageName + ": appName=" + appName + "; activityName=" + activityName 146 + "; icon=" + icon + "; masterEntry=" + masterEntry; 147 } 148 } 149 150 static class AppRestrictionsPreference extends SwitchPreference { 151 private boolean hasSettings; 152 private OnClickListener listener; 153 private ArrayList<RestrictionEntry> restrictions; 154 private boolean panelOpen; 155 private boolean immutable; 156 private List<Preference> mChildren = new ArrayList<Preference>(); 157 158 AppRestrictionsPreference(Context context, OnClickListener listener) { 159 super(context); 160 setLayoutResource(R.layout.preference_app_restrictions); 161 this.listener = listener; 162 } 163 164 private void setSettingsEnabled(boolean enable) { 165 hasSettings = enable; 166 } 167 168 void setRestrictions(ArrayList<RestrictionEntry> restrictions) { 169 this.restrictions = restrictions; 170 } 171 172 void setImmutable(boolean immutable) { 173 this.immutable = immutable; 174 } 175 176 boolean isImmutable() { 177 return immutable; 178 } 179 180 ArrayList<RestrictionEntry> getRestrictions() { 181 return restrictions; 182 } 183 184 boolean isPanelOpen() { 185 return panelOpen; 186 } 187 188 void setPanelOpen(boolean open) { 189 panelOpen = open; 190 } 191 192 List<Preference> getChildren() { 193 return mChildren; 194 } 195 196 @Override 197 protected void onBindView(View view) { 198 super.onBindView(view); 199 200 View appRestrictionsSettings = view.findViewById(R.id.app_restrictions_settings); 201 appRestrictionsSettings.setVisibility(hasSettings ? View.VISIBLE : View.GONE); 202 view.findViewById(R.id.settings_divider).setVisibility( 203 hasSettings ? View.VISIBLE : View.GONE); 204 appRestrictionsSettings.setOnClickListener(listener); 205 appRestrictionsSettings.setTag(this); 206 207 View appRestrictionsPref = view.findViewById(R.id.app_restrictions_pref); 208 appRestrictionsPref.setOnClickListener(listener); 209 appRestrictionsPref.setTag(this); 210 211 ViewGroup widget = (ViewGroup) view.findViewById(android.R.id.widget_frame); 212 widget.setEnabled(!isImmutable()); 213 if (widget.getChildCount() > 0) { 214 final Switch toggle = (Switch) widget.getChildAt(0); 215 toggle.setEnabled(!isImmutable()); 216 toggle.setTag(this); 217 toggle.setClickable(true); 218 toggle.setFocusable(true); 219 toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() { 220 @Override 221 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 222 listener.onClick(toggle); 223 } 224 }); 225 } 226 } 227 } 228 229 protected void init(Bundle icicle) { 230 if (icicle != null) { 231 mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID)); 232 } else { 233 Bundle args = getArguments(); 234 if (args != null) { 235 if (args.containsKey(EXTRA_USER_ID)) { 236 mUser = new UserHandle(args.getInt(EXTRA_USER_ID)); 237 } 238 mNewUser = args.getBoolean(EXTRA_NEW_USER, false); 239 } 240 } 241 242 if (mUser == null) { 243 mUser = android.os.Process.myUserHandle(); 244 } 245 246 mPackageManager = getActivity().getPackageManager(); 247 mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 248 mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); 249 mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted(); 250 try { 251 mSysPackageInfo = mPackageManager.getPackageInfo("android", 252 PackageManager.GET_SIGNATURES); 253 } catch (NameNotFoundException nnfe) { 254 // ? 255 } 256 addPreferencesFromResource(R.xml.app_restrictions); 257 mAppList = getAppPreferenceGroup(); 258 } 259 260 @Override 261 public void onSaveInstanceState(Bundle outState) { 262 super.onSaveInstanceState(outState); 263 outState.putInt(EXTRA_USER_ID, mUser.getIdentifier()); 264 } 265 266 @Override 267 public void onResume() { 268 super.onResume(); 269 270 getActivity().registerReceiver(mUserBackgrounding, 271 new IntentFilter(Intent.ACTION_USER_BACKGROUND)); 272 IntentFilter packageFilter = new IntentFilter(); 273 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 274 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 275 packageFilter.addDataScheme("package"); 276 getActivity().registerReceiver(mPackageObserver, packageFilter); 277 278 mAppListChanged = false; 279 if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) { 280 mAppLoadingTask = new AppLoadingTask().execute((Void[]) null); 281 } 282 } 283 284 @Override 285 public void onPause() { 286 super.onPause(); 287 mNewUser = false; 288 getActivity().unregisterReceiver(mUserBackgrounding); 289 getActivity().unregisterReceiver(mPackageObserver); 290 if (mAppListChanged) { 291 new Thread() { 292 public void run() { 293 applyUserAppsStates(); 294 } 295 }.start(); 296 } 297 } 298 299 private void onPackageChanged(Intent intent) { 300 String action = intent.getAction(); 301 String packageName = intent.getData().getSchemeSpecificPart(); 302 // Package added, check if the preference needs to be enabled 303 AppRestrictionsPreference pref = (AppRestrictionsPreference) 304 findPreference(getKeyForPackage(packageName)); 305 if (pref == null) return; 306 307 if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && pref.isChecked()) 308 || (Intent.ACTION_PACKAGE_REMOVED.equals(action) && !pref.isChecked())) { 309 pref.setEnabled(true); 310 } 311 } 312 313 protected PreferenceGroup getAppPreferenceGroup() { 314 return getPreferenceScreen(); 315 } 316 317 private void applyUserAppsStates() { 318 final int userId = mUser.getIdentifier(); 319 if (!mUserManager.getUserInfo(userId).isRestricted() && userId != UserHandle.myUserId()) { 320 Log.e(TAG, "Cannot apply application restrictions on another user!"); 321 return; 322 } 323 for (Map.Entry<String,Boolean> entry : mSelectedPackages.entrySet()) { 324 String packageName = entry.getKey(); 325 boolean enabled = entry.getValue(); 326 applyUserAppState(packageName, enabled); 327 } 328 } 329 330 private void applyUserAppState(String packageName, boolean enabled) { 331 final int userId = mUser.getIdentifier(); 332 if (enabled) { 333 // Enable selected apps 334 try { 335 ApplicationInfo info = mIPm.getApplicationInfo(packageName, 336 PackageManager.GET_UNINSTALLED_PACKAGES, userId); 337 if (info == null || info.enabled == false 338 || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 339 mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier()); 340 if (DEBUG) { 341 Log.d(TAG, "Installing " + packageName); 342 } 343 } 344 if (info != null && (info.privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) != 0 345 && (info.flags&ApplicationInfo.FLAG_INSTALLED) != 0) { 346 disableUiForPackage(packageName); 347 mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId); 348 if (DEBUG) { 349 Log.d(TAG, "Unhiding " + packageName); 350 } 351 } 352 } catch (RemoteException re) { 353 } 354 } else { 355 // Blacklist all other apps, system or downloaded 356 try { 357 ApplicationInfo info = mIPm.getApplicationInfo(packageName, 0, userId); 358 if (info != null) { 359 if (mRestrictedProfile) { 360 mIPm.deletePackageAsUser(packageName, null, mUser.getIdentifier(), 361 PackageManager.DELETE_SYSTEM_APP); 362 if (DEBUG) { 363 Log.d(TAG, "Uninstalling " + packageName); 364 } 365 } else { 366 disableUiForPackage(packageName); 367 mIPm.setApplicationHiddenSettingAsUser(packageName, true, userId); 368 if (DEBUG) { 369 Log.d(TAG, "Hiding " + packageName); 370 } 371 } 372 } 373 } catch (RemoteException re) { 374 } 375 } 376 } 377 378 private void disableUiForPackage(String packageName) { 379 AppRestrictionsPreference pref = (AppRestrictionsPreference) findPreference( 380 getKeyForPackage(packageName)); 381 if (pref != null) { 382 pref.setEnabled(false); 383 } 384 } 385 386 private boolean isSystemPackage(String packageName) { 387 try { 388 final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0); 389 if (pi.applicationInfo == null) return false; 390 final int flags = pi.applicationInfo.flags; 391 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 392 || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { 393 return true; 394 } 395 } catch (NameNotFoundException nnfe) { 396 // Missing package? 397 } 398 return false; 399 } 400 401 /** 402 * Find all pre-installed input methods that are marked as default 403 * and add them to an exclusion list so that they aren't 404 * presented to the user for toggling. 405 * Don't add non-default ones, as they may include other stuff that we 406 * don't need to auto-include. 407 * @param excludePackages the set of package names to append to 408 */ 409 private void addSystemImes(Set<String> excludePackages) { 410 final Context context = getActivity(); 411 if (context == null) return; 412 InputMethodManager imm = (InputMethodManager) 413 context.getSystemService(Context.INPUT_METHOD_SERVICE); 414 List<InputMethodInfo> imis = imm.getInputMethodList(); 415 for (InputMethodInfo imi : imis) { 416 try { 417 if (imi.isDefault(context) && isSystemPackage(imi.getPackageName())) { 418 excludePackages.add(imi.getPackageName()); 419 } 420 } catch (Resources.NotFoundException rnfe) { 421 // Not default 422 } 423 } 424 } 425 426 /** 427 * Add system apps that match an intent to the list, excluding any packages in the exclude list. 428 * @param visibleApps list of apps to append the new list to 429 * @param intent the intent to match 430 * @param excludePackages the set of package names to be excluded, since they're required 431 */ 432 private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, 433 Set<String> excludePackages) { 434 if (getActivity() == null) return; 435 final PackageManager pm = mPackageManager; 436 List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent, 437 PackageManager.GET_DISABLED_COMPONENTS | PackageManager.GET_UNINSTALLED_PACKAGES); 438 for (ResolveInfo app : launchableApps) { 439 if (app.activityInfo != null && app.activityInfo.applicationInfo != null) { 440 final String packageName = app.activityInfo.packageName; 441 int flags = app.activityInfo.applicationInfo.flags; 442 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 443 || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { 444 // System app 445 // Skip excluded packages 446 if (excludePackages.contains(packageName)) continue; 447 int enabled = pm.getApplicationEnabledSetting(packageName); 448 if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED 449 || enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { 450 // Check if the app is already enabled for the target user 451 ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName, 452 0, mUser); 453 if (targetUserAppInfo == null 454 || (targetUserAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 455 continue; 456 } 457 } 458 SelectableAppInfo info = new SelectableAppInfo(); 459 info.packageName = app.activityInfo.packageName; 460 info.appName = app.activityInfo.applicationInfo.loadLabel(pm); 461 info.icon = app.activityInfo.loadIcon(pm); 462 info.activityName = app.activityInfo.loadLabel(pm); 463 if (info.activityName == null) info.activityName = info.appName; 464 465 visibleApps.add(info); 466 } 467 } 468 } 469 } 470 471 private ApplicationInfo getAppInfoForUser(String packageName, int flags, UserHandle user) { 472 try { 473 ApplicationInfo targetUserAppInfo = mIPm.getApplicationInfo(packageName, flags, 474 user.getIdentifier()); 475 return targetUserAppInfo; 476 } catch (RemoteException re) { 477 return null; 478 } 479 } 480 481 private class AppLoadingTask extends AsyncTask<Void, Void, Void> { 482 483 @Override 484 protected Void doInBackground(Void... params) { 485 fetchAndMergeApps(); 486 return null; 487 } 488 489 @Override 490 protected void onPostExecute(Void result) { 491 populateApps(); 492 } 493 494 @Override 495 protected void onPreExecute() { 496 } 497 } 498 499 private void fetchAndMergeApps() { 500 mAppList.setOrderingAsAdded(false); 501 mVisibleApps = new ArrayList<SelectableAppInfo>(); 502 final Context context = getActivity(); 503 if (context == null) return; 504 final PackageManager pm = mPackageManager; 505 final IPackageManager ipm = mIPm; 506 507 final HashSet<String> excludePackages = new HashSet<String>(); 508 addSystemImes(excludePackages); 509 510 // Add launchers 511 Intent launcherIntent = new Intent(Intent.ACTION_MAIN); 512 launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); 513 addSystemApps(mVisibleApps, launcherIntent, excludePackages); 514 515 // Add widgets 516 Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 517 addSystemApps(mVisibleApps, widgetIntent, excludePackages); 518 519 List<ApplicationInfo> installedApps = pm.getInstalledApplications( 520 PackageManager.GET_UNINSTALLED_PACKAGES); 521 for (ApplicationInfo app : installedApps) { 522 // If it's not installed, skip 523 if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue; 524 525 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 526 && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { 527 // Downloaded app 528 SelectableAppInfo info = new SelectableAppInfo(); 529 info.packageName = app.packageName; 530 info.appName = app.loadLabel(pm); 531 info.activityName = info.appName; 532 info.icon = app.loadIcon(pm); 533 mVisibleApps.add(info); 534 } else { 535 try { 536 PackageInfo pi = pm.getPackageInfo(app.packageName, 0); 537 // If it's a system app that requires an account and doesn't see restricted 538 // accounts, mark for removal. It might get shown in the UI if it has an icon 539 // but will still be marked as false and immutable. 540 if (mRestrictedProfile 541 && pi.requiredAccountType != null && pi.restrictedAccountType == null) { 542 mSelectedPackages.put(app.packageName, false); 543 } 544 } catch (NameNotFoundException re) { 545 } 546 } 547 } 548 549 // Get the list of apps already installed for the user 550 mUserApps = null; 551 try { 552 mUserApps = ipm.getInstalledApplications( 553 PackageManager.GET_UNINSTALLED_PACKAGES, mUser.getIdentifier()).getList(); 554 } catch (RemoteException re) { 555 } 556 557 if (mUserApps != null) { 558 for (ApplicationInfo app : mUserApps) { 559 if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue; 560 561 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 562 && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { 563 // Downloaded app 564 SelectableAppInfo info = new SelectableAppInfo(); 565 info.packageName = app.packageName; 566 info.appName = app.loadLabel(pm); 567 info.activityName = info.appName; 568 info.icon = app.loadIcon(pm); 569 mVisibleApps.add(info); 570 } 571 } 572 } 573 574 // Sort the list of visible apps 575 Collections.sort(mVisibleApps, new AppLabelComparator()); 576 577 // Remove dupes 578 Set<String> dedupPackageSet = new HashSet<String>(); 579 for (int i = mVisibleApps.size() - 1; i >= 0; i--) { 580 SelectableAppInfo info = mVisibleApps.get(i); 581 if (DEBUG) Log.i(TAG, info.toString()); 582 String both = info.packageName + "+" + info.activityName; 583 if (!TextUtils.isEmpty(info.packageName) 584 && !TextUtils.isEmpty(info.activityName) 585 && dedupPackageSet.contains(both)) { 586 mVisibleApps.remove(i); 587 } else { 588 dedupPackageSet.add(both); 589 } 590 } 591 592 // Establish master/slave relationship for entries that share a package name 593 HashMap<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>(); 594 for (SelectableAppInfo info : mVisibleApps) { 595 if (packageMap.containsKey(info.packageName)) { 596 info.masterEntry = packageMap.get(info.packageName); 597 } else { 598 packageMap.put(info.packageName, info); 599 } 600 } 601 } 602 603 private boolean isPlatformSigned(PackageInfo pi) { 604 return (pi != null && pi.signatures != null && 605 mSysPackageInfo.signatures[0].equals(pi.signatures[0])); 606 } 607 608 private boolean isAppEnabledForUser(PackageInfo pi) { 609 if (pi == null) return false; 610 final int flags = pi.applicationInfo.flags; 611 final int privateFlags = pi.applicationInfo.privateFlags; 612 // Return true if it is installed and not hidden 613 return ((flags&ApplicationInfo.FLAG_INSTALLED) != 0 614 && (privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0); 615 } 616 617 private void populateApps() { 618 final Context context = getActivity(); 619 if (context == null) return; 620 final PackageManager pm = mPackageManager; 621 final IPackageManager ipm = mIPm; 622 final int userId = mUser.getIdentifier(); 623 624 // Check if the user was removed in the meantime. 625 if (Utils.getExistingUser(mUserManager, mUser) == null) { 626 return; 627 } 628 mAppList.removeAll(); 629 Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 630 final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0); 631 for (SelectableAppInfo app : mVisibleApps) { 632 String packageName = app.packageName; 633 if (packageName == null) continue; 634 final boolean isSettingsApp = packageName.equals(context.getPackageName()); 635 AppRestrictionsPreference p = new AppRestrictionsPreference(context, this); 636 final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName); 637 if (isSettingsApp) { 638 addLocationAppRestrictionsPreference(app, p); 639 // Settings app should be available to restricted user 640 mSelectedPackages.put(packageName, true); 641 continue; 642 } 643 PackageInfo pi = null; 644 try { 645 pi = ipm.getPackageInfo(packageName, 646 PackageManager.GET_UNINSTALLED_PACKAGES 647 | PackageManager.GET_SIGNATURES, userId); 648 } catch (RemoteException e) { 649 } 650 if (pi == null) { 651 continue; 652 } 653 if (mRestrictedProfile && isAppUnsupportedInRestrictedProfile(pi)) { 654 continue; 655 } 656 p.setIcon(app.icon != null ? app.icon.mutate() : null); 657 p.setChecked(false); 658 p.setTitle(app.activityName); 659 p.setKey(getKeyForPackage(packageName)); 660 p.setSettingsEnabled(hasSettings && app.masterEntry == null); 661 p.setPersistent(false); 662 p.setOnPreferenceChangeListener(this); 663 p.setOnPreferenceClickListener(this); 664 p.setSummary(getPackageSummary(pi, app)); 665 if (pi.requiredForAllUsers || isPlatformSigned(pi)) { 666 p.setChecked(true); 667 p.setImmutable(true); 668 // If the app is required and has no restrictions, skip showing it 669 if (!hasSettings) continue; 670 // Get and populate the defaults, since the user is not going to be 671 // able to toggle this app ON (it's ON by default and immutable). 672 // Only do this for restricted profiles, not single-user restrictions 673 // Also don't do this for slave icons 674 if (app.masterEntry == null) { 675 requestRestrictionsForApp(packageName, p, false); 676 } 677 } else if (!mNewUser && isAppEnabledForUser(pi)) { 678 p.setChecked(true); 679 } 680 if (app.masterEntry != null) { 681 p.setImmutable(true); 682 p.setChecked(mSelectedPackages.get(packageName)); 683 } 684 p.setOrder(MAX_APP_RESTRICTIONS * (mAppList.getPreferenceCount() + 2)); 685 mSelectedPackages.put(packageName, p.isChecked()); 686 mAppList.addPreference(p); 687 } 688 mAppListChanged = true; 689 // If this is the first time for a new profile, install/uninstall default apps for profile 690 // to avoid taking the hit in onPause(), which can cause race conditions on user switch. 691 if (mNewUser && mFirstTime) { 692 mFirstTime = false; 693 applyUserAppsStates(); 694 } 695 } 696 697 private String getPackageSummary(PackageInfo pi, SelectableAppInfo app) { 698 // Check for 3 cases: 699 // - Slave entry that can see primary user accounts 700 // - Slave entry that cannot see primary user accounts 701 // - Master entry that can see primary user accounts 702 // Otherwise no summary is returned 703 if (app.masterEntry != null) { 704 if (mRestrictedProfile && pi.restrictedAccountType != null) { 705 return getString(R.string.app_sees_restricted_accounts_and_controlled_by, 706 app.masterEntry.activityName); 707 } 708 return getString(R.string.user_restrictions_controlled_by, 709 app.masterEntry.activityName); 710 } else if (pi.restrictedAccountType != null) { 711 return getString(R.string.app_sees_restricted_accounts); 712 } 713 return null; 714 } 715 716 private static boolean isAppUnsupportedInRestrictedProfile(PackageInfo pi) { 717 return pi.requiredAccountType != null && pi.restrictedAccountType == null; 718 } 719 720 private void addLocationAppRestrictionsPreference(SelectableAppInfo app, 721 AppRestrictionsPreference p) { 722 String packageName = app.packageName; 723 p.setIcon(R.drawable.ic_settings_location); 724 p.setKey(getKeyForPackage(packageName)); 725 ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions( 726 getActivity(), mUser); 727 RestrictionEntry locationRestriction = restrictions.get(0); 728 p.setTitle(locationRestriction.getTitle()); 729 p.setRestrictions(restrictions); 730 p.setSummary(locationRestriction.getDescription()); 731 p.setChecked(locationRestriction.getSelectedState()); 732 p.setPersistent(false); 733 p.setOnPreferenceClickListener(this); 734 p.setOrder(MAX_APP_RESTRICTIONS); 735 mAppList.addPreference(p); 736 } 737 738 private String getKeyForPackage(String packageName) { 739 return PKG_PREFIX + packageName; 740 } 741 742 private class AppLabelComparator implements Comparator<SelectableAppInfo> { 743 744 @Override 745 public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) { 746 String lhsLabel = lhs.activityName.toString(); 747 String rhsLabel = rhs.activityName.toString(); 748 return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase()); 749 } 750 } 751 752 private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) { 753 for (ResolveInfo info : receivers) { 754 if (info.activityInfo.packageName.equals(packageName)) { 755 return true; 756 } 757 } 758 return false; 759 } 760 761 private void updateAllEntries(String prefKey, boolean checked) { 762 for (int i = 0; i < mAppList.getPreferenceCount(); i++) { 763 Preference pref = mAppList.getPreference(i); 764 if (pref instanceof AppRestrictionsPreference) { 765 if (prefKey.equals(pref.getKey())) { 766 ((AppRestrictionsPreference) pref).setChecked(checked); 767 } 768 } 769 } 770 } 771 772 @Override 773 public void onClick(View v) { 774 if (v.getTag() instanceof AppRestrictionsPreference) { 775 AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag(); 776 if (v.getId() == R.id.app_restrictions_settings) { 777 onAppSettingsIconClicked(pref); 778 } else if (!pref.isImmutable()) { 779 pref.setChecked(!pref.isChecked()); 780 final String packageName = pref.getKey().substring(PKG_PREFIX.length()); 781 // Settings/Location is handled as a top-level entry 782 if (packageName.equals(getActivity().getPackageName())) { 783 pref.restrictions.get(0).setSelectedState(pref.isChecked()); 784 RestrictionUtils.setRestrictions(getActivity(), pref.restrictions, mUser); 785 return; 786 } 787 mSelectedPackages.put(packageName, pref.isChecked()); 788 if (pref.isChecked() && pref.hasSettings 789 && pref.restrictions == null) { 790 // The restrictions have not been initialized, get and save them 791 requestRestrictionsForApp(packageName, pref, false); 792 } 793 mAppListChanged = true; 794 // If it's not a restricted profile, apply the changes immediately 795 if (!mRestrictedProfile) { 796 applyUserAppState(packageName, pref.isChecked()); 797 } 798 updateAllEntries(pref.getKey(), pref.isChecked()); 799 } 800 } 801 } 802 803 @Override 804 public boolean onPreferenceChange(Preference preference, Object newValue) { 805 String key = preference.getKey(); 806 if (key != null && key.contains(DELIMITER)) { 807 StringTokenizer st = new StringTokenizer(key, DELIMITER); 808 final String packageName = st.nextToken(); 809 final String restrictionKey = st.nextToken(); 810 AppRestrictionsPreference appPref = (AppRestrictionsPreference) 811 mAppList.findPreference(PKG_PREFIX+packageName); 812 ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions(); 813 if (restrictions != null) { 814 for (RestrictionEntry entry : restrictions) { 815 if (entry.getKey().equals(restrictionKey)) { 816 switch (entry.getType()) { 817 case RestrictionEntry.TYPE_BOOLEAN: 818 entry.setSelectedState((Boolean) newValue); 819 break; 820 case RestrictionEntry.TYPE_CHOICE: 821 case RestrictionEntry.TYPE_CHOICE_LEVEL: 822 ListPreference listPref = (ListPreference) preference; 823 entry.setSelectedString((String) newValue); 824 String readable = findInArray(entry.getChoiceEntries(), 825 entry.getChoiceValues(), (String) newValue); 826 listPref.setSummary(readable); 827 break; 828 case RestrictionEntry.TYPE_MULTI_SELECT: 829 Set<String> set = (Set<String>) newValue; 830 String [] selectedValues = new String[set.size()]; 831 set.toArray(selectedValues); 832 entry.setAllSelectedStrings(selectedValues); 833 break; 834 default: 835 continue; 836 } 837 mUserManager.setApplicationRestrictions(packageName, 838 RestrictionUtils.restrictionsToBundle(restrictions), 839 mUser); 840 break; 841 } 842 } 843 } 844 } 845 return true; 846 } 847 848 private void removeRestrictionsForApp(AppRestrictionsPreference preference) { 849 for (Preference p : preference.mChildren) { 850 mAppList.removePreference(p); 851 } 852 preference.mChildren.clear(); 853 } 854 855 private void onAppSettingsIconClicked(AppRestrictionsPreference preference) { 856 if (preference.getKey().startsWith(PKG_PREFIX)) { 857 if (preference.isPanelOpen()) { 858 removeRestrictionsForApp(preference); 859 } else { 860 String packageName = preference.getKey().substring(PKG_PREFIX.length()); 861 requestRestrictionsForApp(packageName, preference, true /*invoke if custom*/); 862 } 863 preference.setPanelOpen(!preference.isPanelOpen()); 864 } 865 } 866 867 /** 868 * Send a broadcast to the app to query its restrictions 869 * @param packageName package name of the app with restrictions 870 * @param preference the preference item for the app toggle 871 * @param invokeIfCustom whether to directly launch any custom activity that is returned 872 * for the app. 873 */ 874 private void requestRestrictionsForApp(String packageName, 875 AppRestrictionsPreference preference, boolean invokeIfCustom) { 876 Bundle oldEntries = 877 mUserManager.getApplicationRestrictions(packageName, mUser); 878 Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 879 intent.setPackage(packageName); 880 intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries); 881 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 882 getActivity().sendOrderedBroadcast(intent, null, 883 new RestrictionsResultReceiver(packageName, preference, invokeIfCustom), 884 null, Activity.RESULT_OK, null, null); 885 } 886 887 class RestrictionsResultReceiver extends BroadcastReceiver { 888 889 private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT; 890 String packageName; 891 AppRestrictionsPreference preference; 892 boolean invokeIfCustom; 893 894 RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference, 895 boolean invokeIfCustom) { 896 super(); 897 this.packageName = packageName; 898 this.preference = preference; 899 this.invokeIfCustom = invokeIfCustom; 900 } 901 902 @Override 903 public void onReceive(Context context, Intent intent) { 904 Bundle results = getResultExtras(true); 905 final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList( 906 Intent.EXTRA_RESTRICTIONS_LIST); 907 Intent restrictionsIntent = (Intent) results.getParcelable(CUSTOM_RESTRICTIONS_INTENT); 908 if (restrictions != null && restrictionsIntent == null) { 909 onRestrictionsReceived(preference, packageName, restrictions); 910 if (mRestrictedProfile) { 911 mUserManager.setApplicationRestrictions(packageName, 912 RestrictionUtils.restrictionsToBundle(restrictions), mUser); 913 } 914 } else if (restrictionsIntent != null) { 915 preference.setRestrictions(restrictions); 916 if (invokeIfCustom && AppRestrictionsFragment.this.isResumed()) { 917 assertSafeToStartCustomActivity(restrictionsIntent); 918 int requestCode = generateCustomActivityRequestCode( 919 RestrictionsResultReceiver.this.preference); 920 AppRestrictionsFragment.this.startActivityForResult( 921 restrictionsIntent, requestCode); 922 } 923 } 924 } 925 926 private void assertSafeToStartCustomActivity(Intent intent) { 927 // Activity can be started if it belongs to the same app 928 if (intent.getPackage() != null && intent.getPackage().equals(packageName)) { 929 return; 930 } 931 // Activity can be started if intent resolves to multiple activities 932 List<ResolveInfo> resolveInfos = AppRestrictionsFragment.this.mPackageManager 933 .queryIntentActivities(intent, 0 /* no flags */); 934 if (resolveInfos.size() != 1) { 935 return; 936 } 937 // Prevent potential privilege escalation 938 ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; 939 if (!packageName.equals(activityInfo.packageName)) { 940 throw new SecurityException("Application " + packageName 941 + " is not allowed to start activity " + intent); 942 }; 943 } 944 } 945 946 private void onRestrictionsReceived(AppRestrictionsPreference preference, String packageName, 947 ArrayList<RestrictionEntry> restrictions) { 948 // Remove any earlier restrictions 949 removeRestrictionsForApp(preference); 950 // Non-custom-activity case - expand the restrictions in-place 951 final Context context = preference.getContext(); 952 int count = 1; 953 for (RestrictionEntry entry : restrictions) { 954 Preference p = null; 955 switch (entry.getType()) { 956 case RestrictionEntry.TYPE_BOOLEAN: 957 p = new SwitchPreference(context); 958 p.setTitle(entry.getTitle()); 959 p.setSummary(entry.getDescription()); 960 ((SwitchPreference)p).setChecked(entry.getSelectedState()); 961 break; 962 case RestrictionEntry.TYPE_CHOICE: 963 case RestrictionEntry.TYPE_CHOICE_LEVEL: 964 p = new ListPreference(context); 965 p.setTitle(entry.getTitle()); 966 String value = entry.getSelectedString(); 967 if (value == null) { 968 value = entry.getDescription(); 969 } 970 p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(), 971 value)); 972 ((ListPreference)p).setEntryValues(entry.getChoiceValues()); 973 ((ListPreference)p).setEntries(entry.getChoiceEntries()); 974 ((ListPreference)p).setValue(value); 975 ((ListPreference)p).setDialogTitle(entry.getTitle()); 976 break; 977 case RestrictionEntry.TYPE_MULTI_SELECT: 978 p = new MultiSelectListPreference(context); 979 p.setTitle(entry.getTitle()); 980 ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues()); 981 ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries()); 982 HashSet<String> set = new HashSet<String>(); 983 Collections.addAll(set, entry.getAllSelectedStrings()); 984 ((MultiSelectListPreference)p).setValues(set); 985 ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle()); 986 break; 987 case RestrictionEntry.TYPE_NULL: 988 default: 989 } 990 if (p != null) { 991 p.setPersistent(false); 992 p.setOrder(preference.getOrder() + count); 993 // Store the restrictions key string as a key for the preference 994 p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER 995 + entry.getKey()); 996 mAppList.addPreference(p); 997 p.setOnPreferenceChangeListener(AppRestrictionsFragment.this); 998 p.setIcon(R.drawable.empty_icon); 999 preference.mChildren.add(p); 1000 count++; 1001 } 1002 } 1003 preference.setRestrictions(restrictions); 1004 if (count == 1 // No visible restrictions 1005 && preference.isImmutable() 1006 && preference.isChecked()) { 1007 // Special case of required app with no visible restrictions. Remove it 1008 mAppList.removePreference(preference); 1009 } 1010 } 1011 1012 /** 1013 * Generates a request code that is stored in a map to retrieve the associated 1014 * AppRestrictionsPreference. 1015 * @param preference 1016 * @return 1017 */ 1018 private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) { 1019 mCustomRequestCode++; 1020 mCustomRequestMap.put(mCustomRequestCode, preference); 1021 return mCustomRequestCode; 1022 } 1023 1024 @Override 1025 public void onActivityResult(int requestCode, int resultCode, Intent data) { 1026 super.onActivityResult(requestCode, resultCode, data); 1027 1028 AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode); 1029 if (pref == null) { 1030 Log.w(TAG, "Unknown requestCode " + requestCode); 1031 return; 1032 } 1033 1034 if (resultCode == Activity.RESULT_OK) { 1035 String packageName = pref.getKey().substring(PKG_PREFIX.length()); 1036 ArrayList<RestrictionEntry> list = 1037 data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST); 1038 Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE); 1039 if (list != null) { 1040 // If there's a valid result, persist it to the user manager. 1041 pref.setRestrictions(list); 1042 mUserManager.setApplicationRestrictions(packageName, 1043 RestrictionUtils.restrictionsToBundle(list), mUser); 1044 } else if (bundle != null) { 1045 // If there's a valid result, persist it to the user manager. 1046 mUserManager.setApplicationRestrictions(packageName, bundle, mUser); 1047 } 1048 } 1049 // Remove request from the map 1050 mCustomRequestMap.remove(requestCode); 1051 } 1052 1053 private String findInArray(String[] choiceEntries, String[] choiceValues, 1054 String selectedString) { 1055 for (int i = 0; i < choiceValues.length; i++) { 1056 if (choiceValues[i].equals(selectedString)) { 1057 return choiceEntries[i]; 1058 } 1059 } 1060 return selectedString; 1061 } 1062 1063 @Override 1064 public boolean onPreferenceClick(Preference preference) { 1065 if (preference.getKey().startsWith(PKG_PREFIX)) { 1066 AppRestrictionsPreference arp = (AppRestrictionsPreference) preference; 1067 if (!arp.isImmutable()) { 1068 final String packageName = arp.getKey().substring(PKG_PREFIX.length()); 1069 final boolean newEnabledState = !arp.isChecked(); 1070 arp.setChecked(newEnabledState); 1071 mSelectedPackages.put(packageName, newEnabledState); 1072 updateAllEntries(arp.getKey(), newEnabledState); 1073 mAppListChanged = true; 1074 applyUserAppState(packageName, newEnabledState); 1075 } 1076 return true; 1077 } 1078 return false; 1079 } 1080 1081} 1082