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