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