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