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