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