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