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