AppRestrictionsFragment.java revision f88e6e5ae6a5a31d47677dcbd9be2b26c6615136
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.users;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.AppGlobals;
22import android.app.Dialog;
23import android.app.Fragment;
24import android.appwidget.AppWidgetManager;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.RestrictionEntry;
31import android.content.pm.ApplicationInfo;
32import android.content.pm.IPackageManager;
33import android.content.pm.PackageInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
36import android.content.pm.ResolveInfo;
37import android.content.pm.UserInfo;
38import android.content.res.Resources;
39import android.database.Cursor;
40import android.graphics.Bitmap;
41import android.graphics.BitmapFactory;
42import android.graphics.ColorFilter;
43import android.graphics.ColorMatrix;
44import android.graphics.ColorMatrixColorFilter;
45import android.graphics.drawable.Drawable;
46import android.net.Uri;
47import android.os.AsyncTask;
48import android.os.Bundle;
49import android.os.RemoteException;
50import android.os.ServiceManager;
51import android.os.UserHandle;
52import android.os.UserManager;
53import android.preference.CheckBoxPreference;
54import android.preference.ListPreference;
55import android.preference.MultiSelectListPreference;
56import android.preference.Preference;
57import android.preference.Preference.OnPreferenceChangeListener;
58import android.preference.Preference.OnPreferenceClickListener;
59import android.preference.PreferenceGroup;
60import android.preference.SwitchPreference;
61import android.provider.ContactsContract.DisplayPhoto;
62import android.provider.MediaStore;
63import android.text.TextUtils;
64import android.util.Log;
65import android.view.LayoutInflater;
66import android.view.View;
67import android.view.View.OnClickListener;
68import android.view.inputmethod.InputMethod;
69import android.view.inputmethod.InputMethodInfo;
70import android.view.inputmethod.InputMethodManager;
71import android.view.ViewGroup;
72import android.view.WindowManager;
73import android.widget.AdapterView;
74import android.widget.ArrayAdapter;
75import android.widget.CompoundButton;
76import android.widget.CompoundButton.OnCheckedChangeListener;
77import android.widget.EditText;
78import android.widget.ImageView;
79import android.widget.ListAdapter;
80import android.widget.ListPopupWindow;
81import android.widget.Switch;
82import android.widget.TextView;
83
84import com.android.settings.R;
85import com.android.settings.SettingsPreferenceFragment;
86
87import java.io.File;
88import java.util.ArrayList;
89import java.util.Collections;
90import java.util.Comparator;
91import java.util.HashMap;
92import java.util.HashSet;
93import java.util.List;
94import java.util.Map;
95import java.util.Set;
96import java.util.StringTokenizer;
97
98public class AppRestrictionsFragment extends SettingsPreferenceFragment implements
99        OnPreferenceChangeListener, OnClickListener, OnPreferenceClickListener {
100
101    private static final String TAG = AppRestrictionsFragment.class.getSimpleName();
102
103    private static final boolean DEBUG = false;
104
105    private static final String PKG_PREFIX = "pkg_";
106    private static final String KEY_USER_INFO = "user_info";
107
108    private static final int DIALOG_ID_EDIT_USER_INFO = 1;
109
110    private PackageManager mPackageManager;
111    private UserManager mUserManager;
112    private UserHandle mUser;
113
114    private PreferenceGroup mAppList;
115
116    private static final int MAX_APP_RESTRICTIONS = 100;
117
118    private static final String DELIMITER = ";";
119
120    /** Key for extra passed in from calling fragment for the userId of the user being edited */
121    public static final String EXTRA_USER_ID = "user_id";
122
123    /** Key for extra passed in from calling fragment to indicate if this is a newly created user */
124    public static final String EXTRA_NEW_USER = "new_user";
125
126    private static final String KEY_SAVED_PHOTO = "pending_photo";
127
128    HashMap<String,Boolean> mSelectedPackages = new HashMap<String,Boolean>();
129    private boolean mFirstTime = true;
130    private boolean mNewUser;
131    private boolean mAppListChanged;
132
133    private int mCustomRequestCode;
134    private HashMap<Integer, AppRestrictionsPreference> mCustomRequestMap =
135            new HashMap<Integer,AppRestrictionsPreference>();
136    private View mHeaderView;
137    private ImageView mUserIconView;
138    private TextView mUserNameView;
139
140    private List<SelectableAppInfo> mVisibleApps;
141    private List<ApplicationInfo> mUserApps;
142
143    private Dialog mEditUserInfoDialog;
144
145    private EditUserPhotoController mEditUserPhotoController;
146    private Bitmap mSavedPhoto;
147
148    private BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() {
149        @Override
150        public void onReceive(Context context, Intent intent) {
151            // Update the user's app selection right away without waiting for a pause
152            // onPause() might come in too late, causing apps to disappear after broadcasts
153            // have been scheduled during user startup.
154            if (mAppListChanged) {
155                if (DEBUG) Log.d(TAG, "User backgrounding, update app list");
156                updateUserAppList();
157                if (DEBUG) Log.d(TAG, "User backgrounding, done updating app list");
158            }
159        }
160    };
161
162    static class SelectableAppInfo {
163        String packageName;
164        CharSequence appName;
165        CharSequence activityName;
166        Drawable icon;
167        SelectableAppInfo masterEntry;
168
169        @Override
170        public String toString() {
171            return packageName + ": appName=" + appName + "; activityName=" + activityName
172                    + "; icon=" + icon + "; masterEntry=" + masterEntry;
173        }
174    }
175
176    static class AppRestrictionsPreference extends SwitchPreference {
177        private boolean hasSettings;
178        private OnClickListener listener;
179        private ArrayList<RestrictionEntry> restrictions;
180        boolean panelOpen;
181        private boolean immutable;
182        List<Preference> childPreferences = new ArrayList<Preference>();
183        private SelectableAppInfo appInfo;
184        private final ColorFilter grayscaleFilter;
185
186        AppRestrictionsPreference(Context context, OnClickListener listener) {
187            super(context);
188            setLayoutResource(R.layout.preference_app_restrictions);
189            this.listener = listener;
190
191            ColorMatrix colorMatrix = new ColorMatrix();
192            colorMatrix.setSaturation(0f);
193            float[] matrix = colorMatrix.getArray();
194            matrix[18] = 0.5f;
195            grayscaleFilter = new ColorMatrixColorFilter(colorMatrix);
196        }
197
198        private void setSettingsEnabled(boolean enable) {
199            hasSettings = enable;
200        }
201
202        @Override
203        public void setChecked(boolean checked) {
204            if (checked) {
205                getIcon().setColorFilter(null);
206            } else {
207                getIcon().setColorFilter(grayscaleFilter);
208            }
209            super.setChecked(checked);
210        }
211
212        void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
213            this.restrictions = restrictions;
214        }
215
216        void setImmutable(boolean immutable) {
217            this.immutable = immutable;
218        }
219
220        boolean isImmutable() {
221            return immutable;
222        }
223
224        void setSelectableAppInfo(SelectableAppInfo appInfo) {
225            this.appInfo = appInfo;
226        }
227
228        RestrictionEntry getRestriction(String key) {
229            if (restrictions == null) return null;
230            for (RestrictionEntry entry : restrictions) {
231                if (entry.getKey().equals(key)) {
232                    return entry;
233                }
234            }
235            return null;
236        }
237
238        ArrayList<RestrictionEntry> getRestrictions() {
239            return restrictions;
240        }
241
242        @Override
243        protected void onBindView(View view) {
244            super.onBindView(view);
245
246            View appRestrictionsSettings = view.findViewById(R.id.app_restrictions_settings);
247            appRestrictionsSettings.setVisibility(hasSettings ? View.VISIBLE : View.GONE);
248            view.findViewById(R.id.settings_divider).setVisibility(
249                    hasSettings ? View.VISIBLE : View.GONE);
250            appRestrictionsSettings.setOnClickListener(listener);
251            appRestrictionsSettings.setTag(this);
252
253            View appRestrictionsPref = view.findViewById(R.id.app_restrictions_pref);
254            appRestrictionsPref.setOnClickListener(listener);
255            appRestrictionsPref.setTag(this);
256
257            ViewGroup widget = (ViewGroup) view.findViewById(android.R.id.widget_frame);
258            widget.setEnabled(!isImmutable());
259            if (widget.getChildCount() > 0) {
260                final Switch switchView = (Switch) widget.getChildAt(0);
261                switchView.setEnabled(!isImmutable());
262                switchView.setTag(this);
263                switchView.setOnCheckedChangeListener(new OnCheckedChangeListener() {
264                    @Override
265                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
266                        listener.onClick(switchView);
267                    }
268                });
269            }
270        }
271    }
272
273    @Override
274    public void onCreate(Bundle icicle) {
275        super.onCreate(icicle);
276
277        if (icicle != null) {
278            mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID));
279            mSavedPhoto = (Bitmap) icicle.getParcelable(KEY_SAVED_PHOTO);
280        } else {
281            Bundle args = getArguments();
282
283            if (args.containsKey(EXTRA_USER_ID)) {
284                mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
285            }
286            mNewUser = args.getBoolean(EXTRA_NEW_USER, false);
287        }
288        mPackageManager = getActivity().getPackageManager();
289        mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
290        addPreferencesFromResource(R.xml.app_restrictions);
291        mAppList = getPreferenceScreen();
292        setHasOptionsMenu(true);
293    }
294
295    @Override
296    public void onActivityCreated(Bundle savedInstanceState) {
297        if (mHeaderView == null) {
298            mHeaderView = LayoutInflater.from(getActivity()).inflate(
299                    R.layout.user_info_header, null);
300            ((ViewGroup) getListView().getParent()).addView(mHeaderView, 0);
301            mHeaderView.setOnClickListener(this);
302            mUserIconView = (ImageView) mHeaderView.findViewById(android.R.id.icon);
303            mUserNameView = (TextView) mHeaderView.findViewById(android.R.id.title);
304            getListView().setFastScrollEnabled(true);
305        }
306        // This is going to bind the preferences.
307        super.onActivityCreated(savedInstanceState);
308    }
309
310    @Override
311    public void onSaveInstanceState(Bundle outState) {
312        super.onSaveInstanceState(outState);
313        outState.putInt(EXTRA_USER_ID, mUser.getIdentifier());
314        if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
315                && mEditUserPhotoController != null) {
316            outState.putParcelable(KEY_SAVED_PHOTO,
317                    mEditUserPhotoController.getNewUserPhotoBitmap());
318        }
319    }
320
321    public void onResume() {
322        super.onResume();
323        getActivity().registerReceiver(mUserBackgrounding,
324                new IntentFilter(Intent.ACTION_USER_BACKGROUND));
325        mAppListChanged = false;
326        new AppLoadingTask().execute((Void[]) null);
327
328        UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier());
329        ((TextView) mHeaderView.findViewById(android.R.id.title)).setText(info.name);
330        ((ImageView) mHeaderView.findViewById(android.R.id.icon)).setImageDrawable(
331                getCircularUserIcon());
332    }
333
334    public void onPause() {
335        super.onPause();
336        mNewUser = false;
337        getActivity().unregisterReceiver(mUserBackgrounding);
338        if (mAppListChanged) {
339            new Thread() {
340                public void run() {
341                    updateUserAppList();
342                }
343            }.start();
344        }
345    }
346
347    private Drawable getCircularUserIcon() {
348        Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier());
349        CircleFramedDrawable circularIcon =
350                CircleFramedDrawable.getInstance(this.getActivity(), userIcon);
351        return circularIcon;
352    }
353
354    private void updateUserAppList() {
355        IPackageManager ipm = IPackageManager.Stub.asInterface(
356                ServiceManager.getService("package"));
357        final int userId = mUser.getIdentifier();
358        if (!mUserManager.getUserInfo(userId).isRestricted()) {
359            Log.e(TAG, "Cannot apply application restrictions on a regular user!");
360            return;
361        }
362        for (Map.Entry<String,Boolean> entry : mSelectedPackages.entrySet()) {
363            String packageName = entry.getKey();
364            if (entry.getValue()) {
365                // Enable selected apps
366                try {
367                    ApplicationInfo info = ipm.getApplicationInfo(packageName, 0, userId);
368                    if (info == null || info.enabled == false) {
369                        ipm.installExistingPackageAsUser(packageName, mUser.getIdentifier());
370                        if (DEBUG) {
371                            Log.d(TAG, "Installing " + packageName);
372                        }
373                    }
374                } catch (RemoteException re) {
375                }
376            } else {
377                // Blacklist all other apps, system or downloaded
378                try {
379                    ApplicationInfo info = ipm.getApplicationInfo(packageName, 0, userId);
380                    if (info != null) {
381                        ipm.deletePackageAsUser(entry.getKey(), null, mUser.getIdentifier(),
382                                PackageManager.DELETE_SYSTEM_APP);
383                        if (DEBUG) {
384                            Log.d(TAG, "Uninstalling " + packageName);
385                        }
386                    }
387                } catch (RemoteException re) {
388                }
389            }
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);
445        for (ResolveInfo app : launchableApps) {
446            if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
447                int flags = app.activityInfo.applicationInfo.flags;
448                if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
449                        || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
450                    // System app
451                    // Skip excluded packages
452                    if (excludePackages.contains(app.activityInfo.packageName)) continue;
453
454                    SelectableAppInfo info = new SelectableAppInfo();
455                    info.packageName = app.activityInfo.packageName;
456                    info.appName = app.activityInfo.applicationInfo.loadLabel(pm);
457                    info.icon = app.activityInfo.loadIcon(pm);
458                    info.activityName = app.activityInfo.loadLabel(pm);
459                    if (info.activityName == null) info.activityName = info.appName;
460
461                    visibleApps.add(info);
462                }
463            }
464        }
465    }
466
467    private class AppLoadingTask extends AsyncTask<Void, Void, Void> {
468
469        @Override
470        protected Void doInBackground(Void... params) {
471            fetchAndMergeApps();
472            return null;
473        }
474
475        @Override
476        protected void onPostExecute(Void result) {
477            populateApps();
478        }
479
480        @Override
481        protected void onPreExecute() {
482        }
483    }
484
485    private void fetchAndMergeApps() {
486        mAppList.setOrderingAsAdded(false);
487        mVisibleApps = new ArrayList<SelectableAppInfo>();
488        final Context context = getActivity();
489        if (context == null) return;
490        final PackageManager pm = mPackageManager;
491        IPackageManager ipm = AppGlobals.getPackageManager();
492
493        final HashSet<String> excludePackages = new HashSet<String>();
494        addSystemImes(excludePackages);
495
496        // Add launchers
497        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
498        launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
499        addSystemApps(mVisibleApps, launcherIntent, excludePackages);
500
501        // Add widgets
502        Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
503        addSystemApps(mVisibleApps, widgetIntent, excludePackages);
504
505        List<ApplicationInfo> installedApps = pm.getInstalledApplications(0);
506        for (ApplicationInfo app : installedApps) {
507            if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
508                    && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
509                // Downloaded app
510                SelectableAppInfo info = new SelectableAppInfo();
511                info.packageName = app.packageName;
512                info.appName = app.loadLabel(pm);
513                info.activityName = info.appName;
514                info.icon = app.loadIcon(pm);
515                mVisibleApps.add(info);
516            }
517        }
518
519        mUserApps = null;
520        try {
521            mUserApps = ipm.getInstalledApplications(
522                    0, mUser.getIdentifier()).getList();
523        } catch (RemoteException re) {
524        }
525
526        if (mUserApps != null) {
527            for (ApplicationInfo app : mUserApps) {
528                if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
529                        && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
530                    // Downloaded app
531                    SelectableAppInfo info = new SelectableAppInfo();
532                    info.packageName = app.packageName;
533                    info.appName = app.loadLabel(pm);
534                    info.activityName = info.appName;
535                    info.icon = app.loadIcon(pm);
536                    mVisibleApps.add(info);
537                }
538            }
539        }
540        Collections.sort(mVisibleApps, new AppLabelComparator());
541
542        // Remove dupes
543        Set<String> dedupPackageSet = new HashSet<String>();
544        for (int i = mVisibleApps.size() - 1; i >= 0; i--) {
545            SelectableAppInfo info = mVisibleApps.get(i);
546            if (DEBUG) Log.i(TAG, info.toString());
547            String both = info.packageName + "+" + info.activityName;
548            if (!TextUtils.isEmpty(info.packageName)
549                    && !TextUtils.isEmpty(info.activityName)
550                    && dedupPackageSet.contains(both)) {
551                mVisibleApps.remove(i);
552            } else {
553                dedupPackageSet.add(both);
554            }
555        }
556
557        // Establish master/slave relationship for entries that share a package name
558        HashMap<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>();
559        for (SelectableAppInfo info : mVisibleApps) {
560            if (packageMap.containsKey(info.packageName)) {
561                info.masterEntry = packageMap.get(info.packageName);
562            } else {
563                packageMap.put(info.packageName, info);
564            }
565        }
566    }
567
568    private void populateApps() {
569        final Context context = getActivity();
570        if (context == null) return;
571        final PackageManager pm = mPackageManager;
572        IPackageManager ipm = AppGlobals.getPackageManager();
573        mAppList.removeAll();
574        Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
575        final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0);
576        int i = 0;
577        if (mVisibleApps.size() > 0) {
578            for (SelectableAppInfo app : mVisibleApps) {
579                String packageName = app.packageName;
580                if (packageName == null) continue;
581                final boolean isSettingsApp = packageName.equals(context.getPackageName());
582                AppRestrictionsPreference p = new AppRestrictionsPreference(context, this);
583                final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName);
584                p.setIcon(app.icon != null ? app.icon.mutate() : null);
585                p.setChecked(false);
586                p.setTitle(app.activityName);
587                if (app.masterEntry != null) {
588                    p.setSummary(context.getString(R.string.user_restrictions_controlled_by,
589                            app.masterEntry.activityName));
590                }
591                p.setKey(PKG_PREFIX + packageName);
592                p.setSettingsEnabled(hasSettings || isSettingsApp);
593                p.setPersistent(false);
594                p.setOnPreferenceChangeListener(this);
595                p.setOnPreferenceClickListener(this);
596                PackageInfo pi = null;
597                try {
598                    pi = pm.getPackageInfo(packageName, 0);
599                } catch (NameNotFoundException re) {
600                    try {
601                        pi = ipm.getPackageInfo(packageName, 0, mUser.getIdentifier());
602                    } catch (RemoteException e) {
603                    }
604                }
605                if (pi != null && pi.requiredForAllUsers) {
606                    p.setChecked(true);
607                    p.setImmutable(true);
608                    // If the app is required and has no restrictions, skip showing it
609                    if (!hasSettings && !isSettingsApp) continue;
610                } else if (!mNewUser && appInfoListHasPackage(mUserApps, packageName)) {
611                    p.setChecked(true);
612                }
613                if (pi.requiredAccountType != null && pi.restrictedAccountType == null) {
614                    p.setChecked(false);
615                    p.setImmutable(true);
616                    p.setSummary(R.string.app_not_supported_in_limited);
617                }
618                if (pi.restrictedAccountType != null) {
619                    p.setSummary(R.string.app_sees_restricted_accounts);
620                }
621                if (app.masterEntry != null) {
622                    p.setImmutable(true);
623                    p.setChecked(mSelectedPackages.get(packageName));
624                }
625                mAppList.addPreference(p);
626                if (isSettingsApp) {
627                    p.setOrder(MAX_APP_RESTRICTIONS * 1);
628                } else {
629                    p.setOrder(MAX_APP_RESTRICTIONS * (i + 2));
630                }
631                p.setSelectableAppInfo(app);
632                mSelectedPackages.put(packageName, p.isChecked());
633                mAppListChanged = true;
634                i++;
635            }
636        }
637        // If this is the first time for a new profile, install/uninstall default apps for profile
638        // to avoid taking the hit in onPause(), which can cause race conditions on user switch.
639        if (mNewUser && mFirstTime) {
640            mFirstTime = false;
641            updateUserAppList();
642        }
643    }
644
645    private class AppLabelComparator implements Comparator<SelectableAppInfo> {
646
647        @Override
648        public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
649            String lhsLabel = lhs.activityName.toString();
650            String rhsLabel = rhs.activityName.toString();
651            return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
652        }
653    }
654
655    private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) {
656        for (ResolveInfo info : receivers) {
657            if (info.activityInfo.packageName.equals(packageName)) {
658                return true;
659            }
660        }
661        return false;
662    }
663
664    private boolean appInfoListHasPackage(List<ApplicationInfo> apps, String packageName) {
665        for (ApplicationInfo info : apps) {
666            if (info.packageName.equals(packageName)) {
667                return true;
668            }
669        }
670        return false;
671    }
672
673    private void updateAllEntries(String prefKey, boolean checked) {
674        for (int i = 0; i < mAppList.getPreferenceCount(); i++) {
675            Preference pref = mAppList.getPreference(i);
676            if (pref instanceof AppRestrictionsPreference) {
677                if (prefKey.equals(pref.getKey())) {
678                    ((AppRestrictionsPreference) pref).setChecked(checked);
679                }
680            }
681        }
682    }
683
684    @Override
685    public void onClick(View v) {
686        if (v == mHeaderView) {
687            showDialog(DIALOG_ID_EDIT_USER_INFO);
688        } else if (v.getTag() instanceof AppRestrictionsPreference) {
689            AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag();
690            if (v.getId() == R.id.app_restrictions_settings) {
691                toggleAppPanel(pref);
692            } else if (!pref.isImmutable()) {
693                pref.setChecked(!pref.isChecked());
694                final String packageName = pref.getKey().substring(PKG_PREFIX.length());
695                mSelectedPackages.put(packageName, pref.isChecked());
696                if (pref.isChecked() && pref.hasSettings
697                        && pref.restrictions == null) {
698                    // The restrictions have not been initialized, get and save them
699                    requestRestrictionsForApp(packageName, pref);
700                }
701                mAppListChanged = true;
702                updateAllEntries(pref.getKey(), pref.isChecked());
703            }
704        }
705    }
706
707    @Override
708    public boolean onPreferenceChange(Preference preference, Object newValue) {
709        String key = preference.getKey();
710        if (key != null && key.contains(DELIMITER)) {
711            StringTokenizer st = new StringTokenizer(key, DELIMITER);
712            final String packageName = st.nextToken();
713            final String restrictionKey = st.nextToken();
714            AppRestrictionsPreference appPref = (AppRestrictionsPreference)
715                    mAppList.findPreference(PKG_PREFIX+packageName);
716            ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions();
717            if (restrictions != null) {
718                for (RestrictionEntry entry : restrictions) {
719                    if (entry.getKey().equals(restrictionKey)) {
720                        switch (entry.getType()) {
721                        case RestrictionEntry.TYPE_BOOLEAN:
722                            entry.setSelectedState((Boolean) newValue);
723                            break;
724                        case RestrictionEntry.TYPE_CHOICE:
725                        case RestrictionEntry.TYPE_CHOICE_LEVEL:
726                            ListPreference listPref = (ListPreference) preference;
727                            entry.setSelectedString((String) newValue);
728                            String readable = findInArray(entry.getChoiceEntries(),
729                                    entry.getChoiceValues(), (String) newValue);
730                            listPref.setSummary(readable);
731                            break;
732                        case RestrictionEntry.TYPE_MULTI_SELECT:
733                            MultiSelectListPreference msListPref =
734                                    (MultiSelectListPreference) preference;
735                            Set<String> set = (Set<String>) newValue;
736                            String [] selectedValues = new String[set.size()];
737                            set.toArray(selectedValues);
738                            entry.setAllSelectedStrings(selectedValues);
739                            break;
740                        default:
741                            continue;
742                        }
743                        if (packageName.equals(getActivity().getPackageName())) {
744                            RestrictionUtils.setRestrictions(getActivity(), restrictions, mUser);
745                        } else {
746                            mUserManager.setApplicationRestrictions(packageName,
747                                    RestrictionUtils.restrictionsToBundle(restrictions),
748                                    mUser);
749                        }
750                        break;
751                    }
752                }
753            }
754        }
755        return true;
756    }
757
758    private void toggleAppPanel(AppRestrictionsPreference preference) {
759        if (preference.getKey().startsWith(PKG_PREFIX)) {
760            if (preference.panelOpen) {
761                for (Preference p : preference.childPreferences) {
762                    mAppList.removePreference(p);
763                }
764                preference.childPreferences.clear();
765            } else {
766                String packageName = preference.getKey().substring(PKG_PREFIX.length());
767                if (packageName.equals(getActivity().getPackageName())) {
768                    // Settings, fake it by using user restrictions
769                    ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions(
770                            getActivity(), mUser);
771                    onRestrictionsReceived(preference, packageName, restrictions);
772                } else {
773                    requestRestrictionsForApp(packageName, preference);
774                }
775            }
776            preference.panelOpen = !preference.panelOpen;
777        }
778    }
779
780    private void requestRestrictionsForApp(String packageName,
781            AppRestrictionsPreference preference) {
782        Bundle oldEntries =
783                mUserManager.getApplicationRestrictions(packageName, mUser);
784        Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
785        intent.setPackage(packageName);
786        intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries);
787        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
788        getActivity().sendOrderedBroadcast(intent, null,
789                new RestrictionsResultReceiver(packageName, preference),
790                null, Activity.RESULT_OK, null, null);
791    }
792
793    class RestrictionsResultReceiver extends BroadcastReceiver {
794
795        private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT;
796        String packageName;
797        AppRestrictionsPreference preference;
798
799        RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) {
800            super();
801            this.packageName = packageName;
802            this.preference = preference;
803        }
804
805        @Override
806        public void onReceive(Context context, Intent intent) {
807            Bundle results = getResultExtras(true);
808            final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList(
809                    Intent.EXTRA_RESTRICTIONS_LIST);
810            Intent restrictionsIntent = (Intent) results.getParcelable(CUSTOM_RESTRICTIONS_INTENT);
811            if (restrictions != null && restrictionsIntent == null) {
812                onRestrictionsReceived(preference, packageName, restrictions);
813                mUserManager.setApplicationRestrictions(packageName,
814                        RestrictionUtils.restrictionsToBundle(restrictions), mUser);
815            } else if (restrictionsIntent != null) {
816                final Intent customIntent = restrictionsIntent;
817                if (restrictions != null) {
818                    customIntent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE,
819                            RestrictionUtils.restrictionsToBundle(restrictions));
820                }
821                Preference p = new Preference(context);
822                p.setTitle(R.string.app_restrictions_custom_label);
823                p.setOnPreferenceClickListener(new OnPreferenceClickListener() {
824                    @Override
825                    public boolean onPreferenceClick(Preference preference) {
826                        int requestCode = generateCustomActivityRequestCode(
827                                RestrictionsResultReceiver.this.preference);
828                        AppRestrictionsFragment.this.startActivityForResult(
829                                customIntent, requestCode);
830                        return false;
831                    }
832                });
833                p.setPersistent(false);
834                p.setOrder(preference.getOrder() + 1);
835                preference.childPreferences.add(p);
836                mAppList.addPreference(p);
837                preference.setRestrictions(restrictions);
838            }
839        }
840    }
841
842    private void onRestrictionsReceived(AppRestrictionsPreference preference, String packageName,
843            ArrayList<RestrictionEntry> restrictions) {
844        // Non-custom-activity case - expand the restrictions in-place
845        final Context context = preference.getContext();
846        int count = 1;
847        for (RestrictionEntry entry : restrictions) {
848            Preference p = null;
849            switch (entry.getType()) {
850            case RestrictionEntry.TYPE_BOOLEAN:
851                p = new CheckBoxPreference(context);
852                p.setTitle(entry.getTitle());
853                p.setSummary(entry.getDescription());
854                ((CheckBoxPreference)p).setChecked(entry.getSelectedState());
855                break;
856            case RestrictionEntry.TYPE_CHOICE:
857            case RestrictionEntry.TYPE_CHOICE_LEVEL:
858                p = new ListPreference(context);
859                p.setTitle(entry.getTitle());
860                String value = entry.getSelectedString();
861                if (value == null) {
862                    value = entry.getDescription();
863                }
864                p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(),
865                        value));
866                ((ListPreference)p).setEntryValues(entry.getChoiceValues());
867                ((ListPreference)p).setEntries(entry.getChoiceEntries());
868                ((ListPreference)p).setValue(value);
869                ((ListPreference)p).setDialogTitle(entry.getTitle());
870                break;
871            case RestrictionEntry.TYPE_MULTI_SELECT:
872                p = new MultiSelectListPreference(context);
873                p.setTitle(entry.getTitle());
874                ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues());
875                ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries());
876                HashSet<String> set = new HashSet<String>();
877                for (String s : entry.getAllSelectedStrings()) {
878                    set.add(s);
879                }
880                ((MultiSelectListPreference)p).setValues(set);
881                ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle());
882                break;
883            case RestrictionEntry.TYPE_NULL:
884            default:
885            }
886            if (p != null) {
887                p.setPersistent(false);
888                p.setOrder(preference.getOrder() + count);
889                // Store the restrictions key string as a key for the preference
890                p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER
891                        + entry.getKey());
892                mAppList.addPreference(p);
893                p.setOnPreferenceChangeListener(AppRestrictionsFragment.this);
894                preference.childPreferences.add(p);
895                count++;
896            }
897        }
898        preference.setRestrictions(restrictions);
899    }
900
901    /**
902     * Generates a request code that is stored in a map to retrieve the associated
903     * AppRestrictionsPreference.
904     * @param preference
905     * @return
906     */
907    private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) {
908        mCustomRequestCode++;
909        mCustomRequestMap.put(mCustomRequestCode, preference);
910        return mCustomRequestCode;
911    }
912
913    @Override
914    public void onActivityResult(int requestCode, int resultCode, Intent data) {
915        super.onActivityResult(requestCode, resultCode, data);
916
917        if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
918                && mEditUserPhotoController.onActivityResult(requestCode, resultCode, data)) {
919            return;
920        }
921
922        AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode);
923        if (pref == null) {
924            Log.w(TAG, "Unknown requestCode " + requestCode);
925            return;
926        }
927
928        if (resultCode == Activity.RESULT_OK) {
929            String packageName = pref.getKey().substring(PKG_PREFIX.length());
930            ArrayList<RestrictionEntry> list =
931                    data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST);
932            Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
933            if (list != null) {
934                // If there's a valid result, persist it to the user manager.
935                pref.setRestrictions(list);
936                mUserManager.setApplicationRestrictions(packageName,
937                        RestrictionUtils.restrictionsToBundle(list), mUser);
938            } else if (bundle != null) {
939                // If there's a valid result, persist it to the user manager.
940                mUserManager.setApplicationRestrictions(packageName, bundle, mUser);
941            }
942            toggleAppPanel(pref);
943        }
944        // Remove request from the map
945        mCustomRequestMap.remove(requestCode);
946    }
947
948    private String findInArray(String[] choiceEntries, String[] choiceValues,
949            String selectedString) {
950        for (int i = 0; i < choiceValues.length; i++) {
951            if (choiceValues[i].equals(selectedString)) {
952                return choiceEntries[i];
953            }
954        }
955        return selectedString;
956    }
957
958    @Override
959    public boolean onPreferenceClick(Preference preference) {
960        if (preference.getKey().startsWith(PKG_PREFIX)) {
961            AppRestrictionsPreference arp = (AppRestrictionsPreference) preference;
962            if (!arp.isImmutable()) {
963                arp.setChecked(!arp.isChecked());
964                mSelectedPackages.put(arp.getKey().substring(PKG_PREFIX.length()), arp.isChecked());
965                updateAllEntries(arp.getKey(), arp.isChecked());
966                mAppListChanged = true;
967            }
968            return true;
969        }
970        return false;
971    }
972
973    @Override
974    public Dialog onCreateDialog(int dialogId) {
975        if (dialogId == DIALOG_ID_EDIT_USER_INFO) {
976            if (mEditUserInfoDialog != null) {
977                return mEditUserInfoDialog;
978            }
979
980            LayoutInflater inflater = getActivity().getLayoutInflater();
981            View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
982
983            UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier());
984
985            final EditText userNameView = (EditText) content.findViewById(R.id.user_name);
986            userNameView.setText(info.name);
987
988            final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo);
989            Drawable drawable = null;
990            if (mSavedPhoto != null) {
991                drawable = CircleFramedDrawable.getInstance(getActivity(), mSavedPhoto);
992            } else {
993                drawable = mUserIconView.getDrawable();
994                if (drawable == null) {
995                    drawable = getCircularUserIcon();
996                }
997            }
998            userPhotoView.setImageDrawable(drawable);
999
1000            mEditUserPhotoController = new EditUserPhotoController(this, userPhotoView,
1001                    mSavedPhoto, drawable);
1002
1003            mEditUserInfoDialog = new AlertDialog.Builder(getActivity())
1004                .setTitle(R.string.user_info_settings_title)
1005                .setIconAttribute(R.drawable.ic_settings_multiuser)
1006                .setView(content)
1007                .setCancelable(true)
1008                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1009                    @Override
1010                    public void onClick(DialogInterface dialog, int which) {
1011                        if (which == DialogInterface.BUTTON_POSITIVE) {
1012                            // Update the name if changed.
1013                            CharSequence userName = userNameView.getText();
1014                            if (!TextUtils.isEmpty(userName)) {
1015                                CharSequence oldUserName = mUserNameView.getText();
1016                                if (oldUserName == null
1017                                        || !userName.toString().equals(oldUserName.toString())) {
1018                                    ((TextView) mHeaderView.findViewById(android.R.id.title))
1019                                            .setText(userName.toString());
1020                                    mUserManager.setUserName(mUser.getIdentifier(),
1021                                            userName.toString());
1022                                }
1023                            }
1024                            // Update the photo if changed.
1025                            Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable();
1026                            Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap();
1027                            if (drawable != null && bitmap != null
1028                                    && !drawable.equals(mUserIconView.getDrawable())) {
1029                                mUserIconView.setImageDrawable(drawable);
1030                                new AsyncTask<Void, Void, Void>() {
1031                                    @Override
1032                                    protected Void doInBackground(Void... params) {
1033                                        mUserManager.setUserIcon(mUser.getIdentifier(),
1034                                                mEditUserPhotoController.getNewUserPhotoBitmap());
1035                                        return null;
1036                                    }
1037                                }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
1038                            }
1039                            removeDialog(DIALOG_ID_EDIT_USER_INFO);
1040                        }
1041                        clearEditUserInfoDialog();
1042                    }
1043                })
1044                .setNegativeButton(android.R.string.cancel,  new DialogInterface.OnClickListener() {
1045                    @Override
1046                    public void onClick(DialogInterface dialog, int which) {
1047                        clearEditUserInfoDialog();
1048                    }
1049                 })
1050                .create();
1051
1052            // Make sure the IME is up.
1053            mEditUserInfoDialog.getWindow().setSoftInputMode(
1054                    WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
1055
1056            return mEditUserInfoDialog;
1057        }
1058
1059        return null;
1060    }
1061
1062    private void clearEditUserInfoDialog() {
1063        mEditUserInfoDialog = null;
1064        mSavedPhoto = null;
1065    }
1066
1067    private static class EditUserPhotoController {
1068        private static final int POPUP_LIST_ITEM_ID_CHOOSE_PHOTO = 1;
1069        private static final int POPUP_LIST_ITEM_ID_TAKE_PHOTO = 2;
1070
1071        // It seems that this class generates custom request codes and they may
1072        // collide with ours, these values are very unlikely to have a conflict.
1073        private static final int REQUEST_CODE_CHOOSE_PHOTO = Integer.MAX_VALUE;
1074        private static final int REQUEST_CODE_TAKE_PHOTO = Integer.MAX_VALUE - 1;
1075        private static final int REQUEST_CODE_CROP_PHOTO = Integer.MAX_VALUE - 2;
1076
1077        private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
1078        private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
1079
1080        private final int mPhotoSize;
1081
1082        private final Context mContext;
1083        private final Fragment mFragment;
1084        private final ImageView mImageView;
1085
1086        private final Uri mCropPictureUri;
1087        private final Uri mTakePictureUri;
1088
1089        private Bitmap mNewUserPhotoBitmap;
1090        private Drawable mNewUserPhotoDrawable;
1091
1092        public EditUserPhotoController(Fragment fragment, ImageView view,
1093                Bitmap bitmap, Drawable drawable) {
1094            mContext = view.getContext();
1095            mFragment = fragment;
1096            mImageView = view;
1097            mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME);
1098            mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME);
1099            mPhotoSize = getPhotoSize(mContext);
1100            mImageView.setOnClickListener(new OnClickListener() {
1101                @Override
1102                public void onClick(View v) {
1103                    showUpdatePhotoPopup();
1104                }
1105            });
1106            mNewUserPhotoBitmap = bitmap;
1107            mNewUserPhotoDrawable = drawable;
1108        }
1109
1110        public boolean onActivityResult(int requestCode, int resultCode, final Intent data) {
1111            if (resultCode != Activity.RESULT_OK) {
1112                return false;
1113            }
1114            switch (requestCode) {
1115                case REQUEST_CODE_CHOOSE_PHOTO:
1116                case REQUEST_CODE_CROP_PHOTO: {
1117                    new AsyncTask<Void, Void, Bitmap>() {
1118                        @Override
1119                        protected Bitmap doInBackground(Void... params) {
1120                            return BitmapFactory.decodeFile(mCropPictureUri.getPath());
1121                        }
1122                        @Override
1123                        protected void onPostExecute(Bitmap bitmap) {
1124                            mNewUserPhotoBitmap = bitmap;
1125                            mNewUserPhotoDrawable = CircleFramedDrawable
1126                                    .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
1127                            mImageView.setImageDrawable(mNewUserPhotoDrawable);
1128                            // Delete the files - not needed anymore.
1129                            new File(mCropPictureUri.getPath()).delete();
1130                            new File(mTakePictureUri.getPath()).delete();
1131                        }
1132                    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
1133                } return true;
1134                case REQUEST_CODE_TAKE_PHOTO: {
1135                    cropPhoto();
1136                } break;
1137            }
1138            return false;
1139        }
1140
1141        public Bitmap getNewUserPhotoBitmap() {
1142            return mNewUserPhotoBitmap;
1143        }
1144
1145        public Drawable getNewUserPhotoDrawable() {
1146            return mNewUserPhotoDrawable;
1147        }
1148
1149        private void showUpdatePhotoPopup() {
1150            final boolean canTakePhoto = canTakePhoto();
1151            final boolean canChoosePhoto = canChoosePhoto();
1152
1153            if (!canTakePhoto && !canChoosePhoto) {
1154                return;
1155            }
1156
1157            Context context = mImageView.getContext();
1158            final List<AdapterItem> items = new ArrayList<AdapterItem>();
1159
1160            if (canTakePhoto()) {
1161                String title = mImageView.getContext().getString( R.string.user_image_take_photo);
1162                AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_TAKE_PHOTO);
1163                items.add(item);
1164            }
1165
1166            if (canChoosePhoto) {
1167                String title = context.getString(R.string.user_image_choose_photo);
1168                AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_CHOOSE_PHOTO);
1169                items.add(item);
1170            }
1171
1172            final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
1173
1174            listPopupWindow.setAnchorView(mImageView);
1175            listPopupWindow.setModal(true);
1176            listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
1177
1178            ListAdapter adapter = new ArrayAdapter<AdapterItem>(context,
1179                    R.layout.edit_user_photo_popup_item, items);
1180            listPopupWindow.setAdapter(adapter);
1181
1182            final int width = Math.max(mImageView.getWidth(), context.getResources()
1183                    .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
1184            listPopupWindow.setWidth(width);
1185
1186            listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
1187                @Override
1188                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1189                    AdapterItem item = items.get(position);
1190                    switch (item.id) {
1191                        case POPUP_LIST_ITEM_ID_CHOOSE_PHOTO: {
1192                            choosePhoto();
1193                            listPopupWindow.dismiss();
1194                        } break;
1195                        case POPUP_LIST_ITEM_ID_TAKE_PHOTO: {
1196                            takePhoto();
1197                            listPopupWindow.dismiss();
1198                        } break;
1199                    }
1200                }
1201            });
1202
1203            listPopupWindow.show();
1204        }
1205
1206        private boolean canTakePhoto() {
1207            return mImageView.getContext().getPackageManager().queryIntentActivities(
1208                    new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
1209                    PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
1210        }
1211
1212        private boolean canChoosePhoto() {
1213            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
1214            intent.setType("image/*");
1215            return mImageView.getContext().getPackageManager().queryIntentActivities(
1216                    intent, 0).size() > 0;
1217        }
1218
1219        private void takePhoto() {
1220            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
1221            intent.putExtra(MediaStore.EXTRA_OUTPUT, mTakePictureUri);
1222            mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
1223        }
1224
1225        private void choosePhoto() {
1226            Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
1227            intent.setType("image/*");
1228            intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPictureUri);
1229            appendCropExtras(intent);
1230            mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
1231        }
1232
1233        private void cropPhoto() {
1234            Intent intent = new Intent("com.android.camera.action.CROP");
1235            intent.setDataAndType(mTakePictureUri, "image/*");
1236            intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPictureUri);
1237            appendCropExtras(intent);
1238            mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
1239        }
1240
1241        private void appendCropExtras(Intent intent) {
1242            intent.putExtra("crop", "true");
1243            intent.putExtra("scale", true);
1244            intent.putExtra("scaleUpIfNeeded", true);
1245            intent.putExtra("aspectX", 1);
1246            intent.putExtra("aspectY", 1);
1247            intent.putExtra("outputX", mPhotoSize);
1248            intent.putExtra("outputY", mPhotoSize);
1249        }
1250
1251        private static int getPhotoSize(Context context) {
1252            Cursor cursor = context.getContentResolver().query(
1253                    DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
1254                    new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
1255            try {
1256                cursor.moveToFirst();
1257                return cursor.getInt(0);
1258            } finally {
1259                cursor.close();
1260            }
1261        }
1262
1263        private static Uri createTempImageUri(Context context, String fileName) {
1264            File folder = context.getExternalCacheDir();
1265            folder.mkdirs();
1266            File fullPath = new File(folder, fileName);
1267            fullPath.delete();
1268            return Uri.fromFile(fullPath.getAbsoluteFile());
1269        }
1270
1271        private static final class AdapterItem {
1272            final String title;
1273            final int id;
1274
1275            public AdapterItem(String title, int id) {
1276                this.title = title;
1277                this.id = id;
1278            }
1279
1280            @Override
1281            public String toString() {
1282                return title;
1283            }
1284        }
1285    }
1286}
1287