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