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