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