1 /* 2 * Copyright (C) 2016 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.tv.settings.users; 18 19 import android.app.Activity; 20 import android.app.AppGlobals; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.RestrictionEntry; 26 import android.content.RestrictionsManager; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.IPackageManager; 30 import android.content.pm.PackageInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.content.pm.UserInfo; 34 import android.graphics.Color; 35 import android.graphics.drawable.ColorDrawable; 36 import android.graphics.drawable.Drawable; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.os.UserManager; 42 import android.support.annotation.NonNull; 43 import android.support.v14.preference.MultiSelectListPreference; 44 import android.support.v14.preference.SwitchPreference; 45 import android.support.v17.preference.LeanbackPreferenceFragment; 46 import android.support.v4.util.ArrayMap; 47 import android.support.v7.preference.ListPreference; 48 import android.support.v7.preference.Preference; 49 import android.support.v7.preference.PreferenceGroup; 50 import android.support.v7.preference.PreferenceScreen; 51 import android.support.v7.preference.PreferenceViewHolder; 52 import android.util.Log; 53 import android.view.View; 54 import android.widget.Checkable; 55 import android.widget.CompoundButton; 56 import android.widget.Switch; 57 58 import com.android.settingslib.users.AppRestrictionsHelper; 59 import com.android.tv.settings.R; 60 61 import java.util.ArrayList; 62 import java.util.Collections; 63 import java.util.HashSet; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Set; 67 import java.util.StringTokenizer; 68 69 public class AppRestrictionsFragment extends LeanbackPreferenceFragment implements 70 Preference.OnPreferenceChangeListener, 71 AppRestrictionsHelper.OnDisableUiForPackageListener { 72 73 private static final String TAG = AppRestrictionsFragment.class.getSimpleName(); 74 75 private static final boolean DEBUG = false; 76 77 private static final String PKG_PREFIX = "pkg_"; 78 private static final String ACTIVITY_PREFIX = "activity_"; 79 80 private static final Drawable BLANK_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); 81 82 protected PackageManager mPackageManager; 83 protected UserManager mUserManager; 84 protected IPackageManager mIPm; 85 protected UserHandle mUser; 86 private PackageInfo mSysPackageInfo; 87 88 private AppRestrictionsHelper mHelper; 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 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 Map<Integer, String> mCustomRequestMap = new ArrayMap<>(); 111 112 private AsyncTask mAppLoadingTask; 113 114 private BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() { 115 @Override 116 public void onReceive(Context context, Intent intent) { 117 // Update the user's app selection right away without waiting for a pause 118 // onPause() might come in too late, causing apps to disappear after broadcasts 119 // have been scheduled during user startup. 120 if (mAppListChanged) { 121 if (DEBUG) Log.d(TAG, "User backgrounding, update app list"); 122 mHelper.applyUserAppsStates(AppRestrictionsFragment.this); 123 if (DEBUG) Log.d(TAG, "User backgrounding, done updating app list"); 124 } 125 } 126 }; 127 128 private BroadcastReceiver mPackageObserver = new BroadcastReceiver() { 129 @Override 130 public void onReceive(Context context, Intent intent) { 131 onPackageChanged(intent); 132 } 133 }; 134 135 static class AppRestrictionsPreference extends PreferenceGroup { 136 private final Listener mListener = new Listener(); 137 private ArrayList<RestrictionEntry> mRestrictions; 138 private boolean mImmutable; 139 private boolean mChecked; 140 private boolean mCheckedSet; 141 142 AppRestrictionsPreference(Context context) { 143 super(context, null, 0, R.style.LeanbackPreference_SwitchPreference); 144 } 145 146 void setRestrictions(ArrayList<RestrictionEntry> restrictions) { 147 this.mRestrictions = restrictions; 148 } 149 150 void setImmutable(boolean immutable) { 151 this.mImmutable = immutable; 152 } 153 154 boolean isImmutable() { 155 return mImmutable; 156 } 157 158 ArrayList<RestrictionEntry> getRestrictions() { 159 return mRestrictions; 160 } 161 162 public void setChecked(boolean checked) { 163 // Always persist/notify the first time; don't assume the field's default of false. 164 final boolean changed = mChecked != checked; 165 if (changed || !mCheckedSet) { 166 mChecked = checked; 167 mCheckedSet = true; 168 persistBoolean(checked); 169 if (changed) { 170 notifyDependencyChange(shouldDisableDependents()); 171 notifyChanged(); 172 notifyHierarchyChanged(); 173 } 174 } 175 } 176 177 @Override 178 public int getPreferenceCount() { 179 if (isChecked()) { 180 return super.getPreferenceCount(); 181 } else { 182 return 0; 183 } 184 } 185 186 public boolean isChecked() { 187 return mChecked; 188 } 189 190 @Override 191 public void onBindViewHolder(PreferenceViewHolder holder) { 192 super.onBindViewHolder(holder); 193 View switchView = holder.findViewById(android.R.id.switch_widget); 194 syncSwitchView(switchView); 195 } 196 197 private void syncSwitchView(View view) { 198 if (view instanceof Switch) { 199 final Switch switchView = (Switch) view; 200 switchView.setOnCheckedChangeListener(null); 201 } 202 if (view instanceof Checkable) { 203 ((Checkable) view).setChecked(mChecked); 204 } 205 if (view instanceof Switch) { 206 final Switch switchView = (Switch) view; 207 switchView.setOnCheckedChangeListener(mListener); 208 } 209 } 210 211 private class Listener implements CompoundButton.OnCheckedChangeListener { 212 @Override 213 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 214 if (!callChangeListener(isChecked)) { 215 // Listener didn't like it, change it back. 216 // CompoundButton will make sure we don't recurse. 217 buttonView.setChecked(!isChecked); 218 return; 219 } 220 221 AppRestrictionsPreference.this.setChecked(isChecked); 222 } 223 } 224 } 225 226 public static void prepareArgs(@NonNull Bundle bundle, int userId, boolean newUser) { 227 bundle.putInt(EXTRA_USER_ID, userId); 228 bundle.putBoolean(EXTRA_NEW_USER, newUser); 229 } 230 231 public static AppRestrictionsFragment newInstance(int userId, boolean newUser) { 232 final Bundle args = new Bundle(2); 233 prepareArgs(args, userId, newUser); 234 AppRestrictionsFragment fragment = new AppRestrictionsFragment(); 235 fragment.setArguments(args); 236 return fragment; 237 } 238 239 @Override 240 public void onCreate(Bundle savedInstanceState) { 241 super.onCreate(savedInstanceState); 242 if (savedInstanceState != null) { 243 mUser = new UserHandle(savedInstanceState.getInt(EXTRA_USER_ID)); 244 } else { 245 Bundle args = getArguments(); 246 if (args != null) { 247 if (args.containsKey(EXTRA_USER_ID)) { 248 mUser = new UserHandle(args.getInt(EXTRA_USER_ID)); 249 } 250 mNewUser = args.getBoolean(EXTRA_NEW_USER, false); 251 } 252 } 253 254 if (mUser == null) { 255 mUser = android.os.Process.myUserHandle(); 256 } 257 258 mHelper = new AppRestrictionsHelper(getContext(), mUser); 259 mHelper.setLeanback(true); 260 mPackageManager = getActivity().getPackageManager(); 261 mIPm = AppGlobals.getPackageManager(); 262 mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); 263 mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted(); 264 try { 265 mSysPackageInfo = mPackageManager.getPackageInfo("android", 266 PackageManager.GET_SIGNATURES); 267 } catch (PackageManager.NameNotFoundException nnfe) { 268 Log.e(TAG, "Could not find system package signatures", nnfe); 269 } 270 mAppList = getAppPreferenceGroup(); 271 mAppList.setOrderingAsAdded(false); 272 } 273 274 @Override 275 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 276 final PreferenceScreen screen = getPreferenceManager() 277 .createPreferenceScreen(getPreferenceManager().getContext()); 278 screen.setTitle(R.string.restricted_profile_configure_apps_title); 279 setPreferenceScreen(screen); 280 } 281 282 @Override 283 public void onSaveInstanceState(Bundle outState) { 284 super.onSaveInstanceState(outState); 285 outState.putInt(EXTRA_USER_ID, mUser.getIdentifier()); 286 } 287 288 @Override 289 public void onResume() { 290 super.onResume(); 291 292 getActivity().registerReceiver(mUserBackgrounding, 293 new IntentFilter(Intent.ACTION_USER_BACKGROUND)); 294 IntentFilter packageFilter = new IntentFilter(); 295 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 296 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 297 packageFilter.addDataScheme("package"); 298 getActivity().registerReceiver(mPackageObserver, packageFilter); 299 300 mAppListChanged = false; 301 if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) { 302 mAppLoadingTask = new AppLoadingTask().execute(); 303 } 304 } 305 306 @Override 307 public void onPause() { 308 super.onPause(); 309 mNewUser = false; 310 getActivity().unregisterReceiver(mUserBackgrounding); 311 getActivity().unregisterReceiver(mPackageObserver); 312 if (mAppListChanged) { 313 new AsyncTask<Void, Void, Void>() { 314 @Override 315 protected Void doInBackground(Void... params) { 316 mHelper.applyUserAppsStates(AppRestrictionsFragment.this); 317 return null; 318 } 319 }.execute(); 320 } 321 } 322 323 private void onPackageChanged(Intent intent) { 324 String action = intent.getAction(); 325 String packageName = intent.getData().getSchemeSpecificPart(); 326 // Package added, check if the preference needs to be enabled 327 AppRestrictionsPreference pref = (AppRestrictionsPreference) 328 findPreference(getKeyForPackage(packageName)); 329 if (pref == null) return; 330 331 if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && pref.isChecked()) 332 || (Intent.ACTION_PACKAGE_REMOVED.equals(action) && !pref.isChecked())) { 333 pref.setEnabled(true); 334 } 335 } 336 337 protected PreferenceGroup getAppPreferenceGroup() { 338 return getPreferenceScreen(); 339 } 340 341 @Override 342 public void onDisableUiForPackage(String packageName) { 343 AppRestrictionsPreference pref = (AppRestrictionsPreference) findPreference( 344 getKeyForPackage(packageName)); 345 if (pref != null) { 346 pref.setEnabled(false); 347 } 348 } 349 350 private class AppLoadingTask extends AsyncTask<Void, Void, Void> { 351 352 @Override 353 protected Void doInBackground(Void... params) { 354 mHelper.fetchAndMergeApps(); 355 return null; 356 } 357 358 @Override 359 protected void onPostExecute(Void result) { 360 populateApps(); 361 } 362 } 363 364 private boolean isPlatformSigned(PackageInfo pi) { 365 return (pi != null && pi.signatures != null && 366 mSysPackageInfo.signatures[0].equals(pi.signatures[0])); 367 } 368 369 private boolean isAppEnabledForUser(PackageInfo pi) { 370 if (pi == null) return false; 371 final int flags = pi.applicationInfo.flags; 372 final int privateFlags = pi.applicationInfo.privateFlags; 373 // Return true if it is installed and not hidden 374 return ((flags& ApplicationInfo.FLAG_INSTALLED) != 0 375 && (privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0); 376 } 377 378 private void populateApps() { 379 final Context context = getActivity(); 380 if (context == null) return; 381 final PackageManager pm = mPackageManager; 382 final int userId = mUser.getIdentifier(); 383 384 // Check if the user was removed in the meantime. 385 if (getExistingUser(mUserManager, mUser) == null) { 386 return; 387 } 388 mAppList.removeAll(); 389 addLocationAppRestrictionsPreference(); 390 Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 391 final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0); 392 for (AppRestrictionsHelper.SelectableAppInfo app : mHelper.getVisibleApps()) { 393 String packageName = app.packageName; 394 if (packageName == null) continue; 395 final boolean isSettingsApp = packageName.equals(context.getPackageName()); 396 AppRestrictionsPreference p = 397 new AppRestrictionsPreference(getPreferenceManager().getContext()); 398 final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName); 399 if (isSettingsApp) { 400 // Settings app should be available to restricted user 401 mHelper.setPackageSelected(packageName, true); 402 continue; 403 } 404 PackageInfo pi = null; 405 try { 406 pi = mIPm.getPackageInfo(packageName, 407 PackageManager.MATCH_UNINSTALLED_PACKAGES 408 | PackageManager.GET_SIGNATURES, userId); 409 } catch (RemoteException e) { 410 // Ignore 411 } 412 if (pi == null) { 413 continue; 414 } 415 if (mRestrictedProfile && isAppUnsupportedInRestrictedProfile(pi)) { 416 continue; 417 } 418 p.setIcon(app.icon != null ? app.icon.mutate() : null); 419 p.setChecked(false); 420 p.setTitle(app.activityName); 421 p.setKey(getKeyForPackage(packageName)); 422 p.setPersistent(false); 423 p.setOnPreferenceChangeListener(this); 424 p.setSummary(getPackageSummary(pi, app)); 425 if (pi.requiredForAllUsers || isPlatformSigned(pi)) { 426 p.setChecked(true); 427 p.setImmutable(true); 428 // If the app is required and has no restrictions, skip showing it 429 if (!hasSettings) continue; 430 } else if (!mNewUser && isAppEnabledForUser(pi)) { 431 p.setChecked(true); 432 } 433 if (app.masterEntry == null && hasSettings) { 434 requestRestrictionsForApp(packageName, p); 435 } 436 if (app.masterEntry != null) { 437 p.setImmutable(true); 438 p.setChecked(mHelper.isPackageSelected(packageName)); 439 } 440 p.setOrder(MAX_APP_RESTRICTIONS * (mAppList.getPreferenceCount() + 2)); 441 mHelper.setPackageSelected(packageName, p.isChecked()); 442 mAppList.addPreference(p); 443 } 444 mAppListChanged = true; 445 // If this is the first time for a new profile, install/uninstall default apps for profile 446 // to avoid taking the hit in onPause(), which can cause race conditions on user switch. 447 if (mNewUser && mFirstTime) { 448 mFirstTime = false; 449 mHelper.applyUserAppsStates(this); 450 } 451 } 452 453 private String getPackageSummary(PackageInfo pi, AppRestrictionsHelper.SelectableAppInfo app) { 454 // Check for 3 cases: 455 // - Slave entry that can see primary user accounts 456 // - Slave entry that cannot see primary user accounts 457 // - Master entry that can see primary user accounts 458 // Otherwise no summary is returned 459 if (app.masterEntry != null) { 460 if (mRestrictedProfile && pi.restrictedAccountType != null) { 461 return getString(R.string.app_sees_restricted_accounts_and_controlled_by, 462 app.masterEntry.activityName); 463 } 464 return getString(R.string.user_restrictions_controlled_by, 465 app.masterEntry.activityName); 466 } else if (pi.restrictedAccountType != null) { 467 return getString(R.string.app_sees_restricted_accounts); 468 } 469 return null; 470 } 471 472 private static boolean isAppUnsupportedInRestrictedProfile(PackageInfo pi) { 473 return pi.requiredAccountType != null && pi.restrictedAccountType == null; 474 } 475 476 private void addLocationAppRestrictionsPreference() { 477 AppRestrictionsPreference p = 478 new AppRestrictionsPreference(getPreferenceManager().getContext()); 479 String packageName = getContext().getPackageName(); 480 p.setIcon(R.drawable.ic_location_on); 481 p.setKey(getKeyForPackage(packageName)); 482 ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions( 483 getActivity(), mUser); 484 RestrictionEntry locationRestriction = restrictions.get(0); 485 p.setTitle(locationRestriction.getTitle()); 486 p.setRestrictions(restrictions); 487 p.setSummary(locationRestriction.getDescription()); 488 p.setChecked(locationRestriction.getSelectedState()); 489 p.setPersistent(false); 490 p.setOrder(MAX_APP_RESTRICTIONS); 491 mAppList.addPreference(p); 492 } 493 494 private String getKeyForPackage(String packageName) { 495 return PKG_PREFIX + packageName; 496 } 497 498 private String getKeyForPackageActivity(String packageName) { 499 return ACTIVITY_PREFIX + packageName; 500 } 501 502 private String getPackageFromKey(String key) { 503 if (key.startsWith(PKG_PREFIX)) { 504 return key.substring(PKG_PREFIX.length()); 505 } else if (key.startsWith(ACTIVITY_PREFIX)) { 506 return key.substring(ACTIVITY_PREFIX.length()); 507 } else { 508 throw new IllegalArgumentException("Tried to extract package from wrong key: " + key); 509 } 510 } 511 512 private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) { 513 for (ResolveInfo info : receivers) { 514 if (info.activityInfo.packageName.equals(packageName)) { 515 return true; 516 } 517 } 518 return false; 519 } 520 521 private void updateAllEntries(String prefKey, boolean checked) { 522 for (int i = 0; i < mAppList.getPreferenceCount(); i++) { 523 Preference pref = mAppList.getPreference(i); 524 if (pref instanceof AppRestrictionsPreference) { 525 if (prefKey.equals(pref.getKey())) { 526 ((AppRestrictionsPreference) pref).setChecked(checked); 527 } 528 } 529 } 530 } 531 532 private void assertSafeToStartCustomActivity(Intent intent, String packageName) { 533 // Activity can be started if it belongs to the same app 534 if (intent.getPackage() != null && intent.getPackage().equals(packageName)) { 535 return; 536 } 537 // Activity can be started if intent resolves to multiple activities 538 List<ResolveInfo> resolveInfos = AppRestrictionsFragment.this.mPackageManager 539 .queryIntentActivities(intent, 0 /* no flags */); 540 if (resolveInfos.size() != 1) { 541 return; 542 } 543 // Prevent potential privilege escalation 544 ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; 545 if (!packageName.equals(activityInfo.packageName)) { 546 throw new SecurityException("Application " + packageName 547 + " is not allowed to start activity " + intent); 548 } 549 } 550 551 @Override 552 public boolean onPreferenceTreeClick(Preference preference) { 553 if (preference instanceof AppRestrictionsPreference) { 554 AppRestrictionsPreference pref = (AppRestrictionsPreference) preference; 555 if (!pref.isImmutable()) { 556 pref.setChecked(!pref.isChecked()); 557 final String packageName = getPackageFromKey(pref.getKey()); 558 // Settings/Location is handled as a top-level entry 559 if (packageName.equals(getActivity().getPackageName())) { 560 pref.getRestrictions().get(0).setSelectedState(pref.isChecked()); 561 RestrictionUtils.setRestrictions(getActivity(), pref.getRestrictions(), mUser); 562 return true; 563 } 564 mHelper.setPackageSelected(packageName, pref.isChecked()); 565 mAppListChanged = true; 566 // If it's not a restricted profile, apply the changes immediately 567 if (!mRestrictedProfile) { 568 mHelper.applyUserAppState(packageName, pref.isChecked(), this); 569 } 570 updateAllEntries(pref.getKey(), pref.isChecked()); 571 } 572 return true; 573 } else if (preference.getIntent() != null) { 574 assertSafeToStartCustomActivity(preference.getIntent(), 575 getPackageFromKey(preference.getKey())); 576 startActivityForResult(preference.getIntent(), 577 generateCustomActivityRequestCode(preference)); 578 return true; 579 } else { 580 return super.onPreferenceTreeClick(preference); 581 } 582 } 583 584 @Override 585 public boolean onPreferenceChange(Preference preference, Object newValue) { 586 String key = preference.getKey(); 587 if (key != null && key.contains(DELIMITER)) { 588 StringTokenizer st = new StringTokenizer(key, DELIMITER); 589 final String packageName = st.nextToken(); 590 final String restrictionKey = st.nextToken(); 591 AppRestrictionsPreference appPref = (AppRestrictionsPreference) 592 mAppList.findPreference(getKeyForPackage(packageName)); 593 ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions(); 594 if (restrictions != null) { 595 for (RestrictionEntry entry : restrictions) { 596 if (entry.getKey().equals(restrictionKey)) { 597 switch (entry.getType()) { 598 case RestrictionEntry.TYPE_BOOLEAN: 599 entry.setSelectedState((Boolean) newValue); 600 break; 601 case RestrictionEntry.TYPE_CHOICE: 602 case RestrictionEntry.TYPE_CHOICE_LEVEL: 603 ListPreference listPref = (ListPreference) preference; 604 entry.setSelectedString((String) newValue); 605 String readable = findInArray(entry.getChoiceEntries(), 606 entry.getChoiceValues(), (String) newValue); 607 listPref.setSummary(readable); 608 break; 609 case RestrictionEntry.TYPE_MULTI_SELECT: 610 Set<String> set = (Set<String>) newValue; 611 String [] selectedValues = new String[set.size()]; 612 set.toArray(selectedValues); 613 entry.setAllSelectedStrings(selectedValues); 614 break; 615 default: 616 continue; 617 } 618 mUserManager.setApplicationRestrictions(packageName, 619 RestrictionsManager.convertRestrictionsToBundle(restrictions), 620 mUser); 621 break; 622 } 623 } 624 } 625 } 626 return true; 627 } 628 629 /** 630 * Send a broadcast to the app to query its restrictions 631 * @param packageName package name of the app with restrictions 632 * @param preference the preference item for the app toggle 633 */ 634 private void requestRestrictionsForApp(String packageName, 635 AppRestrictionsPreference preference) { 636 Bundle oldEntries = 637 mUserManager.getApplicationRestrictions(packageName, mUser); 638 Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 639 intent.setPackage(packageName); 640 intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries); 641 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 642 getActivity().sendOrderedBroadcast(intent, null, 643 new RestrictionsResultReceiver(packageName, preference), 644 null, Activity.RESULT_OK, null, null); 645 } 646 647 class RestrictionsResultReceiver extends BroadcastReceiver { 648 649 private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT; 650 private final String mPackageName; 651 private final AppRestrictionsPreference mPreference; 652 653 RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) { 654 super(); 655 mPackageName = packageName; 656 mPreference = preference; 657 } 658 659 @Override 660 public void onReceive(Context context, Intent intent) { 661 Bundle results = getResultExtras(true); 662 final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList( 663 Intent.EXTRA_RESTRICTIONS_LIST); 664 Intent restrictionsIntent = results.getParcelable(CUSTOM_RESTRICTIONS_INTENT); 665 if (restrictions != null && restrictionsIntent == null) { 666 onRestrictionsReceived(mPreference, restrictions); 667 if (mRestrictedProfile) { 668 mUserManager.setApplicationRestrictions(mPackageName, 669 RestrictionsManager.convertRestrictionsToBundle(restrictions), mUser); 670 } 671 } else if (restrictionsIntent != null) { 672 mPreference.setRestrictions(null); 673 mPreference.removeAll(); 674 final Preference p = new Preference(mPreference.getContext()); 675 p.setKey(getKeyForPackageActivity(mPackageName)); 676 p.setIcon(BLANK_DRAWABLE); 677 p.setTitle(R.string.restricted_profile_customize_restrictions); 678 p.setIntent(restrictionsIntent); 679 mPreference.addPreference(p); 680 } 681 } 682 } 683 684 private void onRestrictionsReceived(AppRestrictionsPreference preference, 685 ArrayList<RestrictionEntry> restrictions) { 686 // Remove any earlier restrictions 687 preference.removeAll(); 688 // Non-custom-activity case - expand the restrictions in-place 689 int count = 1; 690 final Context themedContext = getPreferenceManager().getContext(); 691 for (RestrictionEntry entry : restrictions) { 692 Preference p = null; 693 switch (entry.getType()) { 694 case RestrictionEntry.TYPE_BOOLEAN: 695 p = new SwitchPreference(themedContext); 696 p.setTitle(entry.getTitle()); 697 p.setSummary(entry.getDescription()); 698 ((SwitchPreference)p).setChecked(entry.getSelectedState()); 699 break; 700 case RestrictionEntry.TYPE_CHOICE: 701 case RestrictionEntry.TYPE_CHOICE_LEVEL: 702 p = new ListPreference(themedContext); 703 p.setTitle(entry.getTitle()); 704 String value = entry.getSelectedString(); 705 if (value == null) { 706 value = entry.getDescription(); 707 } 708 p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(), 709 value)); 710 ((ListPreference)p).setEntryValues(entry.getChoiceValues()); 711 ((ListPreference)p).setEntries(entry.getChoiceEntries()); 712 ((ListPreference)p).setValue(value); 713 ((ListPreference)p).setDialogTitle(entry.getTitle()); 714 break; 715 case RestrictionEntry.TYPE_MULTI_SELECT: 716 p = new MultiSelectListPreference(themedContext); 717 p.setTitle(entry.getTitle()); 718 ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues()); 719 ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries()); 720 HashSet<String> set = new HashSet<>(); 721 Collections.addAll(set, entry.getAllSelectedStrings()); 722 ((MultiSelectListPreference)p).setValues(set); 723 ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle()); 724 break; 725 case RestrictionEntry.TYPE_NULL: 726 default: 727 } 728 if (p != null) { 729 p.setPersistent(false); 730 p.setOrder(preference.getOrder() + count); 731 // Store the restrictions key string as a key for the preference 732 p.setKey(getPackageFromKey(preference.getKey()) + DELIMITER + entry.getKey()); 733 preference.addPreference(p); 734 p.setOnPreferenceChangeListener(AppRestrictionsFragment.this); 735 p.setIcon(BLANK_DRAWABLE); 736 count++; 737 } 738 } 739 preference.setRestrictions(restrictions); 740 if (count == 1 // No visible restrictions 741 && preference.isImmutable() 742 && preference.isChecked()) { 743 // Special case of required app with no visible restrictions. Remove it 744 mAppList.removePreference(preference); 745 } 746 } 747 748 /** 749 * Generates a request code that is stored in a map to retrieve the associated 750 * AppRestrictionsPreference. 751 */ 752 private int generateCustomActivityRequestCode(Preference preference) { 753 mCustomRequestCode++; 754 final String key = getKeyForPackage(getPackageFromKey(preference.getKey())); 755 mCustomRequestMap.put(mCustomRequestCode, key); 756 return mCustomRequestCode; 757 } 758 759 @Override 760 public void onActivityResult(int requestCode, int resultCode, Intent data) { 761 super.onActivityResult(requestCode, resultCode, data); 762 763 AppRestrictionsPreference pref = 764 (AppRestrictionsPreference) findPreference(mCustomRequestMap.get(requestCode)); 765 if (pref == null) { 766 Log.w(TAG, "Unknown requestCode " + requestCode); 767 return; 768 } 769 770 if (resultCode == Activity.RESULT_OK) { 771 String packageName = getPackageFromKey(pref.getKey()); 772 ArrayList<RestrictionEntry> list = 773 data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST); 774 Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE); 775 if (list != null) { 776 // If there's a valid result, persist it to the user manager. 777 pref.setRestrictions(list); 778 mUserManager.setApplicationRestrictions(packageName, 779 RestrictionsManager.convertRestrictionsToBundle(list), mUser); 780 } else if (bundle != null) { 781 // If there's a valid result, persist it to the user manager. 782 mUserManager.setApplicationRestrictions(packageName, bundle, mUser); 783 } 784 } 785 // Remove request from the map 786 mCustomRequestMap.remove(requestCode); 787 } 788 789 private String findInArray(String[] choiceEntries, String[] choiceValues, 790 String selectedString) { 791 for (int i = 0; i < choiceValues.length; i++) { 792 if (choiceValues[i].equals(selectedString)) { 793 return choiceEntries[i]; 794 } 795 } 796 return selectedString; 797 } 798 799 /** 800 * Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed). 801 * @param userManager Instance of UserManager 802 * @param checkUser The user to check the existence of. 803 * @return UserInfo of the user or null for non-existent user. 804 */ 805 public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) { 806 final List<UserInfo> users = userManager.getUsers(true /* excludeDying */); 807 final int checkUserId = checkUser.getIdentifier(); 808 for (UserInfo user : users) { 809 if (user.id == checkUserId) { 810 return user; 811 } 812 } 813 return null; 814 } 815 } 816