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