am 05ee74dc: Log memory consumption at end of onCreate (Camera2). Bug: 13280671
* commit '05ee74dcf3b1c8e65229f43441d282fcf89744a6':
Log memory consumption at end of onCreate (Camera2). Bug: 13280671
diff --git a/Android.mk b/Android.mk
index 8779081..4f18789 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,6 +6,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
LOCAL_STATIC_JAVA_LIBRARIES += xmp_toolkit
+LOCAL_STATIC_JAVA_LIBRARIES += glide
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, src_pd)
diff --git a/res/layout/filmstrip_video.xml b/res/layout/filmstrip_video.xml
new file mode 100644
index 0000000..8439b61
--- /dev/null
+++ b/res/layout/filmstrip_video.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/video_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+ <ImageView
+ android:id="@+id/play_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:contentDescription="@string/video_control_play"
+ android:src="@drawable/ic_control_play" />
+</FrameLayout>
\ No newline at end of file
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 405e3a2..52c584c 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -84,6 +84,7 @@
import com.android.camera.app.OrientationManager;
import com.android.camera.app.OrientationManagerImpl;
import com.android.camera.data.CameraDataAdapter;
+import com.android.camera.data.LocalDataViewType;
import com.android.camera.data.FixedLastDataAdapter;
import com.android.camera.data.LocalData;
import com.android.camera.data.LocalDataAdapter;
@@ -129,6 +130,8 @@
import com.android.camera.widget.FilmstripView;
import com.android.camera.widget.Preloader;
import com.android.camera2.R;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.resize.ImageManager;
import com.google.common.logging.eventprotos;
import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
import com.google.common.logging.eventprotos.MediaInteraction;
@@ -141,6 +144,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.Executors;
public class CameraActivity extends Activity
implements AppController, CameraManager.CameraOpenCallback,
@@ -1157,6 +1161,13 @@
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
super.onCreate(state);
+ final Glide glide = Glide.get();
+ if (!glide.isImageManagerSet()) {
+ // We load exclusively large images, so we want fewer threads to minimize jank.
+ glide.setImageManager(new ImageManager.Builder(getApplicationContext())
+ .setResizeService(Executors.newSingleThreadExecutor()));
+ }
+
mOnCreateTime = System.currentTimeMillis();
mAppContext = getApplicationContext();
mSettingsManager = new SettingsManager(mAppContext, this);
@@ -1238,8 +1249,7 @@
mPanoramaViewHelper = new PanoramaViewHelper(this);
mPanoramaViewHelper.onCreate();
// Set up the camera preview first so the preview shows up ASAP.
- mDataAdapter = new CameraDataAdapter(mAppContext,
- new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
+ mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder);
mDataAdapter.setLocalDataListener(mLocalDataListener);
mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
@@ -1289,6 +1299,7 @@
mDataAdapter,
new SimpleViewData(
v,
+ LocalDataViewType.SECURE_ALBUM_PLACEHOLDER,
v.getDrawable().getIntrinsicWidth(),
v.getDrawable().getIntrinsicHeight(),
0, 0));
diff --git a/src/com/android/camera/CameraModule.java b/src/com/android/camera/CameraModule.java
index 41be023..4abfe6b 100644
--- a/src/com/android/camera/CameraModule.java
+++ b/src/com/android/camera/CameraModule.java
@@ -83,8 +83,7 @@
}
}
- @Override
- public void onPreviewInitialDataReceived() {};
+ public void onPreviewInitialDataReceived() {}
/**
* Releases the back camera through {@link CameraProvider}.
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 010ab06..1002720 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -44,6 +44,7 @@
import android.view.KeyEvent;
import android.view.OrientationEventListener;
import android.view.View;
+import android.view.ViewGroup;
import com.android.camera.PhotoModule.NamedImages.NamedEntity;
import com.android.camera.app.AppController;
@@ -64,12 +65,14 @@
import com.android.camera.hardware.HardwareSpec;
import com.android.camera.hardware.HardwareSpecImpl;
import com.android.camera.module.ModuleController;
+import com.android.camera.remote.RemoteCameraModule;
import com.android.camera.settings.SettingsManager;
import com.android.camera.settings.SettingsUtil;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.GcamHelper;
import com.android.camera.util.SessionStatsCollector;
+import com.android.camera.util.SmartCameraHelper;
import com.android.camera.util.UsageStatistics;
import com.android.camera2.R;
import com.google.common.logging.eventprotos;
@@ -90,7 +93,8 @@
MemoryListener,
FocusOverlayManager.Listener,
SensorEventListener,
- SettingsManager.OnSettingChangedListener {
+ SettingsManager.OnSettingChangedListener,
+ RemoteCameraModule {
private static final Log.Tag TAG = new Log.Tag("PhotoModule");
@@ -374,10 +378,11 @@
setCameraState(IDLE);
startFaceDetection();
locationFirstRun();
- }
-
- @Override
- public void onPreviewInitialDataReceived() {
+ // TODO(teresako): Check with Camera team re: starting the Smart Camera here rather than
+ // in the onPreviewInitialDataReceived() function which is no longer being called in
+ // Denali. The original issue of the blue overlay not going away no longer seems to be
+ // an issue. Related CL: https://googleplex-android-review.git.corp.google.com/#/c/428719/.
+ startSmartCamera();
}
// Prompt the user to pick to record location for the very first run of
@@ -631,6 +636,15 @@
});
}
+ private void startSmartCamera() {
+ SmartCameraHelper.register(mCameraDevice, mParameters.getPreviewSize(), mActivity,
+ (ViewGroup) mActivity.findViewById(R.id.camera_app_root));
+ }
+
+ private void stopSmartCamera() {
+ SmartCameraHelper.tearDown();
+ }
+
@Override
public void startFaceDetection() {
if (mFaceDetectionStarted) {
@@ -819,6 +833,9 @@
}
}
+ // Send the taken photo to remote shutter listeners, if any are registered.
+ getServices().getRemoteShutterListener().onPictureTaken(jpegData);
+
// Check this in advance of each shot so we don't add to shutter
// latency. It's true that someone else could write to the SD card
// in the mean time and fill it, but that could have happened
@@ -1254,11 +1271,13 @@
Log.v(TAG, "On resume.");
onResumeTasks();
}
+ getServices().getRemoteShutterListener().onModuleReady(this);
SessionStatsCollector.instance().sessionActive(true);
}
@Override
public void pause() {
+ getServices().getRemoteShutterListener().onModuleExit();
mPaused = true;
SessionStatsCollector.instance().sessionActive(false);
@@ -1535,6 +1554,7 @@
if (mFocusManager != null) {
mFocusManager.onPreviewStopped();
}
+ stopSmartCamera();
SessionStatsCollector.instance().previewActive(false);
}
@@ -1928,4 +1948,9 @@
}
}
}
+
+ @Override
+ public void onRemoteShutterPress() {
+ capture();
+ }
}
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index e35a718..7d3a930 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -49,6 +49,7 @@
import android.view.KeyEvent;
import android.view.OrientationEventListener;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.Toast;
import com.android.camera.app.AppController;
@@ -69,6 +70,7 @@
import com.android.camera.settings.SettingsUtil;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
+import com.android.camera.util.SmartCameraHelper;
import com.android.camera.util.UsageStatistics;
import com.android.camera2.R;
import com.google.common.logging.eventprotos;
@@ -890,6 +892,12 @@
}
@Override
+ public void onPreviewInitialDataReceived() {
+ SmartCameraHelper.register(mCameraDevice, mParameters.getPreviewSize(), mActivity,
+ (ViewGroup) mActivity.findViewById(R.id.camera_app_root));
+ }
+
+ @Override
public void stopPreview() {
if (!mPreviewing) {
return;
@@ -899,6 +907,7 @@
mFocusManager.onPreviewStopped();
}
mPreviewing = false;
+ SmartCameraHelper.tearDown();
}
private void closeCamera() {
diff --git a/src/com/android/camera/app/CameraApp.java b/src/com/android/camera/app/CameraApp.java
index 79b2aa9..9d1b422 100644
--- a/src/com/android/camera/app/CameraApp.java
+++ b/src/com/android/camera/app/CameraApp.java
@@ -22,12 +22,14 @@
import com.android.camera.MediaSaverImpl;
import com.android.camera.processing.ProcessingServiceManager;
+import com.android.camera.remote.RemoteShutterListener;
import com.android.camera.session.CaptureSessionManager;
import com.android.camera.session.CaptureSessionManagerImpl;
import com.android.camera.session.PlaceholderManager;
import com.android.camera.session.SessionStorageManager;
import com.android.camera.session.SessionStorageManagerImpl;
import com.android.camera.util.CameraUtil;
+import com.android.camera.util.RemoteShutterHelper;
import com.android.camera.util.SessionStatsCollector;
import com.android.camera.util.UsageStatistics;
@@ -41,6 +43,7 @@
private SessionStorageManager mSessionStorageManager;
private MemoryManagerImpl mMemoryManager;
private PlaceholderManager mPlaceHolderManager;
+ private RemoteShutterListener mRemoteShutterListener;
@Override
public void onCreate() {
@@ -59,6 +62,7 @@
mSessionManager = new CaptureSessionManagerImpl(mMediaSaver, getContentResolver(),
mPlaceHolderManager, mSessionStorageManager);
mMemoryManager = MemoryManagerImpl.create(getApplicationContext(), mMediaSaver);
+ mRemoteShutterListener = RemoteShutterHelper.create(this);
clearNotifications();
}
@@ -79,6 +83,11 @@
return mMediaSaver;
}
+ @Override
+ public RemoteShutterListener getRemoteShutterListener() {
+ return mRemoteShutterListener;
+ }
+
/**
* Clears all notifications. This cleans up notifications that we might have
* created earlier but remained after a crash.
diff --git a/src/com/android/camera/app/CameraServices.java b/src/com/android/camera/app/CameraServices.java
index 2c0216e..b6c8ec7 100644
--- a/src/com/android/camera/app/CameraServices.java
+++ b/src/com/android/camera/app/CameraServices.java
@@ -16,6 +16,7 @@
package com.android.camera.app;
+import com.android.camera.remote.RemoteShutterListener;
import com.android.camera.session.CaptureSessionManager;
/**
@@ -43,4 +44,10 @@
*/
@Deprecated
public MediaSaver getMediaSaver();
+
+ /**
+ * @return A listener to be informed by events interesting for remote
+ * capture apps. Will never return null.
+ */
+ public RemoteShutterListener getRemoteShutterListener();
}
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index a0387cf..a689d27 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -18,7 +18,6 @@
import android.content.ContentResolver;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.View;
@@ -46,7 +45,7 @@
private Listener mListener;
private LocalDataListener mLocalDataListener;
- private final Drawable mPlaceHolder;
+ private final int mPlaceHolderResourceId;
private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
@@ -54,10 +53,10 @@
private LocalData mLocalDataToDelete;
- public CameraDataAdapter(Context context, Drawable placeHolder) {
+ public CameraDataAdapter(Context context, int placeholderResource) {
mContext = context;
mImages = new LocalDataList();
- mPlaceHolder = placeHolder;
+ mPlaceHolderResourceId = placeholderResource;
}
@Override
@@ -93,6 +92,15 @@
}
@Override
+ public int getItemViewType(int dataId) {
+ if (dataId > mImages.size() || dataId < 0) {
+ return -1;
+ }
+
+ return mImages.get(dataId).getItemViewType().ordinal();
+ }
+
+ @Override
public LocalData getLocalData(int dataID) {
if (dataID < 0 || dataID >= mImages.size()) {
return null;
@@ -117,14 +125,14 @@
}
@Override
- public View getView(Context context, int dataID) {
+ public View getView(Context context, View recycled, int dataID) {
if (dataID >= mImages.size() || dataID < 0) {
return null;
}
return mImages.get(dataID).getView(
- context, mSuggestedWidth, mSuggestedHeight,
- mPlaceHolder.getConstantState().newDrawable(), this, /* inProgress */ false);
+ context, recycled, mSuggestedWidth, mSuggestedHeight,
+ mPlaceHolderResourceId, this, /* inProgress */ false);
}
@Override
@@ -132,7 +140,7 @@
if (dataID >= mImages.size() || dataID < 0) {
return;
}
- mImages.get(dataID).resizeView(context, w, h, view, this);
+ mImages.get(dataID).loadFullImage(context, w, h, view, this);
}
@Override
diff --git a/src/com/android/camera/data/CameraPreviewData.java b/src/com/android/camera/data/CameraPreviewData.java
index 350ee8d..efbbe35 100644
--- a/src/com/android/camera/data/CameraPreviewData.java
+++ b/src/com/android/camera/data/CameraPreviewData.java
@@ -35,7 +35,7 @@
* @param height The height of the camera preview.
*/
public CameraPreviewData(View v, int width, int height) {
- super(v, width, height, -1, -1);
+ super(v, LocalDataViewType.CAMERA_PREVIEW, width, height, -1, -1);
mPreviewLocked = true;
}
diff --git a/src/com/android/camera/data/FixedFirstDataAdapter.java b/src/com/android/camera/data/FixedFirstDataAdapter.java
index f5e8f06..471fd19 100644
--- a/src/com/android/camera/data/FixedFirstDataAdapter.java
+++ b/src/com/android/camera/data/FixedFirstDataAdapter.java
@@ -110,12 +110,20 @@
}
@Override
- public View getView(Context context, int dataID) {
+ public View getView(Context context, View recycled, int dataID) {
if (dataID == 0) {
return mFirstData.getView(
- context, mSuggestedWidth, mSuggestedHeight, null, null, false);
+ context, recycled, mSuggestedWidth, mSuggestedHeight, 0, null, false);
}
- return mAdapter.getView(context, dataID - 1);
+ return mAdapter.getView(context, recycled, dataID - 1);
+ }
+
+ @Override
+ public int getItemViewType(int dataId) {
+ if (dataId == 0) {
+ return mFirstData.getItemViewType().ordinal();
+ }
+ return mAdapter.getItemViewType(dataId);
}
@Override
diff --git a/src/com/android/camera/data/FixedLastDataAdapter.java b/src/com/android/camera/data/FixedLastDataAdapter.java
index c94c1b4..35978d8 100644
--- a/src/com/android/camera/data/FixedLastDataAdapter.java
+++ b/src/com/android/camera/data/FixedLastDataAdapter.java
@@ -112,20 +112,33 @@
}
@Override
- public View getView(Context context, int dataID) {
+ public View getView(Context context, View recycled, int dataID) {
int totalNumber = mAdapter.getTotalNumber();
if (dataID < totalNumber) {
- return mAdapter.getView(context, dataID);
+ return mAdapter.getView(context, recycled, dataID);
} else if (dataID == totalNumber) {
- return mLastData.getView(context,
- mSuggestedWidth, mSuggestedHeight, null, null, false);
+ return mLastData.getView(context, recycled,
+ mSuggestedWidth, mSuggestedHeight, 0, null, false);
}
return null;
}
@Override
+ public int getItemViewType(int dataId) {
+ int totalNumber = mAdapter.getTotalNumber();
+
+ if (dataId < totalNumber) {
+ return mAdapter.getItemViewType(dataId);
+ } else if (dataId == totalNumber) {
+ return mLastData.getItemViewType().ordinal();
+ }
+
+ return -1;
+ }
+
+ @Override
public void resizeView(Context context, int dataID, View view, int w, int h) {
// Do nothing.
}
diff --git a/src/com/android/camera/data/ImageModelLoader.java b/src/com/android/camera/data/ImageModelLoader.java
new file mode 100644
index 0000000..9eea4d3
--- /dev/null
+++ b/src/com/android/camera/data/ImageModelLoader.java
@@ -0,0 +1,31 @@
+package com.android.camera.data;
+
+import android.content.Context;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.loader.bitmap.model.ModelLoader;
+import com.bumptech.glide.loader.bitmap.model.stream.StreamModelLoader;
+import com.bumptech.glide.loader.bitmap.resource.ResourceFetcher;
+
+import java.io.InputStream;
+
+/**
+ * Translates a local data representing an image into an InputStream that can be loaded by the
+ * Glide library.
+ */
+public class ImageModelLoader implements StreamModelLoader<LocalData> {
+ private final ModelLoader<String, InputStream> mPathLoader;
+
+ public ImageModelLoader(Context context) {
+ mPathLoader = Glide.buildStreamModelLoader(String.class, context);
+ }
+
+ @Override
+ public ResourceFetcher<InputStream> getResourceFetcher(LocalData model, int width, int height) {
+ return mPathLoader.getResourceFetcher(model.getPath(), width, height);
+ }
+
+ @Override
+ public String getId(LocalData model) {
+ return mPathLoader.getId(model.getPath()) + model.getSignature();
+ }
+}
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
index 36c602e..eb3a092 100644
--- a/src/com/android/camera/data/LocalData.java
+++ b/src/com/android/camera/data/LocalData.java
@@ -80,9 +80,16 @@
* @param height Height in pixels of rendered view.
* @param adapter Data adapter for this data item.
*/
- View getView(Context context, int width, int height, Drawable placeHolder,
+ View getView(Context context, View recycled, int width, int height, int placeHolderResourceId,
LocalDataAdapter adapter, boolean isInProgress);
+ /** Returns a unique identifier for the view created by this data so that the view
+ * can be reused.
+ *
+ * @see android.widget.BaseAdapter#getItemViewType(int)
+ */
+ LocalDataViewType getItemViewType();
+
/**
* Request resize of View created by getView().
*
@@ -92,7 +99,7 @@
* @param view View created by getView();
* @param adapter Data adapter for this data item.
*/
- public void resizeView(Context context, int width, int height, View view, LocalDataAdapter adapter);
+ public void loadFullImage(Context context, int width, int height, View view, LocalDataAdapter adapter);
/**
* Gets the date when this data is created. The returned date is also used
@@ -126,19 +133,6 @@
/** Removes the data from the storage if possible. */
boolean delete(Context c);
- /**
- * Rotate the image in 90 degrees. This is a no-op for non-image.
- *
- * @param context Used to update the content provider when rotation is done.
- * @param adapter Used to update the view.
- * @param currentDataId Used to update the view.
- * @param clockwise True if the rotation goes clockwise.
- *
- * @return Whether the rotation is supported.
- */
- boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
- int currentDataId, boolean clockwise);
-
void onFullScreen(boolean fullScreen);
/** Returns {@code true} if it allows swipe to filmstrip in full screen. */
@@ -197,6 +191,15 @@
Bundle getMetadata();
/**
+ * Any media store attribute that can potentially change the local data
+ * should be included in this signature, primarily oriented at detecting
+ * edits.
+ *
+ * @return A string identifying the set of changeable attributes.
+ */
+ String getSignature();
+
+ /**
* @return whether the metadata is updated.
*/
public boolean isMetadataUpdated();
diff --git a/src/com/android/camera/data/LocalDataViewType.java b/src/com/android/camera/data/LocalDataViewType.java
new file mode 100644
index 0000000..39a0697
--- /dev/null
+++ b/src/com/android/camera/data/LocalDataViewType.java
@@ -0,0 +1,13 @@
+package com.android.camera.data;
+
+/**
+ * The set of all unique identifiers for all different views that may be shown
+ * in the Filmstrip.
+ */
+public enum LocalDataViewType {
+ CAMERA_PREVIEW,
+ PHOTO,
+ VIDEO,
+ SESSION,
+ SECURE_ALBUM_PLACEHOLDER,
+}
diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java
index f0b696c..2dc7d98 100644
--- a/src/com/android/camera/data/LocalMediaData.java
+++ b/src/com/android/camera/data/LocalMediaData.java
@@ -18,36 +18,29 @@
import android.app.Activity;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.CamcorderProfile;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
-import android.provider.MediaStore.Images;
+import android.view.LayoutInflater;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.camera.Storage;
import com.android.camera.debug.Log;
import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.presenter.target.ImageViewTarget;
+import com.bumptech.glide.presenter.target.Target;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -91,11 +84,11 @@
int width, int height, long sizeInBytes, double latitude,
double longitude) {
mContentId = contentId;
- mTitle = new String(title);
- mMimeType = new String(mimeType);
+ mTitle = title;
+ mMimeType = mimeType;
mDateTakenInSeconds = dateTakenInSeconds;
mDateModifiedInSeconds = dateModifiedInSeconds;
- mPath = new String(path);
+ mPath = path;
mWidth = width;
mHeight = height;
mSizeInBytes = sizeInBytes;
@@ -150,7 +143,7 @@
@Override
public String getTitle() {
- return new String(mTitle);
+ return mTitle;
}
@Override
@@ -205,34 +198,45 @@
}
protected ImageView fillImageView(Context context, ImageView v,
- int decodeWidth, int decodeHeight, Drawable placeHolder,
+ int decodeWidth, int decodeHeight, int placeHolderResourceId,
LocalDataAdapter adapter, boolean isInProgress) {
- v.setScaleType(ImageView.ScaleType.FIT_XY);
- if (placeHolder != null) {
- v.setImageDrawable(placeHolder);
+
+ SizedImageViewTarget target = (SizedImageViewTarget) v.getTag();
+ if (target != null) {
+ target = new SizedImageViewTarget(v);
+ v.setTag(target);
}
- // TODO: Load MediaStore or embedded-in-JPEG-stream thumbnail.
+ Glide.with(context)
+ .load(mPath)
+ .fitCenter()
+ .placeholder(placeHolderResourceId)
+ .into(target);
- BitmapLoadTask task = getBitmapLoadTask(context, v, decodeWidth, decodeHeight,
- context.getContentResolver(), adapter);
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
v.setContentDescription(context.getResources().getString(
R.string.media_date_content_description,
getReadableDate(mDateModifiedInSeconds)));
+
return v;
}
@Override
- public View getView(Context context, int decodeWidth, int decodeHeight, Drawable placeHolder,
- LocalDataAdapter adapter, boolean isInProgress) {
- return fillImageView(context, new ImageView(context), decodeWidth, decodeHeight,
- placeHolder, adapter, isInProgress);
+ public View getView(Context context, View recycled, int decodeWidth, int decodeHeight,
+ int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress) {
+ final ImageView imageView;
+ if (recycled != null) {
+ imageView = (ImageView) recycled;
+ } else {
+ imageView = new ImageView(context);
+ }
+
+ return fillImageView(context, imageView, decodeWidth, decodeHeight,
+ placeHolderResourceId, adapter, isInProgress);
}
@Override
- public void resizeView(Context context, int width, int height, View view,
- LocalDataAdapter adapter) {
+ public void loadFullImage(Context context, int width, int height, View view,
+ LocalDataAdapter adapter) {
// Default is do nothing.
// Can be implemented by sub-classes.
}
@@ -245,7 +249,7 @@
}
@Override
- public void recycle() {
+ public void recycle(View view) {
synchronized (mUsing) {
mUsing = false;
}
@@ -310,15 +314,6 @@
return MetadataLoader.isMetadataCached(this);
}
- /**
- * A background task that loads the provided ImageView with a Bitmap.
- * A Bitmap of maximum size that fits into a decodeWidth x decodeHeight
- * box will be decoded.
- */
- protected abstract BitmapLoadTask getBitmapLoadTask(
- Context context, ImageView v, int decodeWidth, int decodeHeight,
- ContentResolver resolver, LocalDataAdapter adapter);
-
public static final class PhotoData extends LocalMediaData {
private static final Log.Tag TAG = new Log.Tag("PhotoData");
@@ -337,8 +332,6 @@
// GL max texture size: keep bitmaps below this value.
private static final int MAXIMUM_TEXTURE_SIZE = 2048;
- // Maximum pixel count for Bitmaps. To limit RAM consumption.
- private static final int MAXIMUM_DECODE_PIXELS = 4000000;
static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
@@ -368,6 +361,8 @@
/** from MediaStore, can only be 0, 90, 180, 270 */
private final int mOrientation;
+ /** @see #getSignature() */
+ private final String mSignature;
public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
List<LocalData> newPhotos = query(cr, contentUri, QUERY_ALL_MEDIA_ID);
@@ -384,7 +379,7 @@
super(id, title, mimeType, dateTakenInSeconds, dateModifiedInSeconds,
path, width, height, sizeInBytes, latitude, longitude);
mOrientation = orientation;
-
+ mSignature = mimeType + orientation + dateModifiedInSeconds;
}
static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) {
@@ -506,93 +501,58 @@
}
@Override
- public void resizeView(Context context, int w, int h, View v, LocalDataAdapter adapter)
+ public String getSignature() {
+ return mSignature;
+ }
+
+ @Override
+ protected ImageView fillImageView(Context context, final ImageView v, final int decodeWidth,
+ final int decodeHeight, int placeHolderResourceId, LocalDataAdapter adapter,
+ boolean isInProgress) {
+ loadImage(context, v, decodeWidth, decodeHeight, placeHolderResourceId, false);
+ return v;
+ }
+
+ private void loadImage(Context context, ImageView imageView, int decodeWidth,
+ int decodeHeight, int placeHolderResourceId, boolean full) {
+ ThumbTarget thumbTarget = (ThumbTarget) imageView.getTag();
+ if (thumbTarget == null) {
+ thumbTarget = new ThumbTarget(imageView);
+ imageView.setTag(thumbTarget);
+ }
+ // Make sure we've reset all state related to showing thumb and full whenever
+ // we load a thumbnail because loading a thumbnail only happens when we're changing
+ // images.
+ if (!full) {
+ thumbTarget.clearTargets();
+ }
+
+ Glide.with(context)
+ .using(new ImageModelLoader(context))
+ .load(this)
+ .placeholder(placeHolderResourceId)
+ .fitCenter()
+ .into(thumbTarget.getTarget(decodeWidth, decodeHeight, full));
+ }
+
+ @Override
+ public void recycle(View view) {
+ super.recycle(view);
+ if (view != null) {
+ ThumbTarget thumbTarget = (ThumbTarget) view.getTag();
+ thumbTarget.clearTargets();
+ }
+ }
+
+ @Override
+ public LocalDataViewType getItemViewType() {
+ return LocalDataViewType.PHOTO;
+ }
+
+ @Override
+ public void loadFullImage(Context context, int w, int h, View v, LocalDataAdapter adapter)
{
- // This will call PhotoBitmapLoadTask.
- fillImageView(context, (ImageView) v, w, h, null, adapter, false);
- }
-
- @Override
- protected BitmapLoadTask getBitmapLoadTask(Context context, ImageView v, int decodeWidth,
- int decodeHeight, ContentResolver resolver, LocalDataAdapter adapter) {
- return new PhotoBitmapLoadTask(context, v, decodeWidth, decodeHeight, resolver,
- adapter);
- }
-
- @Override
- public boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
- int currentDataId, boolean clockwise) {
- RotationTask task = new RotationTask(context, adapter,
- currentDataId, clockwise);
- task.execute(this);
- return true;
- }
-
- private final class PhotoBitmapLoadTask extends BitmapLoadTask {
- private final int mDecodeWidth;
- private final int mDecodeHeight;
- private final Context mContext;
- private final LocalDataAdapter mAdapter;
-
- private boolean mNeedsRefresh;
-
- public PhotoBitmapLoadTask(Context context, ImageView v, int decodeWidth,
- int decodeHeight, ContentResolver resolver, LocalDataAdapter adapter) {
- super(context, v);
- mDecodeWidth = decodeWidth;
- mDecodeHeight = decodeHeight;
- mContext = context;
- mAdapter = adapter;
- }
-
- @Override
- protected Bitmap doInBackground(Void... v) {
- // TODO: Implement image cache, which can verify image dims.
-
- // For correctness, double check image size here.
- // This only takes 1% of full decode time.
- Point decodedSize = LocalDataUtil.decodeBitmapDimension(mPath);
-
- // If the width and height are valid and not matching the values
- // from MediaStore, then update the MediaStore. This only
- // happens when the MediaStore has been told incorrect values.
- if (decodedSize != null && (decodedSize.x != mWidth || decodedSize.y != mHeight)) {
- ContentValues values = new ContentValues();
- values.put(Images.Media.WIDTH, decodedSize.x);
- values.put(Images.Media.HEIGHT, decodedSize.y);
- mContext.getContentResolver().update(getUri(), values, null, null);
- mNeedsRefresh = true;
- Log.w(TAG, "Uri " + getUri() + " has been updated with" +
- " the correct size!");
- return null;
- }
-
- InputStream stream;
- Bitmap bitmap;
- try {
- stream = new FileInputStream(mPath);
- bitmap = LocalDataUtil
- .loadImageThumbnailFromStream(stream, mWidth, mHeight, mDecodeWidth,
- mDecodeHeight, mOrientation, MAXIMUM_DECODE_PIXELS);
- stream.close();
- } catch (FileNotFoundException e) {
- Log.v(TAG, "File not found:" + mPath);
- bitmap = null;
- } catch (IOException e) {
- Log.v(TAG, "IOException for " + mPath, e);
- bitmap = null;
- }
-
- return bitmap;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- super.onPostExecute(bitmap);
- if (mNeedsRefresh && mAdapter != null) {
- mAdapter.refresh(getUri());
- }
- }
+ loadImage(context, (ImageView) v, w, h, 0, true);
}
private static class PhotoDataBuilder implements CursorToLocalData {
@@ -601,6 +561,67 @@
return LocalMediaData.PhotoData.buildFromCursor(cursor);
}
}
+
+ /**
+ * In the filmstrip we want to load a small version of an image and then load a
+ * larger version when we switch to full screen mode. This class manages
+ * that transition and loading process by keeping one target for the smaller thumbnail
+ * and a second target for the larger version.
+ */
+ private static class ThumbTarget {
+ private final SizedImageViewTarget mThumbTarget;
+ private final SizedImageViewTarget mFullTarget;
+ private boolean fullLoaded = false;
+
+ public ThumbTarget(ImageView imageView) {
+ mThumbTarget = new SizedImageViewTarget(imageView) {
+ @Override
+ public void onImageReady(Bitmap bitmap) {
+ // If we manage to load the thumb after the full, we don't
+ // want to replace the higher quality full with the thumb.
+ if (!fullLoaded) {
+ super.onImageReady(bitmap);
+ }
+ }
+ };
+
+ mFullTarget = new SizedImageViewTarget(imageView) {
+
+ @Override
+ public void onImageReady(Bitmap bitmap) {
+ // When the full is loaded, we no longer need the thumb.
+ fullLoaded = true;
+ Glide.cancel(mThumbTarget);
+ super.onImageReady(bitmap);
+ }
+
+ @Override
+ public void setPlaceholder(Drawable placeholder) {
+ // We always load the thumb first which will set the placeholder.
+ // If we were to set the placeholder here too, instead of showing
+ // the thumb while we load the full, we will instead revert back
+ // to the placeholder.
+ }
+ };
+ }
+
+ public Target getTarget(int width, int height, boolean full) {
+ final SizedImageViewTarget result = full ? mFullTarget : mThumbTarget;
+ // Limit the target size so we don't load a bitmap larger than the max size we
+ // can display.
+ width = Math.min(width, MAXIMUM_TEXTURE_SIZE);
+ height = Math.min(height, MAXIMUM_TEXTURE_SIZE);
+
+ result.setSize(width, height);
+ return result;
+ }
+
+ public void clearTargets() {
+ fullLoaded = false;
+ Glide.cancel(mThumbTarget);
+ Glide.cancel(mFullTarget);
+ }
+ }
}
public static final class VideoData extends LocalMediaData {
@@ -612,11 +633,10 @@
public static final int COL_DATA = 5;
public static final int COL_WIDTH = 6;
public static final int COL_HEIGHT = 7;
- public static final int COL_RESOLUTION = 8;
- public static final int COL_SIZE = 9;
- public static final int COL_LATITUDE = 10;
- public static final int COL_LONGITUDE = 11;
- public static final int COL_DURATION = 12;
+ public static final int COL_SIZE = 8;
+ public static final int COL_LATITUDE = 9;
+ public static final int COL_LONGITUDE = 10;
+ public static final int COL_DURATION = 11;
static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
@@ -638,15 +658,15 @@
MediaStore.Video.VideoColumns.DATA, // 5, string
MediaStore.Video.VideoColumns.WIDTH, // 6, int
MediaStore.Video.VideoColumns.HEIGHT, // 7, int
- MediaStore.Video.VideoColumns.RESOLUTION, // 8 string
- MediaStore.Video.VideoColumns.SIZE, // 9 long
- MediaStore.Video.VideoColumns.LATITUDE, // 10 double
- MediaStore.Video.VideoColumns.LONGITUDE, // 11 double
- MediaStore.Video.VideoColumns.DURATION // 12 long
+ MediaStore.Video.VideoColumns.SIZE, // 8 long
+ MediaStore.Video.VideoColumns.LATITUDE, // 9 double
+ MediaStore.Video.VideoColumns.LONGITUDE, // 10 double
+ MediaStore.Video.VideoColumns.DURATION // 11 long
};
/** The duration in milliseconds. */
private final long mDurationInSeconds;
+ private final String mSignature;
public VideoData(long id, String title, String mimeType,
long dateTakenInSeconds, long dateModifiedInSeconds,
@@ -655,6 +675,7 @@
super(id, title, mimeType, dateTakenInSeconds, dateModifiedInSeconds,
path, width, height, sizeInBytes, latitude, longitude);
mDurationInSeconds = durationInSeconds;
+ mSignature = mimeType + dateModifiedInSeconds;
}
public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
@@ -798,28 +819,54 @@
}
@Override
- public View getView(final Context context,
- int decodeWidth, int decodeHeight, Drawable placeHolder,
+ public String getSignature() {
+ return mSignature;
+ }
+
+ @Override
+ protected ImageView fillImageView(Context context, final ImageView v, final int decodeWidth,
+ final int decodeHeight, int placeHolderResourceId, LocalDataAdapter adapter,
+ boolean isInProgress) {
+ SizedImageViewTarget target = (SizedImageViewTarget) v.getTag();
+ if (target == null) {
+ target = new SizedImageViewTarget(v);
+ v.setTag(target);
+ }
+ target.setSize(decodeWidth, decodeHeight);
+
+ Glide.with(context)
+ .using(new VideoModelLoader(context))
+ .loadFromVideo(this)
+ .placeholder(placeHolderResourceId)
+ .fitCenter()
+ .into(target);
+
+ return v;
+ }
+
+ @Override
+ public View getView(final Context context, View recycled,
+ int decodeWidth, int decodeHeight, int placeHolderResourceId,
LocalDataAdapter adapter, boolean isInProgress) {
- // ImageView for the bitmap.
- ImageView iv = new ImageView(context);
- iv.setLayoutParams(new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
- fillImageView(context, iv, decodeWidth, decodeHeight, placeHolder,
- adapter, isInProgress);
+ final VideoViewHolder viewHolder;
+ final View result;
+ if (recycled != null) {
+ result = recycled;
+ viewHolder = (VideoViewHolder) recycled.getTag();
+ } else {
+ result = LayoutInflater.from(context).inflate(R.layout.filmstrip_video, null);
+ ImageView videoView = (ImageView) result.findViewById(R.id.video_view);
+ ImageView playButton = (ImageView) result.findViewById(R.id.play_button);
+ viewHolder = new VideoViewHolder(videoView, playButton);
+ result.setTag(viewHolder);
+ }
+
+ fillImageView(context, viewHolder.mVideoView, decodeWidth, decodeHeight,
+ placeHolderResourceId, adapter, isInProgress);
// ImageView for the play icon.
- ImageView icon = new ImageView(context);
- icon.setImageResource(R.drawable.ic_control_play);
- icon.setScaleType(ImageView.ScaleType.CENTER);
- icon.setLayoutParams(new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
- icon.setContentDescription(
- context.getResources().getString(R.string.video_control_play));
- icon.setOnClickListener(new View.OnClickListener() {
+ viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO: refactor this into activities to avoid this class
@@ -828,76 +875,20 @@
}
});
- FrameLayout f = new FrameLayout(context);
- f.addView(iv);
- f.addView(icon);
- return f;
+ return result;
}
@Override
- protected BitmapLoadTask getBitmapLoadTask(
- Context context, ImageView v, int decodeWidth, int decodeHeight,
- ContentResolver resolver, LocalDataAdapter adapter) {
- // TODO: Support isInProgressSession for videos when we need it.
- return new VideoBitmapLoadTask(context, v);
- }
-
- private final class VideoBitmapLoadTask extends BitmapLoadTask {
-
- public VideoBitmapLoadTask(Context context, ImageView v) {
- super(context, v);
- }
-
- @Override
- protected Bitmap doInBackground(Void... v) {
- if (isCancelled() || !isUsing()) {
- return null;
- }
- Bitmap bitmap = null;
- bitmap = LocalDataUtil.loadVideoThumbnail(mPath);
-
- if (isCancelled() || !isUsing()) {
- return null;
- }
- return bitmap;
- }
+ public void recycle(View view) {
+ super.recycle(view);
+ VideoViewHolder videoViewHolder = (VideoViewHolder) view.getTag();
+ Target target = (Target) videoViewHolder.mVideoView.getTag();
+ Glide.cancel(target);
}
@Override
- public boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
- int currentDataId, boolean clockwise) {
- // We don't support rotation for video data.
- Log.e(TAG, "Unexpected call in rotate90Degrees()");
- return false;
- }
- }
-
- /**
- * An {@link AsyncTask} class that loads the bitmap in the background
- * thread. Sub-classes should implement their own
- * {@code BitmapLoadTask#doInBackground(Void...)}."
- */
- protected abstract class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> {
- protected final Context mContext;
- protected ImageView mView;
-
- protected BitmapLoadTask(Context context, ImageView v) {
- mContext = context;
- mView = v;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (!isUsing()) {
- return;
- }
- if (bitmap == null) {
- Log.e(TAG, "Failed decoding bitmap for file:" + mPath);
- return;
- }
- BitmapDrawable d = new BitmapDrawable(mContext.getResources(), bitmap);
- mView.setScaleType(ImageView.ScaleType.FIT_XY);
- mView.setImageDrawable(d);
+ public LocalDataViewType getItemViewType() {
+ return LocalDataViewType.VIDEO;
}
}
@@ -909,4 +900,39 @@
}
}
+ private static class VideoViewHolder {
+ private final ImageView mVideoView;
+ private final ImageView mPlayButton;
+
+ public VideoViewHolder(ImageView videoView, ImageView playButton) {
+ mVideoView = videoView;
+ mPlayButton = playButton;
+ }
+ }
+
+ /**
+ * Normally Glide will figure out the necessary size based on the view
+ * the image is being loaded into. In filmstrip the view isn't immediately
+ * laid out after being requested from the data, which can cause Glide to give
+ * up on obtaining the view dimensions. To avoid that, we manually set the
+ * dimensions.
+ */
+ private static class SizedImageViewTarget extends ImageViewTarget {
+ private int mWidth;
+ private int mHeight;
+
+ public SizedImageViewTarget(ImageView imageView) {
+ super(imageView);
+ }
+
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public void getSize(final SizeReadyCallback cb) {
+ cb.onSizeReady(mWidth, mHeight);
+ }
+ }
}
diff --git a/src/com/android/camera/data/LocalSessionData.java b/src/com/android/camera/data/LocalSessionData.java
index f20ca52..585fa92 100644
--- a/src/com/android/camera/data/LocalSessionData.java
+++ b/src/com/android/camera/data/LocalSessionData.java
@@ -53,12 +53,18 @@
}
@Override
- public View getView(Context context, int width, int height, Drawable placeHolder,
- LocalDataAdapter adapter, boolean isInProgress) {
- //TODO do this on a background thread
+ public View getView(Context context, View recycled, int width, int height,
+ int placeholderResourcedId, LocalDataAdapter adapter, boolean isInProgress) {
+ final ImageView imageView;
+ if (recycled != null) {
+ imageView = (ImageView) recycled;
+ } else {
+ imageView = new ImageView(context);
+ }
+
byte[] jpegData = Storage.getJpegForSession(mUri);
+ //TODO do this on a background thread
Bitmap bmp = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
- ImageView imageView = new ImageView(context);
imageView.setImageBitmap(bmp);
imageView.setContentDescription(context.getResources().getString(
R.string.media_processing_content_description));
@@ -66,8 +72,13 @@
}
@Override
- public void resizeView(Context context, int width, int height, View view,
- LocalDataAdapter adapter) {
+ public LocalDataViewType getItemViewType() {
+ return LocalDataViewType.SESSION;
+ }
+
+ @Override
+ public void loadFullImage(Context context, int width, int height, View view,
+ LocalDataAdapter adapter) {
}
@@ -97,11 +108,6 @@
}
@Override
- public boolean rotate90Degrees(Context context, LocalDataAdapter adapter, int currentDataId, boolean clockwise) {
- return false;
- }
-
- @Override
public void onFullScreen(boolean fullScreen) {
}
@@ -152,6 +158,11 @@
}
@Override
+ public String getSignature() {
+ return "";
+ }
+
+ @Override
public boolean isMetadataUpdated() {
return true;
}
@@ -192,7 +203,7 @@
}
@Override
- public void recycle() {
+ public void recycle(View view) {
}
diff --git a/src/com/android/camera/data/SimpleViewData.java b/src/com/android/camera/data/SimpleViewData.java
index 1101857..74f1073 100644
--- a/src/com/android/camera/data/SimpleViewData.java
+++ b/src/com/android/camera/data/SimpleViewData.java
@@ -41,11 +41,13 @@
private final long mDateModified;
private final Bundle mMetaData;
private final Uri mUri;
+ private final LocalDataViewType mItemViewType;
public SimpleViewData(
- View v, int width, int height,
+ View v, LocalDataViewType viewType, int width, int height,
int dateTaken, int dateModified) {
mView = v;
+ mItemViewType = viewType;
mWidth = width;
mHeight = height;
mDateTaken = dateTaken;
@@ -93,6 +95,11 @@
}
@Override
+ public LocalDataViewType getItemViewType() {
+ return mItemViewType;
+ }
+
+ @Override
public String getPath() {
return "";
}
@@ -128,13 +135,13 @@
}
@Override
- public View getView(Context context, int width, int height, Drawable placeHolder,
+ public View getView(Context context, View recycled, int width, int height, int placeHolderResourceId,
LocalDataAdapter adapter, boolean isInProgressSession) {
return mView;
}
@Override
- public void resizeView(Context context, int w, int h, View view, LocalDataAdapter adapter) {
+ public void loadFullImage(Context context, int w, int h, View view, LocalDataAdapter adapter) {
// do nothing.
}
@@ -144,8 +151,8 @@
}
@Override
- public void recycle() {
- // do nothing.
+ public void recycle(View view) {
+ // Do nothing.
}
@Override
@@ -174,14 +181,6 @@
}
@Override
- public boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
- int currentDataId, boolean clockwise) {
- // We don't support rotation for SimpleViewData.
- Log.w(TAG, "Unexpected call in rotate90Degrees()");
- return false;
- }
-
- @Override
public long getSizeInBytes() {
return 0;
}
@@ -197,6 +196,11 @@
}
@Override
+ public String getSignature() {
+ return "";
+ }
+
+ @Override
public boolean isMetadataUpdated() {
return true;
}
diff --git a/src/com/android/camera/data/VideoModelLoader.java b/src/com/android/camera/data/VideoModelLoader.java
new file mode 100644
index 0000000..b140037
--- /dev/null
+++ b/src/com/android/camera/data/VideoModelLoader.java
@@ -0,0 +1,29 @@
+package com.android.camera.data;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.loader.bitmap.model.ModelLoader;
+import com.bumptech.glide.loader.bitmap.model.file_descriptor.FileDescriptorModelLoader;
+import com.bumptech.glide.loader.bitmap.resource.ResourceFetcher;
+
+/**
+ * Translates a video data into an InputStream for the Glide library.
+ */
+public class VideoModelLoader implements FileDescriptorModelLoader<LocalMediaData.VideoData> {
+ private final ModelLoader<String, ParcelFileDescriptor> mPathLoader;
+
+ public VideoModelLoader(Context context) {
+ mPathLoader = Glide.buildFileDescriptorModelLoader(String.class, context);
+ }
+
+ @Override
+ public ResourceFetcher<ParcelFileDescriptor> getResourceFetcher(LocalMediaData.VideoData model,
+ int width, int height) {
+ return mPathLoader.getResourceFetcher(model.getPath(), width, height);
+ }
+
+ public String getId(LocalMediaData.VideoData model) {
+ return mPathLoader.getId(model.getPath()) + model.getSignature();
+ }
+}
diff --git a/src/com/android/camera/filmstrip/DataAdapter.java b/src/com/android/camera/filmstrip/DataAdapter.java
index e896908..e1ca61d 100644
--- a/src/com/android/camera/filmstrip/DataAdapter.java
+++ b/src/com/android/camera/filmstrip/DataAdapter.java
@@ -80,11 +80,19 @@
* Returns the view to visually present the image data.
*
* @param context The {@link android.content.Context} to create the view.
+ * @param recycled A view that can be reused if one is available, or null.
* @param dataID The ID of the image data to be presented.
* @return The view representing the image data. Null if unavailable or
* the {@code dataID} is out of range.
*/
- public View getView(Context context, int dataID);
+ public View getView(Context context, View recycled, int dataID);
+
+ /** Returns a unique identifier for the view created by this data so that the view
+ * can be reused.
+ *
+ * @see android.widget.BaseAdapter#getItemViewType(int)
+ */
+ public int getItemViewType(int dataId);
/**
* Resizes the view used to visually present the image data. This is
diff --git a/src/com/android/camera/filmstrip/ImageData.java b/src/com/android/camera/filmstrip/ImageData.java
index 1bbea11..f080f8e 100644
--- a/src/com/android/camera/filmstrip/ImageData.java
+++ b/src/com/android/camera/filmstrip/ImageData.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.net.Uri;
+import android.view.View;
/**
* Common interface for all images in the filmstrip.
@@ -111,7 +112,7 @@
* function after its corresponding view is removed from the view
* hierarchy.
*/
- public void recycle();
+ public void recycle(View view);
/**
* @return The URI of this data. Must be a unique one and not null.
diff --git a/src/com/android/camera/module/ModuleController.java b/src/com/android/camera/module/ModuleController.java
index ea6b7e4..49d8b63 100644
--- a/src/com/android/camera/module/ModuleController.java
+++ b/src/com/android/camera/module/ModuleController.java
@@ -75,11 +75,6 @@
public void onPreviewVisibilityChanged(int visibility);
/**
- * Called when the first preview data is received.
- */
- public void onPreviewInitialDataReceived();
-
- /**
* Called when the framework layout orientation changed.
*
* @param isLandscape Whether the new orientation is landscape or portrait.
diff --git a/src/com/android/camera/remote/RemoteCameraModule.java b/src/com/android/camera/remote/RemoteCameraModule.java
new file mode 100644
index 0000000..d094224
--- /dev/null
+++ b/src/com/android/camera/remote/RemoteCameraModule.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ * 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.remote;
+
+/**
+ * Modules implementing this interface signal that they do support remote
+ * shutter. Such modules need to signal to the remote interface that they are
+ * available or unavailable through calling
+ * {@link RemoteShutterListener#onModuleReady(RemoteCameraModule)} and
+ * {@link RemoteShutterListener#onModuleExit()}.
+ */
+public interface RemoteCameraModule {
+ /**
+ * Called when a remote client wants the module to take a picture. The
+ * module should trigger a capture and send the result via
+ * {@link RemoteShutterListener#onPictureTaken(byte[])}.
+ */
+ void onRemoteShutterPress();
+}
diff --git a/src/com/android/camera/remote/RemoteShutterListener.java b/src/com/android/camera/remote/RemoteShutterListener.java
new file mode 100644
index 0000000..b24a4a1
--- /dev/null
+++ b/src/com/android/camera/remote/RemoteShutterListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ * 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.remote;
+
+/**
+ * Classes implementing this interface can be informed when events relevant to
+ * remote shutter apps are occurring.
+ */
+public interface RemoteShutterListener {
+ /**
+ * Called when the module is active and ready for shutter presses.
+ */
+ void onModuleReady(RemoteCameraModule module);
+
+ /**
+ * Called when module is no longer ready for shutter presses.
+ */
+ void onModuleExit();
+
+ /**
+ * Called when a picture is taken.
+ */
+ void onPictureTaken(byte[] photoData);
+}
diff --git a/src/com/android/camera/widget/FilmstripView.java b/src/com/android/camera/widget/FilmstripView.java
index 460e302..fb84777 100644
--- a/src/com/android/camera/widget/FilmstripView.java
+++ b/src/com/android/camera/widget/FilmstripView.java
@@ -30,6 +30,7 @@
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -46,7 +47,11 @@
import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
public class FilmstripView extends ViewGroup {
private static final Log.Tag TAG = new Log.Tag("FilmstripView");
@@ -105,6 +110,8 @@
private float mOverScaleFactor = 1f;
private boolean mFullScreenUIHidden = false;
+ private SparseArray<Queue<View>> recycledViews = new SparseArray<Queue<View>>();
+
/**
* A helper class to tract and calculate the view coordination.
@@ -353,7 +360,8 @@
public void removeViewFromHierarchy(boolean force) {
if (force || mData.getViewType() != ImageData.VIEW_TYPE_STICKY) {
removeView(mView);
- mData.recycle();
+ mData.recycle(mView);
+ recycleView(mView, mDataId);
} else {
setVisibility(View.INVISIBLE);
}
@@ -595,6 +603,26 @@
}
}
+ private void recycleView(View view, int dataId) {
+ final int viewType = mDataAdapter.getItemViewType(dataId);
+ Queue<View> recycledViewsForType = recycledViews.get(viewType);
+ if (recycledViewsForType == null) {
+ recycledViewsForType = new ArrayDeque<View>();
+ recycledViews.put(viewType, recycledViewsForType);
+ }
+ recycledViewsForType.offer(view);
+ }
+
+ private View getRecycledView(int dataId) {
+ final int viewType = mDataAdapter.getItemViewType(dataId);
+ Queue<View> recycledViewsForType = recycledViews.get(viewType);
+ View result = null;
+ if (recycledViewsForType != null) {
+ result = recycledViewsForType.poll();
+ }
+ return result;
+ }
+
/**
* Returns the controller.
*
@@ -709,11 +737,13 @@
return null;
}
- int maxEdge = (int) ((float) Math.max(this.getHeight(), this.getWidth())
- * FILM_STRIP_SCALE);
- mDataAdapter.suggestViewSizeBound(maxEdge, maxEdge);
+ int width = Math.round(mScale * getWidth());
+ int height = Math.round(mScale * getHeight());
+ mDataAdapter.suggestViewSizeBound(width, height);
+
data.prepare();
- View v = mDataAdapter.getView(mActivity, dataID);
+ View recycled = getRecycledView(dataID);
+ View v = mDataAdapter.getView(mActivity, recycled, dataID);
if (v == null) {
return null;
}
@@ -925,6 +955,7 @@
curr.layoutWithTranslationX(mDrawArea, mCenterX, mScale);
curr.setAlpha(1f);
+ curr.setVisibility(VISIBLE);
if (inFullScreen()) {
curr.setTranslationX(translate * (mCenterX - currCenterX) / (nextCenterX - currCenterX));
@@ -1409,7 +1440,9 @@
private void setDataAdapter(DataAdapter adapter) {
mDataAdapter = adapter;
- mDataAdapter.suggestViewSizeBound(getMeasuredWidth(), getMeasuredHeight());
+ int maxEdge = (int) ((float) Math.max(this.getHeight(), this.getWidth())
+ * FILM_STRIP_SCALE);
+ mDataAdapter.suggestViewSizeBound(maxEdge, maxEdge);
mDataAdapter.setListener(new DataAdapter.Listener() {
@Override
public void onDataLoaded() {
diff --git a/src_pd/com/android/camera/util/RemoteShutterHelper.java b/src_pd/com/android/camera/util/RemoteShutterHelper.java
new file mode 100644
index 0000000..78028ad
--- /dev/null
+++ b/src_pd/com/android/camera/util/RemoteShutterHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ * 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.util;
+
+import android.content.Context;
+
+import com.android.camera.remote.RemoteCameraModule;
+import com.android.camera.remote.RemoteShutterListener;
+
+public class RemoteShutterHelper {
+ public static RemoteShutterListener create(Context context) {
+ return new RemoteShutterListener() {
+ @Override
+ public void onPictureTaken(byte[] photoData) {
+ }
+
+ @Override
+ public void onModuleReady(RemoteCameraModule module) {
+ }
+
+ @Override
+ public void onModuleExit() {
+ }
+ };
+ }
+}
diff --git a/src_pd/com/android/camera/util/SmartCameraHelper.java b/src_pd/com/android/camera/util/SmartCameraHelper.java
new file mode 100644
index 0000000..43c0e83
--- /dev/null
+++ b/src_pd/com/android/camera/util/SmartCameraHelper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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.util;
+
+import android.app.Activity;
+import android.hardware.Camera;
+import android.view.ViewGroup;
+
+import com.android.camera.app.CameraManager.CameraProxy;
+
+public class SmartCameraHelper {
+ public static void register(CameraProxy camera, Camera.Size previewSize, Activity activity,
+ ViewGroup parentView) {
+ }
+
+ public static void tearDown() {
+ }
+}