Home | History | Annotate | Download | only in users
      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
    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             } 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 
    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
    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 
    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 
    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 
    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
    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
    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 
    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 
    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 
    815         RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) {
    816             super();
    817             this.packageName = packageName;
    818             this.preference = preference;
    819         }
    820 
    821         @Override
    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 
    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      */
    929     private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) {
    930         mCustomRequestCode++;
    931         mCustomRequestMap.put(mCustomRequestCode, preference);
    932         return mCustomRequestCode;
    933     }
    934 
    935     @Override
    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 
    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
    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
    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 
   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 
   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 
   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 
   1163         public Bitmap getNewUserPhotoBitmap() {
   1164             return mNewUserPhotoBitmap;
   1165         }
   1166 
   1167         public Drawable getNewUserPhotoDrawable() {
   1168             return mNewUserPhotoDrawable;
   1169         }
   1170 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   1297             public AdapterItem(String title, int id) {
   1298                 this.title = title;
   1299                 this.id = id;
   1300             }
   1301 
   1302             @Override
   1303             public String toString() {
   1304                 return title;
   1305             }
   1306         }
   1307     }
   1308 }
   1309