• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.settings.users;
18 
19 import android.app.Activity;
20 import android.app.settings.SettingsEnums;
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.PackageManager.NameNotFoundException;
34 import android.content.pm.ResolveInfo;
35 import android.os.AsyncTask;
36 import android.os.Bundle;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.os.UserHandle;
40 import android.os.UserManager;
41 import android.util.EventLog;
42 import android.util.Log;
43 import android.view.View;
44 import android.view.View.OnClickListener;
45 import android.view.ViewGroup;
46 import android.widget.CompoundButton;
47 import android.widget.CompoundButton.OnCheckedChangeListener;
48 import android.widget.Switch;
49 
50 import androidx.preference.ListPreference;
51 import androidx.preference.MultiSelectListPreference;
52 import androidx.preference.Preference;
53 import androidx.preference.Preference.OnPreferenceChangeListener;
54 import androidx.preference.Preference.OnPreferenceClickListener;
55 import androidx.preference.PreferenceGroup;
56 import androidx.preference.PreferenceViewHolder;
57 import androidx.preference.SwitchPreference;
58 
59 import com.android.settings.R;
60 import com.android.settings.SettingsPreferenceFragment;
61 import com.android.settings.Utils;
62 import com.android.settingslib.users.AppRestrictionsHelper;
63 
64 import java.util.ArrayList;
65 import java.util.Collections;
66 import java.util.HashMap;
67 import java.util.HashSet;
68 import java.util.List;
69 import java.util.Set;
70 import java.util.StringTokenizer;
71 
72 public class AppRestrictionsFragment extends SettingsPreferenceFragment implements
73         OnPreferenceChangeListener, OnClickListener, OnPreferenceClickListener,
74         AppRestrictionsHelper.OnDisableUiForPackageListener {
75 
76     private static final String TAG = AppRestrictionsFragment.class.getSimpleName();
77 
78     private static final boolean DEBUG = false;
79 
80     private static final String PKG_PREFIX = "pkg_";
81 
82     protected PackageManager mPackageManager;
83     protected UserManager mUserManager;
84     protected IPackageManager mIPm;
85     protected 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     public 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     public static final String EXTRA_NEW_USER = "new_user";
101 
102     private boolean mFirstTime = true;
103     private boolean mNewUser;
104     private boolean mAppListChanged;
105     protected boolean mRestrictedProfile;
106 
107     private static final int CUSTOM_REQUEST_CODE_START = 1000;
108     private int mCustomRequestCode = CUSTOM_REQUEST_CODE_START;
109 
110     private HashMap<Integer, AppRestrictionsPreference> mCustomRequestMap = new HashMap<>();
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     static class AppRestrictionsPreference extends SwitchPreference {
136         private boolean hasSettings;
137         private OnClickListener listener;
138         private ArrayList<RestrictionEntry> restrictions;
139         private boolean panelOpen;
140         private boolean immutable;
141         private List<Preference> mChildren = new ArrayList<>();
142 
AppRestrictionsPreference(Context context, OnClickListener listener)143         AppRestrictionsPreference(Context context, OnClickListener listener) {
144             super(context);
145             setLayoutResource(R.layout.preference_app_restrictions);
146             this.listener = listener;
147         }
148 
setSettingsEnabled(boolean enable)149         private void setSettingsEnabled(boolean enable) {
150             hasSettings = enable;
151         }
152 
setRestrictions(ArrayList<RestrictionEntry> restrictions)153         void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
154             this.restrictions = restrictions;
155         }
156 
setImmutable(boolean immutable)157         void setImmutable(boolean immutable) {
158             this.immutable = immutable;
159         }
160 
isImmutable()161         boolean isImmutable() {
162             return immutable;
163         }
164 
getRestrictions()165         ArrayList<RestrictionEntry> getRestrictions() {
166             return restrictions;
167         }
168 
isPanelOpen()169         boolean isPanelOpen() {
170             return panelOpen;
171         }
172 
setPanelOpen(boolean open)173         void setPanelOpen(boolean open) {
174             panelOpen = open;
175         }
176 
getChildren()177         List<Preference> getChildren() {
178             return mChildren;
179         }
180 
181         @Override
onBindViewHolder(PreferenceViewHolder view)182         public void onBindViewHolder(PreferenceViewHolder view) {
183             super.onBindViewHolder(view);
184 
185             View appRestrictionsSettings = view.findViewById(R.id.app_restrictions_settings);
186             appRestrictionsSettings.setVisibility(hasSettings ? View.VISIBLE : View.GONE);
187             view.findViewById(R.id.settings_divider).setVisibility(
188                     hasSettings ? View.VISIBLE : View.GONE);
189             appRestrictionsSettings.setOnClickListener(listener);
190             appRestrictionsSettings.setTag(this);
191 
192             View appRestrictionsPref = view.findViewById(R.id.app_restrictions_pref);
193             appRestrictionsPref.setOnClickListener(listener);
194             appRestrictionsPref.setTag(this);
195 
196             ViewGroup widget = (ViewGroup) view.findViewById(android.R.id.widget_frame);
197             widget.setEnabled(!isImmutable());
198             if (widget.getChildCount() > 0) {
199                 final Switch toggle = (Switch) widget.getChildAt(0);
200                 toggle.setEnabled(!isImmutable());
201                 toggle.setTag(this);
202                 toggle.setClickable(true);
203                 toggle.setFocusable(true);
204                 toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {
205                     @Override
206                     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
207                         listener.onClick(toggle);
208                     }
209                 });
210             }
211         }
212     }
213 
214     @Override
onCreate(Bundle icicle)215     public void onCreate(Bundle icicle) {
216         super.onCreate(icicle);
217         init(icicle);
218     }
219 
init(Bundle icicle)220     protected void init(Bundle icicle) {
221         if (icicle != null) {
222             mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID));
223         } else {
224             Bundle args = getArguments();
225             if (args != null) {
226                 if (args.containsKey(EXTRA_USER_ID)) {
227                     mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
228                 }
229                 mNewUser = args.getBoolean(EXTRA_NEW_USER, false);
230             }
231         }
232 
233         if (mUser == null) {
234             mUser = android.os.Process.myUserHandle();
235         }
236 
237         mHelper = new AppRestrictionsHelper(getContext(), mUser);
238         mPackageManager = getActivity().getPackageManager();
239         mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
240         mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
241         mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted();
242         try {
243             mSysPackageInfo = mPackageManager.getPackageInfo("android",
244                 PackageManager.GET_SIGNATURES);
245         } catch (NameNotFoundException nnfe) {
246             // ?
247         }
248         addPreferencesFromResource(R.xml.app_restrictions);
249         mAppList = getAppPreferenceGroup();
250         mAppList.setOrderingAsAdded(false);
251     }
252 
253     @Override
getMetricsCategory()254     public int getMetricsCategory() {
255         return SettingsEnums.USERS_APP_RESTRICTIONS;
256     }
257 
258     @Override
onSaveInstanceState(Bundle outState)259     public void onSaveInstanceState(Bundle outState) {
260         super.onSaveInstanceState(outState);
261         outState.putInt(EXTRA_USER_ID, mUser.getIdentifier());
262     }
263 
264     @Override
onResume()265     public void onResume() {
266         super.onResume();
267 
268         getActivity().registerReceiver(mUserBackgrounding,
269                 new IntentFilter(Intent.ACTION_USER_BACKGROUND));
270         IntentFilter packageFilter = new IntentFilter();
271         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
272         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
273         packageFilter.addDataScheme("package");
274         getActivity().registerReceiver(mPackageObserver, packageFilter);
275 
276         mAppListChanged = false;
277         if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) {
278             mAppLoadingTask = new AppLoadingTask().execute();
279         }
280     }
281 
282     @Override
onPause()283     public void onPause() {
284         super.onPause();
285         mNewUser = false;
286         getActivity().unregisterReceiver(mUserBackgrounding);
287         getActivity().unregisterReceiver(mPackageObserver);
288         if (mAppListChanged) {
289             new AsyncTask<Void, Void, Void>() {
290                 @Override
291                 protected Void doInBackground(Void... params) {
292                     mHelper.applyUserAppsStates(AppRestrictionsFragment.this);
293                     return null;
294                 }
295             }.execute();
296         }
297     }
298 
onPackageChanged(Intent intent)299     private void onPackageChanged(Intent intent) {
300         String action = intent.getAction();
301         String packageName = intent.getData().getSchemeSpecificPart();
302         // Package added, check if the preference needs to be enabled
303         AppRestrictionsPreference pref = (AppRestrictionsPreference)
304                 findPreference(getKeyForPackage(packageName));
305         if (pref == null) return;
306 
307         if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && pref.isChecked())
308                 || (Intent.ACTION_PACKAGE_REMOVED.equals(action) && !pref.isChecked())) {
309             pref.setEnabled(true);
310         }
311     }
312 
getAppPreferenceGroup()313     protected PreferenceGroup getAppPreferenceGroup() {
314         return getPreferenceScreen();
315     }
316 
317     @Override
onDisableUiForPackage(String packageName)318     public void onDisableUiForPackage(String packageName) {
319         AppRestrictionsPreference pref = (AppRestrictionsPreference) findPreference(
320                 getKeyForPackage(packageName));
321         if (pref != null) {
322             pref.setEnabled(false);
323         }
324     }
325 
326     private class AppLoadingTask extends AsyncTask<Void, Void, Void> {
327 
328         @Override
doInBackground(Void... params)329         protected Void doInBackground(Void... params) {
330             mHelper.fetchAndMergeApps();
331             return null;
332         }
333 
334         @Override
onPostExecute(Void result)335         protected void onPostExecute(Void result) {
336             populateApps();
337         }
338     }
339 
isPlatformSigned(PackageInfo pi)340     private boolean isPlatformSigned(PackageInfo pi) {
341         return (pi != null && pi.signatures != null &&
342                     mSysPackageInfo.signatures[0].equals(pi.signatures[0]));
343     }
344 
isAppEnabledForUser(PackageInfo pi)345     private boolean isAppEnabledForUser(PackageInfo pi) {
346         if (pi == null) return false;
347         final int flags = pi.applicationInfo.flags;
348         final int privateFlags = pi.applicationInfo.privateFlags;
349         // Return true if it is installed and not hidden
350         return ((flags&ApplicationInfo.FLAG_INSTALLED) != 0
351                 && (privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0);
352     }
353 
populateApps()354     private void populateApps() {
355         final Context context = getActivity();
356         if (context == null) return;
357         final PackageManager pm = mPackageManager;
358         final IPackageManager ipm = mIPm;
359         final int userId = mUser.getIdentifier();
360 
361         // Check if the user was removed in the meantime.
362         if (Utils.getExistingUser(mUserManager, mUser) == null) {
363             return;
364         }
365         mAppList.removeAll();
366         Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
367         final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0);
368         for (AppRestrictionsHelper.SelectableAppInfo app : mHelper.getVisibleApps()) {
369             String packageName = app.packageName;
370             if (packageName == null) continue;
371             final boolean isSettingsApp = packageName.equals(context.getPackageName());
372             AppRestrictionsPreference p = new AppRestrictionsPreference(getPrefContext(), this);
373             final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName);
374             if (isSettingsApp) {
375                 addLocationAppRestrictionsPreference(app, p);
376                 // Settings app should be available to restricted user
377                 mHelper.setPackageSelected(packageName, true);
378                 continue;
379             }
380             PackageInfo pi = null;
381             try {
382                 pi = ipm.getPackageInfo(packageName,
383                         PackageManager.MATCH_ANY_USER
384                         | PackageManager.GET_SIGNATURES, userId);
385             } catch (RemoteException e) {
386                 // Ignore
387             }
388             if (pi == null) {
389                 continue;
390             }
391             if (mRestrictedProfile && isAppUnsupportedInRestrictedProfile(pi)) {
392                 continue;
393             }
394             p.setIcon(app.icon != null ? app.icon.mutate() : null);
395             p.setChecked(false);
396             p.setTitle(app.activityName);
397             p.setKey(getKeyForPackage(packageName));
398             p.setSettingsEnabled(hasSettings && app.primaryEntry == null);
399             p.setPersistent(false);
400             p.setOnPreferenceChangeListener(this);
401             p.setOnPreferenceClickListener(this);
402             p.setSummary(getPackageSummary(pi, app));
403             if (pi.requiredForAllUsers || isPlatformSigned(pi)) {
404                 p.setChecked(true);
405                 p.setImmutable(true);
406                 // If the app is required and has no restrictions, skip showing it
407                 if (!hasSettings) continue;
408                 // Get and populate the defaults, since the user is not going to be
409                 // able to toggle this app ON (it's ON by default and immutable).
410                 // Only do this for restricted profiles, not single-user restrictions
411                 // Also don't do this for secondary icons
412                 if (app.primaryEntry == null) {
413                     requestRestrictionsForApp(packageName, p, false);
414                 }
415             } else if (!mNewUser && isAppEnabledForUser(pi)) {
416                 p.setChecked(true);
417             }
418             if (app.primaryEntry != null) {
419                 p.setImmutable(true);
420                 p.setChecked(mHelper.isPackageSelected(packageName));
421             }
422             p.setOrder(MAX_APP_RESTRICTIONS * (mAppList.getPreferenceCount() + 2));
423             mHelper.setPackageSelected(packageName, p.isChecked());
424             mAppList.addPreference(p);
425         }
426         mAppListChanged = true;
427         // If this is the first time for a new profile, install/uninstall default apps for profile
428         // to avoid taking the hit in onPause(), which can cause race conditions on user switch.
429         if (mNewUser && mFirstTime) {
430             mFirstTime = false;
431             mHelper.applyUserAppsStates(this);
432         }
433     }
434 
getPackageSummary(PackageInfo pi, AppRestrictionsHelper.SelectableAppInfo app)435     private String getPackageSummary(PackageInfo pi, AppRestrictionsHelper.SelectableAppInfo app) {
436         // Check for 3 cases:
437         // - Secondary entry that can see primary user accounts
438         // - Secondary entry that cannot see primary user accounts
439         // - Primary entry that can see primary user accounts
440         // Otherwise no summary is returned
441         if (app.primaryEntry != null) {
442             if (mRestrictedProfile && pi.restrictedAccountType != null) {
443                 return getString(R.string.app_sees_restricted_accounts_and_controlled_by,
444                         app.primaryEntry.activityName);
445             }
446             return getString(R.string.user_restrictions_controlled_by,
447                     app.primaryEntry.activityName);
448         } else if (pi.restrictedAccountType != null) {
449             return getString(R.string.app_sees_restricted_accounts);
450         }
451         return null;
452     }
453 
isAppUnsupportedInRestrictedProfile(PackageInfo pi)454     private static boolean isAppUnsupportedInRestrictedProfile(PackageInfo pi) {
455         return pi.requiredAccountType != null && pi.restrictedAccountType == null;
456     }
457 
addLocationAppRestrictionsPreference(AppRestrictionsHelper.SelectableAppInfo app, AppRestrictionsPreference p)458     private void addLocationAppRestrictionsPreference(AppRestrictionsHelper.SelectableAppInfo app,
459             AppRestrictionsPreference p) {
460         String packageName = app.packageName;
461         p.setIcon(R.drawable.ic_preference_location);
462         p.setKey(getKeyForPackage(packageName));
463         ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions(
464                 getActivity(), mUser);
465         RestrictionEntry locationRestriction = restrictions.get(0);
466         p.setTitle(locationRestriction.getTitle());
467         p.setRestrictions(restrictions);
468         p.setSummary(locationRestriction.getDescription());
469         p.setChecked(locationRestriction.getSelectedState());
470         p.setPersistent(false);
471         p.setOnPreferenceClickListener(this);
472         p.setOrder(MAX_APP_RESTRICTIONS);
473         mAppList.addPreference(p);
474     }
475 
getKeyForPackage(String packageName)476     private String getKeyForPackage(String packageName) {
477         return PKG_PREFIX + packageName;
478     }
479 
resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName)480     private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) {
481         for (ResolveInfo info : receivers) {
482             if (info.activityInfo.packageName.equals(packageName)) {
483                 return true;
484             }
485         }
486         return false;
487     }
488 
updateAllEntries(String prefKey, boolean checked)489     private void updateAllEntries(String prefKey, boolean checked) {
490         for (int i = 0; i < mAppList.getPreferenceCount(); i++) {
491             Preference pref = mAppList.getPreference(i);
492             if (pref instanceof AppRestrictionsPreference) {
493                 if (prefKey.equals(pref.getKey())) {
494                     ((AppRestrictionsPreference) pref).setChecked(checked);
495                 }
496             }
497         }
498     }
499 
500     @Override
onClick(View v)501     public void onClick(View v) {
502         if (v.getTag() instanceof AppRestrictionsPreference) {
503             AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag();
504             if (v.getId() == R.id.app_restrictions_settings) {
505                 onAppSettingsIconClicked(pref);
506             } else if (!pref.isImmutable()) {
507                 pref.setChecked(!pref.isChecked());
508                 final String packageName = pref.getKey().substring(PKG_PREFIX.length());
509                 // Settings/Location is handled as a top-level entry
510                 if (packageName.equals(getActivity().getPackageName())) {
511                     pref.restrictions.get(0).setSelectedState(pref.isChecked());
512                     RestrictionUtils.setRestrictions(getActivity(), pref.restrictions, mUser);
513                     return;
514                 }
515                 mHelper.setPackageSelected(packageName, pref.isChecked());
516                 if (pref.isChecked() && pref.hasSettings
517                         && pref.restrictions == null) {
518                     // The restrictions have not been initialized, get and save them
519                     requestRestrictionsForApp(packageName, pref, false);
520                 }
521                 mAppListChanged = true;
522                 // If it's not a restricted profile, apply the changes immediately
523                 if (!mRestrictedProfile) {
524                     mHelper.applyUserAppState(packageName, pref.isChecked(), this);
525                 }
526                 updateAllEntries(pref.getKey(), pref.isChecked());
527             }
528         }
529     }
530 
531     @Override
onPreferenceChange(Preference preference, Object newValue)532     public boolean onPreferenceChange(Preference preference, Object newValue) {
533         String key = preference.getKey();
534         if (key != null && key.contains(DELIMITER)) {
535             StringTokenizer st = new StringTokenizer(key, DELIMITER);
536             final String packageName = st.nextToken();
537             final String restrictionKey = st.nextToken();
538             AppRestrictionsPreference appPref = (AppRestrictionsPreference)
539                     mAppList.findPreference(PKG_PREFIX+packageName);
540             ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions();
541             if (restrictions != null) {
542                 for (RestrictionEntry entry : restrictions) {
543                     if (entry.getKey().equals(restrictionKey)) {
544                         switch (entry.getType()) {
545                         case RestrictionEntry.TYPE_BOOLEAN:
546                             entry.setSelectedState((Boolean) newValue);
547                             break;
548                         case RestrictionEntry.TYPE_CHOICE:
549                         case RestrictionEntry.TYPE_CHOICE_LEVEL:
550                             ListPreference listPref = (ListPreference) preference;
551                             entry.setSelectedString((String) newValue);
552                             String readable = findInArray(entry.getChoiceEntries(),
553                                     entry.getChoiceValues(), (String) newValue);
554                             listPref.setSummary(readable);
555                             break;
556                         case RestrictionEntry.TYPE_MULTI_SELECT:
557                             Set<String> set = (Set<String>) newValue;
558                             String [] selectedValues = new String[set.size()];
559                             set.toArray(selectedValues);
560                             entry.setAllSelectedStrings(selectedValues);
561                             break;
562                         default:
563                             continue;
564                         }
565                         mUserManager.setApplicationRestrictions(packageName,
566                                 RestrictionsManager.convertRestrictionsToBundle(restrictions),
567                                 mUser);
568                         break;
569                     }
570                 }
571             }
572             return true;
573         }
574         return false;
575     }
576 
removeRestrictionsForApp(AppRestrictionsPreference preference)577     private void removeRestrictionsForApp(AppRestrictionsPreference preference) {
578         for (Preference p : preference.mChildren) {
579             mAppList.removePreference(p);
580         }
581         preference.mChildren.clear();
582     }
583 
onAppSettingsIconClicked(AppRestrictionsPreference preference)584     private void onAppSettingsIconClicked(AppRestrictionsPreference preference) {
585         if (preference.getKey().startsWith(PKG_PREFIX)) {
586             if (preference.isPanelOpen()) {
587                 removeRestrictionsForApp(preference);
588             } else {
589                 String packageName = preference.getKey().substring(PKG_PREFIX.length());
590                 requestRestrictionsForApp(packageName, preference, true /*invoke if custom*/);
591             }
592             preference.setPanelOpen(!preference.isPanelOpen());
593         }
594     }
595 
596     /**
597      * Send a broadcast to the app to query its restrictions
598      * @param packageName package name of the app with restrictions
599      * @param preference the preference item for the app toggle
600      * @param invokeIfCustom whether to directly launch any custom activity that is returned
601      *        for the app.
602      */
requestRestrictionsForApp(String packageName, AppRestrictionsPreference preference, boolean invokeIfCustom)603     private void requestRestrictionsForApp(String packageName,
604             AppRestrictionsPreference preference, boolean invokeIfCustom) {
605         Bundle oldEntries =
606                 mUserManager.getApplicationRestrictions(packageName, mUser);
607         Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
608         intent.setPackage(packageName);
609         intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries);
610         intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
611         getActivity().sendOrderedBroadcast(intent, null,
612                 new RestrictionsResultReceiver(packageName, preference, invokeIfCustom),
613                 null, Activity.RESULT_OK, null, null);
614     }
615 
616     class RestrictionsResultReceiver extends BroadcastReceiver {
617 
618         private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT;
619         String packageName;
620         AppRestrictionsPreference preference;
621         boolean invokeIfCustom;
622 
RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference, boolean invokeIfCustom)623         RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference,
624                 boolean invokeIfCustom) {
625             super();
626             this.packageName = packageName;
627             this.preference = preference;
628             this.invokeIfCustom = invokeIfCustom;
629         }
630 
631         @Override
onReceive(Context context, Intent intent)632         public void onReceive(Context context, Intent intent) {
633             Bundle results = getResultExtras(true);
634             final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList(
635                     Intent.EXTRA_RESTRICTIONS_LIST);
636             Intent restrictionsIntent = results.getParcelable(CUSTOM_RESTRICTIONS_INTENT);
637             if (restrictions != null && restrictionsIntent == null) {
638                 onRestrictionsReceived(preference, restrictions);
639                 if (mRestrictedProfile) {
640                     mUserManager.setApplicationRestrictions(packageName,
641                             RestrictionsManager.convertRestrictionsToBundle(restrictions), mUser);
642                 }
643             } else if (restrictionsIntent != null) {
644                 preference.setRestrictions(restrictions);
645                 if (invokeIfCustom && AppRestrictionsFragment.this.isResumed()) {
646                     try {
647                         assertSafeToStartCustomActivity(restrictionsIntent);
648                     } catch (ActivityNotFoundException | SecurityException e) {
649                         // return without startActivity
650                         Log.e(TAG, "Cannot start restrictionsIntent " + e);
651                         EventLog.writeEvent(0x534e4554, "200688991", -1 /* UID */, "");
652                         return;
653                     }
654 
655                     int requestCode = generateCustomActivityRequestCode(
656                             RestrictionsResultReceiver.this.preference);
657                     AppRestrictionsFragment.this.startActivityForResult(
658                             restrictionsIntent, requestCode);
659                 }
660             }
661         }
662 
assertSafeToStartCustomActivity(Intent intent)663         private void assertSafeToStartCustomActivity(Intent intent) {
664             EventLog.writeEvent(0x534e4554, "223578534", -1 /* UID */, "");
665             ResolveInfo resolveInfo = mPackageManager.resolveActivity(
666                     intent, PackageManager.MATCH_DEFAULT_ONLY);
667 
668             if (resolveInfo == null) {
669                 throw new ActivityNotFoundException("No result for resolving " + intent);
670             }
671             // Prevent potential privilege escalation
672             ActivityInfo activityInfo = resolveInfo.activityInfo;
673             if (!packageName.equals(activityInfo.packageName)) {
674                 throw new SecurityException("Application " + packageName
675                         + " is not allowed to start activity " + intent);
676             }
677         }
678     }
679 
onRestrictionsReceived(AppRestrictionsPreference preference, ArrayList<RestrictionEntry> restrictions)680     private void onRestrictionsReceived(AppRestrictionsPreference preference,
681             ArrayList<RestrictionEntry> restrictions) {
682         // Remove any earlier restrictions
683         removeRestrictionsForApp(preference);
684         // Non-custom-activity case - expand the restrictions in-place
685         int count = 1;
686         for (RestrictionEntry entry : restrictions) {
687             Preference p = null;
688             switch (entry.getType()) {
689             case RestrictionEntry.TYPE_BOOLEAN:
690                 p = new SwitchPreference(getPrefContext());
691                 p.setTitle(entry.getTitle());
692                 p.setSummary(entry.getDescription());
693                 ((SwitchPreference)p).setChecked(entry.getSelectedState());
694                 break;
695             case RestrictionEntry.TYPE_CHOICE:
696             case RestrictionEntry.TYPE_CHOICE_LEVEL:
697                 p = new ListPreference(getPrefContext());
698                 p.setTitle(entry.getTitle());
699                 String value = entry.getSelectedString();
700                 if (value == null) {
701                     value = entry.getDescription();
702                 }
703                 p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(),
704                         value));
705                 ((ListPreference)p).setEntryValues(entry.getChoiceValues());
706                 ((ListPreference)p).setEntries(entry.getChoiceEntries());
707                 ((ListPreference)p).setValue(value);
708                 ((ListPreference)p).setDialogTitle(entry.getTitle());
709                 break;
710             case RestrictionEntry.TYPE_MULTI_SELECT:
711                 p = new MultiSelectListPreference(getPrefContext());
712                 p.setTitle(entry.getTitle());
713                 ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues());
714                 ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries());
715                 HashSet<String> set = new HashSet<>();
716                 Collections.addAll(set, entry.getAllSelectedStrings());
717                 ((MultiSelectListPreference)p).setValues(set);
718                 ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle());
719                 break;
720             case RestrictionEntry.TYPE_NULL:
721             default:
722             }
723             if (p != null) {
724                 p.setPersistent(false);
725                 p.setOrder(preference.getOrder() + count);
726                 // Store the restrictions key string as a key for the preference
727                 p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER
728                         + entry.getKey());
729                 mAppList.addPreference(p);
730                 p.setOnPreferenceChangeListener(AppRestrictionsFragment.this);
731                 p.setIcon(R.drawable.empty_icon);
732                 preference.mChildren.add(p);
733                 count++;
734             }
735         }
736         preference.setRestrictions(restrictions);
737         if (count == 1 // No visible restrictions
738                 && preference.isImmutable()
739                 && preference.isChecked()) {
740             // Special case of required app with no visible restrictions. Remove it
741             mAppList.removePreference(preference);
742         }
743     }
744 
745     /**
746      * Generates a request code that is stored in a map to retrieve the associated
747      * AppRestrictionsPreference.
748      */
generateCustomActivityRequestCode(AppRestrictionsPreference preference)749     private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) {
750         mCustomRequestCode++;
751         mCustomRequestMap.put(mCustomRequestCode, preference);
752         return mCustomRequestCode;
753     }
754 
755     @Override
onActivityResult(int requestCode, int resultCode, Intent data)756     public void onActivityResult(int requestCode, int resultCode, Intent data) {
757         super.onActivityResult(requestCode, resultCode, data);
758 
759         AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode);
760         if (pref == null) {
761             Log.w(TAG, "Unknown requestCode " + requestCode);
762             return;
763         }
764 
765         if (resultCode == Activity.RESULT_OK) {
766             String packageName = pref.getKey().substring(PKG_PREFIX.length());
767             ArrayList<RestrictionEntry> list =
768                     data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST);
769             Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
770             if (list != null) {
771                 // If there's a valid result, persist it to the user manager.
772                 pref.setRestrictions(list);
773                 mUserManager.setApplicationRestrictions(packageName,
774                         RestrictionsManager.convertRestrictionsToBundle(list), mUser);
775             } else if (bundle != null) {
776                 // If there's a valid result, persist it to the user manager.
777                 mUserManager.setApplicationRestrictions(packageName, bundle, mUser);
778             }
779         }
780         // Remove request from the map
781         mCustomRequestMap.remove(requestCode);
782     }
783 
findInArray(String[] choiceEntries, String[] choiceValues, String selectedString)784     private String findInArray(String[] choiceEntries, String[] choiceValues,
785             String selectedString) {
786         for (int i = 0; i < choiceValues.length; i++) {
787             if (choiceValues[i].equals(selectedString)) {
788                 return choiceEntries[i];
789             }
790         }
791         return selectedString;
792     }
793 
794     @Override
onPreferenceClick(Preference preference)795     public boolean onPreferenceClick(Preference preference) {
796         if (preference.getKey().startsWith(PKG_PREFIX)) {
797             AppRestrictionsPreference arp = (AppRestrictionsPreference) preference;
798             if (!arp.isImmutable()) {
799                 final String packageName = arp.getKey().substring(PKG_PREFIX.length());
800                 final boolean newEnabledState = !arp.isChecked();
801                 arp.setChecked(newEnabledState);
802                 mHelper.setPackageSelected(packageName, newEnabledState);
803                 updateAllEntries(arp.getKey(), newEnabledState);
804                 mAppListChanged = true;
805                 mHelper.applyUserAppState(packageName, newEnabledState, this);
806             }
807             return true;
808         }
809         return false;
810     }
811 
812 }
813