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