• 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.appwidget.AppWidgetManager;
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.pm.ApplicationInfo;
27 import android.content.pm.IPackageManager;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.content.pm.ResolveInfo;
32 import android.content.res.Resources;
33 import android.graphics.Bitmap;
34 import android.graphics.ColorFilter;
35 import android.graphics.ColorMatrix;
36 import android.graphics.ColorMatrixColorFilter;
37 import android.graphics.drawable.Drawable;
38 import android.os.AsyncTask;
39 import android.os.Bundle;
40 import android.os.RemoteException;
41 import android.os.ServiceManager;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.preference.CheckBoxPreference;
45 import android.preference.ListPreference;
46 import android.preference.MultiSelectListPreference;
47 import android.preference.Preference;
48 import android.preference.Preference.OnPreferenceChangeListener;
49 import android.preference.Preference.OnPreferenceClickListener;
50 import android.preference.PreferenceGroup;
51 import android.preference.SwitchPreference;
52 import android.text.TextUtils;
53 import android.util.Log;
54 import android.view.View;
55 import android.view.View.OnClickListener;
56 import android.view.inputmethod.InputMethodInfo;
57 import android.view.inputmethod.InputMethodManager;
58 import android.view.ViewGroup;
59 import android.widget.CompoundButton;
60 import android.widget.CompoundButton.OnCheckedChangeListener;
61 import android.widget.Switch;
62 
63 import com.android.settings.R;
64 import com.android.settings.SettingsPreferenceFragment;
65 import com.android.settings.drawable.CircleFramedDrawable;
66 
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.Comparator;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.List;
73 import java.util.Map;
74 import java.util.Set;
75 import java.util.StringTokenizer;
76 
77 public class AppRestrictionsFragment extends SettingsPreferenceFragment implements
78         OnPreferenceChangeListener, OnClickListener, OnPreferenceClickListener {
79 
80     private static final String TAG = AppRestrictionsFragment.class.getSimpleName();
81 
82     private static final boolean DEBUG = false;
83 
84     private static final String PKG_PREFIX = "pkg_";
85 
86     protected PackageManager mPackageManager;
87     protected UserManager mUserManager;
88     protected IPackageManager mIPm;
89     protected UserHandle mUser;
90     private PackageInfo mSysPackageInfo;
91 
92     private PreferenceGroup mAppList;
93 
94     private static final int MAX_APP_RESTRICTIONS = 100;
95 
96     private static final String DELIMITER = ";";
97 
98     /** Key for extra passed in from calling fragment for the userId of the user being edited */
99     public static final String EXTRA_USER_ID = "user_id";
100 
101     /** Key for extra passed in from calling fragment to indicate if this is a newly created user */
102     public static final String EXTRA_NEW_USER = "new_user";
103 
104     HashMap<String,Boolean> mSelectedPackages = new HashMap<String,Boolean>();
105     private boolean mFirstTime = true;
106     private boolean mNewUser;
107     private boolean mAppListChanged;
108     protected boolean mRestrictedProfile;
109 
110     private static final int CUSTOM_REQUEST_CODE_START = 1000;
111     private int mCustomRequestCode = CUSTOM_REQUEST_CODE_START;
112 
113     private HashMap<Integer, AppRestrictionsPreference> mCustomRequestMap =
114             new HashMap<Integer,AppRestrictionsPreference>();
115 
116     private List<SelectableAppInfo> mVisibleApps;
117     private List<ApplicationInfo> mUserApps;
118     private AsyncTask mAppLoadingTask;
119 
120     private BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() {
121         @Override
122         public void onReceive(Context context, Intent intent) {
123             // Update the user's app selection right away without waiting for a pause
124             // onPause() might come in too late, causing apps to disappear after broadcasts
125             // have been scheduled during user startup.
126             if (mAppListChanged) {
127                 if (DEBUG) Log.d(TAG, "User backgrounding, update app list");
128                 applyUserAppsStates();
129                 if (DEBUG) Log.d(TAG, "User backgrounding, done updating app list");
130             }
131         }
132     };
133 
134     private BroadcastReceiver mPackageObserver = new BroadcastReceiver() {
135         @Override
136         public void onReceive(Context context, Intent intent) {
137             onPackageChanged(intent);
138         }
139     };
140 
141     static class SelectableAppInfo {
142         String packageName;
143         CharSequence appName;
144         CharSequence activityName;
145         Drawable icon;
146         SelectableAppInfo masterEntry;
147 
148         @Override
toString()149         public String toString() {
150             return packageName + ": appName=" + appName + "; activityName=" + activityName
151                     + "; icon=" + icon + "; masterEntry=" + masterEntry;
152         }
153     }
154 
155     static class AppRestrictionsPreference extends SwitchPreference {
156         private boolean hasSettings;
157         private OnClickListener listener;
158         private ArrayList<RestrictionEntry> restrictions;
159         private boolean panelOpen;
160         private boolean immutable;
161         private List<Preference> mChildren = new ArrayList<Preference>();
162 
AppRestrictionsPreference(Context context, OnClickListener listener)163         AppRestrictionsPreference(Context context, OnClickListener listener) {
164             super(context);
165             setLayoutResource(R.layout.preference_app_restrictions);
166             this.listener = listener;
167         }
168 
setSettingsEnabled(boolean enable)169         private void setSettingsEnabled(boolean enable) {
170             hasSettings = enable;
171         }
172 
setRestrictions(ArrayList<RestrictionEntry> restrictions)173         void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
174             this.restrictions = restrictions;
175         }
176 
setImmutable(boolean immutable)177         void setImmutable(boolean immutable) {
178             this.immutable = immutable;
179         }
180 
isImmutable()181         boolean isImmutable() {
182             return immutable;
183         }
184 
getRestriction(String key)185         RestrictionEntry getRestriction(String key) {
186             if (restrictions == null) return null;
187             for (RestrictionEntry entry : restrictions) {
188                 if (entry.getKey().equals(key)) {
189                     return entry;
190                 }
191             }
192             return null;
193         }
194 
getRestrictions()195         ArrayList<RestrictionEntry> getRestrictions() {
196             return restrictions;
197         }
198 
isPanelOpen()199         boolean isPanelOpen() {
200             return panelOpen;
201         }
202 
setPanelOpen(boolean open)203         void setPanelOpen(boolean open) {
204             panelOpen = open;
205         }
206 
getChildren()207         List<Preference> getChildren() {
208             return mChildren;
209         }
210 
211         @Override
onBindView(View view)212         protected void onBindView(View view) {
213             super.onBindView(view);
214 
215             View appRestrictionsSettings = view.findViewById(R.id.app_restrictions_settings);
216             appRestrictionsSettings.setVisibility(hasSettings ? View.VISIBLE : View.GONE);
217             view.findViewById(R.id.settings_divider).setVisibility(
218                     hasSettings ? View.VISIBLE : View.GONE);
219             appRestrictionsSettings.setOnClickListener(listener);
220             appRestrictionsSettings.setTag(this);
221 
222             View appRestrictionsPref = view.findViewById(R.id.app_restrictions_pref);
223             appRestrictionsPref.setOnClickListener(listener);
224             appRestrictionsPref.setTag(this);
225 
226             ViewGroup widget = (ViewGroup) view.findViewById(android.R.id.widget_frame);
227             widget.setEnabled(!isImmutable());
228             if (widget.getChildCount() > 0) {
229                 final Switch toggle = (Switch) widget.getChildAt(0);
230                 toggle.setEnabled(!isImmutable());
231                 toggle.setTag(this);
232                 toggle.setClickable(true);
233                 toggle.setFocusable(true);
234                 toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {
235                     @Override
236                     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
237                         listener.onClick(toggle);
238                     }
239                 });
240             }
241         }
242     }
243 
init(Bundle icicle)244     protected void init(Bundle icicle) {
245         if (icicle != null) {
246             mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID));
247         } else {
248             Bundle args = getArguments();
249             if (args != null) {
250                 if (args.containsKey(EXTRA_USER_ID)) {
251                     mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
252                 }
253                 mNewUser = args.getBoolean(EXTRA_NEW_USER, false);
254             }
255         }
256 
257         if (mUser == null) {
258             mUser = android.os.Process.myUserHandle();
259         }
260 
261         mPackageManager = getActivity().getPackageManager();
262         mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
263         mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
264         mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted();
265         try {
266             mSysPackageInfo = mPackageManager.getPackageInfo("android",
267                 PackageManager.GET_SIGNATURES);
268         } catch (NameNotFoundException nnfe) {
269             // ?
270         }
271         addPreferencesFromResource(R.xml.app_restrictions);
272         mAppList = getAppPreferenceGroup();
273     }
274 
275     @Override
onSaveInstanceState(Bundle outState)276     public void onSaveInstanceState(Bundle outState) {
277         super.onSaveInstanceState(outState);
278         outState.putInt(EXTRA_USER_ID, mUser.getIdentifier());
279     }
280 
281     @Override
onResume()282     public void onResume() {
283         super.onResume();
284 
285         getActivity().registerReceiver(mUserBackgrounding,
286                 new IntentFilter(Intent.ACTION_USER_BACKGROUND));
287         IntentFilter packageFilter = new IntentFilter();
288         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
289         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
290         packageFilter.addDataScheme("package");
291         getActivity().registerReceiver(mPackageObserver, packageFilter);
292 
293         mAppListChanged = false;
294         if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) {
295             mAppLoadingTask = new AppLoadingTask().execute((Void[]) null);
296         }
297     }
298 
299     @Override
onPause()300     public void onPause() {
301         super.onPause();
302         mNewUser = false;
303         getActivity().unregisterReceiver(mUserBackgrounding);
304         getActivity().unregisterReceiver(mPackageObserver);
305         if (mAppListChanged) {
306             new Thread() {
307                 public void run() {
308                     applyUserAppsStates();
309                 }
310             }.start();
311         }
312     }
313 
onPackageChanged(Intent intent)314     private void onPackageChanged(Intent intent) {
315         String action = intent.getAction();
316         String packageName = intent.getData().getSchemeSpecificPart();
317         // Package added, check if the preference needs to be enabled
318         AppRestrictionsPreference pref = (AppRestrictionsPreference)
319                 findPreference(getKeyForPackage(packageName));
320         if (pref == null) return;
321 
322         if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && pref.isChecked())
323                 || (Intent.ACTION_PACKAGE_REMOVED.equals(action) && !pref.isChecked())) {
324             pref.setEnabled(true);
325         }
326     }
327 
getAppPreferenceGroup()328     protected PreferenceGroup getAppPreferenceGroup() {
329         return getPreferenceScreen();
330     }
331 
getCircularUserIcon()332     Drawable getCircularUserIcon() {
333         Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier());
334         if (userIcon == null) {
335             return null;
336         }
337         CircleFramedDrawable circularIcon =
338                 CircleFramedDrawable.getInstance(this.getActivity(), userIcon);
339         return circularIcon;
340     }
341 
clearSelectedApps()342     protected void clearSelectedApps() {
343         mSelectedPackages.clear();
344     }
345 
applyUserAppsStates()346     private void applyUserAppsStates() {
347         final int userId = mUser.getIdentifier();
348         if (!mUserManager.getUserInfo(userId).isRestricted() && userId != UserHandle.myUserId()) {
349             Log.e(TAG, "Cannot apply application restrictions on another user!");
350             return;
351         }
352         for (Map.Entry<String,Boolean> entry : mSelectedPackages.entrySet()) {
353             String packageName = entry.getKey();
354             boolean enabled = entry.getValue();
355             applyUserAppState(packageName, enabled);
356         }
357     }
358 
applyUserAppState(String packageName, boolean enabled)359     private void applyUserAppState(String packageName, boolean enabled) {
360         final int userId = mUser.getIdentifier();
361         if (enabled) {
362             // Enable selected apps
363             try {
364                 ApplicationInfo info = mIPm.getApplicationInfo(packageName,
365                         PackageManager.GET_UNINSTALLED_PACKAGES, userId);
366                 if (info == null || info.enabled == false
367                         || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
368                     mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier());
369                     if (DEBUG) {
370                         Log.d(TAG, "Installing " + packageName);
371                     }
372                 }
373                 if (info != null && (info.flags&ApplicationInfo.FLAG_HIDDEN) != 0
374                         && (info.flags&ApplicationInfo.FLAG_INSTALLED) != 0) {
375                     disableUiForPackage(packageName);
376                     mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId);
377                     if (DEBUG) {
378                         Log.d(TAG, "Unhiding " + packageName);
379                     }
380                 }
381             } catch (RemoteException re) {
382             }
383         } else {
384             // Blacklist all other apps, system or downloaded
385             try {
386                 ApplicationInfo info = mIPm.getApplicationInfo(packageName, 0, userId);
387                 if (info != null) {
388                     if (mRestrictedProfile) {
389                         mIPm.deletePackageAsUser(packageName, null, mUser.getIdentifier(),
390                                 PackageManager.DELETE_SYSTEM_APP);
391                         if (DEBUG) {
392                             Log.d(TAG, "Uninstalling " + packageName);
393                         }
394                     } else {
395                         disableUiForPackage(packageName);
396                         mIPm.setApplicationHiddenSettingAsUser(packageName, true, userId);
397                         if (DEBUG) {
398                             Log.d(TAG, "Hiding " + packageName);
399                         }
400                     }
401                 }
402             } catch (RemoteException re) {
403             }
404         }
405     }
406 
disableUiForPackage(String packageName)407     private void disableUiForPackage(String packageName) {
408         AppRestrictionsPreference pref = (AppRestrictionsPreference) findPreference(
409                 getKeyForPackage(packageName));
410         if (pref != null) {
411             pref.setEnabled(false);
412         }
413     }
414 
isSystemPackage(String packageName)415     private boolean isSystemPackage(String packageName) {
416         try {
417             final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
418             if (pi.applicationInfo == null) return false;
419             final int flags = pi.applicationInfo.flags;
420             if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
421                     || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
422                 return true;
423             }
424         } catch (NameNotFoundException nnfe) {
425             // Missing package?
426         }
427         return false;
428     }
429 
430     /**
431      * Find all pre-installed input methods that are marked as default
432      * and add them to an exclusion list so that they aren't
433      * presented to the user for toggling.
434      * Don't add non-default ones, as they may include other stuff that we
435      * don't need to auto-include.
436      * @param excludePackages the set of package names to append to
437      */
addSystemImes(Set<String> excludePackages)438     private void addSystemImes(Set<String> excludePackages) {
439         final Context context = getActivity();
440         if (context == null) return;
441         InputMethodManager imm = (InputMethodManager)
442                 context.getSystemService(Context.INPUT_METHOD_SERVICE);
443         List<InputMethodInfo> imis = imm.getInputMethodList();
444         for (InputMethodInfo imi : imis) {
445             try {
446                 if (imi.isDefault(context) && isSystemPackage(imi.getPackageName())) {
447                     excludePackages.add(imi.getPackageName());
448                 }
449             } catch (Resources.NotFoundException rnfe) {
450                 // Not default
451             }
452         }
453     }
454 
455     /**
456      * Add system apps that match an intent to the list, excluding any packages in the exclude list.
457      * @param visibleApps list of apps to append the new list to
458      * @param intent the intent to match
459      * @param excludePackages the set of package names to be excluded, since they're required
460      */
addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, Set<String> excludePackages)461     private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent,
462             Set<String> excludePackages) {
463         if (getActivity() == null) return;
464         final PackageManager pm = mPackageManager;
465         List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent,
466                 PackageManager.GET_DISABLED_COMPONENTS | PackageManager.GET_UNINSTALLED_PACKAGES);
467         for (ResolveInfo app : launchableApps) {
468             if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
469                 final String packageName = app.activityInfo.packageName;
470                 int flags = app.activityInfo.applicationInfo.flags;
471                 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
472                         || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
473                     // System app
474                     // Skip excluded packages
475                     if (excludePackages.contains(packageName)) continue;
476                     int enabled = pm.getApplicationEnabledSetting(packageName);
477                     if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
478                             || enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
479                         // Check if the app is already enabled for the target user
480                         ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName,
481                                 0, mUser);
482                         if (targetUserAppInfo == null
483                                 || (targetUserAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
484                             continue;
485                         }
486                     }
487                     SelectableAppInfo info = new SelectableAppInfo();
488                     info.packageName = app.activityInfo.packageName;
489                     info.appName = app.activityInfo.applicationInfo.loadLabel(pm);
490                     info.icon = app.activityInfo.loadIcon(pm);
491                     info.activityName = app.activityInfo.loadLabel(pm);
492                     if (info.activityName == null) info.activityName = info.appName;
493 
494                     visibleApps.add(info);
495                 }
496             }
497         }
498     }
499 
getAppInfoForUser(String packageName, int flags, UserHandle user)500     private ApplicationInfo getAppInfoForUser(String packageName, int flags, UserHandle user) {
501         try {
502             ApplicationInfo targetUserAppInfo = mIPm.getApplicationInfo(packageName, flags,
503                     user.getIdentifier());
504             return targetUserAppInfo;
505         } catch (RemoteException re) {
506             return null;
507         }
508     }
509 
510     private class AppLoadingTask extends AsyncTask<Void, Void, Void> {
511 
512         @Override
doInBackground(Void... params)513         protected Void doInBackground(Void... params) {
514             fetchAndMergeApps();
515             return null;
516         }
517 
518         @Override
onPostExecute(Void result)519         protected void onPostExecute(Void result) {
520             populateApps();
521         }
522 
523         @Override
onPreExecute()524         protected void onPreExecute() {
525         }
526     }
527 
fetchAndMergeApps()528     private void fetchAndMergeApps() {
529         mAppList.setOrderingAsAdded(false);
530         mVisibleApps = new ArrayList<SelectableAppInfo>();
531         final Context context = getActivity();
532         if (context == null) return;
533         final PackageManager pm = mPackageManager;
534         final IPackageManager ipm = mIPm;
535 
536         final HashSet<String> excludePackages = new HashSet<String>();
537         addSystemImes(excludePackages);
538 
539         // Add launchers
540         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
541         launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
542         addSystemApps(mVisibleApps, launcherIntent, excludePackages);
543 
544         // Add widgets
545         Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
546         addSystemApps(mVisibleApps, widgetIntent, excludePackages);
547 
548         List<ApplicationInfo> installedApps = pm.getInstalledApplications(
549                 PackageManager.GET_UNINSTALLED_PACKAGES);
550         for (ApplicationInfo app : installedApps) {
551             // If it's not installed, skip
552             if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
553 
554             if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
555                     && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
556                 // Downloaded app
557                 SelectableAppInfo info = new SelectableAppInfo();
558                 info.packageName = app.packageName;
559                 info.appName = app.loadLabel(pm);
560                 info.activityName = info.appName;
561                 info.icon = app.loadIcon(pm);
562                 mVisibleApps.add(info);
563             } else {
564                 try {
565                     PackageInfo pi = pm.getPackageInfo(app.packageName, 0);
566                     // If it's a system app that requires an account and doesn't see restricted
567                     // accounts, mark for removal. It might get shown in the UI if it has an icon
568                     // but will still be marked as false and immutable.
569                     if (mRestrictedProfile
570                             && pi.requiredAccountType != null && pi.restrictedAccountType == null) {
571                         mSelectedPackages.put(app.packageName, false);
572                     }
573                 } catch (NameNotFoundException re) {
574                 }
575             }
576         }
577 
578         // Get the list of apps already installed for the user
579         mUserApps = null;
580         try {
581             mUserApps = ipm.getInstalledApplications(
582                     PackageManager.GET_UNINSTALLED_PACKAGES, mUser.getIdentifier()).getList();
583         } catch (RemoteException re) {
584         }
585 
586         if (mUserApps != null) {
587             for (ApplicationInfo app : mUserApps) {
588                 if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
589 
590                 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
591                         && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
592                     // Downloaded app
593                     SelectableAppInfo info = new SelectableAppInfo();
594                     info.packageName = app.packageName;
595                     info.appName = app.loadLabel(pm);
596                     info.activityName = info.appName;
597                     info.icon = app.loadIcon(pm);
598                     mVisibleApps.add(info);
599                 }
600             }
601         }
602 
603         // Sort the list of visible apps
604         Collections.sort(mVisibleApps, new AppLabelComparator());
605 
606         // Remove dupes
607         Set<String> dedupPackageSet = new HashSet<String>();
608         for (int i = mVisibleApps.size() - 1; i >= 0; i--) {
609             SelectableAppInfo info = mVisibleApps.get(i);
610             if (DEBUG) Log.i(TAG, info.toString());
611             String both = info.packageName + "+" + info.activityName;
612             if (!TextUtils.isEmpty(info.packageName)
613                     && !TextUtils.isEmpty(info.activityName)
614                     && dedupPackageSet.contains(both)) {
615                 mVisibleApps.remove(i);
616             } else {
617                 dedupPackageSet.add(both);
618             }
619         }
620 
621         // Establish master/slave relationship for entries that share a package name
622         HashMap<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>();
623         for (SelectableAppInfo info : mVisibleApps) {
624             if (packageMap.containsKey(info.packageName)) {
625                 info.masterEntry = packageMap.get(info.packageName);
626             } else {
627                 packageMap.put(info.packageName, info);
628             }
629         }
630     }
631 
isPlatformSigned(PackageInfo pi)632     private boolean isPlatformSigned(PackageInfo pi) {
633         return (pi != null && pi.signatures != null &&
634                     mSysPackageInfo.signatures[0].equals(pi.signatures[0]));
635     }
636 
isAppEnabledForUser(PackageInfo pi)637     private boolean isAppEnabledForUser(PackageInfo pi) {
638         if (pi == null) return false;
639         final int flags = pi.applicationInfo.flags;
640         // Return true if it is installed and not hidden
641         return ((flags&ApplicationInfo.FLAG_INSTALLED) != 0
642                 && (flags&ApplicationInfo.FLAG_HIDDEN) == 0);
643     }
644 
populateApps()645     private void populateApps() {
646         final Context context = getActivity();
647         if (context == null) return;
648         final PackageManager pm = mPackageManager;
649         final IPackageManager ipm = mIPm;
650 
651         mAppList.removeAll();
652         Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
653         final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0);
654         int i = 0;
655         if (mVisibleApps.size() > 0) {
656             for (SelectableAppInfo app : mVisibleApps) {
657                 String packageName = app.packageName;
658                 if (packageName == null) continue;
659                 final boolean isSettingsApp = packageName.equals(context.getPackageName());
660                 AppRestrictionsPreference p = new AppRestrictionsPreference(context, this);
661                 final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName);
662                 p.setIcon(app.icon != null ? app.icon.mutate() : null);
663                 p.setChecked(false);
664                 p.setTitle(app.activityName);
665                 if (app.masterEntry != null) {
666                     p.setSummary(context.getString(R.string.user_restrictions_controlled_by,
667                             app.masterEntry.activityName));
668                 }
669                 p.setKey(getKeyForPackage(packageName));
670                 p.setSettingsEnabled((hasSettings || isSettingsApp) && app.masterEntry == null);
671                 p.setPersistent(false);
672                 p.setOnPreferenceChangeListener(this);
673                 p.setOnPreferenceClickListener(this);
674                 PackageInfo pi = null;
675                 try {
676                     pi = ipm.getPackageInfo(packageName,
677                             PackageManager.GET_UNINSTALLED_PACKAGES
678                             | PackageManager.GET_SIGNATURES, mUser.getIdentifier());
679                 } catch (RemoteException e) {
680                 }
681                 if (pi != null && (pi.requiredForAllUsers || isPlatformSigned(pi))) {
682                     p.setChecked(true);
683                     p.setImmutable(true);
684                     // If the app is required and has no restrictions, skip showing it
685                     if (!hasSettings && !isSettingsApp) continue;
686                     // Get and populate the defaults, since the user is not going to be
687                     // able to toggle this app ON (it's ON by default and immutable).
688                     // Only do this for restricted profiles, not single-user restrictions
689                     // Also don't do this for slave icons
690                     if (hasSettings && app.masterEntry == null) {
691                         requestRestrictionsForApp(packageName, p, false);
692                     }
693                 } else if (!mNewUser && isAppEnabledForUser(pi)) {
694                     p.setChecked(true);
695                 }
696                 if (mRestrictedProfile
697                         && pi.requiredAccountType != null && pi.restrictedAccountType == null) {
698                     p.setChecked(false);
699                     p.setImmutable(true);
700                     p.setSummary(R.string.app_not_supported_in_limited);
701                 }
702                 if (mRestrictedProfile && pi.restrictedAccountType != null) {
703                     p.setSummary(R.string.app_sees_restricted_accounts);
704                 }
705                 if (app.masterEntry != null) {
706                     p.setImmutable(true);
707                     p.setChecked(mSelectedPackages.get(packageName));
708                 }
709                 mAppList.addPreference(p);
710                 if (isSettingsApp) {
711                     p.setOrder(MAX_APP_RESTRICTIONS * 1);
712                 } else {
713                     p.setOrder(MAX_APP_RESTRICTIONS * (i + 2));
714                 }
715                 mSelectedPackages.put(packageName, p.isChecked());
716                 mAppListChanged = true;
717                 i++;
718             }
719         }
720         // If this is the first time for a new profile, install/uninstall default apps for profile
721         // to avoid taking the hit in onPause(), which can cause race conditions on user switch.
722         if (mNewUser && mFirstTime) {
723             mFirstTime = false;
724             applyUserAppsStates();
725         }
726     }
727 
getKeyForPackage(String packageName)728     private String getKeyForPackage(String packageName) {
729         return PKG_PREFIX + packageName;
730     }
731 
732     private class AppLabelComparator implements Comparator<SelectableAppInfo> {
733 
734         @Override
compare(SelectableAppInfo lhs, SelectableAppInfo rhs)735         public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
736             String lhsLabel = lhs.activityName.toString();
737             String rhsLabel = rhs.activityName.toString();
738             return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
739         }
740     }
741 
resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName)742     private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) {
743         for (ResolveInfo info : receivers) {
744             if (info.activityInfo.packageName.equals(packageName)) {
745                 return true;
746             }
747         }
748         return false;
749     }
750 
updateAllEntries(String prefKey, boolean checked)751     private void updateAllEntries(String prefKey, boolean checked) {
752         for (int i = 0; i < mAppList.getPreferenceCount(); i++) {
753             Preference pref = mAppList.getPreference(i);
754             if (pref instanceof AppRestrictionsPreference) {
755                 if (prefKey.equals(pref.getKey())) {
756                     ((AppRestrictionsPreference) pref).setChecked(checked);
757                 }
758             }
759         }
760     }
761 
762     @Override
onClick(View v)763     public void onClick(View v) {
764         if (v.getTag() instanceof AppRestrictionsPreference) {
765             AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag();
766             if (v.getId() == R.id.app_restrictions_settings) {
767                 onAppSettingsIconClicked(pref);
768             } else if (!pref.isImmutable()) {
769                 pref.setChecked(!pref.isChecked());
770                 final String packageName = pref.getKey().substring(PKG_PREFIX.length());
771                 mSelectedPackages.put(packageName, pref.isChecked());
772                 if (pref.isChecked() && pref.hasSettings
773                         && pref.restrictions == null) {
774                     // The restrictions have not been initialized, get and save them
775                     requestRestrictionsForApp(packageName, pref, false);
776                 }
777                 mAppListChanged = true;
778                 // If it's not a restricted profile, apply the changes immediately
779                 if (!mRestrictedProfile) {
780                     applyUserAppState(packageName, pref.isChecked());
781                 }
782                 updateAllEntries(pref.getKey(), pref.isChecked());
783             }
784         }
785     }
786 
787     @Override
onPreferenceChange(Preference preference, Object newValue)788     public boolean onPreferenceChange(Preference preference, Object newValue) {
789         String key = preference.getKey();
790         if (key != null && key.contains(DELIMITER)) {
791             StringTokenizer st = new StringTokenizer(key, DELIMITER);
792             final String packageName = st.nextToken();
793             final String restrictionKey = st.nextToken();
794             AppRestrictionsPreference appPref = (AppRestrictionsPreference)
795                     mAppList.findPreference(PKG_PREFIX+packageName);
796             ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions();
797             if (restrictions != null) {
798                 for (RestrictionEntry entry : restrictions) {
799                     if (entry.getKey().equals(restrictionKey)) {
800                         switch (entry.getType()) {
801                         case RestrictionEntry.TYPE_BOOLEAN:
802                             entry.setSelectedState((Boolean) newValue);
803                             break;
804                         case RestrictionEntry.TYPE_CHOICE:
805                         case RestrictionEntry.TYPE_CHOICE_LEVEL:
806                             ListPreference listPref = (ListPreference) preference;
807                             entry.setSelectedString((String) newValue);
808                             String readable = findInArray(entry.getChoiceEntries(),
809                                     entry.getChoiceValues(), (String) newValue);
810                             listPref.setSummary(readable);
811                             break;
812                         case RestrictionEntry.TYPE_MULTI_SELECT:
813                             Set<String> set = (Set<String>) newValue;
814                             String [] selectedValues = new String[set.size()];
815                             set.toArray(selectedValues);
816                             entry.setAllSelectedStrings(selectedValues);
817                             break;
818                         default:
819                             continue;
820                         }
821                         if (packageName.equals(getActivity().getPackageName())) {
822                             RestrictionUtils.setRestrictions(getActivity(), restrictions, mUser);
823                         } else {
824                             mUserManager.setApplicationRestrictions(packageName,
825                                     RestrictionUtils.restrictionsToBundle(restrictions),
826                                     mUser);
827                         }
828                         break;
829                     }
830                 }
831             }
832         }
833         return true;
834     }
835 
removeRestrictionsForApp(AppRestrictionsPreference preference)836     private void removeRestrictionsForApp(AppRestrictionsPreference preference) {
837         for (Preference p : preference.mChildren) {
838             mAppList.removePreference(p);
839         }
840         preference.mChildren.clear();
841     }
842 
onAppSettingsIconClicked(AppRestrictionsPreference preference)843     private void onAppSettingsIconClicked(AppRestrictionsPreference preference) {
844         if (preference.getKey().startsWith(PKG_PREFIX)) {
845             if (preference.isPanelOpen()) {
846                 removeRestrictionsForApp(preference);
847             } else {
848                 String packageName = preference.getKey().substring(PKG_PREFIX.length());
849                 if (packageName.equals(getActivity().getPackageName())) {
850                     // Settings, fake it by using user restrictions
851                     ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions(
852                             getActivity(), mUser);
853                     onRestrictionsReceived(preference, packageName, restrictions);
854                 } else {
855                     requestRestrictionsForApp(packageName, preference, true /*invoke if custom*/);
856                 }
857             }
858             preference.setPanelOpen(!preference.isPanelOpen());
859         }
860     }
861 
862     /**
863      * Send a broadcast to the app to query its restrictions
864      * @param packageName package name of the app with restrictions
865      * @param preference the preference item for the app toggle
866      * @param invokeIfCustom whether to directly launch any custom activity that is returned
867      *        for the app.
868      */
requestRestrictionsForApp(String packageName, AppRestrictionsPreference preference, boolean invokeIfCustom)869     private void requestRestrictionsForApp(String packageName,
870             AppRestrictionsPreference preference, boolean invokeIfCustom) {
871         Bundle oldEntries =
872                 mUserManager.getApplicationRestrictions(packageName, mUser);
873         Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
874         intent.setPackage(packageName);
875         intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries);
876         intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
877         getActivity().sendOrderedBroadcast(intent, null,
878                 new RestrictionsResultReceiver(packageName, preference, invokeIfCustom),
879                 null, Activity.RESULT_OK, null, null);
880     }
881 
882     class RestrictionsResultReceiver extends BroadcastReceiver {
883 
884         private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT;
885         String packageName;
886         AppRestrictionsPreference preference;
887         boolean invokeIfCustom;
888 
RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference, boolean invokeIfCustom)889         RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference,
890                 boolean invokeIfCustom) {
891             super();
892             this.packageName = packageName;
893             this.preference = preference;
894             this.invokeIfCustom = invokeIfCustom;
895         }
896 
897         @Override
onReceive(Context context, Intent intent)898         public void onReceive(Context context, Intent intent) {
899             Bundle results = getResultExtras(true);
900             final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList(
901                     Intent.EXTRA_RESTRICTIONS_LIST);
902             Intent restrictionsIntent = (Intent) results.getParcelable(CUSTOM_RESTRICTIONS_INTENT);
903             if (restrictions != null && restrictionsIntent == null) {
904                 onRestrictionsReceived(preference, packageName, restrictions);
905                 if (mRestrictedProfile) {
906                     mUserManager.setApplicationRestrictions(packageName,
907                             RestrictionUtils.restrictionsToBundle(restrictions), mUser);
908                 }
909             } else if (restrictionsIntent != null) {
910                 preference.setRestrictions(restrictions);
911                 if (invokeIfCustom && AppRestrictionsFragment.this.isResumed()) {
912                     int requestCode = generateCustomActivityRequestCode(
913                             RestrictionsResultReceiver.this.preference);
914                     AppRestrictionsFragment.this.startActivityForResult(
915                             restrictionsIntent, requestCode);
916                 }
917             }
918         }
919     }
920 
onRestrictionsReceived(AppRestrictionsPreference preference, String packageName, ArrayList<RestrictionEntry> restrictions)921     private void onRestrictionsReceived(AppRestrictionsPreference preference, String packageName,
922             ArrayList<RestrictionEntry> restrictions) {
923         // Remove any earlier restrictions
924         removeRestrictionsForApp(preference);
925         // Non-custom-activity case - expand the restrictions in-place
926         final Context context = preference.getContext();
927         int count = 1;
928         for (RestrictionEntry entry : restrictions) {
929             Preference p = null;
930             switch (entry.getType()) {
931             case RestrictionEntry.TYPE_BOOLEAN:
932                 p = new CheckBoxPreference(context);
933                 p.setTitle(entry.getTitle());
934                 p.setSummary(entry.getDescription());
935                 ((CheckBoxPreference)p).setChecked(entry.getSelectedState());
936                 break;
937             case RestrictionEntry.TYPE_CHOICE:
938             case RestrictionEntry.TYPE_CHOICE_LEVEL:
939                 p = new ListPreference(context);
940                 p.setTitle(entry.getTitle());
941                 String value = entry.getSelectedString();
942                 if (value == null) {
943                     value = entry.getDescription();
944                 }
945                 p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(),
946                         value));
947                 ((ListPreference)p).setEntryValues(entry.getChoiceValues());
948                 ((ListPreference)p).setEntries(entry.getChoiceEntries());
949                 ((ListPreference)p).setValue(value);
950                 ((ListPreference)p).setDialogTitle(entry.getTitle());
951                 break;
952             case RestrictionEntry.TYPE_MULTI_SELECT:
953                 p = new MultiSelectListPreference(context);
954                 p.setTitle(entry.getTitle());
955                 ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues());
956                 ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries());
957                 HashSet<String> set = new HashSet<String>();
958                 for (String s : entry.getAllSelectedStrings()) {
959                     set.add(s);
960                 }
961                 ((MultiSelectListPreference)p).setValues(set);
962                 ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle());
963                 break;
964             case RestrictionEntry.TYPE_NULL:
965             default:
966             }
967             if (p != null) {
968                 p.setPersistent(false);
969                 p.setOrder(preference.getOrder() + count);
970                 // Store the restrictions key string as a key for the preference
971                 p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER
972                         + entry.getKey());
973                 mAppList.addPreference(p);
974                 p.setOnPreferenceChangeListener(AppRestrictionsFragment.this);
975                 p.setIcon(R.drawable.empty_icon);
976                 preference.mChildren.add(p);
977                 count++;
978             }
979         }
980         preference.setRestrictions(restrictions);
981         if (count == 1 // No visible restrictions
982                 && preference.isImmutable()
983                 && preference.isChecked()) {
984             // Special case of required app with no visible restrictions. Remove it
985             mAppList.removePreference(preference);
986         }
987     }
988 
989     /**
990      * Generates a request code that is stored in a map to retrieve the associated
991      * AppRestrictionsPreference.
992      * @param preference
993      * @return
994      */
generateCustomActivityRequestCode(AppRestrictionsPreference preference)995     private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) {
996         mCustomRequestCode++;
997         mCustomRequestMap.put(mCustomRequestCode, preference);
998         return mCustomRequestCode;
999     }
1000 
1001     @Override
onActivityResult(int requestCode, int resultCode, Intent data)1002     public void onActivityResult(int requestCode, int resultCode, Intent data) {
1003         super.onActivityResult(requestCode, resultCode, data);
1004 
1005         AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode);
1006         if (pref == null) {
1007             Log.w(TAG, "Unknown requestCode " + requestCode);
1008             return;
1009         }
1010 
1011         if (resultCode == Activity.RESULT_OK) {
1012             String packageName = pref.getKey().substring(PKG_PREFIX.length());
1013             ArrayList<RestrictionEntry> list =
1014                     data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST);
1015             Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
1016             if (list != null) {
1017                 // If there's a valid result, persist it to the user manager.
1018                 pref.setRestrictions(list);
1019                 mUserManager.setApplicationRestrictions(packageName,
1020                         RestrictionUtils.restrictionsToBundle(list), mUser);
1021             } else if (bundle != null) {
1022                 // If there's a valid result, persist it to the user manager.
1023                 mUserManager.setApplicationRestrictions(packageName, bundle, mUser);
1024             }
1025         }
1026         // Remove request from the map
1027         mCustomRequestMap.remove(requestCode);
1028     }
1029 
findInArray(String[] choiceEntries, String[] choiceValues, String selectedString)1030     private String findInArray(String[] choiceEntries, String[] choiceValues,
1031             String selectedString) {
1032         for (int i = 0; i < choiceValues.length; i++) {
1033             if (choiceValues[i].equals(selectedString)) {
1034                 return choiceEntries[i];
1035             }
1036         }
1037         return selectedString;
1038     }
1039 
1040     @Override
onPreferenceClick(Preference preference)1041     public boolean onPreferenceClick(Preference preference) {
1042         if (preference.getKey().startsWith(PKG_PREFIX)) {
1043             AppRestrictionsPreference arp = (AppRestrictionsPreference) preference;
1044             if (!arp.isImmutable()) {
1045                 final String packageName = arp.getKey().substring(PKG_PREFIX.length());
1046                 final boolean newEnabledState = !arp.isChecked();
1047                 arp.setChecked(newEnabledState);
1048                 mSelectedPackages.put(packageName, newEnabledState);
1049                 updateAllEntries(arp.getKey(), newEnabledState);
1050                 mAppListChanged = true;
1051                 applyUserAppState(packageName, newEnabledState);
1052             }
1053             return true;
1054         }
1055         return false;
1056     }
1057 
1058 }
1059