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