1 /* 2 * Copyright (C) 2019 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.car.developeroptions.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.car.developeroptions.R; 58 import com.android.car.developeroptions.SettingsPreferenceFragment; 59 import com.android.car.developeroptions.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 init(Bundle icicle)212 protected void init(Bundle icicle) { 213 if (icicle != null) { 214 mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID)); 215 } else { 216 Bundle args = getArguments(); 217 if (args != null) { 218 if (args.containsKey(EXTRA_USER_ID)) { 219 mUser = new UserHandle(args.getInt(EXTRA_USER_ID)); 220 } 221 mNewUser = args.getBoolean(EXTRA_NEW_USER, false); 222 } 223 } 224 225 if (mUser == null) { 226 mUser = android.os.Process.myUserHandle(); 227 } 228 229 mHelper = new AppRestrictionsHelper(getContext(), mUser); 230 mPackageManager = getActivity().getPackageManager(); 231 mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 232 mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); 233 mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted(); 234 try { 235 mSysPackageInfo = mPackageManager.getPackageInfo("android", 236 PackageManager.GET_SIGNATURES); 237 } catch (NameNotFoundException nnfe) { 238 // ? 239 } 240 addPreferencesFromResource(R.xml.app_restrictions); 241 mAppList = getAppPreferenceGroup(); 242 mAppList.setOrderingAsAdded(false); 243 } 244 245 @Override getMetricsCategory()246 public int getMetricsCategory() { 247 return SettingsEnums.USERS_APP_RESTRICTIONS; 248 } 249 250 @Override onSaveInstanceState(Bundle outState)251 public void onSaveInstanceState(Bundle outState) { 252 super.onSaveInstanceState(outState); 253 outState.putInt(EXTRA_USER_ID, mUser.getIdentifier()); 254 } 255 256 @Override onResume()257 public void onResume() { 258 super.onResume(); 259 260 getActivity().registerReceiver(mUserBackgrounding, 261 new IntentFilter(Intent.ACTION_USER_BACKGROUND)); 262 IntentFilter packageFilter = new IntentFilter(); 263 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 264 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 265 packageFilter.addDataScheme("package"); 266 getActivity().registerReceiver(mPackageObserver, packageFilter); 267 268 mAppListChanged = false; 269 if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) { 270 mAppLoadingTask = new AppLoadingTask().execute(); 271 } 272 } 273 274 @Override onPause()275 public void onPause() { 276 super.onPause(); 277 mNewUser = false; 278 getActivity().unregisterReceiver(mUserBackgrounding); 279 getActivity().unregisterReceiver(mPackageObserver); 280 if (mAppListChanged) { 281 new AsyncTask<Void, Void, Void>() { 282 @Override 283 protected Void doInBackground(Void... params) { 284 mHelper.applyUserAppsStates(AppRestrictionsFragment.this); 285 return null; 286 } 287 }.execute(); 288 } 289 } 290 onPackageChanged(Intent intent)291 private void onPackageChanged(Intent intent) { 292 String action = intent.getAction(); 293 String packageName = intent.getData().getSchemeSpecificPart(); 294 // Package added, check if the preference needs to be enabled 295 AppRestrictionsPreference pref = (AppRestrictionsPreference) 296 findPreference(getKeyForPackage(packageName)); 297 if (pref == null) return; 298 299 if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && pref.isChecked()) 300 || (Intent.ACTION_PACKAGE_REMOVED.equals(action) && !pref.isChecked())) { 301 pref.setEnabled(true); 302 } 303 } 304 getAppPreferenceGroup()305 protected PreferenceGroup getAppPreferenceGroup() { 306 return getPreferenceScreen(); 307 } 308 309 @Override onDisableUiForPackage(String packageName)310 public void onDisableUiForPackage(String packageName) { 311 AppRestrictionsPreference pref = (AppRestrictionsPreference) findPreference( 312 getKeyForPackage(packageName)); 313 if (pref != null) { 314 pref.setEnabled(false); 315 } 316 } 317 318 private class AppLoadingTask extends AsyncTask<Void, Void, Void> { 319 320 @Override doInBackground(Void... params)321 protected Void doInBackground(Void... params) { 322 mHelper.fetchAndMergeApps(); 323 return null; 324 } 325 326 @Override onPostExecute(Void result)327 protected void onPostExecute(Void result) { 328 populateApps(); 329 } 330 } 331 isPlatformSigned(PackageInfo pi)332 private boolean isPlatformSigned(PackageInfo pi) { 333 return (pi != null && pi.signatures != null && 334 mSysPackageInfo.signatures[0].equals(pi.signatures[0])); 335 } 336 isAppEnabledForUser(PackageInfo pi)337 private boolean isAppEnabledForUser(PackageInfo pi) { 338 if (pi == null) return false; 339 final int flags = pi.applicationInfo.flags; 340 final int privateFlags = pi.applicationInfo.privateFlags; 341 // Return true if it is installed and not hidden 342 return ((flags&ApplicationInfo.FLAG_INSTALLED) != 0 343 && (privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0); 344 } 345 populateApps()346 private void populateApps() { 347 final Context context = getActivity(); 348 if (context == null) return; 349 final PackageManager pm = mPackageManager; 350 final IPackageManager ipm = mIPm; 351 final int userId = mUser.getIdentifier(); 352 353 // Check if the user was removed in the meantime. 354 if (Utils.getExistingUser(mUserManager, mUser) == null) { 355 return; 356 } 357 mAppList.removeAll(); 358 Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 359 final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0); 360 for (AppRestrictionsHelper.SelectableAppInfo app : mHelper.getVisibleApps()) { 361 String packageName = app.packageName; 362 if (packageName == null) continue; 363 final boolean isSettingsApp = packageName.equals(context.getPackageName()); 364 AppRestrictionsPreference p = new AppRestrictionsPreference(getPrefContext(), this); 365 final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName); 366 if (isSettingsApp) { 367 addLocationAppRestrictionsPreference(app, p); 368 // Settings app should be available to restricted user 369 mHelper.setPackageSelected(packageName, true); 370 continue; 371 } 372 PackageInfo pi = null; 373 try { 374 pi = ipm.getPackageInfo(packageName, 375 PackageManager.MATCH_ANY_USER 376 | PackageManager.GET_SIGNATURES, userId); 377 } catch (RemoteException e) { 378 // Ignore 379 } 380 if (pi == null) { 381 continue; 382 } 383 if (mRestrictedProfile && isAppUnsupportedInRestrictedProfile(pi)) { 384 continue; 385 } 386 p.setIcon(app.icon != null ? app.icon.mutate() : null); 387 p.setChecked(false); 388 p.setTitle(app.activityName); 389 p.setKey(getKeyForPackage(packageName)); 390 p.setSettingsEnabled(hasSettings && app.masterEntry == null); 391 p.setPersistent(false); 392 p.setOnPreferenceChangeListener(this); 393 p.setOnPreferenceClickListener(this); 394 p.setSummary(getPackageSummary(pi, app)); 395 if (pi.requiredForAllUsers || isPlatformSigned(pi)) { 396 p.setChecked(true); 397 p.setImmutable(true); 398 // If the app is required and has no restrictions, skip showing it 399 if (!hasSettings) continue; 400 // Get and populate the defaults, since the user is not going to be 401 // able to toggle this app ON (it's ON by default and immutable). 402 // Only do this for restricted profiles, not single-user restrictions 403 // Also don't do this for child icons 404 if (app.masterEntry == null) { 405 requestRestrictionsForApp(packageName, p, false); 406 } 407 } else if (!mNewUser && isAppEnabledForUser(pi)) { 408 p.setChecked(true); 409 } 410 if (app.masterEntry != null) { 411 p.setImmutable(true); 412 p.setChecked(mHelper.isPackageSelected(packageName)); 413 } 414 p.setOrder(MAX_APP_RESTRICTIONS * (mAppList.getPreferenceCount() + 2)); 415 mHelper.setPackageSelected(packageName, p.isChecked()); 416 mAppList.addPreference(p); 417 } 418 mAppListChanged = true; 419 // If this is the first time for a new profile, install/uninstall default apps for profile 420 // to avoid taking the hit in onPause(), which can cause race conditions on user switch. 421 if (mNewUser && mFirstTime) { 422 mFirstTime = false; 423 mHelper.applyUserAppsStates(this); 424 } 425 } 426 getPackageSummary(PackageInfo pi, AppRestrictionsHelper.SelectableAppInfo app)427 private String getPackageSummary(PackageInfo pi, AppRestrictionsHelper.SelectableAppInfo app) { 428 // Check for 3 cases: 429 // - Child entry that can see primary user accounts 430 // - Child entry that cannot see primary user accounts 431 // - Parent entry that can see primary user accounts 432 // Otherwise no summary is returned 433 if (app.masterEntry != null) { 434 if (mRestrictedProfile && pi.restrictedAccountType != null) { 435 return getString(R.string.app_sees_restricted_accounts_and_controlled_by, 436 app.masterEntry.activityName); 437 } 438 return getString(R.string.user_restrictions_controlled_by, 439 app.masterEntry.activityName); 440 } else if (pi.restrictedAccountType != null) { 441 return getString(R.string.app_sees_restricted_accounts); 442 } 443 return null; 444 } 445 isAppUnsupportedInRestrictedProfile(PackageInfo pi)446 private static boolean isAppUnsupportedInRestrictedProfile(PackageInfo pi) { 447 return pi.requiredAccountType != null && pi.restrictedAccountType == null; 448 } 449 addLocationAppRestrictionsPreference(AppRestrictionsHelper.SelectableAppInfo app, AppRestrictionsPreference p)450 private void addLocationAppRestrictionsPreference(AppRestrictionsHelper.SelectableAppInfo app, 451 AppRestrictionsPreference p) { 452 String packageName = app.packageName; 453 p.setIcon(R.drawable.ic_preference_location); 454 p.setKey(getKeyForPackage(packageName)); 455 ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions( 456 getActivity(), mUser); 457 RestrictionEntry locationRestriction = restrictions.get(0); 458 p.setTitle(locationRestriction.getTitle()); 459 p.setRestrictions(restrictions); 460 p.setSummary(locationRestriction.getDescription()); 461 p.setChecked(locationRestriction.getSelectedState()); 462 p.setPersistent(false); 463 p.setOnPreferenceClickListener(this); 464 p.setOrder(MAX_APP_RESTRICTIONS); 465 mAppList.addPreference(p); 466 } 467 getKeyForPackage(String packageName)468 private String getKeyForPackage(String packageName) { 469 return PKG_PREFIX + packageName; 470 } 471 resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName)472 private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) { 473 for (ResolveInfo info : receivers) { 474 if (info.activityInfo.packageName.equals(packageName)) { 475 return true; 476 } 477 } 478 return false; 479 } 480 updateAllEntries(String prefKey, boolean checked)481 private void updateAllEntries(String prefKey, boolean checked) { 482 for (int i = 0; i < mAppList.getPreferenceCount(); i++) { 483 Preference pref = mAppList.getPreference(i); 484 if (pref instanceof AppRestrictionsPreference) { 485 if (prefKey.equals(pref.getKey())) { 486 ((AppRestrictionsPreference) pref).setChecked(checked); 487 } 488 } 489 } 490 } 491 492 @Override onClick(View v)493 public void onClick(View v) { 494 if (v.getTag() instanceof AppRestrictionsPreference) { 495 AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag(); 496 if (v.getId() == R.id.app_restrictions_settings) { 497 onAppSettingsIconClicked(pref); 498 } else if (!pref.isImmutable()) { 499 pref.setChecked(!pref.isChecked()); 500 final String packageName = pref.getKey().substring(PKG_PREFIX.length()); 501 // Settings/Location is handled as a top-level entry 502 if (packageName.equals(getActivity().getPackageName())) { 503 pref.restrictions.get(0).setSelectedState(pref.isChecked()); 504 RestrictionUtils.setRestrictions(getActivity(), pref.restrictions, mUser); 505 return; 506 } 507 mHelper.setPackageSelected(packageName, pref.isChecked()); 508 if (pref.isChecked() && pref.hasSettings 509 && pref.restrictions == null) { 510 // The restrictions have not been initialized, get and save them 511 requestRestrictionsForApp(packageName, pref, false); 512 } 513 mAppListChanged = true; 514 // If it's not a restricted profile, apply the changes immediately 515 if (!mRestrictedProfile) { 516 mHelper.applyUserAppState(packageName, pref.isChecked(), this); 517 } 518 updateAllEntries(pref.getKey(), pref.isChecked()); 519 } 520 } 521 } 522 523 @Override onPreferenceChange(Preference preference, Object newValue)524 public boolean onPreferenceChange(Preference preference, Object newValue) { 525 String key = preference.getKey(); 526 if (key != null && key.contains(DELIMITER)) { 527 StringTokenizer st = new StringTokenizer(key, DELIMITER); 528 final String packageName = st.nextToken(); 529 final String restrictionKey = st.nextToken(); 530 AppRestrictionsPreference appPref = (AppRestrictionsPreference) 531 mAppList.findPreference(PKG_PREFIX+packageName); 532 ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions(); 533 if (restrictions != null) { 534 for (RestrictionEntry entry : restrictions) { 535 if (entry.getKey().equals(restrictionKey)) { 536 switch (entry.getType()) { 537 case RestrictionEntry.TYPE_BOOLEAN: 538 entry.setSelectedState((Boolean) newValue); 539 break; 540 case RestrictionEntry.TYPE_CHOICE: 541 case RestrictionEntry.TYPE_CHOICE_LEVEL: 542 ListPreference listPref = (ListPreference) preference; 543 entry.setSelectedString((String) newValue); 544 String readable = findInArray(entry.getChoiceEntries(), 545 entry.getChoiceValues(), (String) newValue); 546 listPref.setSummary(readable); 547 break; 548 case RestrictionEntry.TYPE_MULTI_SELECT: 549 Set<String> set = (Set<String>) newValue; 550 String [] selectedValues = new String[set.size()]; 551 set.toArray(selectedValues); 552 entry.setAllSelectedStrings(selectedValues); 553 break; 554 default: 555 continue; 556 } 557 mUserManager.setApplicationRestrictions(packageName, 558 RestrictionsManager.convertRestrictionsToBundle(restrictions), 559 mUser); 560 break; 561 } 562 } 563 } 564 return true; 565 } 566 return false; 567 } 568 removeRestrictionsForApp(AppRestrictionsPreference preference)569 private void removeRestrictionsForApp(AppRestrictionsPreference preference) { 570 for (Preference p : preference.mChildren) { 571 mAppList.removePreference(p); 572 } 573 preference.mChildren.clear(); 574 } 575 onAppSettingsIconClicked(AppRestrictionsPreference preference)576 private void onAppSettingsIconClicked(AppRestrictionsPreference preference) { 577 if (preference.getKey().startsWith(PKG_PREFIX)) { 578 if (preference.isPanelOpen()) { 579 removeRestrictionsForApp(preference); 580 } else { 581 String packageName = preference.getKey().substring(PKG_PREFIX.length()); 582 requestRestrictionsForApp(packageName, preference, true /*invoke if custom*/); 583 } 584 preference.setPanelOpen(!preference.isPanelOpen()); 585 } 586 } 587 588 /** 589 * Send a broadcast to the app to query its restrictions 590 * @param packageName package name of the app with restrictions 591 * @param preference the preference item for the app toggle 592 * @param invokeIfCustom whether to directly launch any custom activity that is returned 593 * for the app. 594 */ requestRestrictionsForApp(String packageName, AppRestrictionsPreference preference, boolean invokeIfCustom)595 private void requestRestrictionsForApp(String packageName, 596 AppRestrictionsPreference preference, boolean invokeIfCustom) { 597 Bundle oldEntries = 598 mUserManager.getApplicationRestrictions(packageName, mUser); 599 Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 600 intent.setPackage(packageName); 601 intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries); 602 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 603 getActivity().sendOrderedBroadcast(intent, null, 604 new RestrictionsResultReceiver(packageName, preference, invokeIfCustom), 605 null, Activity.RESULT_OK, null, null); 606 } 607 608 class RestrictionsResultReceiver extends BroadcastReceiver { 609 610 private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT; 611 String packageName; 612 AppRestrictionsPreference preference; 613 boolean invokeIfCustom; 614 RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference, boolean invokeIfCustom)615 RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference, 616 boolean invokeIfCustom) { 617 super(); 618 this.packageName = packageName; 619 this.preference = preference; 620 this.invokeIfCustom = invokeIfCustom; 621 } 622 623 @Override onReceive(Context context, Intent intent)624 public void onReceive(Context context, Intent intent) { 625 Bundle results = getResultExtras(true); 626 final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList( 627 Intent.EXTRA_RESTRICTIONS_LIST); 628 Intent restrictionsIntent = results.getParcelable(CUSTOM_RESTRICTIONS_INTENT); 629 if (restrictions != null && restrictionsIntent == null) { 630 onRestrictionsReceived(preference, restrictions); 631 if (mRestrictedProfile) { 632 mUserManager.setApplicationRestrictions(packageName, 633 RestrictionsManager.convertRestrictionsToBundle(restrictions), mUser); 634 } 635 } else if (restrictionsIntent != null) { 636 preference.setRestrictions(restrictions); 637 if (invokeIfCustom && AppRestrictionsFragment.this.isResumed()) { 638 assertSafeToStartCustomActivity(restrictionsIntent); 639 int requestCode = generateCustomActivityRequestCode( 640 RestrictionsResultReceiver.this.preference); 641 AppRestrictionsFragment.this.startActivityForResult( 642 restrictionsIntent, requestCode); 643 } 644 } 645 } 646 assertSafeToStartCustomActivity(Intent intent)647 private void assertSafeToStartCustomActivity(Intent intent) { 648 // Activity can be started if it belongs to the same app 649 if (intent.getPackage() != null && intent.getPackage().equals(packageName)) { 650 return; 651 } 652 // Activity can be started if intent resolves to multiple activities 653 List<ResolveInfo> resolveInfos = AppRestrictionsFragment.this.mPackageManager 654 .queryIntentActivities(intent, 0 /* no flags */); 655 if (resolveInfos.size() != 1) { 656 return; 657 } 658 // Prevent potential privilege escalation 659 ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; 660 if (!packageName.equals(activityInfo.packageName)) { 661 throw new SecurityException("Application " + packageName 662 + " is not allowed to start activity " + intent); 663 } 664 } 665 } 666 onRestrictionsReceived(AppRestrictionsPreference preference, ArrayList<RestrictionEntry> restrictions)667 private void onRestrictionsReceived(AppRestrictionsPreference preference, 668 ArrayList<RestrictionEntry> restrictions) { 669 // Remove any earlier restrictions 670 removeRestrictionsForApp(preference); 671 // Non-custom-activity case - expand the restrictions in-place 672 int count = 1; 673 for (RestrictionEntry entry : restrictions) { 674 Preference p = null; 675 switch (entry.getType()) { 676 case RestrictionEntry.TYPE_BOOLEAN: 677 p = new SwitchPreference(getPrefContext()); 678 p.setTitle(entry.getTitle()); 679 p.setSummary(entry.getDescription()); 680 ((SwitchPreference)p).setChecked(entry.getSelectedState()); 681 break; 682 case RestrictionEntry.TYPE_CHOICE: 683 case RestrictionEntry.TYPE_CHOICE_LEVEL: 684 p = new ListPreference(getPrefContext()); 685 p.setTitle(entry.getTitle()); 686 String value = entry.getSelectedString(); 687 if (value == null) { 688 value = entry.getDescription(); 689 } 690 p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(), 691 value)); 692 ((ListPreference)p).setEntryValues(entry.getChoiceValues()); 693 ((ListPreference)p).setEntries(entry.getChoiceEntries()); 694 ((ListPreference)p).setValue(value); 695 ((ListPreference)p).setDialogTitle(entry.getTitle()); 696 break; 697 case RestrictionEntry.TYPE_MULTI_SELECT: 698 p = new MultiSelectListPreference(getPrefContext()); 699 p.setTitle(entry.getTitle()); 700 ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues()); 701 ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries()); 702 HashSet<String> set = new HashSet<>(); 703 Collections.addAll(set, entry.getAllSelectedStrings()); 704 ((MultiSelectListPreference)p).setValues(set); 705 ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle()); 706 break; 707 case RestrictionEntry.TYPE_NULL: 708 default: 709 } 710 if (p != null) { 711 p.setPersistent(false); 712 p.setOrder(preference.getOrder() + count); 713 // Store the restrictions key string as a key for the preference 714 p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER 715 + entry.getKey()); 716 mAppList.addPreference(p); 717 p.setOnPreferenceChangeListener(AppRestrictionsFragment.this); 718 p.setIcon(R.drawable.empty_icon); 719 preference.mChildren.add(p); 720 count++; 721 } 722 } 723 preference.setRestrictions(restrictions); 724 if (count == 1 // No visible restrictions 725 && preference.isImmutable() 726 && preference.isChecked()) { 727 // Special case of required app with no visible restrictions. Remove it 728 mAppList.removePreference(preference); 729 } 730 } 731 732 /** 733 * Generates a request code that is stored in a map to retrieve the associated 734 * AppRestrictionsPreference. 735 */ generateCustomActivityRequestCode(AppRestrictionsPreference preference)736 private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) { 737 mCustomRequestCode++; 738 mCustomRequestMap.put(mCustomRequestCode, preference); 739 return mCustomRequestCode; 740 } 741 742 @Override onActivityResult(int requestCode, int resultCode, Intent data)743 public void onActivityResult(int requestCode, int resultCode, Intent data) { 744 super.onActivityResult(requestCode, resultCode, data); 745 746 AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode); 747 if (pref == null) { 748 Log.w(TAG, "Unknown requestCode " + requestCode); 749 return; 750 } 751 752 if (resultCode == Activity.RESULT_OK) { 753 String packageName = pref.getKey().substring(PKG_PREFIX.length()); 754 ArrayList<RestrictionEntry> list = 755 data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST); 756 Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE); 757 if (list != null) { 758 // If there's a valid result, persist it to the user manager. 759 pref.setRestrictions(list); 760 mUserManager.setApplicationRestrictions(packageName, 761 RestrictionsManager.convertRestrictionsToBundle(list), mUser); 762 } else if (bundle != null) { 763 // If there's a valid result, persist it to the user manager. 764 mUserManager.setApplicationRestrictions(packageName, bundle, mUser); 765 } 766 } 767 // Remove request from the map 768 mCustomRequestMap.remove(requestCode); 769 } 770 findInArray(String[] choiceEntries, String[] choiceValues, String selectedString)771 private String findInArray(String[] choiceEntries, String[] choiceValues, 772 String selectedString) { 773 for (int i = 0; i < choiceValues.length; i++) { 774 if (choiceValues[i].equals(selectedString)) { 775 return choiceEntries[i]; 776 } 777 } 778 return selectedString; 779 } 780 781 @Override onPreferenceClick(Preference preference)782 public boolean onPreferenceClick(Preference preference) { 783 if (preference.getKey().startsWith(PKG_PREFIX)) { 784 AppRestrictionsPreference arp = (AppRestrictionsPreference) preference; 785 if (!arp.isImmutable()) { 786 final String packageName = arp.getKey().substring(PKG_PREFIX.length()); 787 final boolean newEnabledState = !arp.isChecked(); 788 arp.setChecked(newEnabledState); 789 mHelper.setPackageSelected(packageName, newEnabledState); 790 updateAllEntries(arp.getKey(), newEnabledState); 791 mAppListChanged = true; 792 mHelper.applyUserAppState(packageName, newEnabledState, this); 793 } 794 return true; 795 } 796 return false; 797 } 798 799 } 800