• 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.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 
AppRestrictionsPreference(Context context)153         AppRestrictionsPreference(Context context) {
154             super(context, null, 0, R.style.LeanbackPreference_SwitchPreference);
155         }
156 
setRestrictions(ArrayList<RestrictionEntry> restrictions)157         void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
158             this.mRestrictions = restrictions;
159         }
160 
setImmutable(boolean immutable)161         void setImmutable(boolean immutable) {
162             this.mImmutable = immutable;
163         }
164 
isImmutable()165         boolean isImmutable() {
166             return mImmutable;
167         }
168 
getRestrictions()169         ArrayList<RestrictionEntry> getRestrictions() {
170             return mRestrictions;
171         }
172 
setChecked(boolean checked)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
getPreferenceCount()189         public int getPreferenceCount() {
190             if (isChecked()) {
191                 return super.getPreferenceCount();
192             } else {
193                 return 0;
194             }
195         }
196 
isChecked()197         public boolean isChecked() {
198             return mChecked;
199         }
200 
201         @Override
onBindViewHolder(PreferenceViewHolder holder)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 
syncSwitchView(View view)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
onCheckedChanged(CompoundButton buttonView, boolean isChecked)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 
prepareArgs(@onNull Bundle bundle, int userId, boolean newUser)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 
newInstance(int userId, boolean newUser)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
onCreate(Bundle savedInstanceState)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
onCreatePreferences(Bundle savedInstanceState, String rootKey)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
onSaveInstanceState(Bundle outState)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
onResume()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
onPause()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 
onPackageChanged(Intent intent)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 
getAppPreferenceGroup()360     private PreferenceGroup getAppPreferenceGroup() {
361         return getPreferenceScreen();
362     }
363 
364     @Override
onDisableUiForPackage(String packageName)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
doInBackground(Void... params)376         protected Void doInBackground(Void... params) {
377             mHelper.fetchAndMergeApps();
378             return null;
379         }
380 
381         @Override
onPostExecute(Void result)382         protected void onPostExecute(Void result) {
383             populateApps();
384         }
385     }
386 
isPlatformSigned(PackageInfo pi)387     private boolean isPlatformSigned(PackageInfo pi) {
388         return (pi != null && pi.signatures != null &&
389                 mSysPackageInfo.signatures[0].equals(pi.signatures[0]));
390     }
391 
isAppEnabledForUser(PackageInfo pi)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 
populateApps()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 
getPackageSummary(PackageInfo pi, AppRestrictionsHelper.SelectableAppInfo app)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 
isAppUnsupportedInRestrictedProfile(PackageInfo pi)495     private static boolean isAppUnsupportedInRestrictedProfile(PackageInfo pi) {
496         return pi.requiredAccountType != null && pi.restrictedAccountType == null;
497     }
498 
addLocationAppRestrictionsPreference()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 
getKeyForPackage(String packageName)517     private String getKeyForPackage(String packageName) {
518         return PKG_PREFIX + packageName;
519     }
520 
getKeyForPackageActivity(String packageName)521     private String getKeyForPackageActivity(String packageName) {
522         return ACTIVITY_PREFIX + packageName;
523     }
524 
getPackageFromKey(String key)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 
resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName)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 
updateAllEntries(String prefKey, boolean checked)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 
assertSafeToStartCustomActivity(Intent intent, String packageName)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
onPreferenceTreeClick(Preference preference)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
onPreferenceChange(Preference preference, Object newValue)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      */
requestRestrictionsForApp(String packageName, AppRestrictionsPreference preference)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 
RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference)681         RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) {
682             super();
683             mPackageName = packageName;
684             mPreference = preference;
685         }
686 
687         @Override
onReceive(Context context, Intent intent)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 
onRestrictionsReceived(AppRestrictionsPreference preference, ArrayList<RestrictionEntry> restrictions)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      */
generateCustomActivityRequestCode(Preference preference)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
onActivityResult(int requestCode, int resultCode, Intent data)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 
findInArray(String[] choiceEntries, String[] choiceValues, String selectedString)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      */
getExistingUser(UserManager userManager, UserHandle checkUser)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
getMetricsCategory()851     public int getMetricsCategory() {
852         return MetricsProto.MetricsEvent.USERS_APP_RESTRICTIONS;
853     }
854 }
855