Home | History | Annotate | Download | only in users
      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
    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 
    163         AppRestrictionsPreference(Context context, OnClickListener listener) {
    164             super(context);
    165             setLayoutResource(R.layout.preference_app_restrictions);
    166             this.listener = listener;
    167         }
    168 
    169         private void setSettingsEnabled(boolean enable) {
    170             hasSettings = enable;
    171         }
    172 
    173         void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
    174             this.restrictions = restrictions;
    175         }
    176 
    177         void setImmutable(boolean immutable) {
    178             this.immutable = immutable;
    179         }
    180 
    181         boolean isImmutable() {
    182             return immutable;
    183         }
    184 
    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 
    195         ArrayList<RestrictionEntry> getRestrictions() {
    196             return restrictions;
    197         }
    198 
    199         boolean isPanelOpen() {
    200             return panelOpen;
    201         }
    202 
    203         void setPanelOpen(boolean open) {
    204             panelOpen = open;
    205         }
    206 
    207         List<Preference> getChildren() {
    208             return mChildren;
    209         }
    210 
    211         @Override
    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 
    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
    276     public void onSaveInstanceState(Bundle outState) {
    277         super.onSaveInstanceState(outState);
    278         outState.putInt(EXTRA_USER_ID, mUser.getIdentifier());
    279     }
    280 
    281     @Override
    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
    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 
    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 
    328     protected PreferenceGroup getAppPreferenceGroup() {
    329         return getPreferenceScreen();
    330     }
    331 
    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 
    342     protected void clearSelectedApps() {
    343         mSelectedPackages.clear();
    344     }
    345 
    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 
    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 
    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 
    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      */
    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      */
    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 
    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
    513         protected Void doInBackground(Void... params) {
    514             fetchAndMergeApps();
    515             return null;
    516         }
    517 
    518         @Override
    519         protected void onPostExecute(Void result) {
    520             populateApps();
    521         }
    522 
    523         @Override
    524         protected void onPreExecute() {
    525         }
    526     }
    527 
    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 
    632     private boolean isPlatformSigned(PackageInfo pi) {
    633         return (pi != null && pi.signatures != null &&
    634                     mSysPackageInfo.signatures[0].equals(pi.signatures[0]));
    635     }
    636 
    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 
    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 
    728     private String getKeyForPackage(String packageName) {
    729         return PKG_PREFIX + packageName;
    730     }
    731 
    732     private class AppLabelComparator implements Comparator<SelectableAppInfo> {
    733 
    734         @Override
    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 
    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 
    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
    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
    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 
    836     private void removeRestrictionsForApp(AppRestrictionsPreference preference) {
    837         for (Preference p : preference.mChildren) {
    838             mAppList.removePreference(p);
    839         }
    840         preference.mChildren.clear();
    841     }
    842 
    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      */
    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 
    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
    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 
    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      */
    995     private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) {
    996         mCustomRequestCode++;
    997         mCustomRequestMap.put(mCustomRequestCode, preference);
    998         return mCustomRequestCode;
    999     }
   1000 
   1001     @Override
   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 
   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
   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