Fix profile picture picking for restricted profiles

Due to the new file picker UI and incorrect assumptions of access to sd card,
have to use content provider Uris instead of file paths.

Also makes the cropping robust in the event of not having a cropping intent
on the platform.

This should make the code play well with third party image
and camera apps as well.

Bug: 10666584

Change-Id: Ie8d834fe7aac96bc14829a7be084512a15ef4001
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 493d7f4..49e8977 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1691,5 +1691,14 @@
             </intent-filter>
         </receiver>
 
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="com.android.settings.files"
+            android:grantUriPermissions="true"
+            android:exported="false">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
     </application>
 </manifest>
diff --git a/res/xml/file_paths.xml b/res/xml/file_paths.xml
new file mode 100644
index 0000000..294c0cb
--- /dev/null
+++ b/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Offer access to files under Context.getCacheDir() -->
+    <cache-path name="my_cache" />
+</paths>
diff --git a/src/com/android/settings/users/RestrictedProfileSettings.java b/src/com/android/settings/users/RestrictedProfileSettings.java
index 99e55ab..73b49bf 100644
--- a/src/com/android/settings/users/RestrictedProfileSettings.java
+++ b/src/com/android/settings/users/RestrictedProfileSettings.java
@@ -20,6 +20,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.Fragment;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -27,14 +28,20 @@
 import android.content.pm.UserInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.MediaStore;
 import android.provider.ContactsContract.DisplayPhoto;
+import android.support.v4.content.FileProvider;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -51,6 +58,8 @@
 import com.android.settings.R;
 
 import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -58,6 +67,7 @@
 
     private static final String KEY_SAVED_PHOTO = "pending_photo";
     private static final int DIALOG_ID_EDIT_USER_INFO = 1;
+    public static final String FILE_PROVIDER_AUTHORITY = "com.android.settings.files";
 
     private View mHeaderView;
     private ImageView mUserIconView;
@@ -269,33 +279,20 @@
             mNewUserPhotoDrawable = drawable;
         }
 
-        public boolean onActivityResult(int requestCode, int resultCode, final Intent data) {
+        public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
             if (resultCode != Activity.RESULT_OK) {
                 return false;
             }
+            final Uri pictureUri = data != null && data.getData() != null
+                    ? data.getData() : mTakePictureUri;
             switch (requestCode) {
+                case REQUEST_CODE_CROP_PHOTO:
+                    onPhotoCropped(pictureUri, true);
+                    return true;
+                case REQUEST_CODE_TAKE_PHOTO:
                 case REQUEST_CODE_CHOOSE_PHOTO:
-                case REQUEST_CODE_CROP_PHOTO: {
-                    new AsyncTask<Void, Void, Bitmap>() {
-                        @Override
-                        protected Bitmap doInBackground(Void... params) {
-                            return BitmapFactory.decodeFile(mCropPictureUri.getPath());
-                        }
-                        @Override
-                        protected void onPostExecute(Bitmap bitmap) {
-                            mNewUserPhotoBitmap = bitmap;
-                            mNewUserPhotoDrawable = CircleFramedDrawable
-                                    .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
-                            mImageView.setImageDrawable(mNewUserPhotoDrawable);
-                            // Delete the files - not needed anymore.
-                            new File(mCropPictureUri.getPath()).delete();
-                            new File(mTakePictureUri.getPath()).delete();
-                        }
-                    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-                } return true;
-                case REQUEST_CODE_TAKE_PHOTO: {
-                    cropPhoto();
-                } break;
+                    cropPhoto(pictureUri);
+                    return true;
             }
             return false;
         }
@@ -380,24 +377,35 @@
 
         private void takePhoto() {
             Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-            intent.putExtra(MediaStore.EXTRA_OUTPUT, mTakePictureUri);
+            appendOutputExtra(intent, mTakePictureUri);
             mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
         }
 
         private void choosePhoto() {
             Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
             intent.setType("image/*");
-            intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPictureUri);
-            appendCropExtras(intent);
+            appendOutputExtra(intent, mTakePictureUri);
             mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
         }
 
