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