From: Seth Raphael Date: Thu, 13 Feb 2014 23:10:06 +0000 (-0800) Subject: Hold in-progress sessions in mem, not media store X-Git-Tag: android-x86-6.0-r3~1064^2~153^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=455ba5a146484d2af4a4fb44eb50ad294834dfa0;p=android-x86%2Fpackages-apps-Camera2.git Hold in-progress sessions in mem, not media store Bug: 12457236 Change-Id: I88f7c3a822010744881b8be966adbcf2774a8115 --- diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index 4308cc8a0..651d4a3c3 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -81,7 +81,6 @@ import com.android.camera.app.OrientationManager; import com.android.camera.app.OrientationManagerImpl; import com.android.camera.data.CameraDataAdapter; import com.android.camera.data.FixedLastDataAdapter; -import com.android.camera.data.InProgressDataWrapper; import com.android.camera.data.LocalData; import com.android.camera.data.LocalDataAdapter; import com.android.camera.data.LocalDataUtil; @@ -750,27 +749,12 @@ public class CameraActivity extends Activity @Override public void onSessionQueued(final Uri uri) { notifyNewMedia(uri); - int dataId = mDataAdapter.findDataByContentUri(uri); - if (dataId != -1) { - // Don't allow special UI actions (swipe to - // delete, for example) on in-progress data. - LocalData d = mDataAdapter.getLocalData(dataId); - InProgressDataWrapper newData = new InProgressDataWrapper(d); - mDataAdapter.updateData(dataId, newData); - } } @Override public void onSessionDone(final Uri uri) { Log.v(TAG, "onSessionDone:" + uri); - int doneId = mDataAdapter.findDataByContentUri(uri); - int currentDataId = mFilmstripController.getCurrentId(); - - if (currentDataId == doneId) { - hideSessionProgress(); - updateSessionProgress(0); - } - mDataAdapter.refresh(uri, /* isInProgress */false); + mDataAdapter.finishSession(uri); } @Override @@ -791,7 +775,7 @@ public class CameraActivity extends Activity @Override public void onSessionUpdated(Uri uri) { - mDataAdapter.refresh(uri, /* isInProgress */true); + mDataAdapter.refresh(uri); } @Override @@ -805,7 +789,8 @@ public class CameraActivity extends Activity updateSessionProgress(0); showProcessError(reason); } - mDataAdapter.refresh(uri, /* isInProgress */false); + // HERE + mDataAdapter.refresh(uri); } }; @@ -1025,19 +1010,22 @@ public class CameraActivity extends Activity @Override public void notifyNewMedia(Uri uri) { - ContentResolver cr = getContentResolver(); - String mimeType = cr.getType(uri); - if (LocalDataUtil.isMimeTypeVideo(mimeType)) { - sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); - mDataAdapter.addNewVideo(uri); - } else if (LocalDataUtil.isMimeTypeImage(mimeType)) { - CameraUtil.broadcastNewPicture(mAppContext, uri); - mDataAdapter.addNewPhoto(uri); - } else if (LocalDataUtil.isMimeTypePlaceHolder(mimeType)) { - mDataAdapter.addNewPhoto(uri); + if (Storage.isSessionUri(uri)) { + mDataAdapter.addNewSession(uri); } else { - android.util.Log.w(TAG, "Unknown new media with MIME type:" - + mimeType + ", uri:" + uri); + + ContentResolver cr = getContentResolver(); + String mimeType = cr.getType(uri); + if (LocalDataUtil.isMimeTypeVideo(mimeType)) { + sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); + mDataAdapter.addNewVideo(uri); + } else if (LocalDataUtil.isMimeTypeImage(mimeType)) { + CameraUtil.broadcastNewPicture(mAppContext, uri); + mDataAdapter.addNewPhoto(uri); + } else { + android.util.Log.w(TAG, "Unknown new media with MIME type:" + + mimeType + ", uri:" + uri); + } } } @@ -1423,7 +1411,7 @@ public class CameraActivity extends Activity } else { LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId()); if (data != null) { - mDataAdapter.refresh(data.getContentUri(), false); + mDataAdapter.refresh(data.getContentUri()); } } // The share button might be disabled to avoid double tapping. diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java index 4e7d66e11..f5aaf9464 100644 --- a/src/com/android/camera/Storage.java +++ b/src/com/android/camera/Storage.java @@ -19,6 +19,7 @@ package com.android.camera; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.ContentValues; +import android.graphics.Point; import android.location.Location; import android.net.Uri; import android.os.Build; @@ -36,24 +37,27 @@ import com.android.camera.util.ApiHelper; import java.io.File; import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.UUID; public class Storage { - private static final String TAG = "CameraStorage"; - public static final String DCIM = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString(); - public static final String DIRECTORY = DCIM + "/Camera"; public static final String JPEG_POSTFIX = ".jpg"; - // Match the code in MediaProvider.computeBucketValues(). public static final String BUCKET_ID = String.valueOf(DIRECTORY.toLowerCase().hashCode()); - public static final long UNAVAILABLE = -1L; public static final long PREPARING = -2L; public static final long UNKNOWN_SIZE = -3L; public static final long LOW_STORAGE_THRESHOLD_BYTES = 50000000; + public static final String CAMERA_SESSION_SCHEME = "camera_session"; + private static final String TAG = "Storage"; + private static final String GOOGLE_COM = "google.com"; + private static HashMap sSessionsToContentUris = new HashMap(); + private static HashMap sSessionsToPlaceholderBytes = new HashMap(); + private static HashMap sSessionsToSizes= new HashMap(); @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private static void setImageSize(ContentValues values, int width, int height) { @@ -136,6 +140,38 @@ public class Storage { return values; } + /** + * Add a placeholder for a new image that does not exist yet. + * @param jpeg the bytes of the placeholder image + * @param width the image's width + * @param height the image's height + * @return A new URI used to reference this placeholder + */ + public static Uri addPlaceholder(byte[] jpeg, int width, int height) { + Uri uri; + Uri.Builder builder = new Uri.Builder(); + String uuid = UUID.randomUUID().toString(); + builder.scheme(CAMERA_SESSION_SCHEME).authority(GOOGLE_COM).appendPath(uuid); + uri = builder.build(); + + replacePlaceholder(uri, jpeg, width, height); + return uri; + } + + /** + * Add or replace placeholder for a new image that does not exist yet. + * @param uri the uri of the placeholder to replace, or null if this is a new one + * @param jpeg the bytes of the placeholder image + * @param width the image's width + * @param height the image's height + * @return A URI used to reference this placeholder + */ + public static void replacePlaceholder(Uri uri, byte[] jpeg, int width, int height) { + Point size = new Point(width, height); + sSessionsToSizes.put(uri, size); + sSessionsToPlaceholderBytes.put(uri, jpeg); + } + // Add the image to media store. public static Uri addImage(ContentResolver resolver, String title, long date, Location location, int orientation, int jpegLength, @@ -160,17 +196,35 @@ public class Storage { } // Overwrites the file and updates the MediaStore - public static void updateImage(Uri imageUri, ContentResolver resolver, String title, long date, - Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, - int height, String mimeType) { + + /** + * Take jpeg bytes and add them to the media store, either replacing an existing item + * or a placeholder uri to replace + * @param imageUri The content uri or session uri of the image being updated + * @param resolver The content resolver to use + * @param title of the image + * @param date of the image + * @param location of the image + * @param orientation of the image + * @param exif of the image + * @param jpeg bytes of the image + * @param width of the image + * @param height of the image + * @param mimeType of the image + * @return The content uri of the newly inserted or replaced item. + */ + public static Uri updateImage(Uri imageUri, ContentResolver resolver, String title, long date, + Location location, int orientation, ExifInterface exif, + byte[] jpeg, int width, int height, String mimeType) { String path = generateFilepath(title); writeFile(path, jpeg, exif); - updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length, path, + return updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length, path, width, height, mimeType); } + // Updates the image values in MediaStore - public static void updateImage(Uri imageUri, ContentResolver resolver, String title, + private static Uri updateImage(Uri imageUri, ContentResolver resolver, String title, long date, Location location, int orientation, int jpegLength, String path, int width, int height, String mimeType) { @@ -178,13 +232,23 @@ public class Storage { getContentValuesForData(title, date, location, orientation, jpegLength, path, width, height, mimeType); - // Update the MediaStore - int rowsModified = resolver.update(imageUri, values, null, null); - if (rowsModified != 1) { - // This should never happen - throw new IllegalStateException("Bad number of rows (" + rowsModified - + ") updated for uri: " + imageUri); + + Uri resultUri = imageUri; + if (Storage.isSessionUri(imageUri)) { + // If this is a session uri, then we need to add the image + resultUri = addImage(resolver, title, date, location, orientation, jpegLength, path, + width, height, mimeType); + sSessionsToContentUris.put(imageUri, resultUri); + } else { + // Update the MediaStore + int rowsModified = resolver.update(imageUri, values, null, null); + if (rowsModified != 1) { + // This should never happen + throw new IllegalStateException("Bad number of rows (" + rowsModified + + ") updated for uri: " + imageUri); + } } + return resultUri; } /** @@ -219,7 +283,7 @@ public class Storage { * switching an image to an in-progress type for re-processing. * * @param uri the URI of the item to change - * @param mimeeType the new mime type of the item + * @param mimeType the new mime type of the item */ public static void updateItemMimeType(Uri uri, String mimeType, ContentResolver resolver) { ContentValues values = new ContentValues(1); @@ -246,6 +310,46 @@ public class Storage { return DIRECTORY + '/' + title + ".jpg"; } + /** + * Returns the jpeg bytes for a placeholder session + * + * @param uri the session uri to look up + * @return The jpeg bytes or null + */ + public static byte[] getJpegForSession(Uri uri) { + return sSessionsToPlaceholderBytes.get(uri); + } + + /** + * Returns the dimensions of the placeholder image + * + * @param uri the session uri to look up + * @return The size + */ + public static Point getSizeForSession(Uri uri) { + return sSessionsToSizes.get(uri); + } + + /** + * Takes a session URI and returns the finished image's content URI + * + * @param uri the uri of the session that was replaced + * @return The uri of the new media item, if it exists, or null. + */ + public static Uri getContentUriForSessionUri(Uri uri) { + return sSessionsToContentUris.get(uri); + } + + /** + * Determines if a URI points to a camera session + * + * @param uri the uri to check + * @return true if it is a session uri. + */ + public static boolean isSessionUri(Uri uri) { + return uri.getScheme().equals(CAMERA_SESSION_SCHEME); + } + public static long getAvailableSpace() { String state = Environment.getExternalStorageState(); Log.d(TAG, "External storage state=" + state); @@ -281,4 +385,5 @@ public class Storage { Log.e(TAG, "Failed to create " + nnnAAAAA.getPath()); } } + } diff --git a/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java b/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java index 4f4746235..26ef0c583 100644 --- a/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java +++ b/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java @@ -82,6 +82,11 @@ public abstract class AbstractLocalDataAdapterWrapper implements LocalDataAdapte } @Override + public void addNewSession(Uri uri) { + mAdapter.addNewSession(uri); + } + + @Override public void insertData(LocalData data) { mAdapter.insertData(data); } @@ -102,8 +107,12 @@ public abstract class AbstractLocalDataAdapterWrapper implements LocalDataAdapte } @Override - public void refresh(Uri uri, boolean isInProgressSession) { - mAdapter.refresh(uri, isInProgressSession); + public void finishSession(Uri uri) { + mAdapter.finishSession(uri); + } + @Override + public void refresh(Uri uri) { + mAdapter.refresh(uri); } @Override diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java index 6428176c7..de098caab 100644 --- a/src/com/android/camera/data/CameraDataAdapter.java +++ b/src/com/android/camera/data/CameraDataAdapter.java @@ -179,9 +179,7 @@ public class CameraDataAdapter implements LocalDataAdapter { } } - // TODO: put the database query on background thread - @Override - public void addNewPhoto(Uri uri) { + private LocalData localDataFromUri(Uri uri) { Cursor cursor = mContext.getContentResolver().query(uri, LocalMediaData.PhotoData.QUERY_PROJECTION, MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, @@ -190,7 +188,7 @@ public class CameraDataAdapter implements LocalDataAdapter { try { if (cursor == null || !cursor.moveToFirst()) { - return; + return null; } newData = LocalMediaData.PhotoData.buildFromCursor(mContext, cursor); } finally { @@ -199,6 +197,23 @@ public class CameraDataAdapter implements LocalDataAdapter { cursor.close(); } } + return newData; + } + + // TODO: put the database query on background thread + @Override + public void addNewPhoto(Uri uri) { + LocalData newData = localDataFromUri(uri); + addData(uri, newData); + } + + @Override + public void addNewSession(Uri uri) { + LocalSessionData newData = new LocalSessionData(uri); + addData(uri, newData); + } + + private void addData(Uri uri, LocalData newData) { int pos = findDataByContentUri(uri); if (pos != -1) { // a duplicate one, just do a substitute. @@ -243,8 +258,23 @@ public class CameraDataAdapter implements LocalDataAdapter { } @Override - public void refresh(Uri contentUri, boolean isInProgressSession) { - final int pos = findDataByContentUri(contentUri); + public void finishSession(Uri sessionUri) { + Uri contentUri = Storage.getContentUriForSessionUri(sessionUri); + if (contentUri == null) { + refresh(sessionUri); + return; + } + final int pos = findDataByContentUri(sessionUri); + if (pos == -1) { + throw new IllegalAccessError("Finishing invalid uri"); + } + LocalData newData = localDataFromUri(contentUri); + updateData(pos, newData); + } + + @Override + public void refresh(Uri uri) { + final int pos = findDataByContentUri(uri); if (pos == -1) { return; } @@ -257,12 +287,6 @@ public class CameraDataAdapter implements LocalDataAdapter { mListener.onDataRemoved(pos, data); return; } - - // Wrap the data item if this represents a session that is in progress. - if (isInProgressSession) { - refreshedData = new InProgressDataWrapper(refreshedData); - } - updateData(pos, refreshedData); } @@ -335,11 +359,7 @@ public class CameraDataAdapter implements LocalDataAdapter { while (true) { LocalData data = LocalMediaData.PhotoData.buildFromCursor(mContext, c); if (data != null) { - if (data.getMimeType().equals(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) { - l.add(new InProgressDataWrapper(data)); - } else { - l.add(data); - } + l.add(data); } else { Log.e(TAG, "Error loading data:" + c.getString(LocalMediaData.PhotoData.COL_DATA)); diff --git a/src/com/android/camera/data/LocalDataAdapter.java b/src/com/android/camera/data/LocalDataAdapter.java index f971c8084..8472a6618 100644 --- a/src/com/android/camera/data/LocalDataAdapter.java +++ b/src/com/android/camera/data/LocalDataAdapter.java @@ -81,13 +81,25 @@ public interface LocalDataAdapter extends DataAdapter { public void addNewPhoto(Uri uri); /** + * Adds new placeholder uri. + * + * @param uri the uri of the session to find the placeholder images + */ + public void addNewSession(Uri uri); + + /** + * Called when a session is done processing + * + * @param uri {@link Uri} of the session. + */ + public void finishSession(Uri uri); + + /** * Refresh the data by {@link Uri}. * * @param uri The {@link Uri} of the data to refresh. - * @param isInProgressSession Whether this data item has a session in - * progress associated with it. */ - public void refresh(Uri uri, boolean isInProgressSession); + public void refresh(Uri uri); /** * Finds the {@link LocalData} of the specified content Uri. diff --git a/src/com/android/camera/data/LocalDataUtil.java b/src/com/android/camera/data/LocalDataUtil.java index c51dc1162..f2df1eeab 100644 --- a/src/com/android/camera/data/LocalDataUtil.java +++ b/src/com/android/camera/data/LocalDataUtil.java @@ -53,15 +53,6 @@ public class LocalDataUtil { } /** - * @param mimeType The MIME type to check. - * @return Whether the MIME is a placeholder type. - * @see {@link com.android.camera.session.PlaceholderManager}. - */ - public static boolean isMimeTypePlaceHolder(String mimeType) { - return mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE); - } - - /** * Decodes the dimension of a bitmap. * * @param path The path to the bitmap. diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java index d44c2a515..ee18c641c 100644 --- a/src/com/android/camera/data/LocalMediaData.java +++ b/src/com/android/camera/data/LocalMediaData.java @@ -528,7 +528,7 @@ public abstract class LocalMediaData implements LocalData { protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if (mNeedsRefresh && mAdapter != null) { - mAdapter.refresh(getContentUri(), mIsInProgressSession); + mAdapter.refresh(getContentUri()); } } } diff --git a/src/com/android/camera/data/InProgressDataWrapper.java b/src/com/android/camera/data/LocalSessionData.java similarity index 52% rename from src/com/android/camera/data/InProgressDataWrapper.java rename to src/com/android/camera/data/LocalSessionData.java index 8dc53dc0e..bd45fb93c 100644 --- a/src/com/android/camera/data/InProgressDataWrapper.java +++ b/src/com/android/camera/data/LocalSessionData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2014 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. @@ -17,57 +17,73 @@ package com.android.camera.data; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Point; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.view.LayoutInflater; import android.view.View; -import android.widget.FrameLayout; +import android.widget.ImageView; -import com.android.camera2.R; +import com.android.camera.Storage; + +import java.util.Date; /** - * A wrapper class for in-progress data. Data that's still being processed - * should not supporting any actions. Only methods related to actions like - * {@link #isDataActionSupported(int)} and - * {@link #isUIActionSupported(int)} are implemented by this class. + * This is used to represent a local data item that is in progress and not + * yet in the media store. */ -public class InProgressDataWrapper implements LocalData { +public class LocalSessionData implements LocalData { - final LocalData mLocalData; + private Uri mUri; + private long mDateTaken; + protected final Bundle mMetaData; + private int mWidth; + private int mHeight; - public InProgressDataWrapper(LocalData wrappedData) { - mLocalData = wrappedData; + public LocalSessionData(Uri uri) { + mUri = uri; + mMetaData = new Bundle(); + mDateTaken = new Date().getTime(); + Point size = Storage.getSizeForSession(uri); + mWidth = size.x; + mHeight = size.y; } @Override - public View getView( - Context context, int width, int height, - Drawable placeHolder, LocalDataAdapter adapter, boolean isInProgress) { - - return mLocalData.getView(context, width, height, placeHolder, adapter, true); + public View getView(Context context, int width, int height, Drawable placeHolder, + LocalDataAdapter adapter, boolean isInProgress) { + //TODO do this on a background thread + byte[] jpegData = Storage.getJpegForSession(mUri); + Bitmap bmp = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); + ImageView imageView = new ImageView(context); + imageView.setImageBitmap(bmp); + return imageView; } @Override - public void resizeView(Context context, int w, int h, View v, LocalDataAdapter adapter) { - // do nothing. + public void resizeView(Context context, int width, int height, View view, + LocalDataAdapter adapter) { + } @Override public long getDateTaken() { - return mLocalData.getDateTaken(); + return mDateTaken; } @Override public long getDateModified() { - return mLocalData.getDateModified(); + return mDateTaken; } @Override public String getTitle() { - return mLocalData.getTitle(); + return mUri.toString(); } + @Override public boolean isDataActionSupported(int actions) { return false; @@ -75,97 +91,92 @@ public class InProgressDataWrapper implements LocalData { @Override public boolean delete(Context c) { - // No actions are allowed to modify the wrapped data. return false; } @Override - public boolean rotate90Degrees( - Context context, LocalDataAdapter adapter, - int currentDataId, boolean clockwise) { - // No actions are allowed to modify the wrapped data. + public boolean rotate90Degrees(Context context, LocalDataAdapter adapter, int currentDataId, boolean clockwise) { return false; } @Override public void onFullScreen(boolean fullScreen) { - mLocalData.onFullScreen(fullScreen); + } @Override public boolean canSwipeInFullScreen() { - return mLocalData.canSwipeInFullScreen(); + return true; } @Override public String getPath() { - return mLocalData.getPath(); + return ""; } @Override public String getMimeType() { - return mLocalData.getMimeType(); + return null; } @Override public MediaDetails getMediaDetails(Context context) { - return mLocalData.getMediaDetails(context); + return null; } @Override public int getLocalDataType() { - // Force the data type to be in-progress data. return LOCAL_IN_PROGRESS_DATA; } @Override public long getSizeInBytes() { - return mLocalData.getSizeInBytes(); + return 0; } @Override public LocalData refresh(Context context) { - return mLocalData.refresh(context); + return this; } @Override public long getContentId() { - return mLocalData.getContentId(); + return 0; } @Override public Bundle getMetadata() { - return mLocalData.getMetadata(); + return mMetaData; } @Override public boolean isMetadataUpdated() { - return mLocalData.isMetadataUpdated(); + return true; } @Override - public int getWidth() { - return mLocalData.getWidth(); + public int getRotation() { + return 0; } @Override - public int getHeight() { - return mLocalData.getHeight(); + public int getWidth() { + return mWidth; } @Override - public int getRotation() { - return mLocalData.getRotation(); + public int getHeight() { + return mHeight; } @Override public int getViewType() { - return mLocalData.getViewType(); + return VIEW_TYPE_REMOVABLE; } @Override public double[] getLatLong() { - return mLocalData.getLatLong(); + return null; } @Override @@ -175,16 +186,16 @@ public class InProgressDataWrapper implements LocalData { @Override public void prepare() { - mLocalData.prepare(); + } @Override public void recycle() { - mLocalData.recycle(); + } @Override public Uri getContentUri() { - return mLocalData.getContentUri(); + return mUri; } } diff --git a/src/com/android/camera/session/CaptureSessionManagerImpl.java b/src/com/android/camera/session/CaptureSessionManagerImpl.java index 9277d4409..10a4e44f6 100644 --- a/src/com/android/camera/session/CaptureSessionManagerImpl.java +++ b/src/com/android/camera/session/CaptureSessionManagerImpl.java @@ -17,19 +17,28 @@ package com.android.camera.session; import android.content.ContentResolver; +import android.graphics.BitmapFactory; import android.location.Location; import android.net.Uri; +import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; +import android.util.Log; +import com.android.camera.Exif; import com.android.camera.app.MediaSaver; import com.android.camera.app.MediaSaver.OnMediaSavedListener; -import com.android.camera.crop.ImageLoader; import com.android.camera.data.LocalData; import com.android.camera.exif.ExifInterface; +import com.android.camera.exif.ExifTag; +import com.android.camera.exif.Rational; +import com.android.camera.util.FileUtil; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; @@ -39,6 +48,9 @@ import java.util.Map; */ public class CaptureSessionManagerImpl implements CaptureSessionManager { + private static final String TAG = "CaptureSessionManagerImpl"; + public static final String TEMP_SESSIONS = "TEMP_SESSIONS"; + private class CaptureSessionImpl implements CaptureSession { /** A URI of the item being processed. */ private Uri mUri; @@ -151,8 +163,8 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager { } // TODO: This needs to happen outside the UI thread. - mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, mLocation, orientation, - exif, data, width, height, LocalData.MIME_TYPE_JPEG); + mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation, orientation, exif, + data, width, height, LocalData.MIME_TYPE_JPEG); mNotificationManager.notifyCompletion(mNotificationId); removeSession(mUri.toString()); @@ -166,12 +178,43 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager { "Cannot call finish without calling startSession first."); } - // Set final values in media store, such as mime type and size. - mPlaceholderManager.replacePlaceHolder(mPlaceHolderSession, mLocation, - LocalData.MIME_TYPE_JPEG, /* finalImage */ true); - mNotificationManager.notifyCompletion(mNotificationId); - removeSession(mUri.toString()); - notifyTaskDone(mPlaceHolderSession.outputUri); + final String path = this.getPath(); + + AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + byte[] jpegDataTemp; + try { + jpegDataTemp = FileUtil.readFileToByteArray(new File(path)); + } catch (IOException e) { + return; + } + final byte[] jpegData = jpegDataTemp; + + final CaptureSession session = CaptureSessionImpl.this; + + if (session == null) { + throw new IllegalStateException("No session for captured photo"); + } + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options); + int width = options.outWidth; + int height = options.outHeight; + int rotation = 0; + ExifInterface exif = null; + try { + exif = new ExifInterface(); + exif.readExif(jpegData); + } catch (IOException e) { + Log.w(TAG, "Could not read exif", e); + exif = null; + } + + session.saveAndFinish(jpegData, width, height, rotation, exif, null); + } + }); + } @Override @@ -179,7 +222,26 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager { if (mUri == null) { throw new IllegalStateException("Cannot retrieve URI of not started session."); } - return ImageLoader.getLocalPathFromUri(mContentResolver, mUri); + + File tempDirectory = null; + try { + tempDirectory = new File( + getSessionDirectory(TEMP_SESSIONS), mTitle); + } catch (IOException e) { + Log.e(TAG, "Could not get temp session directory", e); + throw new RuntimeException("Could not get temp session directory", e); + } + tempDirectory.mkdirs(); + File tempFile = new File(tempDirectory, mTitle + ".jpg"); + try { + if (!tempFile.exists()) { + tempFile.createNewFile(); + } + } catch (IOException e) { + Log.e(TAG, "Could not create temp session file", e); + throw new RuntimeException("Could not create temp session file", e); + } + return tempFile.getPath(); } @Override @@ -194,9 +256,32 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager { @Override public void onPreviewChanged() { - mPlaceholderManager.replacePlaceHolder(mPlaceHolderSession, mLocation, - PlaceholderManager.PLACEHOLDER_MIME_TYPE, /* finalImage */ false); - notifySessionUpdate(mPlaceHolderSession.outputUri); + + final Location loc = null; // mLocationManager.getCurrentLocation(); + final int heading = 0; // mHeading; + final String path = this.getPath(); + + AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + byte[] jpegDataTemp; + try { + jpegDataTemp = FileUtil.readFileToByteArray(new File(path)); + } catch (IOException e) { + return; + } + final byte[] jpegData = jpegDataTemp; + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options); + int width = options.outWidth; + int height = options.outHeight; + + mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, jpegData, width, height); + notifySessionUpdate(mPlaceHolderSession.outputUri); + } + }); } @Override @@ -207,9 +292,6 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager { } mProgressMessage = reason; - // Change mime type of session so it's not marked as in progress anymore. - mPlaceholderManager.replacePlaceHolder(mPlaceHolderSession, mLocation, - LocalData.MIME_TYPE_JPEG, /* finalImage */false); mNotificationManager.notifyCompletion(mNotificationId); removeSession(mUri.toString()); mFailedSessionMessages.put(mPlaceHolderSession.outputUri, reason); diff --git a/src/com/android/camera/session/PlaceholderManager.java b/src/com/android/camera/session/PlaceholderManager.java index 5034d40a5..91e0760c3 100644 --- a/src/com/android/camera/session/PlaceholderManager.java +++ b/src/com/android/camera/session/PlaceholderManager.java @@ -35,7 +35,6 @@ import com.android.camera.util.CameraUtil; public class PlaceholderManager { private static final String TAG = "PlaceholderManager"; - public static final String PLACEHOLDER_MIME_TYPE = "application/placeholder-image"; private final Context mContext; public static class Session { @@ -71,9 +70,7 @@ public class PlaceholderManager { } Uri uri = - Storage.addImage(mContext.getContentResolver(), title, timestamp, null, 0, null, - placeholder, width, height, PLACEHOLDER_MIME_TYPE); - + Storage.addPlaceholder(placeholder, width, height); if (uri == null) { return null; } @@ -88,41 +85,43 @@ public class PlaceholderManager { * session. */ public Session convertToPlaceholder(Uri uri) { - Storage.updateItemMimeType(uri, PLACEHOLDER_MIME_TYPE, mContext.getContentResolver()); return createSessionFromUri(uri); } - public void replacePlaceholder(Session session, Location location, int orientation, - ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) { + /** + * This converts the placeholder in to a real media item + * + * @param session the session that is being finished. + * @param location the location of the image + * @param orientation the orientation of the image + * @param exif the exif of the image + * @param jpeg the bytes of the image + * @param width the width of the image + * @param height the height of the image + * @param mimeType the mime type of the image + */ + public void finishPlaceholder(Session session, Location location, int orientation, + ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) { - Storage.updateImage(session.outputUri, mContext.getContentResolver(), session.outputTitle, + Uri resultUri = Storage.updateImage(session.outputUri, mContext.getContentResolver(), session.outputTitle, session.time, location, orientation, exif, jpeg, width, height, mimeType); - CameraUtil.broadcastNewPicture(mContext, session.outputUri); + CameraUtil.broadcastNewPicture(mContext, resultUri); } /** - * Replace the placeholder with an updated image. + * This changes the temporary placeholder jpeg without writing it to the media store * - * @param session the session to update. - * @param loc the location of the new item. - * @param mimeType the mime-type of the new image. - * @param finalImage whether this is the final image. If set, this will - * broadcast that a new picture has been added. + * @param session the session to update + * @param jpeg the new placeholder bytes + * @param width the width of the image + * @param height the height of the image */ - public void replacePlaceHolder(Session session, Location loc, String mimeType, - boolean finalImage) { - Storage.updateImageFromChangedFile(session.outputUri, loc, mContext.getContentResolver(), - mimeType); - if (finalImage) { - CameraUtil.broadcastNewPicture(mContext, session.outputUri); - } - } + public void replacePlaceholder(Session session, + byte[] jpeg, int width, int height) { - /** - * Removes the placeholder for the given session. - */ - public void removePlaceholder(Session session) { - Storage.deleteImage(mContext.getContentResolver(), session.outputUri); + Storage.replacePlaceholder(session.outputUri, + jpeg, width, height); + CameraUtil.broadcastNewPicture(mContext, session.outputUri); } /** diff --git a/src/com/android/camera/util/FileUtil.java b/src/com/android/camera/util/FileUtil.java index e1fac899a..3d22db1d7 100644 --- a/src/com/android/camera/util/FileUtil.java +++ b/src/com/android/camera/util/FileUtil.java @@ -17,6 +17,8 @@ package com.android.camera.util; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; /** * Common file operations. @@ -44,4 +46,29 @@ public class FileUtil { } return directory.delete(); } + + /** + * Reads the content of a {@code File} as a byte array. + * + * @param file The file to read + * @return The content of the file + * @throws java.io.IOException if the content of the {@code File} could not be read + */ + public static byte[] readFileToByteArray(File file) throws IOException { + int length = (int) file.length(); + byte[] data = new byte[length]; + FileInputStream stream = new FileInputStream(file); + try { + int offset = 0; + while (offset < length) { + offset += stream.read(data, offset, length - offset); + } + } catch (IOException e) { + throw e; + } finally { + stream.close(); + } + return data; + } + } diff --git a/src/com/android/camera/widget/FilmstripView.java b/src/com/android/camera/widget/FilmstripView.java index c794854c9..8f4a86d2e 100644 --- a/src/com/android/camera/widget/FilmstripView.java +++ b/src/com/android/camera/widget/FilmstripView.java @@ -2518,7 +2518,7 @@ public class FilmstripView extends ViewGroup { if (currItem == null) { return false; } - if (!mDataAdapter.canSwipeInFullScreen(currItem.getId())) { + if (inFullScreen() && !mDataAdapter.canSwipeInFullScreen(currItem.getId())) { return false; } hideZoomView();