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