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.Fragment;
     21 import android.content.ClipData;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.database.Cursor;
     26 import android.graphics.Bitmap;
     27 import android.graphics.BitmapFactory;
     28 import android.graphics.Canvas;
     29 import android.graphics.Paint;
     30 import android.graphics.Rect;
     31 import android.graphics.Bitmap.Config;
     32 import android.graphics.drawable.Drawable;
     33 import android.net.Uri;
     34 import android.os.AsyncTask;
     35 import android.provider.MediaStore;
     36 import android.provider.ContactsContract.DisplayPhoto;
     37 import android.support.v4.content.FileProvider;
     38 import android.util.Log;
     39 import android.view.View;
     40 import android.view.View.OnClickListener;
     41 import android.widget.AdapterView;
     42 import android.widget.ArrayAdapter;
     43 import android.widget.ImageView;
     44 import android.widget.ListAdapter;
     45 import android.widget.ListPopupWindow;
     46 
     47 import com.android.settings.R;
     48 import com.android.settings.drawable.CircleFramedDrawable;
     49 
     50 import java.io.File;
     51 import java.io.FileNotFoundException;
     52 import java.io.IOException;
     53 import java.io.InputStream;
     54 import java.util.ArrayList;
     55 import java.util.List;
     56 
     57 public class EditUserPhotoController {
     58     private static final String TAG = "EditUserPhotoController";
     59 
     60     private static final int POPUP_LIST_ITEM_ID_CHOOSE_PHOTO = 1;
     61     private static final int POPUP_LIST_ITEM_ID_TAKE_PHOTO = 2;
     62 
     63     // It seems that this class generates custom request codes and they may
     64     // collide with ours, these values are very unlikely to have a conflict.
     65     private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
     66     private static final int REQUEST_CODE_TAKE_PHOTO   = 1002;
     67     private static final int REQUEST_CODE_CROP_PHOTO   = 1003;
     68 
     69     private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
     70     private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
     71 
     72     private final int mPhotoSize;
     73 
     74     private final Context mContext;
     75     private final Fragment mFragment;
     76     private final ImageView mImageView;
     77 
     78     private final Uri mCropPictureUri;
     79     private final Uri mTakePictureUri;
     80 
     81     private Bitmap mNewUserPhotoBitmap;
     82     private Drawable mNewUserPhotoDrawable;
     83 
     84     public EditUserPhotoController(Fragment fragment, ImageView view,
     85             Bitmap bitmap, Drawable drawable, boolean waiting) {
     86         mContext = view.getContext();
     87         mFragment = fragment;
     88         mImageView = view;
     89         mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME, !waiting);
     90         mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME, !waiting);
     91         mPhotoSize = getPhotoSize(mContext);
     92         mImageView.setOnClickListener(new OnClickListener() {
     93             @Override
     94             public void onClick(View v) {
     95                 showUpdatePhotoPopup();
     96             }
     97         });
     98         mNewUserPhotoBitmap = bitmap;
     99         mNewUserPhotoDrawable = drawable;
    100     }
    101 
    102     public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
    103         if (resultCode != Activity.RESULT_OK) {
    104             return false;
    105         }
    106         final Uri pictureUri = data != null && data.getData() != null
    107                 ? data.getData() : mTakePictureUri;
    108         switch (requestCode) {
    109             case REQUEST_CODE_CROP_PHOTO:
    110                 onPhotoCropped(pictureUri, true);
    111                 return true;
    112             case REQUEST_CODE_TAKE_PHOTO:
    113             case REQUEST_CODE_CHOOSE_PHOTO:
    114                 cropPhoto(pictureUri);
    115                 return true;
    116         }
    117         return false;
    118     }
    119 
    120     public Bitmap getNewUserPhotoBitmap() {
    121         return mNewUserPhotoBitmap;
    122     }
    123 
    124     public Drawable getNewUserPhotoDrawable() {
    125         return mNewUserPhotoDrawable;
    126     }
    127 
    128     private void showUpdatePhotoPopup() {
    129         final boolean canTakePhoto = canTakePhoto();
    130         final boolean canChoosePhoto = canChoosePhoto();
    131 
    132         if (!canTakePhoto && !canChoosePhoto) {
    133             return;
    134         }
    135 
    136         Context context = mImageView.getContext();
    137         final List<EditUserPhotoController.AdapterItem> items = new ArrayList<EditUserPhotoController.AdapterItem>();
    138 
    139         if (canTakePhoto()) {
    140             String title = mImageView.getContext().getString( R.string.user_image_take_photo);
    141             EditUserPhotoController.AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_TAKE_PHOTO);
    142             items.add(item);
    143         }
    144 
    145         if (canChoosePhoto) {
    146             String title = context.getString(R.string.user_image_choose_photo);
    147             EditUserPhotoController.AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_CHOOSE_PHOTO);
    148             items.add(item);
    149         }
    150 
    151         final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
    152 
    153         listPopupWindow.setAnchorView(mImageView);
    154         listPopupWindow.setModal(true);
    155         listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
    156 
    157         ListAdapter adapter = new ArrayAdapter<EditUserPhotoController.AdapterItem>(context,
    158                 R.layout.edit_user_photo_popup_item, items);
    159         listPopupWindow.setAdapter(adapter);
    160 
    161         final int width = Math.max(mImageView.getWidth(), context.getResources()
    162                 .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
    163         listPopupWindow.setWidth(width);
    164 
    165         listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    166             @Override
    167             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    168                 EditUserPhotoController.AdapterItem item = items.get(position);
    169                 switch (item.id) {
    170                     case POPUP_LIST_ITEM_ID_CHOOSE_PHOTO: {
    171                         choosePhoto();
    172                         listPopupWindow.dismiss();
    173                     } break;
    174                     case POPUP_LIST_ITEM_ID_TAKE_PHOTO: {
    175                         takePhoto();
    176                         listPopupWindow.dismiss();
    177                     } break;
    178                 }
    179             }
    180         });
    181 
    182         listPopupWindow.show();
    183     }
    184 
    185     private boolean canTakePhoto() {
    186         return mImageView.getContext().getPackageManager().queryIntentActivities(
    187                 new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
    188                 PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
    189     }
    190 
    191     private boolean canChoosePhoto() {
    192         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    193         intent.setType("image/*");
    194         return mImageView.getContext().getPackageManager().queryIntentActivities(
    195                 intent, 0).size() > 0;
    196     }
    197 
    198     private void takePhoto() {
    199         Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    200         appendOutputExtra(intent, mTakePictureUri);
    201         mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
    202     }
    203 
    204     private void choosePhoto() {
    205         Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
    206         intent.setType("image/*");
    207         appendOutputExtra(intent, mTakePictureUri);
    208         mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
    209     }
    210 
    211     private void cropPhoto(Uri pictureUri) {
    212         // TODO: Use a public intent, when there is one.
    213         Intent intent = new Intent("com.android.camera.action.CROP");
    214         intent.setDataAndType(pictureUri, "image/*");
    215         appendOutputExtra(intent, mCropPictureUri);
    216         appendCropExtras(intent);
    217         if (intent.resolveActivity(mContext.getPackageManager()) != null) {
    218             mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
    219         } else {
    220             onPhotoCropped(pictureUri, false);
    221         }
    222     }
    223 
    224     private void appendOutputExtra(Intent intent, Uri pictureUri) {
    225         intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
    226         intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
    227                 | Intent.FLAG_GRANT_READ_URI_PERMISSION);
    228         intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
    229     }
    230 
    231     private void appendCropExtras(Intent intent) {
    232         intent.putExtra("crop", "true");
    233         intent.putExtra("scale", true);
    234         intent.putExtra("scaleUpIfNeeded", true);
    235         intent.putExtra("aspectX", 1);
    236         intent.putExtra("aspectY", 1);
    237         intent.putExtra("outputX", mPhotoSize);
    238         intent.putExtra("outputY", mPhotoSize);
    239     }
    240 
    241     private void onPhotoCropped(final Uri data, final boolean cropped) {
    242         new AsyncTask<Void, Void, Bitmap>() {
    243             @Override
    244             protected Bitmap doInBackground(Void... params) {
    245                 if (cropped) {
    246                     InputStream imageStream = null;
    247                     try {
    248                         imageStream = mContext.getContentResolver()
    249                                 .openInputStream(data);
    250                         return BitmapFactory.decodeStream(imageStream);
    251                     } catch (FileNotFoundException fe) {
    252                         Log.w(TAG, "Cannot find image file", fe);
    253                         return null;
    254                     } finally {
    255                         if (imageStream != null) {
    256                             try {
    257                                 imageStream.close();
    258                             } catch (IOException ioe) {
    259                                 Log.w(TAG, "Cannot close image stream", ioe);
    260                             }
    261                         }
    262                     }
    263                 } else {
    264                     // Scale and crop to a square aspect ratio
    265                     Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
    266                             Config.ARGB_8888);
    267                     Canvas canvas = new Canvas(croppedImage);
    268                     Bitmap fullImage = null;
    269                     try {
    270                         InputStream imageStream = mContext.getContentResolver()
    271                                 .openInputStream(data);
    272                         fullImage = BitmapFactory.decodeStream(imageStream);
    273                     } catch (FileNotFoundException fe) {
    274                         return null;
    275                     }
    276                     if (fullImage != null) {
    277                         final int squareSize = Math.min(fullImage.getWidth(),
    278                                 fullImage.getHeight());
    279                         final int left = (fullImage.getWidth() - squareSize) / 2;
    280                         final int top = (fullImage.getHeight() - squareSize) / 2;
    281                         Rect rectSource = new Rect(left, top,
    282                                 left + squareSize, top + squareSize);
    283                         Rect rectDest = new Rect(0, 0, mPhotoSize, mPhotoSize);
    284                         Paint paint = new Paint();
    285                         canvas.drawBitmap(fullImage, rectSource, rectDest, paint);
    286                         return croppedImage;
    287                     } else {
    288                         // Bah! Got nothin.
    289                         return null;
    290                     }
    291                 }
    292             }
    293 
    294             @Override
    295             protected void onPostExecute(Bitmap bitmap) {
    296                 if (bitmap != null) {
    297                     mNewUserPhotoBitmap = bitmap;
    298                     mNewUserPhotoDrawable = CircleFramedDrawable
    299                             .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
    300                     mImageView.setImageDrawable(mNewUserPhotoDrawable);
    301                 }
    302                 new File(mContext.getCacheDir(), TAKE_PICTURE_FILE_NAME).delete();
    303                 new File(mContext.getCacheDir(), CROP_PICTURE_FILE_NAME).delete();
    304             }
    305         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    306     }
    307 
    308     private static int getPhotoSize(Context context) {
    309         Cursor cursor = context.getContentResolver().query(
    310                 DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
    311                 new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
    312         try {
    313             cursor.moveToFirst();
    314             return cursor.getInt(0);
    315         } finally {
    316             cursor.close();
    317         }
    318     }
    319 
    320     private Uri createTempImageUri(Context context, String fileName, boolean purge) {
    321         final File folder = context.getCacheDir();
    322         folder.mkdirs();
    323         final File fullPath = new File(folder, fileName);
    324         if (purge) {
    325             fullPath.delete();
    326         }
    327         final Uri fileUri =
    328                 FileProvider.getUriForFile(context, RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY, fullPath);
    329         return fileUri;
    330     }
    331 
    332     private static final class AdapterItem {
    333         final String title;
    334         final int id;
    335 
    336         public AdapterItem(String title, int id) {
    337             this.title = title;
    338             this.id = id;
    339         }
    340 
    341         @Override
    342         public String toString() {
    343             return title;
    344         }
    345     }
    346 }