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