From 7cfcafdf8f4a439c8fa87b612616fe409979e8a4 Mon Sep 17 00:00:00 2001 From: Ruben Brunk Date: Thu, 17 Oct 2013 15:41:44 -0700 Subject: gcam: Add placeholder image. Bug: 11050749 Change-Id: I374c5919d6da0609fccd21c09775fa91894d5a24 --- src/com/android/camera/CameraActivity.java | 42 +++++ src/com/android/camera/Storage.java | 98 ++++++++--- .../android/camera/WideAnglePanoramaModule.java | 5 +- src/com/android/camera/app/AppManagerFactory.java | 8 +- src/com/android/camera/app/PlaceholderManager.java | 185 +++++++++++++++++++++ .../android/camera/data/InProgressDataWrapper.java | 22 ++- 6 files changed, 334 insertions(+), 26 deletions(-) create mode 100644 src/com/android/camera/app/PlaceholderManager.java (limited to 'src') diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index b971b284f..eb2da1d1b 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -64,6 +64,7 @@ import android.widget.ProgressBar; import android.widget.ShareActionProvider; import com.android.camera.app.AppManagerFactory; +import com.android.camera.app.PlaceholderManager; import com.android.camera.app.PanoramaStitchingManager; import com.android.camera.crop.CropActivity; import com.android.camera.data.CameraDataAdapter; @@ -143,6 +144,7 @@ public class CameraActivity extends Activity private LocalDataAdapter mWrappedDataAdapter; private PanoramaStitchingManager mPanoramaManager; + private PlaceholderManager mPlaceholderManager; private int mCurrentModuleIndex; private CameraModule mCurrentModule; private FrameLayout mAboveFilmstripControlLayout; @@ -688,6 +690,41 @@ public class CameraActivity extends Activity item.setVisible(visible); } + private ImageTaskManager.TaskListener mPlaceholderListener = + new ImageTaskManager.TaskListener() { + + @Override + public void onTaskQueued(String filePath, final Uri imageUri) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + notifyNewMedia(imageUri); + int dataID = mDataAdapter.findDataByContentUri(imageUri); + if (dataID != -1) { + LocalData d = mDataAdapter.getLocalData(dataID); + InProgressDataWrapper newData = new InProgressDataWrapper(d, true); + mDataAdapter.updateData(dataID, newData); + } + } + }); + } + + @Override + public void onTaskDone(String filePath, final Uri imageUri) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + mDataAdapter.refresh(getContentResolver(), imageUri); + } + }); + } + + @Override + public void onTaskProgress(String filePath, Uri imageUri, int progress) { + // Do nothing + } + }; + private ImageTaskManager.TaskListener mStitchingListener = new ImageTaskManager.TaskListener() { @Override @@ -761,6 +798,8 @@ public class CameraActivity extends Activity mDataAdapter.addNewPhoto(cr, uri); } else if (mimeType.startsWith("application/stitching-preview")) { mDataAdapter.addNewPhoto(cr, uri); + } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) { + mDataAdapter.addNewPhoto(cr, uri); } else { android.util.Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri); @@ -966,7 +1005,10 @@ public class CameraActivity extends Activity this.setSystemBarsVisibility(false); mPanoramaManager = AppManagerFactory.getInstance(this) .getPanoramaStitchingManager(); + mPlaceholderManager = AppManagerFactory.getInstance(this) + .getGcamProcessingManager(); mPanoramaManager.addTaskListener(mStitchingListener); + mPlaceholderManager.addTaskListener(mPlaceholderListener); LayoutInflater inflater = getLayoutInflater(); View rootLayout = inflater.inflate(R.layout.camera, null, false); mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root); diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java index a8ce08b93..499d03029 100644 --- a/src/com/android/camera/Storage.java +++ b/src/com/android/camera/Storage.java @@ -43,6 +43,7 @@ public class Storage { 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 = @@ -62,6 +63,18 @@ public class Storage { } } + public static void writeFile(String path, byte[] jpeg, ExifInterface exif) { + if (exif != null) { + try { + exif.writeExif(jpeg, path); + } catch (Exception e) { + Log.e(TAG, "Failed to write data", e); + } + } else { + writeFile(path, jpeg); + } + } + public static void writeFile(String path, byte[] data) { FileOutputStream out = null; try { @@ -73,39 +86,41 @@ public class Storage { try { out.close(); } catch (Exception e) { + Log.e(TAG, "Failed to close file after write", e); } } } - // Save the image and add it to media store. - public static Uri addImage(ContentResolver resolver, String title, - long date, Location location, int orientation, ExifInterface exif, - byte[] jpeg, int width, int height) { - // Save the image. + // Save the image and add it to the MediaStore. + public static Uri addImage(ContentResolver resolver, String title, long date, + Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, + int height) { + + return addImage(resolver, title, date, location, orientation, exif, jpeg, width, height, + LocalData.MIME_TYPE_JPEG); + } + + // Save the image with a given mimeType and add it the MediaStore. + public static Uri addImage(ContentResolver resolver, String title, long date, + Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, + int height, String mimeType) { + String path = generateFilepath(title); - if (exif != null) { - try { - exif.writeExif(jpeg, path); - } catch (Exception e) { - Log.e(TAG, "Failed to write data", e); - } - } else { - writeFile(path, jpeg); - } + writeFile(path, jpeg, exif); return addImage(resolver, title, date, location, orientation, - jpeg.length, path, width, height); + jpeg.length, path, width, height, mimeType); } - // Add the image to media store. - public static Uri addImage(ContentResolver resolver, String title, + // Get a ContentValues object for the given photo data + public static ContentValues getContentValuesForData(String title, long date, Location location, int orientation, int jpegLength, - String path, int width, int height) { - // Insert into MediaStore. - ContentValues values = new ContentValues(9); + String path, int width, int height, String mimeType) { + + ContentValues values = new ContentValues(11); values.put(ImageColumns.TITLE, title); - values.put(ImageColumns.DISPLAY_NAME, title + ".jpg"); + values.put(ImageColumns.DISPLAY_NAME, title + JPEG_POSTFIX); values.put(ImageColumns.DATE_TAKEN, date); - values.put(ImageColumns.MIME_TYPE, LocalData.MIME_TYPE_JPEG); + values.put(ImageColumns.MIME_TYPE, mimeType); // Clockwise rotation in degrees. 0, 90, 180, or 270. values.put(ImageColumns.ORIENTATION, orientation); values.put(ImageColumns.DATA, path); @@ -117,6 +132,17 @@ public class Storage { values.put(ImageColumns.LATITUDE, location.getLatitude()); values.put(ImageColumns.LONGITUDE, location.getLongitude()); } + return values; + } + + // Add the image to media store. + public static Uri addImage(ContentResolver resolver, String title, + long date, Location location, int orientation, int jpegLength, + String path, int width, int height, String mimeType) { + // Insert into MediaStore. + ContentValues values = + getContentValuesForData(title, date, location, orientation, jpegLength, path, + width, height, mimeType); Uri uri = null; try { @@ -132,6 +158,34 @@ public class Storage { return uri; } + // 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) { + String path = generateFilepath(title); + writeFile(path, jpeg, exif); + 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, + long date, Location location, int orientation, int jpegLength, + String path, int width, int height, String mimeType) { + + ContentValues values = + 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); + } + } + public static void deleteImage(ContentResolver resolver, Uri uri) { try { resolver.delete(uri, null, null); diff --git a/src/com/android/camera/WideAnglePanoramaModule.java b/src/com/android/camera/WideAnglePanoramaModule.java index 20e8885d1..8d00c70c8 100644 --- a/src/com/android/camera/WideAnglePanoramaModule.java +++ b/src/com/android/camera/WideAnglePanoramaModule.java @@ -46,6 +46,7 @@ import android.view.WindowManager; import com.android.camera.CameraManager.CameraProxy; import com.android.camera.app.OrientationManager; +import com.android.camera.data.LocalData; import com.android.camera.exif.ExifInterface; import com.android.camera.util.CameraUtil; import com.android.camera.util.UsageStatistics; @@ -759,8 +760,8 @@ public class WideAnglePanoramaModule Storage.writeFile(filepath, jpegData); } int jpegLength = (int) (new File(filepath).length()); - return Storage.addImage(mContentResolver, filename, mTimeTaken, - loc, orientation, jpegLength, filepath, width, height); + return Storage.addImage(mContentResolver, filename, mTimeTaken, loc, orientation, + jpegLength, filepath, width, height, LocalData.MIME_TYPE_JPEG); } return null; } diff --git a/src/com/android/camera/app/AppManagerFactory.java b/src/com/android/camera/app/AppManagerFactory.java index 9c047aa55..43d2a00cd 100644 --- a/src/com/android/camera/app/AppManagerFactory.java +++ b/src/com/android/camera/app/AppManagerFactory.java @@ -16,9 +16,9 @@ package com.android.camera.app; -import android.app.Application; import android.content.Context; + /** * A singleton class which provides application level utility * classes. @@ -35,13 +35,19 @@ public class AppManagerFactory { } private PanoramaStitchingManager mPanoramaStitchingManager; + private PlaceholderManager mGcamProcessingManager; /** No public constructor. */ private AppManagerFactory(Context ctx) { mPanoramaStitchingManager = new PanoramaStitchingManager(ctx); + mGcamProcessingManager = new PlaceholderManager(ctx); } public PanoramaStitchingManager getPanoramaStitchingManager() { return mPanoramaStitchingManager; } + + public PlaceholderManager getGcamProcessingManager() { + return mGcamProcessingManager; + } } diff --git a/src/com/android/camera/app/PlaceholderManager.java b/src/com/android/camera/app/PlaceholderManager.java new file mode 100644 index 000000000..326f0be80 --- /dev/null +++ b/src/com/android/camera/app/PlaceholderManager.java @@ -0,0 +1,185 @@ +/* + * 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. + */ + +package com.android.camera.app; + +import android.content.Context; +import android.graphics.BitmapFactory; +import android.location.Location; +import android.net.Uri; + +import com.android.camera.ImageTaskManager; +import com.android.camera.Storage; +import com.android.camera.exif.ExifInterface; +import com.android.camera.util.CameraUtil; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; + +public class PlaceholderManager implements ImageTaskManager { + private static final String TAG = "PlaceholderManager"; + + public static final String PLACEHOLDER_MIME_TYPE = "application/placeholder-image"; + private final Context mContext; + + final private ArrayList> mListenerRefs; + + public static class Session { + String outputTitle; + Uri outputUri; + long time; + + Session(String title, Uri uri, long timestamp) { + outputTitle = title; + outputUri = uri; + time = timestamp; + } + } + + public PlaceholderManager(Context context) { + mContext = context; + mListenerRefs = new ArrayList>(); + } + + @Override + public void addTaskListener(TaskListener l) { + synchronized (mListenerRefs) { + if (findTaskListener(l) == -1) { + mListenerRefs.add(new WeakReference(l)); + } + } + } + + @Override + public void removeTaskListener(TaskListener l) { + synchronized (mListenerRefs) { + int i = findTaskListener(l); + if (i != -1) { + mListenerRefs.remove(i); + } + } + } + + @Override + public int getTaskProgress(Uri uri) { + return 0; + } + + private int findTaskListener(TaskListener listener) { + int index = -1; + for (int i = 0; i < mListenerRefs.size(); i++) { + TaskListener l = mListenerRefs.get(i).get(); + if (l != null && l == listener) { + index = i; + break; + } + } + return index; + } + + private Iterable getListeners() { + return new Iterable() { + @Override + public Iterator iterator() { + return new ListenerIterator(); + } + }; + } + + private class ListenerIterator implements Iterator { + private int mIndex = 0; + private TaskListener mNext = null; + + @Override + public boolean hasNext() { + while (mNext == null && mIndex < mListenerRefs.size()) { + mNext = mListenerRefs.get(mIndex).get(); + if (mNext == null) { + mListenerRefs.remove(mIndex); + } + } + return mNext != null; + } + + @Override + public TaskListener next() { + hasNext(); // Populates mNext + mIndex++; + TaskListener next = mNext; + mNext = null; + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public Session insertPlaceholder(String title, byte[] placeholder, long timestamp) { + if (title == null || placeholder == null) { + throw new IllegalArgumentException("Null argument passed to insertPlaceholder"); + } + + // Decode bounds + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(placeholder, 0, placeholder.length, options); + int width = options.outWidth; + int height = options.outHeight; + + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Image had bad height/width"); + } + + Uri uri = + Storage.addImage(mContext.getContentResolver(), title, timestamp, null, 0, null, + placeholder, width, height, PLACEHOLDER_MIME_TYPE); + + if (uri == null) { + return null; + } + + String filePath = uri.getPath(); + synchronized (mListenerRefs) { + for (TaskListener l : getListeners()) { + l.onTaskQueued(filePath, uri); + } + } + + return new Session(title, uri, timestamp); + } + + public void replacePlaceholder(Session session, Location location, int orientation, + ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) { + + Storage.updateImage(session.outputUri, mContext.getContentResolver(), session.outputTitle, + session.time, location, orientation, exif, jpeg, width, height, mimeType); + + synchronized (mListenerRefs) { + for (TaskListener l : getListeners()) { + l.onTaskDone(session.outputUri.getPath(), session.outputUri); + } + } + CameraUtil.broadcastNewPicture(mContext, session.outputUri); + } + + public void removePlaceholder(Session session) { + Storage.deleteImage(mContext.getContentResolver(), session.outputUri); + } + +} diff --git a/src/com/android/camera/data/InProgressDataWrapper.java b/src/com/android/camera/data/InProgressDataWrapper.java index 7de617bae..61e87b722 100644 --- a/src/com/android/camera/data/InProgressDataWrapper.java +++ b/src/com/android/camera/data/InProgressDataWrapper.java @@ -22,8 +22,10 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.net.Uri; import android.view.View; +import android.widget.FrameLayout; import com.android.camera.util.PhotoSphereHelper; +import com.android.camera2.R; /** * A wrapper class for in-progress data. Data that's still being processed @@ -34,16 +36,34 @@ import com.android.camera.util.PhotoSphereHelper; public class InProgressDataWrapper implements LocalData { final LocalData mLocalData; + private boolean mHasProgressBar; public InProgressDataWrapper(LocalData wrappedData) { mLocalData = wrappedData; } + public InProgressDataWrapper(LocalData wrappedData, boolean hasProgressBar) { + this(wrappedData); + mHasProgressBar = hasProgressBar; + } + @Override public View getView( Activity a, int width, int height, Drawable placeHolder, LocalDataAdapter adapter) { - return mLocalData.getView(a, width, height, placeHolder, adapter); + View v = mLocalData.getView(a, width, height, placeHolder, adapter); + + if (mHasProgressBar) { + // Return a framelayout with the progressbar and imageview. + FrameLayout frame = new FrameLayout(a); + frame.setLayoutParams(new FrameLayout.LayoutParams(width, height)); + frame.addView(v); + a.getLayoutInflater() + .inflate(R.layout.placeholder_progressbar, frame); + return frame; + } + + return v; } @Override -- cgit v1.2.3