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