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