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