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