1/*
2 * Copyright (C) 2016 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.tv.settings.users;
18
19import android.app.Activity;
20import android.app.AppGlobals;
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.ResolveInfo;
33import android.content.pm.UserInfo;
34import android.graphics.Color;
35import android.graphics.drawable.ColorDrawable;
36import android.graphics.drawable.Drawable;
37import android.os.AsyncTask;
38import android.os.Bundle;
39import android.os.RemoteException;
40import android.os.UserHandle;
41import android.os.UserManager;
42import android.support.annotation.NonNull;
43import android.support.v14.preference.MultiSelectListPreference;
44import android.support.v14.preference.SwitchPreference;
45import android.support.v17.preference.LeanbackPreferenceFragment;
46import android.support.v4.util.ArrayMap;
47import android.support.v7.preference.ListPreference;
48import android.support.v7.preference.Preference;
49import android.support.v7.preference.PreferenceGroup;
50import android.support.v7.preference.PreferenceScreen;
51import android.support.v7.preference.PreferenceViewHolder;
52import android.util.Log;
53import android.view.View;
54import android.widget.Checkable;
55import android.widget.CompoundButton;
56import android.widget.Switch;
57
58import com.android.settingslib.users.AppRestrictionsHelper;
59import com.android.tv.settings.R;
60
61import java.util.ArrayList;
62import java.util.Collections;
63import java.util.HashSet;
64import java.util.List;
65import java.util.Map;
66import java.util.Set;
67import java.util.StringTokenizer;
68
69public class AppRestrictionsFragment extends LeanbackPreferenceFragment implements
70        Preference.OnPreferenceChangeListener,
71        AppRestrictionsHelper.OnDisableUiForPackageListener {
72
73    private static final String TAG = AppRestrictionsFragment.class.getSimpleName();
74
75    private static final boolean DEBUG = false;
76
77    private static final String PKG_PREFIX = "pkg_";
78    private static final String ACTIVITY_PREFIX = "activity_";
79
80    private static final Drawable BLANK_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
81
82    protected PackageManager mPackageManager;
83    protected UserManager mUserManager;
84    protected IPackageManager mIPm;
85    protected UserHandle mUser;
86    private PackageInfo mSysPackageInfo;
87
88    private AppRestrictionsHelper mHelper;
89
90    private PreferenceGroup mAppList;
91
92    private static final int MAX_APP_RESTRICTIONS = 100;
93
94    private static final String DELIMITER = ";";
95
96    /** Key for extra passed in from calling fragment for the userId of the user being edited */
97    public static final String EXTRA_USER_ID = "user_id";
98
99    /** Key for extra passed in from calling fragment to indicate if this is a newly created user */
100    public static final String EXTRA_NEW_USER = "new_user";
101
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 Map<Integer, String> mCustomRequestMap = new ArrayMap<>();
111
112    private AsyncTask mAppLoadingTask;
113
114    private BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() {
115        @Override
116        public void onReceive(Context context, Intent intent) {
117            // Update the user's app selection right away without waiting for a pause
118            // onPause() might come in too late, causing apps to disappear after broadcasts
119            // have been scheduled during user startup.
120            if (mAppListChanged) {
121                if (DEBUG) Log.d(TAG, "User backgrounding, update app list");
122                mHelper.applyUserAppsStates(AppRestrictionsFragment.this);
123                if (DEBUG) Log.d(TAG, "User backgrounding, done updating app list");
124            }
125        }
126    };
127
128    private BroadcastReceiver mPackageObserver = new BroadcastReceiver() {
129        @Override
130        public void onReceive(Context context, Intent intent) {
131            onPackageChanged(intent);
132        }
133    };
134
135    static class AppRestrictionsPreference extends PreferenceGroup {
136        private final Listener mListener = new Listener();
137        private ArrayList<RestrictionEntry> mRestrictions;
138        private boolean mImmutable;
139        private boolean mChecked;
140        private boolean mCheckedSet;
141
142        AppRestrictionsPreference(Context context) {
143            super(context, null, 0, R.style.LeanbackPreference_SwitchPreference);
144        }
145
146        void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
147            this.mRestrictions = restrictions;
148        }
149
150        void setImmutable(boolean immutable) {
151            this.mImmutable = immutable;
152        }
153
154        boolean isImmutable() {
155            return mImmutable;
156        }
157
158        ArrayList<RestrictionEntry> getRestrictions() {
159            return mRestrictions;
160        }
161
162        public void setChecked(boolean checked) {
163            // Always persist/notify the first time; don't assume the field's default of false.
164            final boolean changed = mChecked != checked;
165            if (changed || !mCheckedSet) {
166                mChecked = checked;
167                mCheckedSet = true;
168                persistBoolean(checked);
169                if (changed) {
170                    notifyDependencyChange(shouldDisableDependents());
171                    notifyChanged();
172                    notifyHierarchyChanged();
173                }
174            }
175        }
176
177        @Override
178        public int getPreferenceCount() {
179            if (isChecked()) {
180                return super.getPreferenceCount();
181            } else {
182                return 0;
183            }
184        }
185
186        public boolean isChecked() {
187            return mChecked;
188        }
189
190        @Override
191        public void onBindViewHolder(PreferenceViewHolder holder) {
192            super.onBindViewHolder(holder);
193            View switchView = holder.findViewById(android.R.id.switch_widget);
194            syncSwitchView(switchView);
195        }
196
197        private void syncSwitchView(View view) {
198            if (view instanceof Switch) {
199                final Switch switchView = (Switch) view;
200                switchView.setOnCheckedChangeListener(null);
201            }
202            if (view instanceof Checkable) {
203                ((Checkable) view).setChecked(mChecked);
204            }
205            if (view instanceof Switch) {
206                final Switch switchView = (Switch) view;
207                switchView.setOnCheckedChangeListener(mListener);
208            }
209        }
210
211        private class Listener implements CompoundButton.OnCheckedChangeListener {
212            @Override
213            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
214                if (!callChangeListener(isChecked)) {
215                    // Listener didn't like it, change it back.
216                    // CompoundButton will make sure we don't recurse.
217                    buttonView.setChecked(!isChecked);
218                    return;
219                }
220
221                AppRestrictionsPreference.this.setChecked(isChecked);
222            }
223        }
224    }
225
226    public static void prepareArgs(@NonNull Bundle bundle, int userId, boolean newUser) {
227        bundle.putInt(EXTRA_USER_ID, userId);
228        bundle.putBoolean(EXTRA_NEW_USER, newUser);
229    }
230
231    public static AppRestrictionsFragment newInstance(int userId, boolean newUser) {
232        final Bundle args = new Bundle(2);
233        prepareArgs(args, userId, newUser);
234        AppRestrictionsFragment fragment = new AppRestrictionsFragment();
235        fragment.setArguments(args);
236        return fragment;
237    }
238
239    @Override
240    public void onCreate(Bundle savedInstanceState) {
241        super.onCreate(savedInstanceState);
242        if (savedInstanceState != null) {
243            mUser = new UserHandle(savedInstanceState.getInt(EXTRA_USER_ID));
244        } else {
245            Bundle args = getArguments();
246            if (args != null) {
247                if (args.containsKey(EXTRA_USER_ID)) {
248                    mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
249                }
250                mNewUser = args.getBoolean(EXTRA_NEW_USER, false);
251            }
252        }
253
254        if (mUser == null) {
255            mUser = android.os.Process.myUserHandle();
256        }
257
258        mHelper = new AppRestrictionsHelper(getContext(), mUser);
259        mHelper.setLeanback(true);
260        mPackageManager = getActivity().getPackageManager();
261        mIPm = AppGlobals.getPackageManager();
262        mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
263        mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted();
264        try {
265            mSysPackageInfo = mPackageManager.getPackageInfo("android",
266                    PackageManager.GET_SIGNATURES);
267        } catch (PackageManager.NameNotFoundException nnfe) {
268            Log.e(TAG, "Could not find system package signatures", nnfe);
269        }
270        mAppList = getAppPreferenceGroup();
271        mAppList.setOrderingAsAdded(false);
272    }
273
274    @Override
275    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
276        final PreferenceScreen screen = getPreferenceManager()
277                .createPreferenceScreen(getPreferenceManager().getContext());
278        screen.setTitle(R.string.restricted_profile_configure_apps_title);
279        setPreferenceScreen(screen);
280    }
281
282    @Override
283    public void onSaveInstanceState(Bundle outState) {
284        super.onSaveInstanceState(outState);
285        outState.putInt(EXTRA_USER_ID, mUser.getIdentifier());
286    }
287
288    @Override
289    public void onResume() {
290        super.onResume();
291
292        getActivity().registerReceiver(mUserBackgrounding,
293                new IntentFilter(Intent.ACTION_USER_BACKGROUND));
294        IntentFilter packageFilter = new IntentFilter();
295        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
296        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
297        packageFilter.addDataScheme("package");
298        getActivity().registerReceiver(mPackageObserver, packageFilter);
299
300        mAppListChanged = false;
301        if (mAppLoadingTask == null || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED) {
302            mAppLoadingTask = new AppLoadingTask().execute();
303        }
304    }
305
306    @Override
307    public void onPause() {
308        super.onPause();
309        mNewUser = false;
310        getActivity().unregisterReceiver(mUserBackgrounding);
311        getActivity().unregisterReceiver(mPackageObserver);
312        if (mAppListChanged) {
313            new AsyncTask<Void, Void, Void>() {
314                @Override
315                protected Void doInBackground(Void... params) {
316                    mHelper.applyUserAppsStates(AppRestrictionsFragment.this);
317                    return null;
318                }
319            }.execute();
320        }
321    }
322
323    private void onPackageChanged(Intent intent) {
324        String action = intent.getAction();
325        String packageName = intent.getData().getSchemeSpecificPart();
326        // Package added, check if the preference needs to be enabled
327        AppRestrictionsPreference pref = (AppRestrictionsPreference)
328                findPreference(getKeyForPackage(packageName));
329        if (pref == null) return;
330
331        if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && pref.isChecked())
332                || (Intent.ACTION_PACKAGE_REMOVED.equals(action) && !pref.isChecked())) {
333            pref.setEnabled(true);
334        }
335    }
336
337    protected PreferenceGroup getAppPreferenceGroup() {
338        return getPreferenceScreen();
339    }
340
341    @Override
342    public void onDisableUiForPackage(String packageName) {
343        AppRestrictionsPreference pref = (AppRestrictionsPreference) findPreference(
344                getKeyForPackage(packageName));
345        if (pref != null) {
346            pref.setEnabled(false);
347        }
348    }
349
350    private class AppLoadingTask extends AsyncTask<Void, Void, Void> {
351
352        @Override
353        protected Void doInBackground(Void... params) {
354            mHelper.fetchAndMergeApps();
355            return null;
356        }
357
358        @Override
359        protected void onPostExecute(Void result) {
360            populateApps();
361        }
362    }
363
364    private boolean isPlatformSigned(PackageInfo pi) {
365        return (pi != null && pi.signatures != null &&
366                mSysPackageInfo.signatures[0].equals(pi.signatures[0]));
367    }
368
369    private boolean isAppEnabledForUser(PackageInfo pi) {
370        if (pi == null) return false;
371        final int flags = pi.applicationInfo.flags;
372        final int privateFlags = pi.applicationInfo.privateFlags;
373        // Return true if it is installed and not hidden
374        return ((flags& ApplicationInfo.FLAG_INSTALLED) != 0
375                && (privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0);
376    }
377
378    private void populateApps() {
379        final Context context = getActivity();
380        if (context == null) return;
381        final PackageManager pm = mPackageManager;
382        final int userId = mUser.getIdentifier();
383
384        // Check if the user was removed in the meantime.
385        if (getExistingUser(mUserManager, mUser) == null) {
386            return;
387        }
388        mAppList.removeAll();
389        addLocationAppRestrictionsPreference();
390        Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
391        final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0);
392        for (AppRestrictionsHelper.SelectableAppInfo app : mHelper.getVisibleApps()) {
393            String packageName = app.packageName;
394            if (packageName == null) continue;
395            final boolean isSettingsApp = packageName.equals(context.getPackageName());
396            AppRestrictionsPreference p =
397                    new AppRestrictionsPreference(getPreferenceManager().getContext());
398            final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName);
399            if (isSettingsApp) {
400                // Settings app should be available to restricted user
401                mHelper.setPackageSelected(packageName, true);
402                continue;
403            }
404            PackageInfo pi = null;
405            try {
406                pi = mIPm.getPackageInfo(packageName,
407                        PackageManager.MATCH_UNINSTALLED_PACKAGES
408                                | PackageManager.GET_SIGNATURES, userId);
409            } catch (RemoteException e) {
410                // Ignore
411            }
412            if (pi == null) {
413                continue;
414            }
415            if (mRestrictedProfile && isAppUnsupportedInRestrictedProfile(pi)) {
416                continue;
417            }
418            p.setIcon(app.icon != null ? app.icon.mutate() : null);
419            p.setChecked(false);
420            p.setTitle(app.activityName);
421            p.setKey(getKeyForPackage(packageName));
422            p.setPersistent(false);
423            p.setOnPreferenceChangeListener(this);
424            p.setSummary(getPackageSummary(pi, app));
425            if (pi.requiredForAllUsers || isPlatformSigned(pi)) {
426                p.setChecked(true);
427                p.setImmutable(true);
428                // If the app is required and has no restrictions, skip showing it
429                if (!hasSettings) continue;
430            } else if (!mNewUser && isAppEnabledForUser(pi)) {
431                p.setChecked(true);
432            }
433            if (app.masterEntry == null && hasSettings) {
434                requestRestrictionsForApp(packageName, p);
435            }
436            if (app.masterEntry != null) {
437                p.setImmutable(true);
438                p.setChecked(mHelper.isPackageSelected(packageName));
439            }
440            p.setOrder(MAX_APP_RESTRICTIONS * (mAppList.getPreferenceCount() + 2));
441            mHelper.setPackageSelected(packageName, p.isChecked());
442            mAppList.addPreference(p);
443        }
444        mAppListChanged = true;
445        // If this is the first time for a new profile, install/uninstall default apps for profile
446        // to avoid taking the hit in onPause(), which can cause race conditions on user switch.
447        if (mNewUser && mFirstTime) {
448            mFirstTime = false;
449            mHelper.applyUserAppsStates(this);
450        }
451    }
452
453    private String getPackageSummary(PackageInfo pi, AppRestrictionsHelper.SelectableAppInfo app) {
454        // Check for 3 cases:
455        // - Slave entry that can see primary user accounts
456        // - Slave entry that cannot see primary user accounts
457        // - Master entry that can see primary user accounts
458        // Otherwise no summary is returned
459        if (app.masterEntry != null) {
460            if (mRestrictedProfile && pi.restrictedAccountType != null) {
461                return getString(R.string.app_sees_restricted_accounts_and_controlled_by,
462                        app.masterEntry.activityName);
463            }
464            return getString(R.string.user_restrictions_controlled_by,
465                    app.masterEntry.activityName);
466        } else if (pi.restrictedAccountType != null) {
467            return getString(R.string.app_sees_restricted_accounts);
468        }
469        return null;
470    }
471
472    private static boolean isAppUnsupportedInRestrictedProfile(PackageInfo pi) {
473        return pi.requiredAccountType != null && pi.restrictedAccountType == null;
474    }
475
476    private void addLocationAppRestrictionsPreference() {
477        AppRestrictionsPreference p =
478                new AppRestrictionsPreference(getPreferenceManager().getContext());
479        String packageName = getContext().getPackageName();
480        p.setIcon(R.drawable.ic_location_on);
481        p.setKey(getKeyForPackage(packageName));
482        ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions(
483                getActivity(), mUser);
484        RestrictionEntry locationRestriction = restrictions.get(0);
485        p.setTitle(locationRestriction.getTitle());
486        p.setRestrictions(restrictions);
487        p.setSummary(locationRestriction.getDescription());
488        p.setChecked(locationRestriction.getSelectedState());
489        p.setPersistent(false);
490        p.setOrder(MAX_APP_RESTRICTIONS);
491        mAppList.addPreference(p);
492    }
493
494    private String getKeyForPackage(String packageName) {
495        return PKG_PREFIX + packageName;
496    }
497
498    private String getKeyForPackageActivity(String packageName) {
499        return ACTIVITY_PREFIX + packageName;
500    }
501
502    private String getPackageFromKey(String key) {
503        if (key.startsWith(PKG_PREFIX)) {
504            return key.substring(PKG_PREFIX.length());
505        } else if (key.startsWith(ACTIVITY_PREFIX)) {
506            return key.substring(ACTIVITY_PREFIX.length());
507        } else {
508            throw new IllegalArgumentException("Tried to extract package from wrong key: " + key);
509        }
510    }
511
512    private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) {
513        for (ResolveInfo info : receivers) {
514            if (info.activityInfo.packageName.equals(packageName)) {
515                return true;
516            }
517        }
518        return false;
519    }
520
521    private void updateAllEntries(String prefKey, boolean checked) {
522        for (int i = 0; i < mAppList.getPreferenceCount(); i++) {
523            Preference pref = mAppList.getPreference(i);
524            if (pref instanceof AppRestrictionsPreference) {
525                if (prefKey.equals(pref.getKey())) {
526                    ((AppRestrictionsPreference) pref).setChecked(checked);
527                }
528            }
529        }
530    }
531
532    private void assertSafeToStartCustomActivity(Intent intent, String packageName) {
533        // Activity can be started if it belongs to the same app
534        if (intent.getPackage() != null && intent.getPackage().equals(packageName)) {
535            return;
536        }
537        // Activity can be started if intent resolves to multiple activities
538        List<ResolveInfo> resolveInfos = AppRestrictionsFragment.this.mPackageManager
539                .queryIntentActivities(intent, 0 /* no flags */);
540        if (resolveInfos.size() != 1) {
541            return;
542        }
543        // Prevent potential privilege escalation
544        ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
545        if (!packageName.equals(activityInfo.packageName)) {
546            throw new SecurityException("Application " + packageName
547                    + " is not allowed to start activity " + intent);
548        }
549    }
550
551    @Override
552    public boolean onPreferenceTreeClick(Preference preference) {
553        if (preference instanceof AppRestrictionsPreference) {
554            AppRestrictionsPreference pref = (AppRestrictionsPreference) preference;
555            if (!pref.isImmutable()) {
556                pref.setChecked(!pref.isChecked());
557                final String packageName = getPackageFromKey(pref.getKey());
558                // Settings/Location is handled as a top-level entry
559                if (packageName.equals(getActivity().getPackageName())) {
560                    pref.getRestrictions().get(0).setSelectedState(pref.isChecked());
561                    RestrictionUtils.setRestrictions(getActivity(), pref.getRestrictions(), mUser);
562                    return true;
563                }
564                mHelper.setPackageSelected(packageName, pref.isChecked());
565                mAppListChanged = true;
566                // If it's not a restricted profile, apply the changes immediately
567                if (!mRestrictedProfile) {
568                    mHelper.applyUserAppState(packageName, pref.isChecked(), this);
569                }
570                updateAllEntries(pref.getKey(), pref.isChecked());
571            }
572            return true;
573        } else if (preference.getIntent() != null) {
574            assertSafeToStartCustomActivity(preference.getIntent(),
575                    getPackageFromKey(preference.getKey()));
576            startActivityForResult(preference.getIntent(),
577                    generateCustomActivityRequestCode(preference));
578            return true;
579        } else {
580            return super.onPreferenceTreeClick(preference);
581        }
582    }
583
584    @Override
585    public boolean onPreferenceChange(Preference preference, Object newValue) {
586        String key = preference.getKey();
587        if (key != null && key.contains(DELIMITER)) {
588            StringTokenizer st = new StringTokenizer(key, DELIMITER);
589            final String packageName = st.nextToken();
590            final String restrictionKey = st.nextToken();
591            AppRestrictionsPreference appPref = (AppRestrictionsPreference)
592                    mAppList.findPreference(getKeyForPackage(packageName));
593            ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions();
594            if (restrictions != null) {
595                for (RestrictionEntry entry : restrictions) {
596                    if (entry.getKey().equals(restrictionKey)) {
597                        switch (entry.getType()) {
598                            case RestrictionEntry.TYPE_BOOLEAN:
599                                entry.setSelectedState((Boolean) newValue);
600                                break;
601                            case RestrictionEntry.TYPE_CHOICE:
602                            case RestrictionEntry.TYPE_CHOICE_LEVEL:
603                                ListPreference listPref = (ListPreference) preference;
604                                entry.setSelectedString((String) newValue);
605                                String readable = findInArray(entry.getChoiceEntries(),
606                                        entry.getChoiceValues(), (String) newValue);
607                                listPref.setSummary(readable);
608                                break;
609                            case RestrictionEntry.TYPE_MULTI_SELECT:
610                                Set<String> set = (Set<String>) newValue;
611                                String [] selectedValues = new String[set.size()];
612                                set.toArray(selectedValues);
613                                entry.setAllSelectedStrings(selectedValues);
614                                break;
615                            default:
616                                continue;
617                        }
618                        mUserManager.setApplicationRestrictions(packageName,
619                                RestrictionsManager.convertRestrictionsToBundle(restrictions),
620                                mUser);
621                        break;
622                    }
623                }
624            }
625        }
626        return true;
627    }
628
629    /**
630     * Send a broadcast to the app to query its restrictions
631     * @param packageName package name of the app with restrictions
632     * @param preference the preference item for the app toggle
633     */
634    private void requestRestrictionsForApp(String packageName,
635            AppRestrictionsPreference preference) {
636        Bundle oldEntries =
637                mUserManager.getApplicationRestrictions(packageName, mUser);
638        Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
639        intent.setPackage(packageName);
640        intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries);
641        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
642        getActivity().sendOrderedBroadcast(intent, null,
643                new RestrictionsResultReceiver(packageName, preference),
644                null, Activity.RESULT_OK, null, null);
645    }
646
647    class RestrictionsResultReceiver extends BroadcastReceiver {
648
649        private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT;
650        private final String mPackageName;
651        private final AppRestrictionsPreference mPreference;
652
653        RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) {
654            super();
655            mPackageName = packageName;
656            mPreference = preference;
657        }
658
659        @Override
660        public void onReceive(Context context, Intent intent) {
661            Bundle results = getResultExtras(true);
662            final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList(
663                    Intent.EXTRA_RESTRICTIONS_LIST);
664            Intent restrictionsIntent = results.getParcelable(CUSTOM_RESTRICTIONS_INTENT);
665            if (restrictions != null && restrictionsIntent == null) {
666                onRestrictionsReceived(mPreference, restrictions);
667                if (mRestrictedProfile) {
668                    mUserManager.setApplicationRestrictions(mPackageName,
669                            RestrictionsManager.convertRestrictionsToBundle(restrictions), mUser);
670                }
671            } else if (restrictionsIntent != null) {
672                mPreference.setRestrictions(null);
673                mPreference.removeAll();
674                final Preference p = new Preference(mPreference.getContext());
675                p.setKey(getKeyForPackageActivity(mPackageName));
676                p.setIcon(BLANK_DRAWABLE);
677                p.setTitle(R.string.restricted_profile_customize_restrictions);
678                p.setIntent(restrictionsIntent);
679                mPreference.addPreference(p);
680            }
681        }
682    }
683
684    private void onRestrictionsReceived(AppRestrictionsPreference preference,
685            ArrayList<RestrictionEntry> restrictions) {
686        // Remove any earlier restrictions
687        preference.removeAll();
688        // Non-custom-activity case - expand the restrictions in-place
689        int count = 1;
690        final Context themedContext = getPreferenceManager().getContext();
691        for (RestrictionEntry entry : restrictions) {
692            Preference p = null;
693            switch (entry.getType()) {
694                case RestrictionEntry.TYPE_BOOLEAN:
695                    p = new SwitchPreference(themedContext);
696                    p.setTitle(entry.getTitle());
697                    p.setSummary(entry.getDescription());
698                    ((SwitchPreference)p).setChecked(entry.getSelectedState());
699                    break;
700                case RestrictionEntry.TYPE_CHOICE:
701                case RestrictionEntry.TYPE_CHOICE_LEVEL:
702                    p = new ListPreference(themedContext);
703                    p.setTitle(entry.getTitle());
704                    String value = entry.getSelectedString();
705                    if (value == null) {
706                        value = entry.getDescription();
707                    }
708                    p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(),
709                            value));
710                    ((ListPreference)p).setEntryValues(entry.getChoiceValues());
711                    ((ListPreference)p).setEntries(entry.getChoiceEntries());
712                    ((ListPreference)p).setValue(value);
713                    ((ListPreference)p).setDialogTitle(entry.getTitle());
714                    break;
715                case RestrictionEntry.TYPE_MULTI_SELECT:
716                    p = new MultiSelectListPreference(themedContext);
717                    p.setTitle(entry.getTitle());
718                    ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues());
719                    ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries());
720                    HashSet<String> set = new HashSet<>();
721                    Collections.addAll(set, entry.getAllSelectedStrings());
722                    ((MultiSelectListPreference)p).setValues(set);
723                    ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle());
724                    break;
725                case RestrictionEntry.TYPE_NULL:
726                default:
727            }
728            if (p != null) {
729                p.setPersistent(false);
730                p.setOrder(preference.getOrder() + count);
731                // Store the restrictions key string as a key for the preference
732                p.setKey(getPackageFromKey(preference.getKey()) + DELIMITER + entry.getKey());
733                preference.addPreference(p);
734                p.setOnPreferenceChangeListener(AppRestrictionsFragment.this);
735                p.setIcon(BLANK_DRAWABLE);
736                count++;
737            }
738        }
739        preference.setRestrictions(restrictions);
740        if (count == 1 // No visible restrictions
741                && preference.isImmutable()
742                && preference.isChecked()) {
743            // Special case of required app with no visible restrictions. Remove it
744            mAppList.removePreference(preference);
745        }
746    }
747
748    /**
749     * Generates a request code that is stored in a map to retrieve the associated
750     * AppRestrictionsPreference.
751     */
752    private int generateCustomActivityRequestCode(Preference preference) {
753        mCustomRequestCode++;
754        final String key = getKeyForPackage(getPackageFromKey(preference.getKey()));
755        mCustomRequestMap.put(mCustomRequestCode, key);
756        return mCustomRequestCode;
757    }
758
759    @Override
760    public void onActivityResult(int requestCode, int resultCode, Intent data) {
761        super.onActivityResult(requestCode, resultCode, data);
762
763        AppRestrictionsPreference pref =
764                (AppRestrictionsPreference) findPreference(mCustomRequestMap.get(requestCode));
765        if (pref == null) {
766            Log.w(TAG, "Unknown requestCode " + requestCode);
767            return;
768        }
769
770        if (resultCode == Activity.RESULT_OK) {
771            String packageName = getPackageFromKey(pref.getKey());
772            ArrayList<RestrictionEntry> list =
773                    data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST);
774            Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
775            if (list != null) {
776                // If there's a valid result, persist it to the user manager.
777                pref.setRestrictions(list);
778                mUserManager.setApplicationRestrictions(packageName,
779                        RestrictionsManager.convertRestrictionsToBundle(list), mUser);
780            } else if (bundle != null) {
781                // If there's a valid result, persist it to the user manager.
782                mUserManager.setApplicationRestrictions(packageName, bundle, mUser);
783            }
784        }
785        // Remove request from the map
786        mCustomRequestMap.remove(requestCode);
787    }
788
789    private String findInArray(String[] choiceEntries, String[] choiceValues,
790            String selectedString) {
791        for (int i = 0; i < choiceValues.length; i++) {
792            if (choiceValues[i].equals(selectedString)) {
793                return choiceEntries[i];
794            }
795        }
796        return selectedString;
797    }
798
799    /**
800     * Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed).
801     * @param userManager Instance of UserManager
802     * @param checkUser The user to check the existence of.
803     * @return UserInfo of the user or null for non-existent user.
804     */
805    public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) {
806        final List<UserInfo> users = userManager.getUsers(true /* excludeDying */);
807        final int checkUserId = checkUser.getIdentifier();
808        for (UserInfo user : users) {
809            if (user.id == checkUserId) {
810                return user;
811            }
812        }
813        return null;
814    }
815}
816