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