-        private void cropPhoto() {
+        private void cropPhoto(Uri pictureUri) {
+            // TODO: Use a public intent, when there is one.
             Intent intent = new Intent("com.android.camera.action.CROP");
-            intent.setDataAndType(mTakePictureUri, "image/*");
-            intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPictureUri);
+            intent.setDataAndType(pictureUri, "image/*");
+            appendOutputExtra(intent, mCropPictureUri);
             appendCropExtras(intent);
-            mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+            if (intent.resolveActivity(mContext.getPackageManager()) != null) {
+                mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+            } else {
+                onPhotoCropped(pictureUri, false);
+            }
+        }
+
+        private void appendOutputExtra(Intent intent, Uri pictureUri) {
+            intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
         }
 
         private void appendCropExtras(Intent intent) {
@@ -410,6 +418,63 @@
             intent.putExtra("outputY", mPhotoSize);
         }
 
+        private void onPhotoCropped(final Uri data, final boolean cropped) {
+            new AsyncTask<Void, Void, Bitmap>() {
+                @Override
+                protected Bitmap doInBackground(Void... params) {
+                    if (cropped) {
+                        try {
+                            InputStream imageStream = mContext.getContentResolver()
+                                    .openInputStream(data);
+                            return BitmapFactory.decodeStream(imageStream);
+                        } catch (FileNotFoundException fe) {
+                            return null;
+                        }
+                    } else {
+                        // Scale and crop to a square aspect ratio
+                        Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+                                Config.ARGB_8888);
+                        Canvas canvas = new Canvas(croppedImage);
+                        Bitmap fullImage = null;
+                        try {
+                            InputStream imageStream = mContext.getContentResolver()
+                                    .openInputStream(data);
+                            fullImage = BitmapFactory.decodeStream(imageStream);
+                        } catch (FileNotFoundException fe) {
+                            return null;
+                        }
+                        if (fullImage != null) {
+                            final int squareSize = Math.min(fullImage.getWidth(),
+                                    fullImage.getHeight());
+                            final int left = (fullImage.getWidth() - squareSize) / 2;
+                            final int top = (fullImage.getHeight() - squareSize) / 2;
+                            Rect rectSource = new Rect(left, top,
+                                    left + squareSize, top + squareSize);
+                            Rect rectDest = new Rect(0, 0, mPhotoSize, mPhotoSize);
+                            Paint paint = new Paint();
+                            canvas.drawBitmap(fullImage, rectSource, rectDest, paint);
+                            return croppedImage;
+                        } else {
+                            // Bah! Got nothin.
+                            return null;
+                        }
+                    }
+                }
+
+                @Override
+                protected void onPostExecute(Bitmap bitmap) {
+                    if (bitmap != null) {
+                        mNewUserPhotoBitmap = bitmap;
+                        mNewUserPhotoDrawable = CircleFramedDrawable
+                                .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
+                        mImageView.setImageDrawable(mNewUserPhotoDrawable);
+                    }
+                    new File(mContext.getCacheDir(), TAKE_PICTURE_FILE_NAME).delete();
+                    new File(mContext.getCacheDir(), CROP_PICTURE_FILE_NAME).delete();
+                }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+        }
+
         private static int getPhotoSize(Context context) {
             Cursor cursor = context.getContentResolver().query(
                     DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
@@ -423,11 +488,13 @@
         }
 
         private static Uri createTempImageUri(Context context, String fileName) {
-            File folder = context.getExternalCacheDir();
+            final File folder = context.getCacheDir();
             folder.mkdirs();
-            File fullPath = new File(folder, fileName);
+            final File fullPath = new File(folder, fileName);
             fullPath.delete();
-            return Uri.fromFile(fullPath.getAbsoluteFile());
+            final Uri fileUri =
+                    FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, fullPath);
+            return fileUri;
         }
 
         private static final class AdapterItem {