Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ca137e05 authored by Yorke Lee's avatar Yorke Lee Committed by Steve Kondik
Browse files

Make contacts photo pickers compatible with new documents UI

The old contacts photo picker code was using unguaranteed behavior
(that Intent.GET_CONTENT would support MediaStore.EXTRA_OUTPUT) and this
caused it to not work anymore with the new document picker.

This CL changes all usages of files to instead use URIs.

Also, a FileProvider has been added to Contacts, to allow us to pass in
URI pointing to our private cache in intent.setClipData with
Intent.FLAG_GRANT_WRITE_URI_PERMISSION and Intent.FLAG_GRANT_READ_URI_PERMISSION
so we no longer have to reply on the MediaStore.EXTRA_OUTPUT being parsed
and supported. The use of the FileProvider also prevents unauthorized access
to temporary files during the caching process.

Bug: 10745342

Change-Id: Iaee3d7d112dd124a2f5596c4b9704ea75d3b3419
parent 78bba93d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -539,6 +539,16 @@
            </intent-filter>
        </service>

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.android.contacts.files"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

        <meta-data android:name="android.nfc.disable_beam_default" android:value="true" />
    </application>
</manifest>

res/xml/file_paths.xml

0 → 100644
+20 −0
Original line number Diff line number Diff line
<?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>
+9 −32
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@ import com.android.contacts.model.RawContactDeltaList;
import com.android.contacts.model.RawContactModifier;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.util.CallerInfoCacheUtils;
import com.android.contacts.util.ContactPhotoUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

@@ -60,6 +62,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -293,9 +296,9 @@ public class ContactSaveService extends IntentService {
    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
            String saveModeExtraKey, int saveMode, boolean isProfile,
            Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId,
            String updatedPhotoPath) {
            Uri updatedPhotoPath) {
        Bundle bundle = new Bundle();
        bundle.putString(String.valueOf(rawContactId), updatedPhotoPath);
        bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath);
        return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile,
                callbackActivity, callbackAction, bundle);
    }
@@ -448,7 +451,7 @@ public class ContactSaveService extends IntentService {
        // the ContactProvider already knows about newly-created contacts.
        if (updatedPhotos != null) {
            for (String key : updatedPhotos.keySet()) {
                String photoFilePath = updatedPhotos.getString(key);
                Uri photoUri = updatedPhotos.getParcelable(key);
                long rawContactId = Long.parseLong(key);

                // If the raw-contact ID is negative, we are saving a new raw-contact;
@@ -461,8 +464,7 @@ public class ContactSaveService extends IntentService {
                    }
                }

                File photoFile = new File(photoFilePath);
                if (!saveUpdatedPhoto(rawContactId, photoFile)) succeeded = false;
                if (!saveUpdatedPhoto(rawContactId, photoUri)) succeeded = false;
            }
        }

