summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/CameraSettings.java4
-rw-r--r--src/com/android/camera/Exif.java45
-rw-r--r--src/com/android/camera/PanoramaModule.java51
-rw-r--r--src/com/android/camera/PhotoUI.java3
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java330
-rw-r--r--src/com/android/camera/ui/CameraSwitcher.java1
-rw-r--r--src/com/android/camera/ui/FilmStripView.java421
-rw-r--r--src/com/android/gallery3d/app/PhotoPage.java4
-rw-r--r--src/com/android/gallery3d/data/Exif.java44
-rw-r--r--src/com/android/gallery3d/data/LocalImage.java74
-rw-r--r--src/com/android/gallery3d/data/MediaDetails.java74
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java1
-rw-r--r--src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java24
-rw-r--r--src/com/android/gallery3d/filtershow/cache/ImageLoader.java50
-rw-r--r--src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java10
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java129
-rw-r--r--src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java181
-rw-r--r--src/com/android/photos/AlbumSetFragment.java126
-rw-r--r--src/com/android/photos/GalleryActivity.java20
-rw-r--r--src/com/android/photos/MultiChoiceManager.java190
-rw-r--r--src/com/android/photos/PhotoSetFragment.java139
-rw-r--r--src/com/android/photos/SelectionManager.java54
-rw-r--r--src/com/android/photos/adapters/AlbumSetCursorAdapter.java89
-rw-r--r--src/com/android/photos/data/AlbumSetLoader.java7
-rw-r--r--src/com/android/photos/data/PhotoSetLoader.java17
-rw-r--r--src/com/android/photos/shims/LoaderCompatShim.java5
-rw-r--r--src/com/android/photos/shims/MediaItemsLoader.java37
-rw-r--r--src/com/android/photos/shims/MediaSetLoader.java48
28 files changed, 1582 insertions, 596 deletions
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java
index 31d31e128..64f3681de 100644
--- a/src/com/android/camera/CameraSettings.java
+++ b/src/com/android/camera/CameraSettings.java
@@ -235,8 +235,8 @@ public class CameraSettings {
private void buildExposureCompensation(
PreferenceGroup group, IconListPreference exposure) {
- int max = mParameters.getMaxExposureCompensation();
- int min = mParameters.getMinExposureCompensation();
+ int max = Math.min(3, mParameters.getMaxExposureCompensation());
+ int min = Math.max(-3, mParameters.getMinExposureCompensation());
if (max == 0 && min == 0) {
removePreference(group, exposure.getKey());
return;
diff --git a/src/com/android/camera/Exif.java b/src/com/android/camera/Exif.java
index 605556599..ee39d675e 100644
--- a/src/com/android/camera/Exif.java
+++ b/src/com/android/camera/Exif.java
@@ -18,9 +18,7 @@ package com.android.camera;
import android.util.Log;
-import com.android.gallery3d.exif.ExifInvalidFormatException;
-import com.android.gallery3d.exif.ExifParser;
-import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.ExifInterface;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -31,44 +29,23 @@ public class Exif {
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(byte[] jpeg) {
- if (jpeg == null) return 0;
+ if (jpeg == null) {
+ return 0;
+ }
+ ExifInterface exif = new ExifInterface();
InputStream is = new ByteArrayInputStream(jpeg);
-
try {
- ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0);
- int event = parser.next();
- while(event != ExifParser.EVENT_END) {
- if (event == ExifParser.EVENT_NEW_TAG) {
- ExifTag tag = parser.getTag();
- if (tag.getTagId() == ExifTag.TAG_ORIENTATION &&
- tag.hasValue()) {
- int orient = (int) tag.getValueAt(0);
- switch (orient) {
- case ExifTag.Orientation.TOP_LEFT:
- return 0;
- case ExifTag.Orientation.BOTTOM_LEFT:
- return 180;
- case ExifTag.Orientation.RIGHT_TOP:
- return 90;
- case ExifTag.Orientation.RIGHT_BOTTOM:
- return 270;
- default:
- Log.i(TAG, "Unsupported orientation");
- return 0;
- }
- }
- }
- event = parser.next();
+ exif.readExif(is);
+ Integer val = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (val == null) {
+ return 0;
+ } else {
+ return ExifInterface.getRotationForOrientationValue(val.shortValue());
}
- Log.i(TAG, "Orientation not found");
- return 0;
} catch (IOException e) {
Log.w(TAG, "Failed to read EXIF orientation", e);
return 0;
- } catch (ExifInvalidFormatException e) {
- Log.w(TAG, "Failed to read EXIF orientation", e);
- return 0;
}
}
}
diff --git a/src/com/android/camera/PanoramaModule.java b/src/com/android/camera/PanoramaModule.java
index 623d96dc6..dc27f56f1 100644
--- a/src/com/android/camera/PanoramaModule.java
+++ b/src/com/android/camera/PanoramaModule.java
@@ -60,10 +60,7 @@ import com.android.camera.ui.PopupManager;
import com.android.camera.ui.Rotatable;
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.exif.ExifData;
-import com.android.gallery3d.exif.ExifInvalidFormatException;
-import com.android.gallery3d.exif.ExifOutputStream;
-import com.android.gallery3d.exif.ExifReader;
+import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.exif.ExifTag;
import com.android.gallery3d.ui.GLRootView;
import com.android.gallery3d.util.UsageStatistics;
@@ -913,31 +910,18 @@ public class PanoramaModule implements CameraModule,
mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);
String filepath = Storage.generateFilepath(filename);
- ExifOutputStream out = null;
- InputStream is = null;
+ ExifInterface exif = new ExifInterface();
try {
- is = new ByteArrayInputStream(jpegData);
- ExifReader reader = new ExifReader();
- ExifData data = reader.read(is);
-
- // Add Exif tags.
- data.addGpsDateTimeStampTag(mTimeTaken);
- data.addDateTimeStampTag(ExifTag.TAG_DATE_TIME, mTimeTaken, TimeZone.getDefault());
- data.addTag(ExifTag.TAG_ORIENTATION).
- setValue(getExifOrientation(orientation));
-
- out = new ExifOutputStream(new FileOutputStream(filepath));
- out.setExifData(data);
- out.write(jpegData);
+ exif.readExif(jpegData);
+ exif.addGpsDateTimeStampTag(mTimeTaken);
+ exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken,
+ TimeZone.getDefault());
+ exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.getOrientationValueForRotation(orientation)));
+ exif.writeExif(jpegData, filepath);
} catch (IOException e) {
- Log.e(TAG, "Cannot set EXIF for " + filepath, e);
+ Log.e(TAG, "Cannot set exif for " + filepath, e);
Storage.writeFile(filepath, jpegData);
- } catch (ExifInvalidFormatException e) {
- Log.e(TAG, "Cannot set EXIF for " + filepath, e);
- Storage.writeFile(filepath, jpegData);
- } finally {
- Util.closeSilently(out);
- Util.closeSilently(is);
}
int jpegLength = (int) (new File(filepath).length());
@@ -947,21 +931,6 @@ public class PanoramaModule implements CameraModule,
return null;
}
- private static int getExifOrientation(int orientation) {
- switch (orientation) {
- case 0:
- return ExifTag.Orientation.TOP_LEFT;
- case 90:
- return ExifTag.Orientation.RIGHT_TOP;
- case 180:
- return ExifTag.Orientation.BOTTOM_LEFT;
- case 270:
- return ExifTag.Orientation.RIGHT_BOTTOM;
- default:
- throw new AssertionError("invalid: " + orientation);
- }
- }
-
private void clearMosaicFrameProcessorIfNeeded() {
if (!mPaused || mThreadRunning) return;
// Only clear the processor if it is initialized by this activity
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index 995996480..18d0d3e86 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -676,7 +676,8 @@ public class PhotoUI implements PieListener,
@Override
public void clearFocus() {
- getFocusIndicator().clear();
+ FocusIndicator indicator = getFocusIndicator();
+ if (indicator != null) indicator.clear();
}
@Override
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
new file mode 100644
index 000000000..5d10953f8
--- /dev/null
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -0,0 +1,330 @@
+/*
+ * 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.data;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.drawable.ColorDrawable;
+import android.os.AsyncTask;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.camera.Storage;
+import com.android.camera.ui.FilmStripView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A FilmStripDataProvider that provide data in the camera folder.
+ *
+ * The given view for camera preview won't be added until the preview info
+ * has been set by setPreviewInfo(int, int, int)
+ */
+public class CameraDataAdapter implements FilmStripView.DataAdapter {
+ private static final String TAG = "CamreaFilmStripDataProvider";
+
+ private static final int DEFAULT_DECODE_SIZE = 3000;
+ private static final String ORDER_CLAUSE = ImageColumns.DATE_TAKEN + " DESC, "
+ + ImageColumns._ID + " DESC";
+ private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" };
+ private static final int COL_ID = 0;
+ private static final int COL_TITLE = 1;
+ private static final int COL_MIME_TYPE = 2;
+ private static final int COL_DATE_TAKEN = 3;
+ private static final int COL_DATE_MODIFIED = 4;
+ private static final int COL_DATA = 5;
+ private static final int COL_ORIENTATION = 6;
+ private static final int COL_WIDTH = 7;
+ private static final int COL_HEIGHT = 8;
+ private static final int COL_SIZE = 9;
+
+ private static final String[] PROJECTION = {
+ ImageColumns._ID, // 0, int
+ ImageColumns.TITLE, // 1, string
+ ImageColumns.MIME_TYPE, // 2, tring
+ ImageColumns.DATE_TAKEN, // 3, int
+ ImageColumns.DATE_MODIFIED, // 4, int
+ ImageColumns.DATA, // 5, string
+ ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
+ ImageColumns.WIDTH, // 7, int
+ ImageColumns.HEIGHT, // 8, int
+ ImageColumns.SIZE // 9, int
+ };
+
+ // 32K buffer.
+ private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024];
+
+ private List<LocalImageData> mImages;
+
+ private FilmStripView mFilmStripView;
+ private View mCameraPreviewView;
+ private ColorDrawable mPlaceHolder;
+
+ private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
+ private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
+
+ public CameraDataAdapter(View cameraPreviewView, int placeHolderColor) {
+ mCameraPreviewView = cameraPreviewView;
+ mPlaceHolder = new ColorDrawable(placeHolderColor);
+ }
+
+ public void setCameraPreviewInfo(int width, int height, int orientation) {
+ addOrReplaceCameraData(buildCameraImageData(width, height, orientation));
+ }
+
+ @Override
+ public int getTotalNumber() {
+ return mImages.size();
+ }
+
+ @Override
+ public FilmStripView.ImageData getImageData(int id) {
+ if (id >= mImages.size()) return null;
+ return mImages.get(id);
+ }
+
+ @Override
+ public void suggestSize(int w, int h) {
+ if (w <= 0 || h <= 0) {
+ mSuggestedWidth = mSuggestedHeight = DEFAULT_DECODE_SIZE;
+ } else {
+ mSuggestedWidth = (w < DEFAULT_DECODE_SIZE ? w : DEFAULT_DECODE_SIZE);
+ mSuggestedHeight = (h < DEFAULT_DECODE_SIZE ? h : DEFAULT_DECODE_SIZE);
+ }
+ }
+
+ @Override
+ public void requestLoad(ContentResolver resolver) {
+ QueryTask qtask = new QueryTask();
+ qtask.execute(resolver);
+ }
+
+ @Override
+ public View getView(Context c, int dataID) {
+ if (dataID >= mImages.size() || dataID < 0) {
+ return null;
+ }
+
+ LocalImageData data = mImages.get(dataID);
+
+ if (data.isCameraData) return mCameraPreviewView;
+
+ ImageView v = new ImageView(c);
+ v.setImageDrawable(mPlaceHolder);
+
+ v.setScaleType(ImageView.ScaleType.FIT_XY);
+ LoadBitmapTask task = new LoadBitmapTask(data, v);
+ task.execute();
+ return v;
+ }
+
+ @Override
+ public void setDataListener(FilmStripView v) {
+ mFilmStripView = v;
+ }
+
+ private LocalImageData buildCameraImageData(int width, int height, int orientation) {
+ LocalImageData d = new LocalImageData();
+ d.width = width;
+ d.height = height;
+ d.orientation = orientation;
+ d.isCameraData = true;
+ return d;
+ }
+
+ private void addOrReplaceCameraData(LocalImageData data) {
+ if (mImages == null) mImages = new ArrayList<LocalImageData>();
+ if (mImages.size() == 0) {
+ mImages.add(0, data);
+ return;
+ }
+
+ LocalImageData first = mImages.get(0);
+ if (first.isCameraData) {
+ mImages.set(0, data);
+ } else {
+ mImages.add(0, data);
+ }
+ }
+
+ private LocalImageData buildCursorImageData(Cursor c) {
+ LocalImageData d = new LocalImageData();
+ d.id = c.getInt(COL_ID);
+ d.title = c.getString(COL_TITLE);
+ d.mimeType = c.getString(COL_MIME_TYPE);
+ d.path = c.getString(COL_DATA);
+ d.orientation = c.getInt(COL_ORIENTATION);
+ d.width = c.getInt(COL_WIDTH);
+ d.height = c.getInt(COL_HEIGHT);
+ if (d.width <= 0 || d.height <= 0) {
+ Log.v(TAG, "warning! zero dimension for "
+ + d.path + ":" + d.width + "x" + d.height);
+ Dimension dim = decodeDimension(d.path);
+ if (dim != null) {
+ d.width = dim.width;
+ d.height = dim.height;
+ } else {
+ Log.v(TAG, "warning! dimension decode failed for " + d.path);
+ Bitmap b = BitmapFactory.decodeFile(d.path);
+ if (b == null) return null;
+ d.width = b.getWidth();
+ d.height = b.getHeight();
+ }
+ }
+ if (d.orientation == 90 || d.orientation == 270) {
+ int b = d.width;
+ d.width = d.height;
+ d.height = b;
+ }
+ return d;
+ }
+
+ private Dimension decodeDimension(String path) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inJustDecodeBounds = true;
+ Bitmap b = BitmapFactory.decodeFile(path, opts);
+ if (b == null) return null;
+ Dimension d = new Dimension();
+ d.width = opts.outWidth;
+ d.height = opts.outHeight;
+ return d;
+ }
+
+ private class Dimension {
+ public int width;
+ public int height;
+ }
+
+ private class LocalImageData implements FilmStripView.ImageData {
+ public boolean isCameraData;
+ public int id;
+ public String title;
+ public String mimeType;
+ public String path;
+ // from MediaStore, can only be 0, 90, 180, 270;
+ public int orientation;
+ // width and height should be adjusted according to orientation.
+ public int width;
+ public int height;
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ @Override
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public String toString() {
+ return "LocalImageData:" + ",data=" + path + ",mimeType=" + mimeType
+ + "," + width + "x" + height + ",orientation=" + orientation;
+ }
+ }
+
+ private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalImageData>> {
+ private ContentResolver mResolver;
+ private LocalImageData mCameraImageData;
+
+ @Override
+ protected List<LocalImageData> doInBackground(ContentResolver... resolver) {
+ List<LocalImageData> l = null;
+ Cursor c = resolver[0].query(Images.Media.EXTERNAL_CONTENT_URI, PROJECTION,
+ MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
+ ORDER_CLAUSE);
+ if (c == null) return null;
+ l = new ArrayList<LocalImageData>();
+ c.moveToFirst();
+ while (!c.isLast()) {
+ LocalImageData data = buildCursorImageData(c);
+ if (data != null) l.add(data);
+ else Log.e(TAG, "Error decoding file:" + c.getString(COL_DATA));
+ c.moveToNext();
+ }
+ c.close();
+ return l;
+ }
+
+ @Override
+ protected void onPostExecute(List<LocalImageData> l) {
+ boolean changed = (l != mImages);
+ LocalImageData first = null;
+ if (mImages != null && mImages.size() > 0) {
+ first = mImages.get(0);
+ if (!first.isCameraData) first = null;
+ }
+ mImages = l;
+ if (first != null) addOrReplaceCameraData(first);
+ // both might be null.
+ if (changed) mFilmStripView.onDataChanged();
+ }
+ }
+
+ private class LoadBitmapTask extends AsyncTask<Void, Void, Bitmap> {
+ private LocalImageData mData;
+ private ImageView mView;
+
+ public LoadBitmapTask(
+ LocalImageData d, ImageView v) {
+ mData = d;
+ mView = v;
+ }
+
+ @Override
+ protected Bitmap doInBackground(Void... v) {
+ BitmapFactory.Options opts = null;
+ Bitmap b;
+ int sample = 1;
+ while (mSuggestedWidth * sample < mData.width
+ || mSuggestedHeight * sample < mData.height) {
+ sample *= 2;
+ }
+ opts = new BitmapFactory.Options();
+ opts.inSampleSize = sample;
+ opts.inTempStorage = DECODE_TEMP_STORAGE;
+ if (isCancelled()) return null;
+ b = BitmapFactory.decodeFile(mData.path, opts);
+ if (mData.orientation != 0) {
+ if (isCancelled()) return null;
+ Matrix m = new Matrix();
+ m.setRotate((float) mData.orientation);
+ b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
+ }
+ return b;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap == null) {
+ Log.e(TAG, "Cannot decode bitmap file:" + mData.path);
+ return;
+ }
+ mView.setImageBitmap(bitmap);
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/CameraSwitcher.java b/src/com/android/camera/ui/CameraSwitcher.java
index 75906acbe..e9551ada2 100644
--- a/src/com/android/camera/ui/CameraSwitcher.java
+++ b/src/com/android/camera/ui/CameraSwitcher.java
@@ -107,6 +107,7 @@ public class CameraSwitcher extends RotateImageView
private void onCameraSelected(int ix) {
hidePopup();
if ((ix != mCurrentIndex) && (mListener != null)) {
+ UsageStatistics.onEvent("CameraModeSwitch", null, null);
UsageStatistics.setPendingTransitionCause(
UsageStatistics.TRANSITION_MENU_TAP);
setCurrentIndex(ix);
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
new file mode 100644
index 000000000..326e969b2
--- /dev/null
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -0,0 +1,421 @@
+/*
+ * 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.ui;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Scroller;
+
+public class FilmStripView extends ViewGroup {
+
+ private static final String TAG = "FilmStripView";
+ private static final int BUFFER_SIZE = 5;
+ // Horizontal padding of children.
+ private static final int H_PADDING = 50;
+ // Duration to go back to the first.
+ private static final int BACK_SCROLL_DURATION = 500;
+ private static final float MIN_SCALE = 0.7f;
+
+ private Context mContext;
+ private GestureDetector mGestureDetector;
+ private DataAdapter mDataAdapter;
+ private final Rect mDrawArea = new Rect();
+
+ private int mCurrentInfo;
+ private Scroller mScroller;
+ private boolean mIsScrolling;
+ private int mCenterPosition = -1;
+ private ViewInfo[] mViewInfo = new ViewInfo[BUFFER_SIZE];
+
+ public interface ImageData {
+ // The values returned by getWidth() and getHeight() will be used for layout.
+ public int getWidth();
+ public int getHeight();
+ }
+
+ public interface DataAdapter {
+
+ public int getTotalNumber();
+ public View getView(Context context, int id);
+ public ImageData getImageData(int id);
+ public void suggestSize(int w, int h);
+
+ public void requestLoad(ContentResolver r);
+ public void setDataListener(FilmStripView v);
+ }
+
+ private static class ViewInfo {
+ private int mDataID;
+ // the position of the left of the view in the whole filmstrip.
+ private int mLeftPosition;
+ private View mView;
+
+ public ViewInfo(int id, View v) {
+ mDataID = id;
+ mView = v;
+ mLeftPosition = -1;
+ }
+
+ public int getId() {
+ return mDataID;
+ }
+
+ public void setLeftPosition(int pos) {
+ mLeftPosition = pos;
+ }
+
+ public int getLeftPosition() {
+ return mLeftPosition;
+ }
+
+ public int getCenterPosition() {
+ return mLeftPosition + mView.getWidth() / 2;
+ }
+
+ public View getView() {
+ return mView;
+ }
+
+ private void layoutAt(int l, int t) {
+ mView.layout(l, t, l + mView.getMeasuredWidth(), t + mView.getMeasuredHeight());
+ }
+
+ public void layoutIn(Rect drawArea, int refCenter) {
+ // drawArea is where to layout in.
+ // refCenter is the absolute horizontal position of the center of drawArea.
+ layoutAt(drawArea.centerX() + mLeftPosition - refCenter,
+ drawArea.centerY() - mView.getMeasuredHeight() / 2);
+ }
+ }
+
+ public FilmStripView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public FilmStripView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public FilmStripView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ private void init(Context context) {
+ mCurrentInfo = (BUFFER_SIZE - 1) / 2;
+ setWillNotDraw(false);
+ mContext = context;
+ mScroller = new Scroller(context);
+ mGestureDetector =
+ new GestureDetector(context, new MyGestureListener(),
+ null, true /* ignoreMultitouch */);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mIsScrolling) {
+ layoutChildren();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int w = MeasureSpec.getSize(widthMeasureSpec);
+ int h = MeasureSpec.getSize(heightMeasureSpec);
+ float scale = MIN_SCALE;
+ if (mDataAdapter != null) mDataAdapter.suggestSize(w / 2, h / 2);
+
+ int boundWidth = (int) (w * scale);
+ int boundHeight = (int) (h * scale);
+
+ int wMode = View.MeasureSpec.EXACTLY;
+ int hMode = View.MeasureSpec.EXACTLY;
+
+ for (int i = 0; i < mViewInfo.length; i++) {
+ ViewInfo info = mViewInfo[i];
+ if (mViewInfo[i] == null) continue;
+
+ int imageWidth = mDataAdapter.getImageData(info.getId()).getWidth();
+ int imageHeight = mDataAdapter.getImageData(info.getId()).getHeight();
+
+ int scaledWidth = boundWidth;
+ int scaledHeight = boundHeight;
+ if (imageWidth * scaledHeight > scaledWidth * imageHeight) {
+ scaledHeight = imageHeight * scaledWidth / imageWidth;
+ } else {
+ scaledWidth = imageWidth * scaledHeight / imageHeight;
+ }
+ scaledWidth += H_PADDING * 2 * scale;
+ mViewInfo[i].getView().measure(
+ View.MeasureSpec.makeMeasureSpec(scaledWidth, wMode)
+ , View.MeasureSpec.makeMeasureSpec(scaledHeight, hMode));
+ }
+ setMeasuredDimension(w, h);
+ }
+
+ private int findTheNearestView(int pointX) {
+
+ int nearest = 0;
+ // find the first non-null ViewInfo.
+ for (; nearest < BUFFER_SIZE
+ && (mViewInfo[nearest] == null || mViewInfo[nearest].getLeftPosition() == -1);
+ nearest++);
+ // no existing available ViewInfo
+ if (nearest == BUFFER_SIZE) return -1;
+ int min = Math.abs(pointX - mViewInfo[nearest].getCenterPosition());
+
+ for (int infoID = nearest + 1;
+ infoID < BUFFER_SIZE && mViewInfo[infoID] != null; infoID++) {
+ // not measured yet.
+ if (mViewInfo[infoID].getLeftPosition() == -1) continue;
+
+ int c = mViewInfo[infoID].getCenterPosition();
+ int dist = Math.abs(pointX - c);
+ if (dist < min) {
+ min = dist;
+ nearest = infoID;
+ }
+ }
+ return nearest;
+ }
+
+ // We try to keep the one closest to the center of the screen at position mCurrentInfo.
+ private void stepIfNeeded() {
+ int nearest = findTheNearestView(mCenterPosition);
+ // no change made.
+ if (nearest == -1 || nearest == mCurrentInfo) return;
+
+ int adjust = nearest - mCurrentInfo;
+ if (adjust > 0) {
+ for (int k = 0; k < adjust; k++) {
+ if (mViewInfo[k] != null) {
+ removeView(mViewInfo[k].getView());
+ }
+ }
+ for (int k = 0; k + adjust < BUFFER_SIZE; k++) {
+ mViewInfo[k] = mViewInfo[k + adjust];
+ }
+ for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) {
+ mViewInfo[k] = null;
+ if (mViewInfo[k - 1] != null) getInfo(k, mViewInfo[k - 1].getId() + 1);
+ }
+ } else {
+ for (int k = BUFFER_SIZE - 1; k >= BUFFER_SIZE + adjust; k--) {
+ if (mViewInfo[k] != null) {
+ removeView(mViewInfo[k].getView());
+ }
+ }
+ for (int k = BUFFER_SIZE - 1; k + adjust >= 0; k--) {
+ mViewInfo[k] = mViewInfo[k + adjust];
+ }
+ for (int k = -1 - adjust; k >= 0; k--) {
+ mViewInfo[k] = null;
+ if (mViewInfo[k + 1] != null) getInfo(k, mViewInfo[k + 1].getId() - 1);
+ }
+ }
+ }
+
+ private void stopScroll() {
+ mScroller.forceFinished(true);
+ mIsScrolling = false;
+ }
+
+ private void adjustCenterPosition() {
+ ViewInfo curr = mViewInfo[mCurrentInfo];
+ if (curr == null) return;
+
+ if (curr.getId() == 0 && mCenterPosition < curr.getCenterPosition()) {
+ mCenterPosition = curr.getCenterPosition();
+ if (mIsScrolling) stopScroll();
+ }
+ if (curr.getId() == mDataAdapter.getTotalNumber() - 1
+ && mCenterPosition > curr.getCenterPosition()) {
+ mCenterPosition = curr.getCenterPosition();
+ if (mIsScrolling) stopScroll();
+ }
+ }
+
+ private void layoutChildren() {
+ mIsScrolling = mScroller.computeScrollOffset();
+
+ if (mIsScrolling) mCenterPosition = mScroller.getCurrX();
+
+ adjustCenterPosition();
+
+ mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition);
+
+ // images on the left
+ for (int infoID = mCurrentInfo - 1; infoID >= 0; infoID--) {
+ ViewInfo curr = mViewInfo[infoID];
+ if (curr != null) {
+ ViewInfo next = mViewInfo[infoID + 1];
+ curr.setLeftPosition(next.getLeftPosition() - curr.getView().getMeasuredWidth());
+ curr.layoutIn(mDrawArea, mCenterPosition);
+ }
+ }
+
+ // images on the right
+ for (int infoID = mCurrentInfo + 1; infoID < BUFFER_SIZE; infoID++) {
+ ViewInfo curr = mViewInfo[infoID];
+ if (curr != null) {
+ ViewInfo prev = mViewInfo[infoID - 1];
+ curr.setLeftPosition(prev.getLeftPosition() + prev.getView().getMeasuredWidth());
+ curr.layoutIn(mDrawArea, mCenterPosition);
+ }
+ }
+
+ stepIfNeeded();
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (mViewInfo[mCurrentInfo] == null) return;
+
+ mDrawArea.left = l;
+ mDrawArea.top = t;
+ mDrawArea.right = r;
+ mDrawArea.bottom = b;
+
+ layoutChildren();
+ }
+
+ public void setDataAdapter(
+ DataAdapter adapter, ContentResolver resolver) {
+ mDataAdapter = adapter;
+ mDataAdapter.suggestSize(getMeasuredWidth(), getMeasuredHeight());
+ mDataAdapter.setDataListener(this);
+ mDataAdapter.requestLoad(resolver);
+ }
+
+ private void getInfo(int infoID, int dataID) {
+ View v = mDataAdapter.getView(mContext, dataID);
+ if (v == null) return;
+ v.setPadding(H_PADDING, 0, H_PADDING, 0);
+ addView(v);
+ ViewInfo info = new ViewInfo(dataID, v);
+ mViewInfo[infoID] = info;
+ }
+
+ public void onDataChanged() {
+ removeAllViews();
+ int dataNumber = mDataAdapter.getTotalNumber();
+ if (dataNumber == 0) return;
+
+ int currentData = 0;
+ int currentLeft = 0;
+ // previous data exists.
+ if (mViewInfo[mCurrentInfo] != null) {
+ currentLeft = mViewInfo[mCurrentInfo].getLeftPosition();
+ currentData = mViewInfo[mCurrentInfo].getId();
+ }
+ getInfo(mCurrentInfo, currentData);
+ mViewInfo[mCurrentInfo].setLeftPosition(currentLeft);
+ for (int i = 1; mCurrentInfo + i < BUFFER_SIZE || mCurrentInfo - i >= 0; i++) {
+ int infoID = mCurrentInfo + i;
+ if (infoID < BUFFER_SIZE && mViewInfo[infoID - 1] != null) {
+ getInfo(infoID, mViewInfo[infoID - 1].getId() + 1);
+ }
+ infoID = mCurrentInfo - i;
+ if (infoID >= 0 && mViewInfo[infoID + 1] != null) {
+ getInfo(infoID, mViewInfo[infoID + 1].getId() - 1);
+ }
+ }
+ layoutChildren();
+ }
+
+ private void movePositionTo(int position) {
+ mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
+ 0, BACK_SCROLL_DURATION);
+ layoutChildren();
+ }
+
+ public void goToFirst() {
+ movePositionTo(0);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return mGestureDetector.onTouchEvent(ev);
+ }
+
+ private class MyGestureListener
+ extends GestureDetector.SimpleOnGestureListener {
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ float x = (float) e.getX();
+ float y = (float) e.getY();
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ if (mViewInfo[i] == null) continue;
+ View v = mViewInfo[i].getView();
+ if (x >= v.getLeft() && x < v.getRight()
+ && y >= v.getTop() && y < v.getBottom()) {
+ Log.v(TAG, "l, r, t, b " + v.getLeft() + ',' + v.getRight()
+ + ',' + v.getTop() + ',' + v.getBottom());
+ movePositionTo(mViewInfo[i].getCenterPosition());
+ break;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent ev) {
+ if (mIsScrolling) stopScroll();
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(
+ MotionEvent e1, MotionEvent e2, float dx, float dy) {
+ stopScroll();
+ mCenterPosition += dx;
+ layoutChildren();
+ return true;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ ViewInfo info = mViewInfo[mCurrentInfo];
+ int w = getWidth();
+ if (info == null) return true;
+ mScroller.fling(mCenterPosition, 0, (int) -velocityX, (int) velocityY,
+ // estimation of possible length on the left
+ info.getLeftPosition() - info.getId() * w * 2,
+ // estimation of possible length on the right
+ info.getLeftPosition()
+ + (mDataAdapter.getTotalNumber() - info.getId()) * w * 2,
+ 0, 0);
+ layoutChildren();
+ return true;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 84030896a..fe6840ecb 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -514,6 +514,10 @@ public abstract class PhotoPage extends ActivityState implements
if (oldIndex == 0 && mCurrentIndex > 0
&& !mPhotoView.getFilmMode()) {
mPhotoView.setFilmMode(true);
+ if (mAppBridge != null) {
+ UsageStatistics.onEvent("CameraToFilmstrip",
+ UsageStatistics.TRANSITION_SWIPE, null);
+ }
} else if (oldIndex == 2 && mCurrentIndex == 1) {
mCameraSwitchCutoff = SystemClock.uptimeMillis() +
CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
diff --git a/src/com/android/gallery3d/data/Exif.java b/src/com/android/gallery3d/data/Exif.java
index 30aba7e97..950e7de18 100644
--- a/src/com/android/gallery3d/data/Exif.java
+++ b/src/com/android/gallery3d/data/Exif.java
@@ -18,55 +18,31 @@ package com.android.gallery3d.data;
import android.util.Log;
-import com.android.gallery3d.exif.ExifInvalidFormatException;
-import com.android.gallery3d.exif.ExifParser;
-import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.ExifInterface;
import java.io.IOException;
import java.io.InputStream;
public class Exif {
- private static final String TAG = "GalleryExif";
+ private static final String TAG = "CameraExif";
+ // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(InputStream is) {
if (is == null) {
return 0;
}
-
+ ExifInterface exif = new ExifInterface();
try {
- ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0);
- int event = parser.next();
- while (event != ExifParser.EVENT_END) {
- if (event == ExifParser.EVENT_NEW_TAG) {
- ExifTag tag = parser.getTag();
- if (tag.getTagId() == ExifTag.TAG_ORIENTATION &&
- tag.hasValue()) {
- int orient = (int) tag.getValueAt(0);
- switch (orient) {
- case ExifTag.Orientation.TOP_LEFT:
- return 0;
- case ExifTag.Orientation.BOTTOM_LEFT:
- return 180;
- case ExifTag.Orientation.RIGHT_TOP:
- return 90;
- case ExifTag.Orientation.RIGHT_BOTTOM:
- return 270;
- default:
- Log.i(TAG, "Unsupported orientation");
- return 0;
- }
- }
- }
- event = parser.next();
+ exif.readExif(is);
+ Integer val = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (val == null) {
+ return 0;
+ } else {
+ return ExifInterface.getRotationForOrientationValue(val.shortValue());
}
- Log.i(TAG, "Orientation not found");
- return 0;
} catch (IOException e) {
Log.w(TAG, "Failed to read EXIF orientation", e);
return 0;
- } catch (ExifInvalidFormatException e) {
- Log.w(TAG, "Failed to read EXIF orientation", e);
- return 0;
}
}
}
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index d5fad5483..1ed67ecf4 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -23,7 +23,6 @@ import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
-import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore.Images;
@@ -37,8 +36,7 @@ import com.android.gallery3d.app.StitchingProgressManager;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifInvalidFormatException;
-import com.android.gallery3d.exif.ExifModifier;
+import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.exif.ExifTag;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.ThreadPool.Job;
@@ -46,6 +44,7 @@ import com.android.gallery3d.util.ThreadPool.JobContext;
import com.android.gallery3d.util.UpdateHelper;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel.MapMode;
@@ -196,15 +195,15 @@ public class LocalImage extends LocalMediaItem {
// try to decode from JPEG EXIF
if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
- ExifInterface exif = null;
- byte [] thumbData = null;
+ ExifInterface exif = new ExifInterface();
+ byte[] thumbData = null;
try {
- exif = new ExifInterface(mLocalFilePath);
- if (exif != null) {
- thumbData = exif.getThumbnail();
- }
- } catch (Throwable t) {
- Log.w(TAG, "fail to get exif thumb", t);
+ exif.readExif(mLocalFilePath);
+ thumbData = exif.getThumbnail();
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "failed to find file to read thumbnail: " + mLocalFilePath);
+ } catch (IOException e) {
+ Log.w(TAG, "failed to get thumbnail from: " + mLocalFilePath);
}
if (thumbData != null) {
Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
@@ -276,21 +275,6 @@ public class LocalImage extends LocalMediaItem {
new String[]{String.valueOf(id)});
}
- private static int getExifOrientation(int orientation) {
- switch (orientation) {
- case 0:
- return ExifInterface.ORIENTATION_NORMAL;
- case 90:
- return ExifInterface.ORIENTATION_ROTATE_90;
- case 180:
- return ExifInterface.ORIENTATION_ROTATE_180;
- case 270:
- return ExifInterface.ORIENTATION_ROTATE_270;
- default:
- throw new AssertionError("invalid: " + orientation);
- }
- }
-
@Override
public void rotate(int degrees) {
GalleryUtils.assertNotInRenderThread();
@@ -300,34 +284,22 @@ public class LocalImage extends LocalMediaItem {
if (rotation < 0) rotation += 360;
if (mimeType.equalsIgnoreCase("image/jpeg")) {
- RandomAccessFile file = null;
- try {
- // Because most of the images contain the orientation tag, we
- // use ExifModifier to modify the tag for better efficiency.
- // If the tag doesn't exist, ExifInterface will be used to replace the entire
- // header.
- file = new RandomAccessFile(filePath, "rw");
- ExifModifier modifier = new ExifModifier(
- file.getChannel().map(MapMode.READ_WRITE, 0, file.length()));
- ExifTag tag = ExifTag.buildTag(ExifTag.TAG_ORIENTATION);
- tag.setValue(getExifOrientation(rotation));
- modifier.modifyTag(tag);
- if (!modifier.commit()) {
- // Need to change the file size, use ExifInterface instead.
- ExifInterface exif = new ExifInterface(filePath);
- exif.setAttribute(ExifInterface.TAG_ORIENTATION,
- String.valueOf(getExifOrientation(rotation)));
- exif.saveAttributes();
- // We need to update the filesize as well
+ ExifInterface exifInterface = new ExifInterface();
+ ExifTag tag = exifInterface.buildTag(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.getOrientationValueForRotation(rotation));
+ if(tag != null) {
+ exifInterface.setTag(tag);
+ try {
+ exifInterface.forceRewriteExif(filePath);
fileSize = new File(filePath).length();
values.put(Images.Media.SIZE, fileSize);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "cannot find file to set exif: " + filePath);
+ } catch (IOException e) {
+ Log.w(TAG, "cannot set exif data: " + filePath);
}
- } catch (IOException e) {
- Log.w(TAG, "cannot set exif data: " + filePath);
- } catch (ExifInvalidFormatException e) {
- Log.w(TAG, "cannot set exif data: " + filePath);
- } finally {
- Utils.closeSilently(file);
+ } else {
+ Log.w(TAG, "Could not build tag: " + ExifInterface.TAG_ORIENTATION);
}
}
diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java
index 662bd141c..cac524b88 100644
--- a/src/com/android/gallery3d/data/MediaDetails.java
+++ b/src/com/android/gallery3d/data/MediaDetails.java
@@ -18,12 +18,12 @@ package com.android.gallery3d.data;
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifData;
-import com.android.gallery3d.exif.ExifInvalidFormatException;
-import com.android.gallery3d.exif.ExifReader;
+import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.Rational;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
@@ -115,11 +115,11 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> {
String value = null;
int type = tag.getDataType();
if (type == ExifTag.TYPE_UNSIGNED_RATIONAL || type == ExifTag.TYPE_RATIONAL) {
- value = String.valueOf(tag.getRational(0).toDouble());
+ value = String.valueOf(tag.getValueAsRational(0).toDouble());
} else if (type == ExifTag.TYPE_ASCII) {
- value = tag.getString();
+ value = tag.getValueAsString();
} else {
- value = String.valueOf(tag.getValueAt(0));
+ value = String.valueOf(tag.forceGetValueAsLong(0));
}
if (key == MediaDetails.INDEX_FLASH) {
MediaDetails.FlashState state = new MediaDetails.FlashState(
@@ -132,37 +132,39 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> {
}
public static void extractExifInfo(MediaDetails details, String filePath) {
- InputStream is = null;
+
+ ExifInterface exif = new ExifInterface();
try {
- is = new FileInputStream(filePath);
- ExifData data = new ExifReader().read(is);
- setExifData(details, data.getTag(ExifTag.TAG_FLASH), MediaDetails.INDEX_FLASH);
- setExifData(details, data.getTag(ExifTag.TAG_IMAGE_WIDTH), MediaDetails.INDEX_WIDTH);
- setExifData(details, data.getTag(ExifTag.TAG_IMAGE_LENGTH), MediaDetails.INDEX_HEIGHT);
- setExifData(details, data.getTag(ExifTag.TAG_MAKE), MediaDetails.INDEX_MAKE);
- setExifData(details, data.getTag(ExifTag.TAG_MODEL),MediaDetails.INDEX_MODEL);
- setExifData(details, data.getTag(ExifTag.TAG_APERTURE_VALUE),
- MediaDetails.INDEX_APERTURE);
- setExifData(details, data.getTag(ExifTag.TAG_ISO_SPEED_RATINGS),
- MediaDetails.INDEX_ISO);
- setExifData(details, data.getTag(ExifTag.TAG_WHITE_BALANCE),
- MediaDetails.INDEX_WHITE_BALANCE);
- setExifData(details, data.getTag(ExifTag.TAG_EXPOSURE_TIME),
- MediaDetails.INDEX_EXPOSURE_TIME);
- ExifTag focalTag = data.getTag(ExifTag.TAG_FOCAL_LENGTH);
- if (focalTag != null) {
- details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH,
- focalTag.getRational(0).toDouble());
- details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm);
- }
- } catch (IOException ex) {
- // ignore it.
- Log.w(TAG, "", ex);
- } catch (ExifInvalidFormatException ex) {
- // ignore it.
- Log.w(TAG, "", ex);
- } finally {
- Utils.closeSilently(is);
+ exif.readExif(filePath);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Could not find file to read exif: " + filePath, e);
+ } catch (IOException e) {
+ Log.w(TAG, "Could not read exif from file: " + filePath, e);
+ }
+
+ setExifData(details, exif.getTag(ExifInterface.TAG_FLASH),
+ MediaDetails.INDEX_FLASH);
+ setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_WIDTH),
+ MediaDetails.INDEX_WIDTH);
+ setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_LENGTH),
+ MediaDetails.INDEX_HEIGHT);
+ setExifData(details, exif.getTag(ExifInterface.TAG_MAKE),
+ MediaDetails.INDEX_MAKE);
+ setExifData(details, exif.getTag(ExifInterface.TAG_MODEL),
+ MediaDetails.INDEX_MODEL);
+ setExifData(details, exif.getTag(ExifInterface.TAG_APERTURE_VALUE),
+ MediaDetails.INDEX_APERTURE);
+ setExifData(details, exif.getTag(ExifInterface.TAG_ISO_SPEED_RATINGS),
+ MediaDetails.INDEX_ISO);
+ setExifData(details, exif.getTag(ExifInterface.TAG_WHITE_BALANCE),
+ MediaDetails.INDEX_WHITE_BALANCE);
+ setExifData(details, exif.getTag(ExifInterface.TAG_EXPOSURE_TIME),
+ MediaDetails.INDEX_EXPOSURE_TIME);
+ ExifTag focalTag = exif.getTag(ExifInterface.TAG_FOCAL_LENGTH);
+ if (focalTag != null) {
+ details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH,
+ focalTag.getValueAsRational(0).toDouble());
+ details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm);
}
}
}
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 93bb02483..37b2cd9da 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -574,6 +574,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
// TODO: Using singletons is a bad design choice for many of these
// due static reference leaks and in general. Please refactor.
MasterImage.reset();
+ ImageFilterRS.destroyRenderScriptContext();
FilteringPipeline.reset();
ImageFilter.resetStatics();
FiltersManager.reset();
diff --git a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
index 4aeb580e6..d7e9a62a7 100644
--- a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
@@ -30,7 +30,7 @@ import com.android.gallery3d.filtershow.presets.ImagePreset;
public class FilteringPipeline implements Handler.Callback {
- private static FilteringPipeline sPipeline;
+ private static volatile FilteringPipeline sPipeline = null;
private static final String LOGTAG = "FilteringPipeline";
private ImagePreset mPreviousGeometryPreset = null;
private ImagePreset mPreviousFiltersPreset = null;
@@ -120,7 +120,7 @@ public class FilteringPipeline implements Handler.Callback {
mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
}
- public static FilteringPipeline getPipeline() {
+ public synchronized static FilteringPipeline getPipeline() {
if (sPipeline == null) {
sPipeline = new FilteringPipeline();
}
@@ -173,8 +173,6 @@ public class FilteringPipeline implements Handler.Callback {
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
mPreviousGeometry = new GeometryMetadata(geometry);
-
- FiltersManager.getManager().resetBitmapsRS();
return true;
}
@@ -259,17 +257,19 @@ public class FilteringPipeline implements Handler.Callback {
preset.setupEnvironment();
if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
- bitmap = MasterImage.getImage().getImageLoader().getScaleOneImageForPreset(null, preset,
+ ImageLoader loader = MasterImage.getImage().getImageLoader();
+ if (loader == null) {
+ Log.w(LOGTAG, "loader not yet setup, cannot handle: " + getType(request));
+ return;
+ }
+ bitmap = loader.getScaleOneImageForPreset(null, preset,
request.getBounds(), request.getDestination(), false);
if (bitmap == null) {
+ Log.w(LOGTAG, "could not get bitmap for: " + getType(request));
return;
}
}
- if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
- FiltersManager.getManager().resetBitmapsRS();
- }
-
if (request.getType() != RenderingRequest.ICON_RENDERING
&& request.getType() != RenderingRequest.PARTIAL_RENDERING) {
updateOriginalAllocation(preset);
@@ -296,9 +296,6 @@ public class FilteringPipeline implements Handler.Callback {
FiltersManager.getManager().freeFilterResources(preset);
}
- if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
- FiltersManager.getManager().resetBitmapsRS();
- }
}
private void compute(TripleBufferBitmap buffer, ImagePreset preset, int type) {
@@ -356,7 +353,8 @@ public class FilteringPipeline implements Handler.Callback {
return mPreviewScaleFactor;
}
- public static void reset() {
+ public static synchronized void reset() {
+ sPipeline.mHandlerThread.quit();
sPipeline = null;
}
}
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index 6cf462269..2c1a847f8 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -27,7 +27,7 @@ import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.media.ExifInterface;
+import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
@@ -36,9 +36,8 @@ import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifInvalidFormatException;
-import com.android.gallery3d.exif.ExifParser;
import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.HistoryAdapter;
import com.android.gallery3d.filtershow.imageshow.ImageShow;
@@ -48,6 +47,8 @@ import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import com.android.gallery3d.util.InterruptableOutputStream;
import com.android.gallery3d.util.XmpUtilHelper;
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -79,14 +80,14 @@ public class ImageLoader {
public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
public static final int DEFAULT_COMPRESS_QUALITY = 95;
- public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL;
- public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90;
- public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180;
- public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270;
- public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
- public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL;
- public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE;
- public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE;
+ public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
+ public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
+ public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
+ public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
+ public static final int ORI_FLIP_HOR = ExifInterface.Orientation.TOP_RIGHT;
+ public static final int ORI_FLIP_VERT = ExifInterface.Orientation.BOTTOM_RIGHT;
+ public static final int ORI_TRANSPOSE = ExifInterface.Orientation.LEFT_TOP;
+ public static final int ORI_TRANSVERSE = ExifInterface.Orientation.LEFT_BOTTOM;
private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
private Context mContext = null;
@@ -147,26 +148,13 @@ public class ImageLoader {
String path = uri.getPath();
int orientation = -1;
InputStream is = null;
+ ExifInterface exif = new ExifInterface();
try {
- is = new FileInputStream(path);
- ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0);
- int event = parser.next();
- while (event != ExifParser.EVENT_END) {
- if (event == ExifParser.EVENT_NEW_TAG) {
- ExifTag tag = parser.getTag();
- if (tag.getTagId() == ExifTag.TAG_ORIENTATION) {
- orientation = (int) tag.getValueAt(0);
- break;
- }
- }
- event = parser.next();
- }
+ exif.readExif(path);
+ orientation = ExifInterface.getRotationForOrientationValue(
+ exif.getTagIntValue(ExifInterface.TAG_ORIENTATION).shortValue());
} catch (IOException e) {
- e.printStackTrace();
- } catch (ExifInvalidFormatException e) {
- e.printStackTrace();
- } finally {
- Utils.closeSilently(is);
+ Log.w(LOGTAG, "Failed to read EXIF orientation", e);
}
return orientation;
}
@@ -196,9 +184,9 @@ public class ImageLoader {
return -1;
}
} catch (SQLiteException e) {
- return ExifInterface.ORIENTATION_UNDEFINED;
+ return -1;
} catch (IllegalArgumentException e) {
- return ExifInterface.ORIENTATION_UNDEFINED;
+ return -1;
} finally {
Utils.closeSilently(cursor);
}
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
index 215d5d438..f6c3bdd89 100644
--- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -61,16 +61,6 @@ public abstract class BaseFiltersManager {
return null;
}
- public void resetBitmapsRS() {
- for (Class c : mFilters.keySet()) {
- ImageFilter filter = mFilters.get(c);
- if (filter instanceof ImageFilterRS) {
- ImageFilterRS filterRS = (ImageFilterRS) filter;
- filterRS.resetBitmap();
- }
- }
- }
-
public void freeFilterResources(ImagePreset preset) {
if (preset == null) {
return;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index 978bc8bd1..595aa9b30 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -21,72 +21,76 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v8.renderscript.*;
import android.util.Log;
+import android.content.res.Resources;
import com.android.gallery3d.R;
public abstract class ImageFilterRS extends ImageFilter {
- private final String LOGTAG = "ImageFilterRS";
+ private static final String LOGTAG = "ImageFilterRS";
- private static RenderScript mRS = null;
- protected static Allocation mInPixelsAllocation;
- protected static Allocation mOutPixelsAllocation;
- private static android.content.res.Resources mResources = null;
- private static Bitmap sOldBitmap = null;
- private Bitmap mOldBitmap = null;
+ protected static volatile Allocation mInPixelsAllocation;
+ protected static volatile Allocation mOutPixelsAllocation;
- private boolean mResourcesLoaded = false;
+ private static volatile RenderScript sRS = null;
+ private static volatile int sWidth = 0;
+ private static volatile int sHeight = 0;
- private final Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888;
+ private static volatile Resources sResources = null;
+ private boolean mResourcesLoaded = false;
- public void resetBitmap() {
- mOldBitmap = null;
- }
+ private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+ // This must be used inside block synchronized on ImageFilterRS class object
public void prepare(Bitmap bitmap, float scaleFactor, int quality) {
- if (sOldBitmap == null
- || (bitmap.getWidth() != sOldBitmap.getWidth())
- || (bitmap.getHeight() != sOldBitmap.getHeight())) {
- if (mInPixelsAllocation != null) {
- mInPixelsAllocation.destroy();
- mInPixelsAllocation = null;
+ if (mOutPixelsAllocation == null || mInPixelsAllocation == null ||
+ bitmap.getWidth() != sWidth || bitmap.getHeight() != sHeight) {
+ destroyPixelAllocations();
+ Bitmap bitmapBuffer = bitmap;
+ if (bitmap.getConfig() == null || bitmap.getConfig() != BITMAP_CONFIG) {
+ bitmapBuffer = bitmap.copy(BITMAP_CONFIG, true);
}
- if (mOutPixelsAllocation != null) {
- mOutPixelsAllocation.destroy();
- mOutPixelsAllocation = null;
- }
- Bitmap bitmapBuffer = bitmap.copy(mBitmapConfig, true);
- mOutPixelsAllocation = Allocation.createFromBitmap(mRS, bitmapBuffer,
+ mOutPixelsAllocation = Allocation.createFromBitmap(sRS, bitmapBuffer,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
- mInPixelsAllocation = Allocation.createTyped(mRS,
+ mInPixelsAllocation = Allocation.createTyped(sRS,
mOutPixelsAllocation.getType());
- sOldBitmap = bitmap;
}
mInPixelsAllocation.copyFrom(bitmap);
- if (mOldBitmap != sOldBitmap || !isResourcesLoaded()) {
+ if (bitmap.getWidth() != sWidth
+ || bitmap.getHeight() != sHeight || !isResourcesLoaded()) {
freeResources();
- createFilter(mResources, scaleFactor, quality);
- mOldBitmap = sOldBitmap;
+ createFilter(sResources, scaleFactor, quality);
+ sWidth = bitmap.getWidth();
+ sHeight = bitmap.getHeight();
setResourcesLoaded(true);
}
}
+ // This must be used inside block synchronized on ImageFilterRS class object
abstract public void createFilter(android.content.res.Resources res,
float scaleFactor, int quality);
+ // This must be used inside block synchronized on ImageFilterRS class object
abstract public void runFilter();
+ // This must be used inside block synchronized on ImageFilterRS class object
public void update(Bitmap bitmap) {
mOutPixelsAllocation.copyTo(bitmap);
}
@Override
public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
- if (bitmap == null) {
+ if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
return bitmap;
}
try {
- prepare(bitmap, scaleFactor, quality);
- runFilter();
- update(bitmap);
+ synchronized(ImageFilterRS.class) {
+ if (sRS == null) {
+ Log.w(LOGTAG, "Cannot apply before calling setRenderScriptContext");
+ return bitmap;
+ }
+ prepare(bitmap, scaleFactor, quality);
+ runFilter();
+ update(bitmap);
+ }
} catch (android.renderscript.RSIllegalArgumentException e) {
Log.e(LOGTAG, "Illegal argument? " + e);
} catch (android.renderscript.RSRuntimeException e) {
@@ -100,15 +104,21 @@ public abstract class ImageFilterRS extends ImageFilter {
return bitmap;
}
- public static RenderScript getRenderScriptContext() {
- return mRS;
+ public static synchronized RenderScript getRenderScriptContext() {
+ return sRS;
}
- public static void setRenderScriptContext(Activity context) {
- if (mRS == null) {
- mRS = RenderScript.create(context);
+ public static synchronized void setRenderScriptContext(Activity context) {
+ if( sRS != null) {
+ Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext");
+ destroyRenderScriptContext();
}
- mResources = context.getResources();
+ sRS = RenderScript.create(context);
+ sResources = context.getResources();
+ destroyPixelAllocations();
+ }
+
+ private static synchronized void destroyPixelAllocations() {
if (mInPixelsAllocation != null) {
mInPixelsAllocation.destroy();
mInPixelsAllocation = null;
@@ -117,36 +127,49 @@ public abstract class ImageFilterRS extends ImageFilter {
mOutPixelsAllocation.destroy();
mOutPixelsAllocation = null;
}
- sOldBitmap = null;
+ sWidth = 0;
+ sHeight = 0;
+ }
+
+ public static synchronized void destroyRenderScriptContext() {
+ destroyPixelAllocations();
+ sRS.destroy();
+ sRS = null;
+ sResources = null;
}
- private Allocation convertBitmap(Bitmap bitmap) {
- return Allocation.createFromBitmap(mRS, bitmap,
+ private static synchronized Allocation convertBitmap(Bitmap bitmap) {
+ return Allocation.createFromBitmap(sRS, bitmap,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
}
- private Allocation convertRGBAtoA(Bitmap bitmap) {
- Type.Builder tb_a8 = new Type.Builder(mRS, Element.U8(mRS));
- ScriptC_grey greyConvert = new ScriptC_grey(mRS, mResources, R.raw.grey);
+ private static synchronized Allocation convertRGBAtoA(Bitmap bitmap) {
+ Type.Builder tb_a8 = new Type.Builder(sRS, Element.U8(sRS));
+ ScriptC_grey greyConvert = new ScriptC_grey(sRS,
+ sRS.getApplicationContext().getResources(), R.raw.grey);
Allocation bitmapTemp = convertBitmap(bitmap);
- if (bitmapTemp.getType().getElement().isCompatible(Element.U8(mRS))) {
+ if (bitmapTemp.getType().getElement().isCompatible(Element.U8(sRS))) {
return bitmapTemp;
}
tb_a8.setX(bitmapTemp.getType().getX());
tb_a8.setY(bitmapTemp.getType().getY());
- Allocation bitmapAlloc = Allocation.createTyped(mRS, tb_a8.create());
+ Allocation bitmapAlloc = Allocation.createTyped(sRS, tb_a8.create());
greyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
return bitmapAlloc;
}
public Allocation loadResourceAlpha(int resource) {
+ Resources res = null;
+ synchronized(ImageFilterRS.class) {
+ res = sRS.getApplicationContext().getResources();
+ }
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ALPHA_8;
Bitmap bitmap = BitmapFactory.decodeResource(
- mRS.getApplicationContext().getResources(),
+ res,
resource, options);
Allocation ret = convertRGBAtoA(bitmap);
bitmap.recycle();
@@ -154,21 +177,25 @@ public abstract class ImageFilterRS extends ImageFilter {
}
public Allocation loadResource(int resource) {
+ Resources res = null;
+ synchronized(ImageFilterRS.class) {
+ res = sRS.getApplicationContext().getResources();
+ }
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeResource(
- mRS.getApplicationContext().getResources(),
+ res,
resource, options);
Allocation ret = convertBitmap(bitmap);
bitmap.recycle();
return ret;
}
- public boolean isResourcesLoaded() {
+ private boolean isResourcesLoaded() {
return mResourcesLoaded;
}
- public void setResourcesLoaded(boolean resourcesLoaded) {
+ private void setResourcesLoaded(boolean resourcesLoaded) {
mResourcesLoaded = resourcesLoaded;
}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
index b88dbbc47..aa7e70065 100644
--- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
+++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
@@ -21,7 +21,6 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
@@ -31,21 +30,15 @@ import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifData;
-import com.android.gallery3d.exif.ExifInvalidFormatException;
-import com.android.gallery3d.exif.ExifOutputStream;
-import com.android.gallery3d.exif.ExifReader;
-import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import com.android.gallery3d.util.XmpUtilHelper;
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
@@ -55,35 +48,7 @@ import java.util.TimeZone;
*/
public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
-
private static final String LOGTAG = "SaveCopyTask";
- /**
- * Saves the bitmap in the final destination
- */
- public static void saveBitmap(Bitmap bitmap, File destination, Object xmp) {
- saveBitmap(bitmap, destination, xmp, null);
- }
-
- private static void saveBitmap(Bitmap bitmap, File destination, Object xmp, ExifData exif) {
- OutputStream os = null;
- try {
- os = new FileOutputStream(destination);
- if (exif != null) {
- ExifOutputStream eos = new ExifOutputStream(os);
- eos.setExifData(exif);
- bitmap.compress(CompressFormat.JPEG, ImageLoader.DEFAULT_COMPRESS_QUALITY, eos);
- } else {
- bitmap.compress(CompressFormat.JPEG, ImageLoader.DEFAULT_COMPRESS_QUALITY, os);
- }
- } catch (FileNotFoundException e) {
- Log.v(LOGTAG,"Error in writing "+destination.getAbsolutePath());
- } finally {
- Utils.closeSilently(os);;
- }
- if (xmp != null) {
- XmpUtilHelper.writeXMPMeta(destination.getAbsolutePath(), xmp);
- }
- }
/**
* Callback for the completed asynchronous task.
@@ -128,7 +93,8 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
ImageLoader.DEFAULT_SAVE_DIRECTORY);
}
// Create the directory if it doesn't exist
- if (!saveDirectory.exists()) saveDirectory.mkdirs();
+ if (!saveDirectory.exists())
+ saveDirectory.mkdirs();
return saveDirectory;
}
@@ -139,28 +105,59 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
return new File(saveDirectory, filename + ".JPG");
}
- private ExifData getExifData(Uri sourceUri) {
+ public Object getPanoramaXMPData(Uri source, ImagePreset preset) {
+ Object xmp = null;
+ if (preset.isPanoramaSafe()) {
+ InputStream is = null;
+ try {
+ is = context.getContentResolver().openInputStream(source);
+ xmp = XmpUtilHelper.extractXMPMeta(is);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "Failed to get XMP data from image: ", e);
+ } finally {
+ Utils.closeSilently(is);
+ }
+ }
+ return xmp;
+ }
+
+ public boolean putPanoramaXMPData(File file, Object xmp) {
+ if (xmp != null) {
+ return XmpUtilHelper.writeXMPMeta(file.getAbsolutePath(), xmp);
+ }
+ return false;
+ }
+
+ public ExifInterface getExifData(Uri source) {
+ ExifInterface exif = new ExifInterface();
String mimeType = context.getContentResolver().getType(sourceUri);
- if (mimeType != ImageLoader.JPEG_MIME_TYPE) {
- return null;
+ if (mimeType == ImageLoader.JPEG_MIME_TYPE) {
+ InputStream inStream = null;
+ try {
+ inStream = context.getContentResolver().openInputStream(source);
+ exif.readExif(inStream);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "Cannot find file: " + source, e);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Cannot read exif for: " + source, e);
+ } finally {
+ Utils.closeSilently(inStream);
+ }
}
- InputStream is = null;
+ return exif;
+ }
+
+ public boolean putExifData(File file, ExifInterface exif, Bitmap image) {
+ boolean ret = false;
try {
- is = context.getContentResolver().openInputStream(sourceUri);
- ExifReader reader = new ExifReader();
- return reader.read(is);
+ exif.writeExif(image, file.getAbsolutePath());
+ ret = true;
} catch (FileNotFoundException e) {
- Log.w(LOGTAG, "Failed to find file", e);
- return null;
- } catch (ExifInvalidFormatException e) {
- Log.w(LOGTAG, "Invalid EXIF data", e);
- return null;
+ Log.w(LOGTAG, "File not found: " + file.getAbsolutePath(), e);
} catch (IOException e) {
- Log.w(LOGTAG, "Failed to read original file", e);
- return null;
- } finally {
- Utils.closeSilently(is);
+ Log.w(LOGTAG, "Could not write exif: ", e);
}
+ return ret;
}
/**
@@ -173,12 +170,12 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
return null;
}
ImagePreset preset = params[0];
- InputStream is = null;
BitmapFactory.Options options = new BitmapFactory.Options();
+ Uri uri = null;
boolean noBitmap = true;
int num_tries = 0;
// Stopgap fix for low-memory devices.
- while(noBitmap) {
+ while (noBitmap) {
try {
// Try to do bitmap operations, downsample if low-memory
Bitmap bitmap = ImageLoader.loadMutableBitmap(context, sourceUri, options);
@@ -189,24 +186,23 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
bitmap = preset.applyGeometry(bitmap);
bitmap = preset.apply(bitmap);
- Object xmp = null;
- if (preset.isPanoramaSafe()) {
- is = context.getContentResolver().openInputStream(sourceUri);
- xmp = XmpUtilHelper.extractXMPMeta(is);
- }
- ExifData exif = getExifData(sourceUri);
- if (exif != null) {
- exif.addDateTimeStampTag(ExifTag.TAG_DATE_TIME, System.currentTimeMillis(),
- TimeZone.getDefault());
- // Since the image has been modified, set the orientation to normal.
- exif.addTag(ExifTag.TAG_ORIENTATION).setValue(ExifTag.Orientation.TOP_LEFT);
+ Object xmp = getPanoramaXMPData(sourceUri, preset);
+ ExifInterface exif = getExifData(sourceUri);
+
+ // Set tags
+ long time = System.currentTimeMillis();
+ exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, time,
+ TimeZone.getDefault());
+ exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.Orientation.TOP_LEFT));
+
+ // If we succeed in writing the bitmap as a jpeg, return a uri.
+ if (putExifData(this.destinationFile, exif, bitmap)) {
+ putPanoramaXMPData(this.destinationFile, xmp);
+ uri = insertContent(context, sourceUri, this.destinationFile, saveFileName,
+ time);
}
- saveBitmap(bitmap, this.destinationFile, xmp, exif);
- bitmap.recycle();
noBitmap = false;
- } catch (FileNotFoundException ex) {
- Log.w(LOGTAG, "Failed to save image!", ex);
- return null;
} catch (java.lang.OutOfMemoryError e) {
// Try 5 times before failing for good.
if (++num_tries >= 5) {
@@ -214,13 +210,9 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
}
System.gc();
options.inSampleSize *= 2;
- } finally {
- Utils.closeSilently(is);
}
}
- Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName);
return uri;
-
}
@Override
@@ -267,16 +259,17 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
/**
* Insert the content (saved file) with proper source photo properties.
*/
- public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName) {
- long now = System.currentTimeMillis() / 1000;
+ public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName,
+ long time) {
+ time /= 1000;
final ContentValues values = new ContentValues();
values.put(Images.Media.TITLE, saveFileName);
values.put(Images.Media.DISPLAY_NAME, file.getName());
values.put(Images.Media.MIME_TYPE, "image/jpeg");
- values.put(Images.Media.DATE_TAKEN, now);
- values.put(Images.Media.DATE_MODIFIED, now);
- values.put(Images.Media.DATE_ADDED, now);
+ values.put(Images.Media.DATE_TAKEN, time);
+ values.put(Images.Media.DATE_MODIFIED, time);
+ values.put(Images.Media.DATE_ADDED, time);
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, file.getAbsolutePath());
values.put(Images.Media.SIZE, file.length());
@@ -288,20 +281,20 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
querySource(context, sourceUri, projection,
new ContentResolverQueryCallback() {
- @Override
- public void onCursorResult(Cursor cursor) {
- values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
-
- double latitude = cursor.getDouble(1);
- double longitude = cursor.getDouble(2);
- // TODO: Change || to && after the default location issue is
- // fixed.
- if ((latitude != 0f) || (longitude != 0f)) {
- values.put(Images.Media.LATITUDE, latitude);
- values.put(Images.Media.LONGITUDE, longitude);
- }
- }
- });
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
+
+ double latitude = cursor.getDouble(1);
+ double longitude = cursor.getDouble(2);
+ // TODO: Change || to && after the default location
+ // issue is fixed.
+ if ((latitude != 0f) || (longitude != 0f)) {
+ values.put(Images.Media.LATITUDE, latitude);
+ values.put(Images.Media.LONGITUDE, longitude);
+ }
+ }
+ });
return context.getContentResolver().insert(
Images.Media.EXTERNAL_CONTENT_URI, values);
diff --git a/src/com/android/photos/AlbumSetFragment.java b/src/com/android/photos/AlbumSetFragment.java
index 0d4fcc023..d0bc81fd6 100644
--- a/src/com/android/photos/AlbumSetFragment.java
+++ b/src/com/android/photos/AlbumSetFragment.java
@@ -21,42 +21,54 @@ import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.Loader;
import android.database.Cursor;
-import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
-import android.text.format.DateFormat;
+import android.provider.MediaStore.Files.FileColumns;
+import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
-import android.widget.CursorAdapter;
import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
import android.widget.Toast;
import com.android.gallery3d.R;
+import com.android.photos.adapters.AlbumSetCursorAdapter;
import com.android.photos.data.AlbumSetLoader;
import com.android.photos.shims.LoaderCompatShim;
import com.android.photos.shims.MediaSetLoader;
-import java.util.Date;
+import java.util.ArrayList;
public class AlbumSetFragment extends Fragment implements OnItemClickListener,
- LoaderCallbacks<Cursor> {
+ LoaderCallbacks<Cursor>, MultiChoiceManager.Delegate, SelectionManager.Client {
private GridView mAlbumSetView;
private View mEmptyView;
private AlbumSetCursorAdapter mAdapter;
+ private LoaderCompatShim<Cursor> mLoaderCompatShim;
+ private MultiChoiceManager mMultiChoiceManager;
+ private SelectionManager mSelectionManager;
private static final int LOADER_ALBUMSET = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mAdapter = new AlbumSetCursorAdapter(getActivity());
+ Context context = getActivity();
+ mAdapter = new AlbumSetCursorAdapter(context);
+ mMultiChoiceManager = new MultiChoiceManager(context, this);
+ mMultiChoiceManager.setSelectionManager(mSelectionManager);
+ }
+
+ @Override
+ public void setSelectionManager(SelectionManager manager) {
+ mSelectionManager = manager;
+ if (mMultiChoiceManager != null) {
+ mMultiChoiceManager.setSelectionManager(manager);
+ }
}
@Override
@@ -67,6 +79,8 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener,
mEmptyView = root.findViewById(android.R.id.empty);
mEmptyView.setVisibility(View.GONE);
mAlbumSetView.setAdapter(mAdapter);
+ mAlbumSetView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL);
+ mAlbumSetView.setMultiChoiceModeListener(mMultiChoiceManager);
mAlbumSetView.setOnItemClickListener(this);
getLoaderManager().initLoader(LOADER_ALBUMSET, null, this);
updateEmptyStatus();
@@ -78,6 +92,7 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener,
// TODO: Switch to AlbumSetLoader
MediaSetLoader loader = new MediaSetLoader(getActivity());
mAdapter.setDrawableFactory(loader);
+ mLoaderCompatShim = loader;
return loader;
}
@@ -100,63 +115,54 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener,
@Override
public void onItemClick(AdapterView<?> av, View v, int pos, long id) {
- Cursor c = (Cursor) av.getItemAtPosition(pos);
- int albumId = c.getInt(AlbumSetLoader.INDEX_ID);
- // TODO launch an activity showing the photos in the album
- Toast.makeText(v.getContext(), "Clicked " + albumId, Toast.LENGTH_SHORT).show();
+ if (mLoaderCompatShim == null) {
+ // Not fully initialized yet, discard
+ return;
+ }
+ Cursor item = (Cursor) mAdapter.getItem(pos);
+ Toast.makeText(v.getContext(),
+ "Tapped " + item.getInt(AlbumSetLoader.INDEX_ID),
+ Toast.LENGTH_SHORT).show();
}
- private static class AlbumSetCursorAdapter extends CursorAdapter {
+ @Override
+ public int getItemMediaType(Object item) {
+ return FileColumns.MEDIA_TYPE_NONE;
+ }
- private LoaderCompatShim<Cursor> mDrawableFactory;
+ @Override
+ public int getItemSupportedOperations(Object item) {
+ return ((Cursor) item).getInt(AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS);
+ }
- public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
- mDrawableFactory = factory;
- }
- private Date mDate = new Date(); // Used for converting timestamps for display
+ @Override
+ public Object getItemAtPosition(int position) {
+ return mAdapter.getItem(position);
+ }
- public AlbumSetCursorAdapter(Context context) {
- super(context, null, false);
- }
+ @Override
+ public ArrayList<Uri> getSubItemUrisForItem(Object item) {
+ return mLoaderCompatShim.urisForSubItems((Cursor) item);
+ }
- @Override
- public void bindView(View v, Context context, Cursor cursor) {
- TextView titleTextView = (TextView) v.findViewById(
- R.id.album_set_item_title);
- titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE));
-
- TextView dateTextView = (TextView) v.findViewById(
- R.id.album_set_item_date);
- long timestamp = cursor.getLong(AlbumSetLoader.INDEX_TIMESTAMP);
- if (timestamp > 0) {
- mDate.setTime(timestamp);
- dateTextView.setText(DateFormat.getMediumDateFormat(context).format(mDate));
- } else {
- dateTextView.setText(null);
- }
-
- ProgressBar uploadProgressBar = (ProgressBar) v.findViewById(
- R.id.album_set_item_upload_progress);
- if (cursor.getInt(AlbumSetLoader.INDEX_COUNT_PENDING_UPLOAD) > 0) {
- uploadProgressBar.setVisibility(View.VISIBLE);
- uploadProgressBar.setProgress(50);
- } else {
- uploadProgressBar.setVisibility(View.INVISIBLE);
- }
-
- ImageView thumbImageView = (ImageView) v.findViewById(
- R.id.album_set_item_image);
- Drawable recycle = thumbImageView.getDrawable();
- Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
- if (recycle != drawable) {
- thumbImageView.setImageDrawable(drawable);
- }
- }
+ @Override
+ public Object getPathForItemAtPosition(int position) {
+ return mLoaderCompatShim.getPathForItem((Cursor) mAdapter.getItem(position));
+ }
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return LayoutInflater.from(context).inflate(
- R.layout.album_set_item, parent, false);
- }
+ @Override
+ public void deleteItemWithPath(Object itemPath) {
+ mLoaderCompatShim.deleteItemWithPath(itemPath);
}
+
+ @Override
+ public SparseBooleanArray getSelectedItemPositions() {
+ return mAlbumSetView.getCheckedItemPositions();
+ }
+
+ @Override
+ public int getSelectedItemCount() {
+ return mAlbumSetView.getCheckedItemCount();
+ }
+
}
diff --git a/src/com/android/photos/GalleryActivity.java b/src/com/android/photos/GalleryActivity.java
index 420fc3d51..ddf04e365 100644
--- a/src/com/android/photos/GalleryActivity.java
+++ b/src/com/android/photos/GalleryActivity.java
@@ -43,7 +43,7 @@ public class GalleryActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+ mSelectionManager = new SelectionManager(this);
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.viewpager);
setContentView(mViewPager);
@@ -70,13 +70,6 @@ public class GalleryActivity extends Activity {
outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}
- protected SelectionManager getSelectionManager() {
- if (mSelectionManager == null) {
- mSelectionManager = new SelectionManager(this);
- }
- return mSelectionManager;
- }
-
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.gallery, menu);
@@ -99,7 +92,7 @@ public class GalleryActivity extends Activity {
public static class TabsAdapter extends FragmentPagerAdapter implements
ActionBar.TabListener, ViewPager.OnPageChangeListener {
- private final Context mContext;
+ private final GalleryActivity mActivity;
private final ActionBar mActionBar;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
@@ -115,9 +108,9 @@ public class GalleryActivity extends Activity {
}
}
- public TabsAdapter(Activity activity, ViewPager pager) {
+ public TabsAdapter(GalleryActivity activity, ViewPager pager) {
super(activity.getFragmentManager());
- mContext = activity;
+ mActivity = activity;
mActionBar = activity.getActionBar();
mViewPager = pager;
mViewPager.setAdapter(this);
@@ -141,8 +134,11 @@ public class GalleryActivity extends Activity {
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
- return Fragment.instantiate(mContext, info.clss.getName(),
+ Fragment item = Fragment.instantiate(mActivity, info.clss.getName(),
info.args);
+ ((SelectionManager.Client) item).setSelectionManager(
+ mActivity.mSelectionManager);
+ return item;
}
@Override
diff --git a/src/com/android/photos/MultiChoiceManager.java b/src/com/android/photos/MultiChoiceManager.java
new file mode 100644
index 000000000..e00c842fe
--- /dev/null
+++ b/src/com/android/photos/MultiChoiceManager.java
@@ -0,0 +1,190 @@
+/*
+ * 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.photos;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.ShareActionProvider;
+import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.MediaObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MultiChoiceManager implements MultiChoiceModeListener,
+ OnShareTargetSelectedListener, SelectionManager.SelectedUriSource {
+
+ public interface Delegate {
+ public SparseBooleanArray getSelectedItemPositions();
+ public int getSelectedItemCount();
+ public int getItemMediaType(Object item);
+ public int getItemSupportedOperations(Object item);
+ public ArrayList<Uri> getSubItemUrisForItem(Object item);
+ public Object getItemAtPosition(int position);
+ public Object getPathForItemAtPosition(int position);
+ public void deleteItemWithPath(Object itemPath);
+ }
+
+ private SelectionManager mSelectionManager;
+ private ShareActionProvider mShareActionProvider;
+ private ActionMode mActionMode;
+ private int numSubItemsCollected = 0;
+ private Context mContext;
+ private Delegate mDelegate;
+
+ private ArrayList<Uri> mSelectedUrisArray = new ArrayList<Uri>();
+
+ public MultiChoiceManager(Context context, Delegate delegate) {
+ mContext = context;
+ mDelegate = delegate;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ mSelectionManager = selectionManager;
+ }
+
+ @Override
+ public ArrayList<Uri> getSelectedShareableUris() {
+ return mSelectedUrisArray;
+ }
+
+ private void updateSelectedTitle(ActionMode mode) {
+ int count = mDelegate.getSelectedItemCount();
+ mode.setTitle(mContext.getResources().getQuantityString(
+ R.plurals.number_of_items_selected, count, count));
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+ boolean checked) {
+ updateSelectedTitle(mode);
+ Object item = mDelegate.getItemAtPosition(position);
+
+ ArrayList<Uri> subItems = mDelegate.getSubItemUrisForItem(item);
+ if (checked) {
+ mSelectedUrisArray.addAll(subItems);
+ numSubItemsCollected += subItems.size();
+ } else {
+ mSelectedUrisArray.removeAll(subItems);
+ numSubItemsCollected -= subItems.size();
+ }
+
+ mSelectionManager.onItemSelectedStateChanged(mShareActionProvider,
+ mDelegate.getItemMediaType(item),
+ mDelegate.getItemSupportedOperations(item),
+ checked);
+ updateActionItemVisibilities(mode.getMenu(),
+ mSelectionManager.getSupportedOperations());
+ }
+
+ private void updateActionItemVisibilities(Menu menu, int supportedOperations) {
+ MenuItem shareItem = menu.findItem(R.id.menu_share);
+ MenuItem deleteItem = menu.findItem(R.id.menu_delete);
+ shareItem.setVisible((supportedOperations & MediaObject.SUPPORT_SHARE) > 0);
+ deleteItem.setVisible((supportedOperations & MediaObject.SUPPORT_DELETE) > 0);
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mSelectionManager.setSelectedUriSource(this);
+ mActionMode = mode;
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.gallery_multiselect, menu);
+ MenuItem menuItem = menu.findItem(R.id.menu_share);
+ mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
+ mShareActionProvider.setOnShareTargetSelectedListener(this);
+ updateSelectedTitle(mode);
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ // onDestroyActionMode gets called when the share target was selected,
+ // but apparently before the ArrayList is serialized in the intent
+ // so we can't clear the old one here.
+ mSelectedUrisArray = new ArrayList<Uri>();
+ mSelectionManager.onClearSelection();
+ mSelectionManager.setSelectedUriSource(null);
+ mShareActionProvider = null;
+ mActionMode = null;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ updateSelectedTitle(mode);
+ return false;
+ }
+
+ @Override
+ public boolean onShareTargetSelected(ShareActionProvider provider, Intent intent) {
+ mActionMode.finish();
+ return false;
+ }
+
+ private static class BulkDeleteTask extends AsyncTask<Void, Void, Void> {
+ private Delegate mDelegate;
+ private List<Object> mPaths;
+
+ public BulkDeleteTask(Delegate delegate, List<Object> paths) {
+ mDelegate = delegate;
+ mPaths = paths;
+ }
+
+ @Override
+ protected Void doInBackground(Void... ignored) {
+ for (Object path : mPaths) {
+ mDelegate.deleteItemWithPath(path);
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_delete:
+ BulkDeleteTask deleteTask = new BulkDeleteTask(mDelegate,
+ getPathsForSelectedItems());
+ deleteTask.execute();
+ mode.finish();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private List<Object> getPathsForSelectedItems() {
+ List<Object> paths = new ArrayList<Object>();
+ SparseBooleanArray selected = mDelegate.getSelectedItemPositions();
+ for (int i = 0; i < selected.size(); i++) {
+ if (selected.valueAt(i)) {
+ paths.add(mDelegate.getPathForItemAtPosition(i));
+ }
+ }
+ return paths;
+ }
+}
diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java
index 25d80360d..b485cd051 100644
--- a/src/com/android/photos/PhotoSetFragment.java
+++ b/src/com/android/photos/PhotoSetFragment.java
@@ -18,41 +18,31 @@ package com.android.photos;
import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.SparseBooleanArray;
-import android.view.ActionMode;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
-import android.widget.ShareActionProvider;
-import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
import com.android.gallery3d.R;
import com.android.gallery3d.app.Gallery;
-import com.android.gallery3d.data.MediaItem;
import com.android.photos.adapters.PhotoThumbnailAdapter;
import com.android.photos.data.PhotoSetLoader;
import com.android.photos.shims.LoaderCompatShim;
import com.android.photos.shims.MediaItemsLoader;
import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor>,
- OnItemClickListener, SelectionManager.SelectedUriSource, MultiChoiceModeListener,
- OnShareTargetSelectedListener {
+public class PhotoSetFragment extends Fragment implements OnItemClickListener,
+ LoaderCallbacks<Cursor>, MultiChoiceManager.Delegate, SelectionManager.Client {
private static final int LOADER_PHOTOSET = 1;
@@ -62,14 +52,24 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
private boolean mInitialLoadComplete = false;
private LoaderCompatShim<Cursor> mLoaderCompatShim;
private PhotoThumbnailAdapter mAdapter;
+ private MultiChoiceManager mMultiChoiceManager;
private SelectionManager mSelectionManager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- GalleryActivity activity = (GalleryActivity) getActivity();
- mSelectionManager = activity.getSelectionManager();
- mAdapter = new PhotoThumbnailAdapter(activity);
+ Context context = getActivity();
+ mAdapter = new PhotoThumbnailAdapter(context);
+ mMultiChoiceManager = new MultiChoiceManager(context, this);
+ mMultiChoiceManager.setSelectionManager(mSelectionManager);
+ }
+
+ @Override
+ public void setSelectionManager(SelectionManager manager) {
+ mSelectionManager = manager;
+ if (mMultiChoiceManager != null) {
+ mMultiChoiceManager.setSelectionManager(manager);
+ }
}
@Override
@@ -84,7 +84,7 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
mEmptyView.setVisibility(View.GONE);
mPhotoSetView.setAdapter(mAdapter);
mPhotoSetView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL);
- mPhotoSetView.setMultiChoiceModeListener(this);
+ mPhotoSetView.setMultiChoiceModeListener(mMultiChoiceManager);
getLoaderManager().initLoader(LOADER_PHOTOSET, null, this);
updateEmptyStatus();
return root;
@@ -129,112 +129,51 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
updateEmptyStatus();
}
- private Set<Uri> mSelectedUris = new HashSet<Uri>();
- private ArrayList<Uri> mSelectedUrisArray = new ArrayList<Uri>();
-
@Override
- public ArrayList<Uri> getSelectedShareableUris() {
- mSelectedUrisArray.clear();
- mSelectedUrisArray.addAll(mSelectedUris);
- return mSelectedUrisArray;
- }
-
- public ArrayList<Uri> getSelectedShareableUrisUncached() {
- mSelectedUrisArray.clear();
- SparseBooleanArray selected = mPhotoSetView.getCheckedItemPositions();
-
- for (int i = 0; i < selected.size(); i++) {
- if (selected.valueAt(i)) {
- Cursor item = mAdapter.getItem(selected.keyAt(i));
- int supported = item.getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS);
- if ((supported & MediaItem.SUPPORT_SHARE) > 0) {
- mSelectedUrisArray.add(mLoaderCompatShim.uriForItem(item));
- }
- }
- }
-
- return mSelectedUrisArray;
+ public void onLoaderReset(Loader<Cursor> loader) {
}
@Override
- public void onLoaderReset(Loader<Cursor> loader) {
+ public int getItemMediaType(Object item) {
+ return ((Cursor) item).getInt(PhotoSetLoader.INDEX_MEDIA_TYPE);
}
-
- private ShareActionProvider mShareActionProvider;
- private ActionMode mActionMode;
- private boolean mSharePending = false;
-
- private void updateSelectedTitle(ActionMode mode) {
- int count = mPhotoSetView.getCheckedItemCount();
- mode.setTitle(getResources().getQuantityString(
- R.plurals.number_of_items_selected, count, count));
+ @Override
+ public int getItemSupportedOperations(Object item) {
+ return ((Cursor) item).getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS);
}
@Override
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
- boolean checked) {
- updateSelectedTitle(mode);
- Cursor item = mAdapter.getItem(position);
-
- if (checked) {
- mSelectedUris.add(mLoaderCompatShim.uriForItem(item));
- } else {
- mSelectedUris.remove(mLoaderCompatShim.uriForItem(item));
- }
-
- mSelectionManager.onItemSelectedStateChanged(mShareActionProvider,
- item.getInt(PhotoSetLoader.INDEX_MEDIA_TYPE),
- item.getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS),
- checked);
+ public Object getItemAtPosition(int position) {
+ return mAdapter.getItem(position);
}
+ private ArrayList<Uri> mSubItemUriTemp = new ArrayList<Uri>(1);
@Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- mSelectionManager.setSelectedUriSource(PhotoSetFragment.this);
- mActionMode = mode;
- MenuInflater inflater = mode.getMenuInflater();
- inflater.inflate(R.menu.gallery_multiselect, menu);
- MenuItem menuItem = menu.findItem(R.id.menu_share);
- mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
- mShareActionProvider.setOnShareTargetSelectedListener(this);
- updateSelectedTitle(mode);
- return true;
+ public ArrayList<Uri> getSubItemUrisForItem(Object item) {
+ mSubItemUriTemp.clear();
+ mSubItemUriTemp.add(mLoaderCompatShim.uriForItem((Cursor) item));
+ return mSubItemUriTemp;
}
+
@Override
- public void onDestroyActionMode(ActionMode mode) {
- mSelectedUris.clear();
- if (mSharePending) {
- // onDestroyActionMode gets called when the share target was selected,
- // but apparently before the ArrayList is serialized in the intent
- // so we can't clear the old one here.
- mSelectedUrisArray = new ArrayList<Uri>();
- mSharePending = false;
- } else {
- mSelectedUrisArray.clear();
- }
- mSelectionManager.onClearSelection();
- mSelectionManager.setSelectedUriSource(null);
- mShareActionProvider = null;
- mActionMode = null;
+ public Object getPathForItemAtPosition(int position) {
+ return mLoaderCompatShim.getPathForItem(mAdapter.getItem(position));
}
@Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- updateSelectedTitle(mode);
- return false;
+ public void deleteItemWithPath(Object itemPath) {
+ mLoaderCompatShim.deleteItemWithPath(itemPath);
}
@Override
- public boolean onShareTargetSelected(ShareActionProvider provider, Intent intent) {
- mSharePending = true;
- mActionMode.finish();
- return false;
+ public SparseBooleanArray getSelectedItemPositions() {
+ return mPhotoSetView.getCheckedItemPositions();
}
@Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return false;
+ public int getSelectedItemCount() {
+ return mPhotoSetView.getCheckedItemCount();
}
}
diff --git a/src/com/android/photos/SelectionManager.java b/src/com/android/photos/SelectionManager.java
index 979dcc7da..ce340c731 100644
--- a/src/com/android/photos/SelectionManager.java
+++ b/src/com/android/photos/SelectionManager.java
@@ -26,7 +26,7 @@ import android.provider.MediaStore.Files.FileColumns;
import android.widget.ShareActionProvider;
import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.util.GalleryUtils;
import java.util.ArrayList;
@@ -41,6 +41,10 @@ public class SelectionManager {
public ArrayList<Uri> getSelectedShareableUris();
}
+ public interface Client {
+ public void setSelectionManager(SelectionManager manager);
+ }
+
public SelectionManager(Activity activity) {
mActivity = activity;
if (ApiHelper.AT_LEAST_16) {
@@ -76,10 +80,10 @@ public class SelectionManager {
mSelectedTotalCount += increment;
mCachedShareableUris = null;
- if ((itemSupportedOperations & MediaItem.SUPPORT_DELETE) > 0) {
+ if ((itemSupportedOperations & MediaObject.SUPPORT_DELETE) > 0) {
mSelectedDeletableCount += increment;
}
- if ((itemSupportedOperations & MediaItem.SUPPORT_SHARE) > 0) {
+ if ((itemSupportedOperations & MediaObject.SUPPORT_SHARE) > 0) {
mSelectedShareableCount += increment;
if (itemType == FileColumns.MEDIA_TYPE_IMAGE) {
mSelectedShareableImageCount += increment;
@@ -93,24 +97,42 @@ public class SelectionManager {
mShareIntent.setAction(null).setType(null);
} else if (mSelectedShareableCount >= 1) {
mCachedShareableUris = mUriSource.getSelectedShareableUris();
- if (mSelectedShareableImageCount == mSelectedShareableCount) {
- mShareIntent.setType(GalleryUtils.MIME_TYPE_IMAGE);
- } else if (mSelectedShareableVideoCount == mSelectedShareableCount) {
- mShareIntent.setType(GalleryUtils.MIME_TYPE_VIDEO);
- } else {
- mShareIntent.setType(GalleryUtils.MIME_TYPE_ALL);
- }
- if (mSelectedShareableCount == 1) {
- mShareIntent.setAction(Intent.ACTION_SEND);
- mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris.get(0));
+ if (mCachedShareableUris.size() == 0) {
+ mShareIntent.setAction(null).setType(null);
} else {
- mShareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
- mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris);
+ if (mSelectedShareableImageCount == mSelectedShareableCount) {
+ mShareIntent.setType(GalleryUtils.MIME_TYPE_IMAGE);
+ } else if (mSelectedShareableVideoCount == mSelectedShareableCount) {
+ mShareIntent.setType(GalleryUtils.MIME_TYPE_VIDEO);
+ } else {
+ mShareIntent.setType(GalleryUtils.MIME_TYPE_ALL);
+ }
+ if (mCachedShareableUris.size() == 1) {
+ mShareIntent.setAction(Intent.ACTION_SEND);
+ mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris.get(0));
+ } else {
+ mShareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
+ mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris);
+ }
}
}
share.setShareIntent(mShareIntent);
- // TODO update deletability, editability, etc.
+ // TODO update editability, etc.
+ }
+
+ public int getSupportedOperations() {
+ if (mSelectedTotalCount == 0) {
+ return 0;
+ }
+ int supported = 0;
+ if (mSelectedDeletableCount == mSelectedTotalCount) {
+ supported |= MediaObject.SUPPORT_DELETE;
+ }
+ if (mSelectedShareableCount > 0) {
+ supported |= MediaObject.SUPPORT_SHARE;
+ }
+ return supported;
}
public void onClearSelection() {
diff --git a/src/com/android/photos/adapters/AlbumSetCursorAdapter.java b/src/com/android/photos/adapters/AlbumSetCursorAdapter.java
new file mode 100644
index 000000000..c387f8f47
--- /dev/null
+++ b/src/com/android/photos/adapters/AlbumSetCursorAdapter.java
@@ -0,0 +1,89 @@
+/*
+ * 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.photos.adapters;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.photos.data.AlbumSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+
+import java.util.Date;
+
+public class AlbumSetCursorAdapter extends CursorAdapter {
+
+ private LoaderCompatShim<Cursor> mDrawableFactory;
+
+ public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
+ mDrawableFactory = factory;
+ }
+ private Date mDate = new Date(); // Used for converting timestamps for display
+
+ public AlbumSetCursorAdapter(Context context) {
+ super(context, null, false);
+ }
+
+ @Override
+ public void bindView(View v, Context context, Cursor cursor) {
+ TextView titleTextView = (TextView) v.findViewById(
+ R.id.album_set_item_title);
+ titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE));
+
+ TextView dateTextView = (TextView) v.findViewById(
+ R.id.album_set_item_date);
+ long timestamp = cursor.getLong(AlbumSetLoader.INDEX_TIMESTAMP);
+ if (timestamp > 0) {
+ mDate.setTime(timestamp);
+ dateTextView.setText(DateFormat.getMediumDateFormat(context).format(mDate));
+ } else {
+ dateTextView.setText(null);
+ }
+
+ ProgressBar uploadProgressBar = (ProgressBar) v.findViewById(
+ R.id.album_set_item_upload_progress);
+ if (cursor.getInt(AlbumSetLoader.INDEX_COUNT_PENDING_UPLOAD) > 0) {
+ uploadProgressBar.setVisibility(View.VISIBLE);
+ uploadProgressBar.setProgress(50);
+ } else {
+ uploadProgressBar.setVisibility(View.INVISIBLE);
+ }
+
+ ImageView thumbImageView = (ImageView) v.findViewById(
+ R.id.album_set_item_image);
+ Drawable recycle = thumbImageView.getDrawable();
+ Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+ if (recycle != drawable) {
+ thumbImageView.setImageDrawable(drawable);
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return LayoutInflater.from(context).inflate(
+ R.layout.album_set_item, parent, false);
+ }
+}
diff --git a/src/com/android/photos/data/AlbumSetLoader.java b/src/com/android/photos/data/AlbumSetLoader.java
index b2b5204e6..940473255 100644
--- a/src/com/android/photos/data/AlbumSetLoader.java
+++ b/src/com/android/photos/data/AlbumSetLoader.java
@@ -12,6 +12,7 @@ public class AlbumSetLoader {
public static final int INDEX_THUMBNAIL_HEIGHT = 5;
public static final int INDEX_COUNT_PENDING_UPLOAD = 6;
public static final int INDEX_COUNT = 7;
+ public static final int INDEX_SUPPORTED_OPERATIONS = 8;
public static final String[] PROJECTION = {
"_id",
@@ -21,7 +22,8 @@ public class AlbumSetLoader {
"thumb_width",
"thumb_height",
"count_pending_upload",
- "_count"
+ "_count",
+ "supported_operations"
};
public static final MatrixCursor MOCK = createRandomCursor(30);
@@ -44,7 +46,8 @@ public class AlbumSetLoader {
0,
0,
(random < .3 ? 1 : 0),
- 1
+ 1,
+ 0
};
return row;
}
diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java
index 72c8e93cc..56c82c4a9 100644
--- a/src/com/android/photos/data/PhotoSetLoader.java
+++ b/src/com/android/photos/data/PhotoSetLoader.java
@@ -29,6 +29,8 @@ import android.provider.MediaStore.Files.FileColumns;
import com.android.photos.drawables.DataUriThumbnailDrawable;
import com.android.photos.shims.LoaderCompatShim;
+import java.util.ArrayList;
+
public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cursor> {
public static final String SUPPORTED_OPERATIONS = "supported_operations";
@@ -95,4 +97,19 @@ public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cur
public Uri uriForItem(Cursor item) {
return null;
}
+
+ @Override
+ public ArrayList<Uri> urisForSubItems(Cursor item) {
+ return null;
+ }
+
+ @Override
+ public void deleteItemWithPath(Object path) {
+
+ }
+
+ @Override
+ public Object getPathForItem(Cursor item) {
+ return null;
+ }
}
diff --git a/src/com/android/photos/shims/LoaderCompatShim.java b/src/com/android/photos/shims/LoaderCompatShim.java
index 9da4436aa..d5bf710de 100644
--- a/src/com/android/photos/shims/LoaderCompatShim.java
+++ b/src/com/android/photos/shims/LoaderCompatShim.java
@@ -19,8 +19,13 @@ package com.android.photos.shims;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import java.util.ArrayList;
+
public interface LoaderCompatShim<T> {
Drawable drawableForItem(T item, Drawable recycle);
Uri uriForItem(T item);
+ ArrayList<Uri> urisForSubItems(T item);
+ void deleteItemWithPath(Object path);
+ Object getPathForItem(T item);
}
diff --git a/src/com/android/photos/shims/MediaItemsLoader.java b/src/com/android/photos/shims/MediaItemsLoader.java
index fa41c8ec8..d75823404 100644
--- a/src/com/android/photos/shims/MediaItemsLoader.java
+++ b/src/com/android/photos/shims/MediaItemsLoader.java
@@ -28,12 +28,16 @@ import android.util.SparseArray;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.MediaSet.ItemConsumer;
import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.data.Path;
import com.android.gallery3d.util.Future;
import com.android.photos.data.PhotoSetLoader;
+import java.util.ArrayList;
+
/**
* Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear
* like a PhotoSetLoader
@@ -47,6 +51,7 @@ public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderC
};
private final MediaSet mMediaSet;
+ private final DataManager mDataManager;
private Future<Integer> mSyncTask = null;
private ContentListener mObserver = new ContentListener() {
@Override
@@ -58,14 +63,15 @@ public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderC
public MediaItemsLoader(Context context) {
super(context);
- DataManager dm = DataManager.from(context);
- String path = dm.getTopSetPath(DataManager.INCLUDE_ALL);
- mMediaSet = dm.getMediaSet(path);
+ mDataManager = DataManager.from(context);
+ String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL);
+ mMediaSet = mDataManager.getMediaSet(path);
}
public MediaItemsLoader(Context context, String parentPath) {
super(context);
- mMediaSet = DataManager.from(getContext()).getMediaSet(parentPath);
+ mDataManager = DataManager.from(getContext());
+ mMediaSet = mDataManager.getMediaSet(parentPath);
}
@Override
@@ -157,4 +163,27 @@ public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderC
return mi == null ? null : mi.getContentUri();
}
+ @Override
+ public ArrayList<Uri> urisForSubItems(Cursor item) {
+ return null;
+ }
+
+ @Override
+ public void deleteItemWithPath(Object path) {
+ MediaObject o = mDataManager.getMediaObject((Path) path);
+ if (o != null) {
+ o.delete();
+ }
+ }
+
+ @Override
+ public Object getPathForItem(Cursor item) {
+ int index = item.getInt(PhotoSetLoader.INDEX_ID);
+ MediaItem mi = mMediaItems.get(index);
+ if (mi != null) {
+ return mi.getPath();
+ }
+ return null;
+ }
+
}
diff --git a/src/com/android/photos/shims/MediaSetLoader.java b/src/com/android/photos/shims/MediaSetLoader.java
index 96c7485bb..d200807f9 100644
--- a/src/com/android/photos/shims/MediaSetLoader.java
+++ b/src/com/android/photos/shims/MediaSetLoader.java
@@ -26,7 +26,9 @@ import android.net.Uri;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
import com.android.gallery3d.data.MediaSet.SyncListener;
import com.android.gallery3d.util.Future;
import com.android.photos.data.AlbumSetLoader;
@@ -46,6 +48,7 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom
};
private final MediaSet mMediaSet;
+ private final DataManager mDataManager;
private Future<Integer> mSyncTask = null;
private ContentListener mObserver = new ContentListener() {
@Override
@@ -58,14 +61,15 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom
public MediaSetLoader(Context context) {
super(context);
- DataManager dm = DataManager.from(context);
- String path = dm.getTopSetPath(DataManager.INCLUDE_ALL);
- mMediaSet = dm.getMediaSet(path);
+ mDataManager = DataManager.from(context);
+ String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL);
+ mMediaSet = mDataManager.getMediaSet(path);
}
public MediaSetLoader(Context context, String path) {
super(context);
- mMediaSet = DataManager.from(getContext()).getMediaSet(path);
+ mDataManager = DataManager.from(getContext());
+ mMediaSet = mDataManager.getMediaSet(path);
}
@Override
@@ -111,6 +115,7 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom
row[AlbumSetLoader.INDEX_ID] = i;
row[AlbumSetLoader.INDEX_TITLE] = m.getName();
row[AlbumSetLoader.INDEX_COUNT] = m.getMediaItemCount();
+ row[AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS] = m.getSupportedOperations();
MediaItem coverItem = m.getCoverMediaItem();
if (coverItem != null) {
row[AlbumSetLoader.INDEX_TIMESTAMP] = coverItem.getDateInMs();
@@ -147,4 +152,39 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom
MediaSet ms = mMediaSet.getSubMediaSet(index);
return ms == null ? null : ms.getContentUri();
}
+
+ @Override
+ public ArrayList<Uri> urisForSubItems(Cursor item) {
+ int index = item.getInt(AlbumSetLoader.INDEX_ID);
+ MediaSet ms = mMediaSet.getSubMediaSet(index);
+ if (ms == null) return null;
+ final ArrayList<Uri> result = new ArrayList<Uri>();
+ ms.enumerateMediaItems(new MediaSet.ItemConsumer() {
+ @Override
+ public void consume(int index, MediaItem item) {
+ if (item != null) {
+ result.add(item.getContentUri());
+ }
+ }
+ });
+ return result;
+ }
+
+ @Override
+ public void deleteItemWithPath(Object path) {
+ MediaObject o = mDataManager.getMediaObject((Path) path);
+ if (o != null) {
+ o.delete();
+ }
+ }
+
+ @Override
+ public Object getPathForItem(Cursor item) {
+ int index = item.getInt(AlbumSetLoader.INDEX_ID);
+ MediaSet ms = mMediaSet.getSubMediaSet(index);
+ if (ms != null) {
+ return ms.getPath();
+ }
+ return null;
+ }
}