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