@@ -483,37 +485,12 @@ public class ContactSaveService extends IntentService {
     * Save updated photo for the specified raw-contact.
     * @return true for success, false for failure
     */
    private boolean saveUpdatedPhoto(long rawContactId, File photoFile) {
    private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri) {
        final Uri outputUri = Uri.withAppendedPath(
                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
                RawContacts.DisplayPhoto.CONTENT_DIRECTORY);

        try {
            final FileOutputStream outputStream = getContentResolver()
                    .openAssetFileDescriptor(outputUri, "rw").createOutputStream();
            try {
                final FileInputStream inputStream = new FileInputStream(photoFile);
                try {
                    final byte[] buffer = new byte[16 * 1024];
                    int length;
                    int totalLength = 0;
                    while ((length = inputStream.read(buffer)) > 0) {
                        outputStream.write(buffer, 0, length);
                        totalLength += length;
                    }
                    Log.v(TAG, "Wrote " + totalLength + " bytes for photo " + photoFile.toString());
                } finally {
                    inputStream.close();
                }
            } finally {
                outputStream.close();
                photoFile.delete();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to write photo: " + photoFile.toString() + " because: " + e);
            return false;
        }
        return true;
        return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true);
    }

    /**
+21 −12
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.util.ContactPhotoUtils;

import java.io.File;
import java.io.FileNotFoundException;

/**
 * Provides an external interface for other applications to attach images
@@ -59,7 +60,6 @@ public class AttachPhotoActivity extends ContactsActivity {
    private static final String KEY_CONTACT_URI = "contact_uri";
    private static final String KEY_TEMP_PHOTO_URI = "temp_photo_uri";

    private File mTempPhotoFile;
    private Uri mTempPhotoUri;

    private ContentResolver mContentResolver;
@@ -76,13 +76,9 @@ public class AttachPhotoActivity extends ContactsActivity {
        if (icicle != null) {
            final String uri = icicle.getString(KEY_CONTACT_URI);
            mContactUri = (uri == null) ? null : Uri.parse(uri);

            mTempPhotoUri = Uri.parse(icicle.getString(KEY_TEMP_PHOTO_URI));
            mTempPhotoFile = new File(mTempPhotoUri.getPath());
        } else {
            mTempPhotoFile = ContactPhotoUtils.generateTempPhotoFile(this);
            mTempPhotoUri = Uri.fromFile(mTempPhotoFile);

            mTempPhotoUri = ContactPhotoUtils.generateTempImageUri(this);
            Intent intent = new Intent(Intent.ACTION_PICK);
            intent.setType(Contacts.CONTENT_TYPE);
            startActivityForResult(intent, REQUEST_PICK_CONTACT);
@@ -104,9 +100,13 @@ public class AttachPhotoActivity extends ContactsActivity {
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mContactUri != null) outState.putString(KEY_CONTACT_URI, mContactUri.toString());
        if (mContactUri != null) {
            outState.putString(KEY_CONTACT_URI, mContactUri.toString());
        }
        if (mTempPhotoUri != null) {
            outState.putString(KEY_TEMP_PHOTO_URI, mTempPhotoUri.toString());
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent result) {
@@ -123,7 +123,9 @@ public class AttachPhotoActivity extends ContactsActivity {
            if (myIntent.getStringExtra("mimeType") != null) {
                intent.setDataAndType(myIntent.getData(), myIntent.getStringExtra("mimeType"));
            }
            ContactPhotoUtils.addGalleryIntentExtras(intent, mTempPhotoUri, mPhotoDim);

            ContactPhotoUtils.addPhotoPickerExtras(intent, mTempPhotoUri);
            ContactPhotoUtils.addCropExtras(intent, mPhotoDim);

            startActivityForResult(intent, REQUEST_CROP_PHOTO);

@@ -183,14 +185,20 @@ public class AttachPhotoActivity extends ContactsActivity {

        // Create a scaled, compressed bitmap to add to the entity-delta list.
        final int size = ContactsUtils.getThumbnailSize(this);
        final Bitmap bitmap = BitmapFactory.decodeFile(mTempPhotoFile.getAbsolutePath());
        Bitmap bitmap;
        try {
            bitmap = ContactPhotoUtils.getBitmapFromUri(this, mTempPhotoUri);
        } catch (FileNotFoundException e) {
            Log.w(TAG, "Could not find bitmap");
            return;
        }

        final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, size, size, false);
        final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled);
        if (compressed == null) {
            Log.w(TAG, "could not create scaled and compressed Bitmap");
            return;
        }

        // Add compressed bitmap to entity-delta... this allows us to save to
        // a new contact; otherwise the entity-delta-list would be empty, and
        // the ContactSaveService would not create the new contact, and the
@@ -213,7 +221,8 @@ public class AttachPhotoActivity extends ContactsActivity {
                contact.isUserProfile(),
                null, null,
                raw.getRawContactId(),
                mTempPhotoFile.getAbsolutePath());
                mTempPhotoUri
                );
        startService(intent);
        finish();
    }
+17 −14
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.content.FileProvider;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.FrameLayout.LayoutParams;
@@ -42,6 +43,9 @@ import com.android.contacts.model.RawContactDeltaList;
import com.android.contacts.util.ContactPhotoUtils;
import com.android.contacts.util.SchedulingUtils;

import java.io.File;
import java.io.FileNotFoundException;


/**
 * Popup activity for choosing a contact photo within the Contacts app.
@@ -59,8 +63,8 @@ public class PhotoSelectionActivity extends Activity {
    /** Number of ms for the animation to hide the backdrop on finish. */
    private static final int BACKDROP_FADEOUT_DURATION = 100;

    /** Key used to persist photo-filename (NOT full file-path). */
    private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile";
    /** Key used to persist photo uri. */
    private static final String KEY_CURRENT_PHOTO_URI = "currentphotouri";

    /** Key used to persist whether a sub-activity is currently in progress. */
    private static final String KEY_SUB_ACTIVITY_IN_PROGRESS = "subinprogress";
@@ -151,16 +155,16 @@ public class PhotoSelectionActivity extends Activity {
    private PendingPhotoResult mPendingPhotoResult;

    /**
     * The photo file being interacted with, if any.  Saved/restored between activity instances.
     * The photo uri being interacted with, if any.  Saved/restored between activity instances.
     */
    private String mCurrentPhotoFile;
    private Uri mCurrentPhotoUri;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.photoselection_activity);
        if (savedInstanceState != null) {
            mCurrentPhotoFile = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE);
            mCurrentPhotoUri = savedInstanceState.getParcelable(KEY_CURRENT_PHOTO_URI);
            mSubActivityInProgress = savedInstanceState.getBoolean(KEY_SUB_ACTIVITY_IN_PROGRESS);
        }

@@ -456,7 +460,7 @@ public class PhotoSelectionActivity extends Activity {
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile);
        outState.putParcelable(KEY_CURRENT_PHOTO_URI, mCurrentPhotoUri);
        outState.putBoolean(KEY_SUB_ACTIVITY_IN_PROGRESS, mSubActivityInProgress);
    }

@@ -527,28 +531,27 @@ public class PhotoSelectionActivity extends Activity {
        }

        @Override
        public void startPhotoActivity(Intent intent, int requestCode, String photoFile) {
        public void startPhotoActivity(Intent intent, int requestCode, Uri photoUri) {
            mSubActivityInProgress = true;
            mCurrentPhotoFile = photoFile;
            mCurrentPhotoUri = photoUri;
            PhotoSelectionActivity.this.startActivityForResult(intent, requestCode);
        }

        private final class PhotoListener extends PhotoActionListener {
            @Override
            public void onPhotoSelected(Bitmap bitmap) {
            public void onPhotoSelected(Uri uri) {
                RawContactDeltaList delta = getDeltaForAttachingPhotoToContact();
                long rawContactId = getWritableEntityId();
                final String croppedPath = ContactPhotoUtils.pathForCroppedPhoto(
                        PhotoSelectionActivity.this, mCurrentPhotoFile);

                Intent intent = ContactSaveService.createSaveContactIntent(
                        mContext, delta, "", 0, mIsProfile, null, null, rawContactId, croppedPath);
                        mContext, delta, "", 0, mIsProfile, null, null, rawContactId, uri);
                startService(intent);
                finish();
            }

            @Override
            public String getCurrentPhotoFile() {
                return mCurrentPhotoFile;
            public Uri getCurrentPhotoUri() {
                return mCurrentPhotoUri;
            }

            @Override
Loading