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