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