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