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