AppRestrictionsFragment.java revision 80b1dfe46a85e3ecdbeca5575c205086e5ab07e0
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.appwidget.AppWidgetManager;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.RestrictionEntry;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.pm.ResolveInfo;
29import android.content.pm.UserInfo;
30import android.graphics.Bitmap;
31import android.graphics.Color;
32import android.graphics.drawable.BitmapDrawable;
33import android.graphics.drawable.ColorDrawable;
34import android.graphics.drawable.Drawable;
35import android.os.Bundle;
36import android.os.Parcelable;
37import android.os.UserHandle;
38import android.os.UserManager;
39import android.preference.CheckBoxPreference;
40import android.preference.EditTextPreference;
41import android.preference.ListPreference;
42import android.preference.MultiSelectListPreference;
43import android.preference.Preference;
44import android.preference.PreferenceActivity;
45import android.preference.PreferenceCategory;
46import android.preference.Preference.OnPreferenceChangeListener;
47import android.preference.Preference.OnPreferenceClickListener;
48import android.preference.PreferenceGroup;
49import android.preference.SwitchPreference;
50import android.text.InputType;
51import android.text.TextUtils;
52import android.util.Log;
53import android.view.View;
54import android.view.View.OnClickListener;
55import android.view.ViewGroup;
56import android.widget.CompoundButton;
57import android.widget.CompoundButton.OnCheckedChangeListener;
58import android.widget.Switch;
59
60import com.android.settings.R;
61import com.android.settings.SelectableEditTextPreference;
62import com.android.settings.SettingsPreferenceFragment;
63
64import java.util.ArrayList;
65import java.util.Collection;
66import java.util.Collections;
67import java.util.Comparator;
68import java.util.HashMap;
69import java.util.HashSet;
70import java.util.List;
71import java.util.Set;
72import java.util.StringTokenizer;
73
74import libcore.util.CollectionUtils;
75
76public class AppRestrictionsFragment extends SettingsPreferenceFragment implements
77        OnPreferenceChangeListener, OnClickListener, OnPreferenceClickListener {
78
79    private static final String TAG = AppRestrictionsFragment.class.getSimpleName();
80
81    private static final String PKG_PREFIX = "pkg_";
82    private static final String KEY_USER_INFO = "user_info";
83
84    private UserManager mUserManager;
85    private UserHandle mUser;
86
87    private SelectableEditTextPreference mUserPreference;
88    private PreferenceGroup mAppList;
89
90    private static final int MAX_APP_RESTRICTIONS = 100;
91
92    private static final String DELIMITER = ";";
93    HashMap<String,Boolean> mSelectedPackages = new HashMap<String,Boolean>();
94    private boolean mFirstTime = true;
95    private boolean mNewUser;
96
97    private int mCustomRequestCode;
98    private HashMap<Integer, AppRestrictionsPreference> mCustomRequestMap =
99            new HashMap<Integer,AppRestrictionsPreference>();
100
101    public static class Activity extends PreferenceActivity {
102        @Override
103        public Intent getIntent() {
104            Intent modIntent = new Intent(super.getIntent());
105            modIntent.putExtra(EXTRA_SHOW_FRAGMENT, AppRestrictionsFragment.class.getName());
106            modIntent.putExtra(EXTRA_NO_HEADERS, true);
107            return modIntent;
108        }
109    }
110
111    static class AppRestrictionsPreference extends SwitchPreference {
112        private boolean hasSettings;
113        private OnClickListener listener;
114        private ArrayList<RestrictionEntry> restrictions;
115        boolean panelOpen;
116        private boolean required;
117        List<Preference> childPreferences = new ArrayList<Preference>();
118
119        AppRestrictionsPreference(Context context, OnClickListener listener) {
120            super(context);
121            setLayoutResource(R.layout.preference_app_restrictions);
122            this.listener = listener;
123        }
124
125        private void setSettingsEnabled(boolean enable) {
126            hasSettings = enable;
127        }
128
129        void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
130            this.restrictions = restrictions;
131        }
132
133        void setRequired(boolean required) {
134            this.required = required;
135        }
136
137        boolean isRequired() {
138            return required;
139        }
140
141        RestrictionEntry getRestriction(String key) {
142            if (restrictions == null) return null;
143            for (RestrictionEntry entry : restrictions) {
144                if (entry.getKey().equals(key)) {
145                    return entry;
146                }
147            }
148            return null;
149        }
150
151        ArrayList<RestrictionEntry> getRestrictions() {
152            return restrictions;
153        }
154
155        @Override
156        protected void onBindView(View view) {
157            super.onBindView(view);
158
159            View appRestrictionsSettings = view.findViewById(R.id.app_restrictions_settings);
160            appRestrictionsSettings.setVisibility(hasSettings ? View.VISIBLE : View.GONE);
161            view.findViewById(R.id.settings_divider).setVisibility(
162                    hasSettings ? View.VISIBLE : View.GONE);
163            appRestrictionsSettings.setOnClickListener(listener);
164            appRestrictionsSettings.setTag(this);
165
166            View appRestrictionsPref = view.findViewById(R.id.app_restrictions_pref);
167            appRestrictionsPref.setOnClickListener(listener);
168            appRestrictionsPref.setTag(this);
169
170            ViewGroup widget = (ViewGroup) view.findViewById(android.R.id.widget_frame);
171            widget.setEnabled(!isRequired());
172            if (widget.getChildCount() > 0) {
173                final Switch switchView = (Switch) widget.getChildAt(0);
174                switchView.setEnabled(!isRequired());
175                switchView.setTag(this);
176                switchView.setOnCheckedChangeListener(new OnCheckedChangeListener() {
177                    @Override
178                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
179                        listener.onClick(switchView);
180                    }
181                });
182            }
183        }
184    }
185
186    @Override
187    public void onCreate(Bundle icicle) {
188        super.onCreate(icicle);
189
190        mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
191        addPreferencesFromResource(R.xml.app_restrictions);
192        mAppList = getPreferenceScreen();
193        mUserPreference = (SelectableEditTextPreference) findPreference(KEY_USER_INFO);
194        mUserPreference.setOnPreferenceChangeListener(this);
195        mUserPreference.getEditText().setInputType(
196                InputType.TYPE_TEXT_VARIATION_NORMAL | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
197        mUserPreference.setInitialSelectionMode(
198                SelectableEditTextPreference.SELECTION_SELECT_ALL);
199        setHasOptionsMenu(true);
200    }
201
202    void setUser(UserHandle user, boolean newUser) {
203        mUser = user;
204        mNewUser = newUser;
205    }
206
207    public void onResume() {
208        super.onResume();
209        if (mFirstTime) {
210            mFirstTime = false;
211            populateApps();
212        }
213        UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier());
214        mUserPreference.setTitle(info.name);
215        Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier());
216        CircleFramedDrawable circularIcon =
217                CircleFramedDrawable.getInstance(this.getActivity(), userIcon);
218        mUserPreference.setIcon(circularIcon);
219        mUserPreference.setText(info.name);
220    }
221
222    private void addSystemApps(List<ApplicationInfo> visibleApps, Intent intent) {
223        final PackageManager pm = getActivity().getPackageManager();
224        List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent, 0);
225        for (ResolveInfo app : launchableApps) {
226            if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
227                int flags = app.activityInfo.applicationInfo.flags;
228                if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
229                        || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
230                    // System app
231                    visibleApps.add(app.activityInfo.applicationInfo);
232                }
233            }
234        }
235    }
236
237    private void populateApps() {
238        mAppList.setOrderingAsAdded(false);
239        List<ApplicationInfo> visibleApps = new ArrayList<ApplicationInfo>();
240        // TODO: Do this asynchronously since it can be a long operation
241        final Context context = getActivity();
242        PackageManager pm = context.getPackageManager();
243
244        // Add launchers
245        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
246        launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
247        addSystemApps(visibleApps, launcherIntent);
248
249        // Add widgets
250        Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
251        addSystemApps(visibleApps, widgetIntent);
252
253        List<ApplicationInfo> installedApps = pm.getInstalledApplications(0);
254        for (ApplicationInfo app : installedApps) {
255            if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
256                    && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
257                // Downloaded app
258                visibleApps.add(app);
259            }
260        }
261        Collections.sort(visibleApps, new AppLabelComparator(pm));
262
263        for (int i = visibleApps.size() - 1; i > 1; i--) {
264            ApplicationInfo appInfo = visibleApps.get(i);
265            if (appInfo.packageName.equals(visibleApps.get(i-1).packageName)) {
266                visibleApps.remove(i);
267            }
268        }
269        Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
270        final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0);
271        final List<ResolveInfo> existingApps = pm.queryIntentActivitiesAsUser(launcherIntent,
272                0, mUser.getIdentifier());
273        int i = 0;
274        if (visibleApps.size() > 0) {
275            for (ApplicationInfo app : visibleApps) {
276                if (app.packageName == null) continue;
277                String packageName = app.packageName;
278                Drawable icon = app.loadIcon(pm);
279                CharSequence label = app.loadLabel(pm);
280                AppRestrictionsPreference p = new AppRestrictionsPreference(context, this);
281                p.setIcon(icon);
282                p.setTitle(label);
283                p.setKey(PKG_PREFIX + packageName);
284                p.setSettingsEnabled(hasPackage(receivers, packageName)
285                        || packageName.equals(getActivity().getPackageName()));
286                p.setPersistent(false);
287                p.setOnPreferenceChangeListener(this);
288                p.setOnPreferenceClickListener(this);
289                try {
290                    PackageInfo pi = pm.getPackageInfo(packageName, 0);
291                    if (pi.requiredForAllUsers) {
292                        p.setChecked(true);
293                        p.setRequired(true);
294                    } else if (!mNewUser && hasPackage(existingApps, packageName)) {
295                        p.setChecked(true);
296                    }
297                } catch (NameNotFoundException re) {
298                    // This would be bad
299                }
300
301                mAppList.addPreference(p);
302                if (packageName.equals(getActivity().getPackageName())) {
303                    p.setOrder(MAX_APP_RESTRICTIONS * 1);
304                } else {
305                    p.setOrder(MAX_APP_RESTRICTIONS * (i + 2));
306                }
307                mSelectedPackages.put(packageName, p.isChecked());
308                i++;
309            }
310        }
311    }
312
313    private class AppLabelComparator implements Comparator<ApplicationInfo> {
314
315        PackageManager pm;
316
317        private AppLabelComparator(PackageManager pm) {
318            this.pm = pm;
319        }
320
321        private CharSequence getLabel(ApplicationInfo info) {
322            // TODO: Optimize this with a cache
323            return info.loadLabel(pm);
324        }
325
326        @Override
327        public int compare(ApplicationInfo lhs, ApplicationInfo rhs) {
328            String lhsLabel = getLabel(lhs).toString();
329            String rhsLabel = getLabel(rhs).toString();
330            return lhsLabel.compareTo(rhsLabel);
331        }
332    }
333
334    private boolean hasPackage(List<ResolveInfo> receivers, String packageName) {
335        for (ResolveInfo info : receivers) {
336            if (info.activityInfo.packageName.equals(packageName)) {
337                return true;
338            }
339        }
340        return false;
341    }
342
343    @Override
344    public void onClick(View v) {
345        if (v.getTag() instanceof AppRestrictionsPreference) {
346            AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag();
347            if (v.getId() == R.id.app_restrictions_settings) {
348                toggleAppPanel(pref);
349            } else if (!pref.isRequired()) {
350                pref.setChecked(!pref.isChecked());
351                mSelectedPackages.put(pref.getKey().substring(PKG_PREFIX.length()),
352                        pref.isChecked());
353            }
354        }
355    }
356
357    @Override
358    public boolean onPreferenceChange(Preference preference, Object newValue) {
359        String key = preference.getKey();
360        if (key != null && key.contains(DELIMITER)) {
361            StringTokenizer st = new StringTokenizer(key, DELIMITER);
362            final String packageName = st.nextToken();
363            final String restrictionKey = st.nextToken();
364            AppRestrictionsPreference appPref = (AppRestrictionsPreference)
365                    mAppList.findPreference(PKG_PREFIX+packageName);
366            ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions();
367            if (restrictions != null) {
368                for (RestrictionEntry entry : restrictions) {
369                    if (entry.getKey().equals(restrictionKey)) {
370                        switch (entry.getType()) {
371                        case RestrictionEntry.TYPE_BOOLEAN:
372                            entry.setSelectedState((Boolean) newValue);
373                            break;
374                        case RestrictionEntry.TYPE_CHOICE:
375                        case RestrictionEntry.TYPE_CHOICE_LEVEL:
376                            ListPreference listPref = (ListPreference) preference;
377                            entry.setSelectedString((String) newValue);
378                            String readable = findInArray(entry.getChoiceEntries(),
379                                    entry.getChoiceValues(), (String) newValue);
380                            listPref.setSummary(readable);
381                            break;
382                        case RestrictionEntry.TYPE_MULTI_SELECT:
383                            MultiSelectListPreference msListPref =
384                                    (MultiSelectListPreference) preference;
385                            Set<String> set = (Set<String>) newValue;
386                            String [] selectedValues = new String[set.size()];
387                            set.toArray(selectedValues);
388                            entry.setAllSelectedStrings(selectedValues);
389                            break;
390                        default:
391                            continue;
392                        }
393                        if (packageName.equals(getActivity().getPackageName())) {
394                            RestrictionUtils.setRestrictions(getActivity(), restrictions, mUser);
395                        } else {
396                            mUserManager.setApplicationRestrictions(packageName, restrictions,
397                                    mUser);
398                        }
399                        break;
400                    }
401                }
402            }
403        } else if (preference == mUserPreference) {
404            String userName = ((CharSequence) newValue).toString();
405            if (!TextUtils.isEmpty(userName)) {
406                mUserManager.setUserName(mUser.getIdentifier(), userName);
407                mUserPreference.setTitle(userName);
408            }
409        }
410        return true;
411    }
412
413    private void toggleAppPanel(AppRestrictionsPreference preference) {
414        if (preference.getKey().startsWith(PKG_PREFIX)) {
415            if (preference.panelOpen) {
416                for (Preference p : preference.childPreferences) {
417                    mAppList.removePreference(p);
418                }
419                preference.childPreferences.clear();
420            } else {
421                String packageName = preference.getKey().substring(PKG_PREFIX.length());
422                if (packageName.equals(getActivity().getPackageName())) {
423                    // Settings, fake it by using user restrictions
424                    ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions(
425                            getActivity(), mUser);
426                    onRestrictionsReceived(preference, packageName, restrictions);
427                } else {
428                    List<RestrictionEntry> oldEntries =
429                            mUserManager.getApplicationRestrictions(packageName, mUser);
430                    Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
431                    intent.setPackage(packageName);
432                    intent.putParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS,
433                            new ArrayList<RestrictionEntry>(oldEntries));
434                    intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
435                    getActivity().sendOrderedBroadcast(intent, null,
436                            new RestrictionsResultReceiver(packageName, preference),
437                            null, Activity.RESULT_OK, null, null);
438                }
439            }
440            preference.panelOpen = !preference.panelOpen;
441        }
442    }
443
444    class RestrictionsResultReceiver extends BroadcastReceiver {
445
446        private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT;
447        String packageName;
448        AppRestrictionsPreference preference;
449
450        RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) {
451            super();
452            this.packageName = packageName;
453            this.preference = preference;
454        }
455
456        @Override
457        public void onReceive(Context context, Intent intent) {
458            Bundle results = getResultExtras(true);
459            final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList(
460                    Intent.EXTRA_RESTRICTIONS);
461            Intent restrictionsIntent = (Intent) results.getParcelable(CUSTOM_RESTRICTIONS_INTENT);
462            if (restrictions != null && restrictionsIntent == null) {
463                onRestrictionsReceived(preference, packageName, restrictions);
464                mUserManager.setApplicationRestrictions(packageName, restrictions, mUser);
465            } else if (restrictionsIntent != null) {
466                final Intent customIntent = restrictionsIntent;
467                customIntent.putParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS, restrictions);
468                Preference p = new Preference(context);
469                p.setTitle(R.string.app_restrictions_custom_label);
470                p.setOnPreferenceClickListener(new OnPreferenceClickListener() {
471                    @Override
472                    public boolean onPreferenceClick(Preference preference) {
473                        int requestCode = generateCustomActivityRequestCode(
474                                RestrictionsResultReceiver.this.preference);
475                        AppRestrictionsFragment.this.startActivityForResult(
476                                customIntent, requestCode);
477                        return false;
478                    }
479                });
480                p.setPersistent(false);
481                p.setOrder(preference.getOrder() + 1);
482                preference.childPreferences.add(p);
483                mAppList.addPreference(p);
484                preference.setRestrictions(restrictions);
485            }
486        }
487    }
488
489    private void onRestrictionsReceived(AppRestrictionsPreference preference, String packageName,
490            ArrayList<RestrictionEntry> restrictions) {
491        // Non-custom-activity case - expand the restrictions in-place
492        final Context context = preference.getContext();
493        int count = 1;
494        for (RestrictionEntry entry : restrictions) {
495            Preference p = null;
496            switch (entry.getType()) {
497            case RestrictionEntry.TYPE_BOOLEAN:
498                p = new CheckBoxPreference(context);
499                p.setTitle(entry.getTitle());
500                p.setSummary(entry.getDescription());
501                ((CheckBoxPreference)p).setChecked(entry.getSelectedState());
502                break;
503            case RestrictionEntry.TYPE_CHOICE:
504            case RestrictionEntry.TYPE_CHOICE_LEVEL:
505                p = new ListPreference(context);
506                p.setTitle(entry.getTitle());
507                String value = entry.getSelectedString();
508                if (value == null) {
509                    value = entry.getDescription();
510                }
511                p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(),
512                        value));
513                ((ListPreference)p).setEntryValues(entry.getChoiceValues());
514                ((ListPreference)p).setEntries(entry.getChoiceEntries());
515                ((ListPreference)p).setValue(value);
516                break;
517            case RestrictionEntry.TYPE_MULTI_SELECT:
518                p = new MultiSelectListPreference(context);
519                p.setTitle(entry.getTitle());
520                ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues());
521                ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries());
522                HashSet<String> set = new HashSet<String>();
523                for (String s : entry.getAllSelectedStrings()) {
524                    set.add(s);
525                }
526                ((MultiSelectListPreference)p).setValues(set);
527                break;
528            case RestrictionEntry.TYPE_NULL:
529            default:
530            }
531            if (p != null) {
532                p.setPersistent(false);
533                p.setOrder(preference.getOrder() + count);
534                // Store the restrictions key string as a key for the preference
535                p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER
536                        + entry.getKey());
537                mAppList.addPreference(p);
538                p.setOnPreferenceChangeListener(AppRestrictionsFragment.this);
539                preference.childPreferences.add(p);
540                count++;
541            }
542        }
543        preference.setRestrictions(restrictions);
544    }
545
546    /**
547     * Generates a request code that is stored in a map to retrieve the associated
548     * AppRestrictionsPreference.
549     * @param preference
550     * @return
551     */
552    private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) {
553        mCustomRequestCode++;
554        mCustomRequestMap.put(mCustomRequestCode, preference);
555        return mCustomRequestCode;
556    }
557
558    @Override
559    public void onActivityResult(int requestCode, int resultCode, Intent data) {
560        super.onActivityResult(requestCode, resultCode, data);
561
562        Log.i(TAG, "Got activity resultCode=" + resultCode + ", requestCode="
563                + requestCode + ", data=" + data);
564
565        AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode);
566        if (pref == null) {
567            Log.w(TAG, "Unknown requestCode " + requestCode);
568            return;
569        }
570
571        if (resultCode == Activity.RESULT_OK) {
572            ArrayList<RestrictionEntry> list =
573                    data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS);
574            if (list != null) {
575                // If there's a valid result, persist it to the user manager.
576                String packageName = pref.getKey().substring(PKG_PREFIX.length());
577                pref.setRestrictions(list);
578                mUserManager.setApplicationRestrictions(packageName, list, mUser);
579            }
580            toggleAppPanel(pref);
581        }
582        // Remove request from the map
583        mCustomRequestMap.remove(requestCode);
584    }
585
586    private String findInArray(String[] choiceEntries, String[] choiceValues,
587            String selectedString) {
588        for (int i = 0; i < choiceValues.length; i++) {
589            if (choiceValues[i].equals(selectedString)) {
590                return choiceEntries[i];
591            }
592        }
593        return selectedString;
594    }
595
596    @Override
597    public boolean onPreferenceClick(Preference preference) {
598        if (preference.getKey().startsWith(PKG_PREFIX)) {
599            AppRestrictionsPreference arp = (AppRestrictionsPreference) preference;
600            if (!arp.isRequired()) {
601                arp.setChecked(!arp.isChecked());
602                mSelectedPackages.put(arp.getKey().substring(PKG_PREFIX.length()), arp.isChecked());
603            }
604            return true;
605        }
606        return false;
607    }
608}
609