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