• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.settings.users;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.AppGlobals;
22 import android.app.Dialog;
23 import android.app.Fragment;
24 import android.appwidget.AppWidgetManager;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.RestrictionEntry;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.IPackageManager;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.content.pm.ResolveInfo;
37 import android.content.pm.UserInfo;
38 import android.content.res.Resources;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.ColorFilter;
43 import android.graphics.ColorMatrix;
44 import android.graphics.ColorMatrixColorFilter;
45 import android.graphics.drawable.Drawable;
46 import android.net.Uri;
47 import android.os.AsyncTask;
48 import android.os.Bundle;
49 import android.os.RemoteException;
50 import android.os.ServiceManager;
51 import android.os.UserHandle;
52 import android.os.UserManager;
53 import android.preference.CheckBoxPreference;
54 import android.preference.ListPreference;
55 import android.preference.MultiSelectListPreference;
56 import android.preference.Preference;
57 import android.preference.Preference.OnPreferenceChangeListener;
58 import android.preference.Preference.OnPreferenceClickListener;
59 import android.preference.PreferenceGroup;
60 import android.preference.SwitchPreference;
61 import android.provider.ContactsContract.DisplayPhoto;
62 import android.provider.MediaStore;
63 import android.text.TextUtils;
64 import android.util.Log;
65 import android.view.LayoutInflater;
66 import android.view.View;
67 import android.view.View.OnClickListener;
68 import android.view.inputmethod.InputMethod;
69 import android.view.inputmethod.InputMethodInfo;
70 import android.view.inputmethod.InputMethodManager;
71 import android.view.ViewGroup;
72 import android.view.WindowManager;
73 import android.widget.AdapterView;
74 import android.widget.ArrayAdapter;
75 import android.widget.CompoundButton;
76 import android.widget.CompoundButton.OnCheckedChangeListener;
77 import android.widget.EditText;
78 import android.widget.ImageView;
79 import android.widget.ListAdapter;
80 import android.widget.ListPopupWindow;
81 import android.widget.Switch;
82 import android.widget.TextView;
83 
84 import com.android.settings.R;
85 import com.android.settings.SettingsPreferenceFragment;
86 
87 import java.io.File;
88 import java.util.ArrayList;
89 import java.util.Collections;
90 import java.util.Comparator;
91 import java.util.HashMap;
92 import java.util.HashSet;
93 import java.util.List;
94 import java.util.Map;
95 import java.util.Set;
96 import java.util.StringTokenizer;
97 
98 public 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
toString()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 
AppRestrictionsPreference(Context context, OnClickListener listener)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 
setSettingsEnabled(boolean enable)198         private void setSettingsEnabled(boolean enable) {
199             hasSettings = enable;
200         }
201 
202         @Override
setChecked(boolean checked)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 
setRestrictions(ArrayList<RestrictionEntry> restrictions)212         void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
213             this.restrictions = restrictions;
214         }
215 
setImmutable(boolean immutable)216         void setImmutable(boolean immutable) {
217             this.immutable = immutable;
218         }
219 
isImmutable()220         boolean isImmutable() {
221             return immutable;
222         }
223 
setSelectableAppInfo(SelectableAppInfo appInfo)224         void setSelectableAppInfo(SelectableAppInfo appInfo) {
225             this.appInfo = appInfo;
226         }
227 
getRestriction(String key)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 
getRestrictions()238         ArrayList<RestrictionEntry> getRestrictions() {
239             return restrictions;
240         }
241 
242         @Override
onBindView(View view)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
onCreate(Bundle icicle)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
onActivityCreated(Bundle savedInstanceState)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
onSaveInstanceState(Bundle outState)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 
onResume()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 
onPause()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 
getCircularUserIcon()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 
updateUserAppList()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 
isSystemPackage(String packageName)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      */
addSystemImes(Set<String> excludePackages)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      */
addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, Set<String> excludePackages)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
doInBackground(Void... params)470         protected Void doInBackground(Void... params) {
471             fetchAndMergeApps();
472             return null;
473         }
474 
475         @Override
onPostExecute(Void result)476         protected void onPostExecute(Void result) {
477             populateApps();
478         }
479 
480         @Override
onPreExecute()481         protected void onPreExecute() {
482         }
483     }
484 
fetchAndMergeApps()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             } else {
517                 try {
518                     PackageInfo pi = pm.getPackageInfo(app.packageName, 0);
519                     // If it's a system app that requires an account and doesn't see restricted
520                     // accounts, mark for removal. It might get shown in the UI if it has an icon
521                     // but will still be marked as false and immutable.
522                     if (pi.requiredAccountType != null && pi.restrictedAccountType == null) {
523                         mSelectedPackages.put(app.packageName, false);
524                     }
525                 } catch (NameNotFoundException re) {
526                 }
527             }
528         }
529 
530         mUserApps = null;
531         try {
532             mUserApps = ipm.getInstalledApplications(
533                     0, mUser.getIdentifier()).getList();
534         } catch (RemoteException re) {
535         }
536 
537         if (mUserApps != null) {
538             for (ApplicationInfo app : mUserApps) {
539                 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
540                         && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
541                     // Downloaded app
542                     SelectableAppInfo info = new SelectableAppInfo();
543                     info.packageName = app.packageName;
544                     info.appName = app.loadLabel(pm);
545                     info.activityName = info.appName;
546                     info.icon = app.loadIcon(pm);
547                     mVisibleApps.add(info);
548                 }
549             }
550         }
551         Collections.sort(mVisibleApps, new AppLabelComparator());
552 
553         // Remove dupes
554         Set<String> dedupPackageSet = new HashSet<String>();
555         for (int i = mVisibleApps.size() - 1; i >= 0; i--) {
556             SelectableAppInfo info = mVisibleApps.get(i);
557             if (DEBUG) Log.i(TAG, info.toString());
558             String both = info.packageName + "+" + info.activityName;
559             if (!TextUtils.isEmpty(info.packageName)
560                     && !TextUtils.isEmpty(info.activityName)
561                     && dedupPackageSet.contains(both)) {
562                 mVisibleApps.remove(i);
563             } else {
564                 dedupPackageSet.add(both);
565             }
566         }
567 
568         // Establish master/slave relationship for entries that share a package name
569         HashMap<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>();
570         for (SelectableAppInfo info : mVisibleApps) {
571             if (packageMap.containsKey(info.packageName)) {
572                 info.masterEntry = packageMap.get(info.packageName);
573             } else {
574                 packageMap.put(info.packageName, info);
575             }
576         }
577     }
578 
populateApps()579     private void populateApps() {
580         final Context context = getActivity();
581         if (context == null) return;
582         final PackageManager pm = mPackageManager;
583         IPackageManager ipm = AppGlobals.getPackageManager();
584         mAppList.removeAll();
585         Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
586         final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0);
587         int i = 0;
588         if (mVisibleApps.size() > 0) {
589             for (SelectableAppInfo app : mVisibleApps) {
590                 String packageName = app.packageName;
591                 if (packageName == null) continue;
592                 final boolean isSettingsApp = packageName.equals(context.getPackageName());
593                 AppRestrictionsPreference p = new AppRestrictionsPreference(context, this);
594                 final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName);
595                 p.setIcon(app.icon != null ? app.icon.mutate() : null);
596                 p.setChecked(false);
597                 p.setTitle(app.activityName);
598                 if (app.masterEntry != null) {
599                     p.setSummary(context.getString(R.string.user_restrictions_controlled_by,
600                             app.masterEntry.activityName));
601                 }
602                 p.setKey(PKG_PREFIX + packageName);
603                 p.setSettingsEnabled(hasSettings || isSettingsApp);
604                 p.setPersistent(false);
605                 p.setOnPreferenceChangeListener(this);
606                 p.setOnPreferenceClickListener(this);
607                 PackageInfo pi = null;
608                 try {
609                     pi = pm.getPackageInfo(packageName, 0);
610                 } catch (NameNotFoundException re) {
611                     try {
612                         pi = ipm.getPackageInfo(packageName, 0, mUser.getIdentifier());
613                     } catch (RemoteException e) {
614                     }
615                 }
616                 if (pi != null && pi.requiredForAllUsers) {
617                     p.setChecked(true);
618                     p.setImmutable(true);
619                     // If the app is required and has no restrictions, skip showing it
620                     if (!hasSettings && !isSettingsApp) continue;
621                     // Get and populate the defaults, since the user is not going to be
622                     // able to toggle this app ON (it's ON by default and immutable).
623                     if (hasSettings) {
624                         requestRestrictionsForApp(packageName, p);
625                     }
626                 } else if (!mNewUser && appInfoListHasPackage(mUserApps, packageName)) {
627                     p.setChecked(true);
628                 }
629                 if (pi.requiredAccountType != null && pi.restrictedAccountType == null) {
630                     p.setChecked(false);
631                     p.setImmutable(true);
632                     p.setSummary(R.string.app_not_supported_in_limited);
633                 }
634                 if (pi.restrictedAccountType != null) {
635                     p.setSummary(R.string.app_sees_restricted_accounts);
636                 }
637                 if (app.masterEntry != null) {
638                     p.setImmutable(true);
639                     p.setChecked(mSelectedPackages.get(packageName));
640                 }
641                 mAppList.addPreference(p);
642                 if (isSettingsApp) {
643                     p.setOrder(MAX_APP_RESTRICTIONS * 1);
644                 } else {
645                     p.setOrder(MAX_APP_RESTRICTIONS * (i + 2));
646                 }
647                 p.setSelectableAppInfo(app);
648                 mSelectedPackages.put(packageName, p.isChecked());
649                 mAppListChanged = true;
650                 i++;
651             }
652         }
653         // If this is the first time for a new profile, install/uninstall default apps for profile
654         // to avoid taking the hit in onPause(), which can cause race conditions on user switch.
655         if (mNewUser && mFirstTime) {
656             mFirstTime = false;
657             updateUserAppList();
658         }
659     }
660 
661     private class AppLabelComparator implements Comparator<SelectableAppInfo> {
662 
663         @Override
compare(SelectableAppInfo lhs, SelectableAppInfo rhs)664         public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
665             String lhsLabel = lhs.activityName.toString();
666             String rhsLabel = rhs.activityName.toString();
667             return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
668         }
669     }
670 
resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName)671     private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) {
672         for (ResolveInfo info : receivers) {
673             if (info.activityInfo.packageName.equals(packageName)) {
674                 return true;
675             }
676         }
677         return false;
678     }
679 
appInfoListHasPackage(List<ApplicationInfo> apps, String packageName)680     private boolean appInfoListHasPackage(List<ApplicationInfo> apps, String packageName) {
681         for (ApplicationInfo info : apps) {
682             if (info.packageName.equals(packageName)) {
683                 return true;
684             }
685         }
686         return false;
687     }
688 
updateAllEntries(String prefKey, boolean checked)689     private void updateAllEntries(String prefKey, boolean checked) {
690         for (int i = 0; i < mAppList.getPreferenceCount(); i++) {
691             Preference pref = mAppList.getPreference(i);
692             if (pref instanceof AppRestrictionsPreference) {
693                 if (prefKey.equals(pref.getKey())) {
694                     ((AppRestrictionsPreference) pref).setChecked(checked);
695                 }
696             }
697         }
698     }
699 
700     @Override
onClick(View v)701     public void onClick(View v) {
702         if (v == mHeaderView) {
703             showDialog(DIALOG_ID_EDIT_USER_INFO);
704         } else if (v.getTag() instanceof AppRestrictionsPreference) {
705             AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag();
706             if (v.getId() == R.id.app_restrictions_settings) {
707                 toggleAppPanel(pref);
708             } else if (!pref.isImmutable()) {
709                 pref.setChecked(!pref.isChecked());
710                 final String packageName = pref.getKey().substring(PKG_PREFIX.length());
711                 mSelectedPackages.put(packageName, pref.isChecked());
712                 if (pref.isChecked() && pref.hasSettings
713                         && pref.restrictions == null) {
714                     // The restrictions have not been initialized, get and save them
715                     requestRestrictionsForApp(packageName, pref);
716                 }
717                 mAppListChanged = true;
718                 updateAllEntries(pref.getKey(), pref.isChecked());
719             }
720         }
721     }
722 
723     @Override
onPreferenceChange(Preference preference, Object newValue)724     public boolean onPreferenceChange(Preference preference, Object newValue) {
725         String key = preference.getKey();
726         if (key != null && key.contains(DELIMITER)) {
727             StringTokenizer st = new StringTokenizer(key, DELIMITER);
728             final String packageName = st.nextToken();
729             final String restrictionKey = st.nextToken();
730             AppRestrictionsPreference appPref = (AppRestrictionsPreference)
731                     mAppList.findPreference(PKG_PREFIX+packageName);
732             ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions();
733             if (restrictions != null) {
734                 for (RestrictionEntry entry : restrictions) {
735                     if (entry.getKey().equals(restrictionKey)) {
736                         switch (entry.getType()) {
737                         case RestrictionEntry.TYPE_BOOLEAN:
738                             entry.setSelectedState((Boolean) newValue);
739                             break;
740                         case RestrictionEntry.TYPE_CHOICE:
741                         case RestrictionEntry.TYPE_CHOICE_LEVEL:
742                             ListPreference listPref = (ListPreference) preference;
743                             entry.setSelectedString((String) newValue);
744                             String readable = findInArray(entry.getChoiceEntries(),
745                                     entry.getChoiceValues(), (String) newValue);
746                             listPref.setSummary(readable);
747                             break;
748                         case RestrictionEntry.TYPE_MULTI_SELECT:
749                             MultiSelectListPreference msListPref =
750                                     (MultiSelectListPreference) preference;
751                             Set<String> set = (Set<String>) newValue;
752                             String [] selectedValues = new String[set.size()];
753                             set.toArray(selectedValues);
754                             entry.setAllSelectedStrings(selectedValues);
755                             break;
756                         default:
757                             continue;
758                         }
759                         if (packageName.equals(getActivity().getPackageName())) {
760                             RestrictionUtils.setRestrictions(getActivity(), restrictions, mUser);
761                         } else {
762                             mUserManager.setApplicationRestrictions(packageName,
763                                     RestrictionUtils.restrictionsToBundle(restrictions),
764                                     mUser);
765                         }
766                         break;
767                     }
768                 }
769             }
770         }
771         return true;
772     }
773 
toggleAppPanel(AppRestrictionsPreference preference)774     private void toggleAppPanel(AppRestrictionsPreference preference) {
775         if (preference.getKey().startsWith(PKG_PREFIX)) {
776             if (preference.panelOpen) {
777                 for (Preference p : preference.childPreferences) {
778                     mAppList.removePreference(p);
779                 }
780                 preference.childPreferences.clear();
781             } else {
782                 String packageName = preference.getKey().substring(PKG_PREFIX.length());
783                 if (packageName.equals(getActivity().getPackageName())) {
784                     // Settings, fake it by using user restrictions
785                     ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions(
786                             getActivity(), mUser);
787                     onRestrictionsReceived(preference, packageName, restrictions);
788                 } else {
789                     requestRestrictionsForApp(packageName, preference);
790                 }
791             }
792             preference.panelOpen = !preference.panelOpen;
793         }
794     }
795 
requestRestrictionsForApp(String packageName, AppRestrictionsPreference preference)796     private void requestRestrictionsForApp(String packageName,
797             AppRestrictionsPreference preference) {
798         Bundle oldEntries =
799                 mUserManager.getApplicationRestrictions(packageName, mUser);
800         Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
801         intent.setPackage(packageName);
802         intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries);
803         intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
804         getActivity().sendOrderedBroadcast(intent, null,
805                 new RestrictionsResultReceiver(packageName, preference),
806                 null, Activity.RESULT_OK, null, null);
807     }
808 
809     class RestrictionsResultReceiver extends BroadcastReceiver {
810 
811         private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT;
812         String packageName;
813         AppRestrictionsPreference preference;
814 
RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference)815         RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) {
816             super();
817             this.packageName = packageName;
818             this.preference = preference;
819         }
820 
821         @Override
onReceive(Context context, Intent intent)822         public void onReceive(Context context, Intent intent) {
823             Bundle results = getResultExtras(true);
824             final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList(
825                     Intent.EXTRA_RESTRICTIONS_LIST);
826             Intent restrictionsIntent = (Intent) results.getParcelable(CUSTOM_RESTRICTIONS_INTENT);
827             if (restrictions != null && restrictionsIntent == null) {
828                 onRestrictionsReceived(preference, packageName, restrictions);
829                 mUserManager.setApplicationRestrictions(packageName,
830                         RestrictionUtils.restrictionsToBundle(restrictions), mUser);
831             } else if (restrictionsIntent != null) {
832                 final Intent customIntent = restrictionsIntent;
833                 if (restrictions != null) {
834                     customIntent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE,
835                             RestrictionUtils.restrictionsToBundle(restrictions));
836                 }
837                 Preference p = new Preference(context);
838                 p.setTitle(R.string.app_restrictions_custom_label);
839                 p.setOnPreferenceClickListener(new OnPreferenceClickListener() {
840                     @Override
841                     public boolean onPreferenceClick(Preference preference) {
842                         int requestCode = generateCustomActivityRequestCode(
843                                 RestrictionsResultReceiver.this.preference);
844                         AppRestrictionsFragment.this.startActivityForResult(
845                                 customIntent, requestCode);
846                         return false;
847                     }
848                 });
849                 p.setPersistent(false);
850                 p.setOrder(preference.getOrder() + 1);
851                 preference.childPreferences.add(p);
852                 mAppList.addPreference(p);
853                 preference.setRestrictions(restrictions);
854             }
855         }
856     }
857 
onRestrictionsReceived(AppRestrictionsPreference preference, String packageName, ArrayList<RestrictionEntry> restrictions)858     private void onRestrictionsReceived(AppRestrictionsPreference preference, String packageName,
859             ArrayList<RestrictionEntry> restrictions) {
860         // Non-custom-activity case - expand the restrictions in-place
861         final Context context = preference.getContext();
862         int count = 1;
863         for (RestrictionEntry entry : restrictions) {
864             Preference p = null;
865             switch (entry.getType()) {
866             case RestrictionEntry.TYPE_BOOLEAN:
867                 p = new CheckBoxPreference(context);
868                 p.setTitle(entry.getTitle());
869                 p.setSummary(entry.getDescription());
870                 ((CheckBoxPreference)p).setChecked(entry.getSelectedState());
871                 break;
872             case RestrictionEntry.TYPE_CHOICE:
873             case RestrictionEntry.TYPE_CHOICE_LEVEL:
874                 p = new ListPreference(context);
875                 p.setTitle(entry.getTitle());
876                 String value = entry.getSelectedString();
877                 if (value == null) {
878                     value = entry.getDescription();
879                 }
880                 p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(),
881                         value));
882                 ((ListPreference)p).setEntryValues(entry.getChoiceValues());
883                 ((ListPreference)p).setEntries(entry.getChoiceEntries());
884                 ((ListPreference)p).setValue(value);
885                 ((ListPreference)p).setDialogTitle(entry.getTitle());
886                 break;
887             case RestrictionEntry.TYPE_MULTI_SELECT:
888                 p = new MultiSelectListPreference(context);
889                 p.setTitle(entry.getTitle());
890                 ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues());
891                 ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries());
892                 HashSet<String> set = new HashSet<String>();
893                 for (String s : entry.getAllSelectedStrings()) {
894                     set.add(s);
895                 }
896                 ((MultiSelectListPreference)p).setValues(set);
897                 ((MultiSelectListPreference)p).setDialogTitle(entry.getTitle());
898                 break;
899             case RestrictionEntry.TYPE_NULL:
900             default:
901             }
902             if (p != null) {
903                 p.setPersistent(false);
904                 p.setOrder(preference.getOrder() + count);
905                 // Store the restrictions key string as a key for the preference
906                 p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER
907                         + entry.getKey());
908                 mAppList.addPreference(p);
909                 p.setOnPreferenceChangeListener(AppRestrictionsFragment.this);
910                 preference.childPreferences.add(p);
911                 count++;
912             }
913         }
914         preference.setRestrictions(restrictions);
915         if (count == 1 // No visible restrictions
916                 && preference.isImmutable()
917                 && preference.isChecked()) {
918             // Special case of required app with no visible restrictions. Remove it
919             mAppList.removePreference(preference);
920         }
921     }
922 
923     /**
924      * Generates a request code that is stored in a map to retrieve the associated
925      * AppRestrictionsPreference.
926      * @param preference
927      * @return
928      */
generateCustomActivityRequestCode(AppRestrictionsPreference preference)929     private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) {
930         mCustomRequestCode++;
931         mCustomRequestMap.put(mCustomRequestCode, preference);
932         return mCustomRequestCode;
933     }
934 
935     @Override
onActivityResult(int requestCode, int resultCode, Intent data)936     public void onActivityResult(int requestCode, int resultCode, Intent data) {
937         super.onActivityResult(requestCode, resultCode, data);
938 
939         if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
940                 && mEditUserPhotoController.onActivityResult(requestCode, resultCode, data)) {
941             return;
942         }
943 
944         AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode);
945         if (pref == null) {
946             Log.w(TAG, "Unknown requestCode " + requestCode);
947             return;
948         }
949 
950         if (resultCode == Activity.RESULT_OK) {
951             String packageName = pref.getKey().substring(PKG_PREFIX.length());
952             ArrayList<RestrictionEntry> list =
953                     data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST);
954             Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
955             if (list != null) {
956                 // If there's a valid result, persist it to the user manager.
957                 pref.setRestrictions(list);
958                 mUserManager.setApplicationRestrictions(packageName,
959                         RestrictionUtils.restrictionsToBundle(list), mUser);
960             } else if (bundle != null) {
961                 // If there's a valid result, persist it to the user manager.
962                 mUserManager.setApplicationRestrictions(packageName, bundle, mUser);
963             }
964             toggleAppPanel(pref);
965         }
966         // Remove request from the map
967         mCustomRequestMap.remove(requestCode);
968     }
969 
findInArray(String[] choiceEntries, String[] choiceValues, String selectedString)970     private String findInArray(String[] choiceEntries, String[] choiceValues,
971             String selectedString) {
972         for (int i = 0; i < choiceValues.length; i++) {
973             if (choiceValues[i].equals(selectedString)) {
974                 return choiceEntries[i];
975             }
976         }
977         return selectedString;
978     }
979 
980     @Override
onPreferenceClick(Preference preference)981     public boolean onPreferenceClick(Preference preference) {
982         if (preference.getKey().startsWith(PKG_PREFIX)) {
983             AppRestrictionsPreference arp = (AppRestrictionsPreference) preference;
984             if (!arp.isImmutable()) {
985                 arp.setChecked(!arp.isChecked());
986                 mSelectedPackages.put(arp.getKey().substring(PKG_PREFIX.length()), arp.isChecked());
987                 updateAllEntries(arp.getKey(), arp.isChecked());
988                 mAppListChanged = true;
989             }
990             return true;
991         }
992         return false;
993     }
994 
995     @Override
onCreateDialog(int dialogId)996     public Dialog onCreateDialog(int dialogId) {
997         if (dialogId == DIALOG_ID_EDIT_USER_INFO) {
998             if (mEditUserInfoDialog != null) {
999                 return mEditUserInfoDialog;
1000             }
1001 
1002             LayoutInflater inflater = getActivity().getLayoutInflater();
1003             View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
1004 
1005             UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier());
1006 
1007             final EditText userNameView = (EditText) content.findViewById(R.id.user_name);
1008             userNameView.setText(info.name);
1009 
1010             final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo);
1011             Drawable drawable = null;
1012             if (mSavedPhoto != null) {
1013                 drawable = CircleFramedDrawable.getInstance(getActivity(), mSavedPhoto);
1014             } else {
1015                 drawable = mUserIconView.getDrawable();
1016                 if (drawable == null) {
1017                     drawable = getCircularUserIcon();
1018                 }
1019             }
1020             userPhotoView.setImageDrawable(drawable);
1021 
1022             mEditUserPhotoController = new EditUserPhotoController(this, userPhotoView,
1023                     mSavedPhoto, drawable);
1024 
1025             mEditUserInfoDialog = new AlertDialog.Builder(getActivity())
1026                 .setTitle(R.string.profile_info_settings_title)
1027                 .setIconAttribute(R.drawable.ic_settings_multiuser)
1028                 .setView(content)
1029                 .setCancelable(true)
1030                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1031                     @Override
1032                     public void onClick(DialogInterface dialog, int which) {
1033                         if (which == DialogInterface.BUTTON_POSITIVE) {
1034                             // Update the name if changed.
1035                             CharSequence userName = userNameView.getText();
1036                             if (!TextUtils.isEmpty(userName)) {
1037                                 CharSequence oldUserName = mUserNameView.getText();
1038                                 if (oldUserName == null
1039                                         || !userName.toString().equals(oldUserName.toString())) {
1040                                     ((TextView) mHeaderView.findViewById(android.R.id.title))
1041                                             .setText(userName.toString());
1042                                     mUserManager.setUserName(mUser.getIdentifier(),
1043                                             userName.toString());
1044                                 }
1045                             }
1046                             // Update the photo if changed.
1047                             Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable();
1048                             Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap();
1049                             if (drawable != null && bitmap != null
1050                                     && !drawable.equals(mUserIconView.getDrawable())) {
1051                                 mUserIconView.setImageDrawable(drawable);
1052                                 new AsyncTask<Void, Void, Void>() {
1053                                     @Override
1054                                     protected Void doInBackground(Void... params) {
1055                                         mUserManager.setUserIcon(mUser.getIdentifier(),
1056                                                 mEditUserPhotoController.getNewUserPhotoBitmap());
1057                                         return null;
1058                                     }
1059                                 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
1060                             }
1061                             removeDialog(DIALOG_ID_EDIT_USER_INFO);
1062                         }
1063                         clearEditUserInfoDialog();
1064                     }
1065                 })
1066                 .setNegativeButton(android.R.string.cancel,  new DialogInterface.OnClickListener() {
1067                     @Override
1068                     public void onClick(DialogInterface dialog, int which) {
1069                         clearEditUserInfoDialog();
1070                     }
1071                  })
1072                 .create();
1073 
1074             // Make sure the IME is up.
1075             mEditUserInfoDialog.getWindow().setSoftInputMode(
1076                     WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
1077 
1078             return mEditUserInfoDialog;
1079         }
1080 
1081         return null;
1082     }
1083 
clearEditUserInfoDialog()1084     private void clearEditUserInfoDialog() {
1085         mEditUserInfoDialog = null;
1086         mSavedPhoto = null;
1087     }
1088 
1089     private static class EditUserPhotoController {
1090         private static final int POPUP_LIST_ITEM_ID_CHOOSE_PHOTO = 1;
1091         private static final int POPUP_LIST_ITEM_ID_TAKE_PHOTO = 2;
1092 
1093         // It seems that this class generates custom request codes and they may
1094         // collide with ours, these values are very unlikely to have a conflict.
1095         private static final int REQUEST_CODE_CHOOSE_PHOTO = Integer.MAX_VALUE;
1096         private static final int REQUEST_CODE_TAKE_PHOTO = Integer.MAX_VALUE - 1;
1097         private static final int REQUEST_CODE_CROP_PHOTO = Integer.MAX_VALUE - 2;
1098 
1099         private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
1100         private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
1101 
1102         private final int mPhotoSize;
1103 
1104         private final Context mContext;
1105         private final Fragment mFragment;
1106         private final ImageView mImageView;
1107 
1108         private final Uri mCropPictureUri;
1109         private final Uri mTakePictureUri;
1110 
1111         private Bitmap mNewUserPhotoBitmap;
1112         private Drawable mNewUserPhotoDrawable;
1113 
EditUserPhotoController(Fragment fragment, ImageView view, Bitmap bitmap, Drawable drawable)1114         public EditUserPhotoController(Fragment fragment, ImageView view,
1115                 Bitmap bitmap, Drawable drawable) {
1116             mContext = view.getContext();
1117             mFragment = fragment;
1118             mImageView = view;
1119             mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME);
1120             mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME);
1121             mPhotoSize = getPhotoSize(mContext);
1122             mImageView.setOnClickListener(new OnClickListener() {
1123                 @Override
1124                 public void onClick(View v) {
1125                     showUpdatePhotoPopup();
1126                 }
1127             });
1128             mNewUserPhotoBitmap = bitmap;
1129             mNewUserPhotoDrawable = drawable;
1130         }
1131 
onActivityResult(int requestCode, int resultCode, final Intent data)1132         public boolean onActivityResult(int requestCode, int resultCode, final Intent data) {
1133             if (resultCode != Activity.RESULT_OK) {
1134                 return false;
1135             }
1136             switch (requestCode) {
1137                 case REQUEST_CODE_CHOOSE_PHOTO:
1138                 case REQUEST_CODE_CROP_PHOTO: {
1139                     new AsyncTask<Void, Void, Bitmap>() {
1140                         @Override
1141                         protected Bitmap doInBackground(Void... params) {
1142                             return BitmapFactory.decodeFile(mCropPictureUri.getPath());
1143                         }
1144                         @Override
1145                         protected void onPostExecute(Bitmap bitmap) {
1146                             mNewUserPhotoBitmap = bitmap;
1147                             mNewUserPhotoDrawable = CircleFramedDrawable
1148                                     .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
1149                             mImageView.setImageDrawable(mNewUserPhotoDrawable);
1150                             // Delete the files - not needed anymore.
1151                             new File(mCropPictureUri.getPath()).delete();
1152                             new File(mTakePictureUri.getPath()).delete();
1153                         }
1154                     }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
1155                 } return true;
1156                 case REQUEST_CODE_TAKE_PHOTO: {
1157                     cropPhoto();
1158                 } break;
1159             }
1160             return false;
1161         }
1162 
getNewUserPhotoBitmap()1163         public Bitmap getNewUserPhotoBitmap() {
1164             return mNewUserPhotoBitmap;
1165         }
1166 
getNewUserPhotoDrawable()1167         public Drawable getNewUserPhotoDrawable() {
1168             return mNewUserPhotoDrawable;
1169         }
1170 
showUpdatePhotoPopup()1171         private void showUpdatePhotoPopup() {
1172             final boolean canTakePhoto = canTakePhoto();
1173             final boolean canChoosePhoto = canChoosePhoto();
1174 
1175             if (!canTakePhoto && !canChoosePhoto) {
1176                 return;
1177             }
1178 
1179             Context context = mImageView.getContext();
1180             final List<AdapterItem> items = new ArrayList<AdapterItem>();
1181 
1182             if (canTakePhoto()) {
1183                 String title = mImageView.getContext().getString( R.string.user_image_take_photo);
1184                 AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_TAKE_PHOTO);
1185                 items.add(item);
1186             }
1187 
1188             if (canChoosePhoto) {
1189                 String title = context.getString(R.string.user_image_choose_photo);
1190                 AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_CHOOSE_PHOTO);
1191                 items.add(item);
1192             }
1193 
1194             final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
1195 
1196             listPopupWindow.setAnchorView(mImageView);
1197             listPopupWindow.setModal(true);
1198             listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
1199 
1200             ListAdapter adapter = new ArrayAdapter<AdapterItem>(context,
1201                     R.layout.edit_user_photo_popup_item, items);
1202             listPopupWindow.setAdapter(adapter);
1203 
1204             final int width = Math.max(mImageView.getWidth(), context.getResources()
1205                     .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
1206             listPopupWindow.setWidth(width);
1207 
1208             listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
1209                 @Override
1210                 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1211                     AdapterItem item = items.get(position);
1212                     switch (item.id) {
1213                         case POPUP_LIST_ITEM_ID_CHOOSE_PHOTO: {
1214                             choosePhoto();
1215                             listPopupWindow.dismiss();
1216                         } break;
1217                         case POPUP_LIST_ITEM_ID_TAKE_PHOTO: {
1218                             takePhoto();
1219                             listPopupWindow.dismiss();
1220                         } break;
1221                     }
1222                 }
1223             });
1224 
1225             listPopupWindow.show();
1226         }
1227 
canTakePhoto()1228         private boolean canTakePhoto() {
1229             return mImageView.getContext().getPackageManager().queryIntentActivities(
1230                     new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
1231                     PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
1232         }
1233 
canChoosePhoto()1234         private boolean canChoosePhoto() {
1235             Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
1236             intent.setType("image/*");
1237             return mImageView.getContext().getPackageManager().queryIntentActivities(
1238                     intent, 0).size() > 0;
1239         }
1240 
takePhoto()1241         private void takePhoto() {
1242             Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
1243             intent.putExtra(MediaStore.EXTRA_OUTPUT, mTakePictureUri);
1244             mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
1245         }
1246 
choosePhoto()1247         private void choosePhoto() {
1248             Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
1249             intent.setType("image/*");
1250             intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPictureUri);
1251             appendCropExtras(intent);
1252             mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
1253         }
1254 
cropPhoto()1255         private void cropPhoto() {
1256             Intent intent = new Intent("com.android.camera.action.CROP");
1257             intent.setDataAndType(mTakePictureUri, "image/*");
1258             intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPictureUri);
1259             appendCropExtras(intent);
1260             mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
1261         }
1262 
appendCropExtras(Intent intent)1263         private void appendCropExtras(Intent intent) {
1264             intent.putExtra("crop", "true");
1265             intent.putExtra("scale", true);
1266             intent.putExtra("scaleUpIfNeeded", true);
1267             intent.putExtra("aspectX", 1);
1268             intent.putExtra("aspectY", 1);
1269             intent.putExtra("outputX", mPhotoSize);
1270             intent.putExtra("outputY", mPhotoSize);
1271         }
1272 
getPhotoSize(Context context)1273         private static int getPhotoSize(Context context) {
1274             Cursor cursor = context.getContentResolver().query(
1275                     DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
1276                     new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
1277             try {
1278                 cursor.moveToFirst();
1279                 return cursor.getInt(0);
1280             } finally {
1281                 cursor.close();
1282             }
1283         }
1284 
createTempImageUri(Context context, String fileName)1285         private static Uri createTempImageUri(Context context, String fileName) {
1286             File folder = context.getExternalCacheDir();
1287             folder.mkdirs();
1288             File fullPath = new File(folder, fileName);
1289             fullPath.delete();
1290             return Uri.fromFile(fullPath.getAbsoluteFile());
1291         }
1292 
1293         private static final class AdapterItem {
1294             final String title;
1295             final int id;
1296 
AdapterItem(String title, int id)1297             public AdapterItem(String title, int id) {
1298                 this.title = title;
1299                 this.id = id;
1300             }
1301 
1302             @Override
toString()1303             public String toString() {
1304                 return title;
1305             }
1306         }
1307     }
1308 }
1309