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

Commit ebc17929 authored by Josh Gargus's avatar Josh Gargus
Browse files

Don't stash cropped photos in /sdcard/DCIM.

Every time we choose a new contact photo (either an existing one from
the gallery or a new one from the camera), we write the cropped image
/sdcard/DCIM.  As a result, the cropped image (eventually) appears in
the gallery, is uploaded to G+, etc.

The new behavior is to write the cropped photo to a temp directory,
and to clear it when we're done with it.

Revert some of the changes from Ib7037a66; we still need to persist
properties in onSaveInstanceState() in case ContactEditorFragment is
killed while the user interacts with the camera/gallery.

Bug: 6423139
Change-Id: Icf8cb5b4824e3d8757ff483ef128527ac9132e72
parent 05cbcc93
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -506,6 +506,7 @@ public class ContactSaveService extends IntentService {
                }
            } finally {
                outputStream.close();
                photoFile.delete();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to write photo: " + photoFile.toString() + " because: " + e);
+1 −1
Original line number Diff line number Diff line
@@ -80,7 +80,7 @@ public class AttachPhotoActivity extends ContactsActivity {
            mTempPhotoUri = Uri.parse(icicle.getString(KEY_TEMP_PHOTO_URI));
            mTempPhotoFile = new File(mTempPhotoUri.getPath());
        } else {
            mTempPhotoFile = ContactPhotoUtils.generateTempPhotoFile();
            mTempPhotoFile = ContactPhotoUtils.generateTempPhotoFile(this);
            mTempPhotoUri = Uri.fromFile(mTempPhotoFile);

            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+58 −18
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.contacts.R;
import com.android.contacts.detail.PhotoSelectionHandler;
import com.android.contacts.editor.PhotoActionPopup;
import com.android.contacts.model.EntityDeltaList;
import com.android.contacts.util.ContactPhotoUtils;
import com.android.contacts.util.SchedulingUtils;

import android.animation.Animator;
@@ -42,7 +43,6 @@ import android.view.ViewGroup.MarginLayoutParams;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;

import java.io.File;

/**
 * Popup activity for choosing a contact photo within the Contacts app.
@@ -60,6 +60,12 @@ 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 whether a sub-activity is currently in progress. */
    private static final String KEY_SUB_ACTIVITY_IN_PROGRESS = "subinprogress";

    /** Intent extra to get the photo URI. */
    public static final String PHOTO_URI = "photo_uri";

@@ -131,15 +137,24 @@ public class PhotoSelectionActivity extends Activity {

    private boolean mCloseActivityWhenCameBackFromSubActivity;

    /**
     * A photo result received by the activity, persisted across activity lifecycle.
     */
    private PendingPhotoResult mPendingPhotoResult;

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

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

        // Pull data out of the intent.
        final Intent intent = getIntent();
@@ -394,12 +409,20 @@ public class PhotoSelectionActivity extends Activity {
                BACKDROP_FADEOUT_DURATION).start();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile);
        outState.putBoolean(KEY_SUB_ACTIVITY_IN_PROGRESS, mSubActivityInProgress);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (mPhotoHandler != null) {
            mSubActivityInProgress = false;
            if (mPhotoHandler.handlePhotoActivityResult(requestCode, resultCode, data)) {
                // Result was handled.  We'll get a callback later.
                // Clear out any pending photo result.
                mPendingPhotoResult = null;
            } else {
                // User cancelled the sub-activity and returning to the photo selection activity.
                if (mCloseActivityWhenCameBackFromSubActivity) {
@@ -410,8 +433,8 @@ public class PhotoSelectionActivity extends Activity {
                }
            }
        } else {
            // The result comes back before we prepare the handler?  This activity won't get
            // re-created for orientation changes, so this shouldn't happen.
            // Create a pending photo result to be handled when the photo handler is created.
            mPendingPhotoResult = new PendingPhotoResult(requestCode, resultCode, data);
        }
    }

@@ -428,6 +451,11 @@ public class PhotoSelectionActivity extends Activity {

        mPhotoHandler = new PhotoHandler(this, mPhotoView, mode, mState);

        if (mPendingPhotoResult != null) {
            mPhotoHandler.handlePhotoActivityResult(mPendingPhotoResult.mRequestCode,
                    mPendingPhotoResult.mResultCode, mPendingPhotoResult.mData);
            mPendingPhotoResult = null;
        } else {
            // Setting the photo in displayPhoto() resulted in a relayout
            // request... to avoid jank, wait until this layout has happened.
            SchedulingUtils.doAfterLayout(mBackdrop, new Runnable() {
@@ -437,6 +465,7 @@ public class PhotoSelectionActivity extends Activity {
                }
            });
        }
    }

    private final class PhotoHandler extends PhotoSelectionHandler {
        private final PhotoActionListener mListener;
@@ -454,27 +483,27 @@ public class PhotoSelectionActivity extends Activity {
        }

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

        private final class PhotoListener extends PhotoActionListener {

            @Override
            public void onPhotoSelected(Bitmap bitmap) {
                EntityDeltaList delta = getDeltaForAttachingPhotoToContact();
                long rawContactId = getWritableEntityId();
                String filePath = mCurrentPhotoFile.getAbsolutePath();
                final String croppedPath = ContactPhotoUtils.pathForCroppedPhoto(
                        PhotoSelectionActivity.this, mCurrentPhotoFile);
                Intent intent = ContactSaveService.createSaveContactIntent(
                        mContext, delta, "", 0, mIsProfile, null, null, rawContactId, filePath);
                        mContext, delta, "", 0, mIsProfile, null, null, rawContactId, croppedPath);
                startService(intent);
                finish();
            }

            @Override
            public File getCurrentPhotoFile() {
            public String getCurrentPhotoFile() {
                return mCurrentPhotoFile;
            }

@@ -486,4 +515,15 @@ public class PhotoSelectionActivity extends Activity {
            }
        }
    }

    private static class PendingPhotoResult {
        final private int mRequestCode;
        final private int mResultCode;
        final private Intent mData;
        private PendingPhotoResult(int requestCode, int resultCode, Intent data) {
            mRequestCode = requestCode;
            mResultCode = resultCode;
            mData = data;
        }
    }
}
+37 −26
Original line number Diff line number Diff line
@@ -115,12 +115,15 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
        final PhotoActionListener listener = getListener();
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                // Photo was chosen (either new or existing from gallery), and cropped.
                case REQUEST_CODE_PHOTO_PICKED_WITH_DATA: {
                    Bitmap bitmap = BitmapFactory.decodeFile(
                            listener.getCurrentPhotoFile().getAbsolutePath());
                    final String path = ContactPhotoUtils.pathForCroppedPhoto(
                            mContext, listener.getCurrentPhotoFile());
                    Bitmap bitmap = BitmapFactory.decodeFile(path);
                    listener.onPhotoSelected(bitmap);
                    return true;
                }
                // Photo was successfully taken, now crop it.
                case REQUEST_CODE_CAMERA_WITH_DATA: {
                    doCropPhoto(listener.getCurrentPhotoFile());
                    return true;
@@ -183,23 +186,28 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
    }

    /** Used by subclasses to delegate to their enclosing Activity or Fragment. */
    protected abstract void startPhotoActivity(Intent intent, int requestCode, File photoFile);
    protected abstract void startPhotoActivity(Intent intent, int requestCode, String photoFile);

    /**
     * Sends a newly acquired photo to Gallery for cropping
     */
    private void doCropPhoto(File f) {
    private void doCropPhoto(String fileName) {
        try {
            // Obtain the absolute paths for the newly-taken photo, and the destination
            // for the soon-to-be-cropped photo.
            final String newPath = ContactPhotoUtils.pathForNewCameraPhoto(fileName);
            final String croppedPath = ContactPhotoUtils.pathForCroppedPhoto(mContext, fileName);

            // Add the image to the media store
            MediaScannerConnection.scanFile(
                    mContext,
                    new String[] { f.getAbsolutePath() },
                    new String[] { newPath },
                    new String[] { null },
                    null);

            // Launch gallery to crop the photo
            final Intent intent = getCropImageIntent(f);
            startPhotoActivity(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA, f);
            final Intent intent = getCropImageIntent(newPath, croppedPath);
            startPhotoActivity(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA, fileName);
        } catch (Exception e) {
            Log.e(TAG, "Cannot crop image", e);
            Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
@@ -212,7 +220,7 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
     *     what should be returned by
     *     {@link PhotoSelectionHandler.PhotoActionListener#getCurrentPhotoFile()}.
     */
    private void startTakePhotoActivity(File photoFile) {
    private void startTakePhotoActivity(String photoFile) {
        final Intent intent = getTakePhotoIntent(photoFile);
        startPhotoActivity(intent, REQUEST_CODE_CAMERA_WITH_DATA, photoFile);
    }
@@ -223,7 +231,7 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
     *     stored by the content-provider.
     *     {@link PhotoSelectionHandler#handlePhotoActivityResult(int, int, Intent)}.
     */
    private void startPickFromGalleryActivity(File photoFile) {
    private void startPickFromGalleryActivity(String photoFile) {
        final Intent intent = getPhotoPickIntent(photoFile);
        startPhotoActivity(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA, photoFile);
    }
@@ -243,41 +251,44 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
    /**
     * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap.
     */
    private Intent getPhotoPickIntent(File photoFile) {
        Uri photoUri = Uri.fromFile(photoFile);
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
    private Intent getPhotoPickIntent(String photoFile) {
        final String croppedPhotoPath = ContactPhotoUtils.pathForCroppedPhoto(mContext, photoFile);
        final Uri croppedPhotoUri = Uri.fromFile(new File(croppedPhotoPath));
        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
        intent.setType("image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", mPhotoPickSize);
        intent.putExtra("outputY", mPhotoPickSize);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, croppedPhotoUri);
        return intent;
    }

    /**
     * Constructs an intent for image cropping.
     */
    private Intent getCropImageIntent(File photoFile) {
        Uri photoUri = Uri.fromFile(photoFile);
    private Intent getCropImageIntent(String inputPhotoPath, String croppedPhotoPath) {
        final Uri inputPhotoUri = Uri.fromFile(new File(inputPhotoPath));
        final Uri croppedPhotoUri = Uri.fromFile(new File(croppedPhotoPath));
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(photoUri, "image/*");
        intent.setDataAndType(inputPhotoUri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", mPhotoPickSize);
        intent.putExtra("outputY", mPhotoPickSize);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, croppedPhotoUri);
        return intent;
    }

    /**
     * Constructs an intent for capturing a photo and storing it in a temporary file.
     */
    public static Intent getTakePhotoIntent(File f) {
    private static Intent getTakePhotoIntent(String fileName) {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
        final String newPhotoPath = ContactPhotoUtils.pathForNewCameraPhoto(fileName);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(newPhotoPath)));
        return intent;
    }

@@ -296,10 +307,10 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
        public void onTakePhotoChosen() {
            try {
                // Launch camera to take photo for selected contact
                startTakePhotoActivity(ContactPhotoUtils.generateTempPhotoFile());
                startTakePhotoActivity(ContactPhotoUtils.generateTempPhotoFileName());
            } catch (ActivityNotFoundException e) {
                Toast.makeText(mContext, R.string.photoPickerNotFoundText,
                        Toast.LENGTH_LONG).show();
                Toast.makeText(
                        mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
            }
        }

@@ -307,10 +318,10 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
        public void onPickFromGalleryChosen() {
            try {
                // Launch picker to choose photo for selected contact
                startPickFromGalleryActivity(ContactPhotoUtils.generateTempPhotoFile());
                startPickFromGalleryActivity(ContactPhotoUtils.generateTempPhotoFileName());
            } catch (ActivityNotFoundException e) {
                Toast.makeText(mContext, R.string.photoPickerNotFoundText,
                        Toast.LENGTH_LONG).show();
                Toast.makeText(
                        mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
            }
        }

@@ -325,7 +336,7 @@ public abstract class PhotoSelectionHandler implements OnClickListener {
         * fragment's responsibility to maintain this in saved state, since this handler instance
         * will not survive rotation.
         */
        public abstract File getCurrentPhotoFile();
        public abstract String getCurrentPhotoFile();

        /**
         * Called when the photo selection dialog is dismissed.
+19 −19
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import com.android.contacts.R;
import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.activities.JoinContactActivity;
import com.android.contacts.activities.PhotoSelectionActivity;
import com.android.contacts.detail.PhotoSelectionHandler;
import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
import com.android.contacts.editor.Editor.EditorListener;
@@ -35,6 +36,7 @@ import com.android.contacts.model.EntityDeltaList;
import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.GoogleAccountType;
import com.android.contacts.util.AccountsListAdapter;
import com.android.contacts.util.ContactPhotoUtils;
import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import com.android.contacts.util.HelpUtils;

@@ -195,7 +197,7 @@ public class ContactEditorFragment extends Fragment implements

    private Cursor mGroupMetaData;

    private File mCurrentPhotoFile;
    private String mCurrentPhotoFile;
    private Bundle mUpdatedPhotos = new Bundle();

    private Context mContext;
@@ -410,10 +412,7 @@ public class ContactEditorFragment extends Fragment implements
            mRawContactIdRequestingPhoto = savedState.getLong(
                    KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
            mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
            String fileName = savedState.getString(KEY_CURRENT_PHOTO_FILE);
            if (fileName != null) {
                mCurrentPhotoFile = new File(fileName);
            }
            mCurrentPhotoFile = savedState.getString(KEY_CURRENT_PHOTO_FILE);
            mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
            mContactWritableForJoin = savedState.getBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN);
            mAggregationSuggestionsRawContactId = savedState.getLong(KEY_SHOW_JOIN_SUGGESTIONS);
@@ -918,10 +917,10 @@ public class ContactEditorFragment extends Fragment implements
        // help menu depending on whether this is inserting or editing
        if (Intent.ACTION_INSERT.equals(mAction)) {
            // inserting
            HelpUtils.prepareHelpMenuItem(getActivity(), helpMenu, R.string.help_url_people_add);
            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_add);
        } else if (Intent.ACTION_EDIT.equals(mAction)) {
            // editing
            HelpUtils.prepareHelpMenuItem(getActivity(), helpMenu, R.string.help_url_people_edit);
            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_edit);
        } else {
            // something else, so don't show the help menu
            helpMenu.setVisible(false);
@@ -966,7 +965,7 @@ public class ContactEditorFragment extends Fragment implements
        // If we just started creating a new contact and haven't added any data, it's too
        // early to do a join
        if (mState.size() == 1 && mState.get(0).isContactInsert() && !hasPendingChanges()) {
            Toast.makeText(getActivity(), R.string.toast_join_with_empty_contact,
            Toast.makeText(mContext, R.string.toast_join_with_empty_contact,
                            Toast.LENGTH_LONG).show();
            return true;
        }
@@ -1024,10 +1023,11 @@ public class ContactEditorFragment extends Fragment implements
        saveDefaultAccountIfNecessary();

        // Save contact
        Intent intent = ContactSaveService.createSaveContactIntent(getActivity(), mState,
                SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(), getActivity().getClass(),
                ContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos);
        getActivity().startService(intent);
        Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
                SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
                ((Activity)mContext).getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED,
                mUpdatedPhotos);
        mContext.startService(intent);

        // Don't try to save the same photos twice.
        mUpdatedPhotos = new Bundle();
@@ -1543,9 +1543,7 @@ public class ContactEditorFragment extends Fragment implements

        outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
        outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
        if (mCurrentPhotoFile != null) {
            outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString());
        }
        outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile);
        outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
        outState.putBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN, mContactWritableForJoin);
        outState.putLong(KEY_SHOW_JOIN_SUGGESTIONS, mAggregationSuggestionsRawContactId);
@@ -1606,7 +1604,7 @@ public class ContactEditorFragment extends Fragment implements
    /**
     * Sets the photo stored in mPhoto and writes it to the RawContact with the given id
     */
    private void setPhoto(long rawContact, Bitmap photo, File photoFile) {
    private void setPhoto(long rawContact, Bitmap photo, String photoFile) {
        BaseRawContactEditorView requestingEditor = getRawContactEditorView(rawContact);

        if (photo == null || photo.getHeight() < 0 || photo.getWidth() < 0) {
@@ -1620,7 +1618,9 @@ public class ContactEditorFragment extends Fragment implements
            Log.w(TAG, "The contact that requested the photo is no longer present.");
        }

        mUpdatedPhotos.putString(String.valueOf(rawContact), photoFile.getAbsolutePath());
        final String croppedPhotoPath =
                ContactPhotoUtils.pathForCroppedPhoto(mContext, mCurrentPhotoFile);
        mUpdatedPhotos.putString(String.valueOf(rawContact), croppedPhotoPath);
    }

    /**
@@ -1757,7 +1757,7 @@ public class ContactEditorFragment extends Fragment implements
        }

        @Override
        public void startPhotoActivity(Intent intent, int requestCode, File photoFile) {
        public void startPhotoActivity(Intent intent, int requestCode, String photoFile) {
            mRawContactIdRequestingPhoto = mEditor.getRawContactId();
            mStatus = Status.SUB_ACTIVITY;
            mCurrentPhotoFile = photoFile;
@@ -1819,7 +1819,7 @@ public class ContactEditorFragment extends Fragment implements
            }

            @Override
            public File getCurrentPhotoFile() {
            public String getCurrentPhotoFile() {
                return mCurrentPhotoFile;
            }

Loading