Move database operation to another thread.
Change-Id: I025f428e1df2202dec6344e8b017a752a50e368a
diff --git a/new3d/src/com/android/gallery3d/app/Gallery.java b/new3d/src/com/android/gallery3d/app/Gallery.java
index bf12da3..74d5cc7 100644
--- a/new3d/src/com/android/gallery3d/app/Gallery.java
+++ b/new3d/src/com/android/gallery3d/app/Gallery.java
@@ -27,11 +27,11 @@
import com.android.gallery3d.data.MediaDbAccessor;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.ui.Compositor;
-import com.android.gallery3d.ui.GLHandler;
import com.android.gallery3d.ui.GLRootView;
import com.android.gallery3d.ui.GridSlotAdapter;
import com.android.gallery3d.ui.MediaSetSlotAdapter;
import com.android.gallery3d.ui.SlotView;
+import com.android.gallery3d.ui.SynchronizedHandler;
public final class Gallery extends Activity implements SlotView.SlotTapListener {
public static final String REVIEW_ACTION = "com.android.gallery3d.app.REVIEW";
@@ -40,7 +40,7 @@
private static final String TAG = "Gallery";
private GLRootView mGLRootView;
- private GLHandler mHandler;
+ private SynchronizedHandler mHandler;
private Compositor mCompositor;
private MediaSet mRootSet;
private SlotView mSlotView;
@@ -48,19 +48,20 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ImageService.initialize(this);
-
setContentView(R.layout.main);
mGLRootView = (GLRootView) findViewById(R.id.gl_root_view);
- mRootSet = MediaDbAccessor.getMediaSets(this);
+ ImageService.initialize(this);
+ MediaDbAccessor.initialize(this, mGLRootView);
+
+ mRootSet = MediaDbAccessor.getInstance().getRootMediaSets();
mCompositor = new Compositor(this);
mSlotView = mCompositor.getSlotView();
mSlotView.setModel(new MediaSetSlotAdapter(this, mRootSet, mSlotView));
mSlotView.setSlotTapListener(this);
mGLRootView.setContentPane(mCompositor);
- mHandler = new GLHandler(mGLRootView) {
+ mHandler = new SynchronizedHandler(mGLRootView) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
@@ -71,7 +72,6 @@
}
}
};
-
}
public void onSingleTapUp(int slotIndex) {
diff --git a/new3d/src/com/android/gallery3d/data/BucketMediaSet.java b/new3d/src/com/android/gallery3d/data/BucketMediaSet.java
new file mode 100644
index 0000000..658ff35
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/data/BucketMediaSet.java
@@ -0,0 +1,165 @@
+ // Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.data;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.gallery3d.ui.SynchronizedHandler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+public class BucketMediaSet implements MediaSet {
+ private static final int MAX_NUM_COVER_ITEMS = 4;
+
+ private static final int MSG_LOAD_DATABASE = 1;
+ private static final int MSG_UPDATE_BUCKET = 2;
+
+ public static final Comparator<BucketMediaSet> sNameComparator = new MyComparator();
+
+ private final MediaDbAccessor mAccessor;
+ private final int mBucketId;
+ private final String mBucketTitle;
+ private final ArrayList<DatabaseMediaItem> mMediaItems =
+ new ArrayList<DatabaseMediaItem>();
+ private ArrayList<DatabaseMediaItem> mLoadBuffer =
+ new ArrayList<DatabaseMediaItem>();
+
+ private final Handler mHandler;
+ private final Handler mMainHandler;
+
+ private MediaSetListener mListener;
+
+ protected void invalidate() {
+ mHandler.sendEmptyMessage(MSG_LOAD_DATABASE);
+ }
+
+ public BucketMediaSet(MediaDbAccessor accessor, int id, String title) {
+ mAccessor = accessor;
+ mBucketId = id;
+ mBucketTitle= title;
+
+ mHandler = new Handler(accessor.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_LOAD_DATABASE:
+ loadMediaItemsFromDatabase();
+ break;
+ default: throw new IllegalArgumentException();
+ }
+ }
+ };
+
+ mMainHandler = new SynchronizedHandler(
+ accessor.getUiMonitor(), accessor.getMainLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_BUCKET:
+ updateContent();
+ break;
+ default: throw new IllegalArgumentException();
+ }
+ }
+ };
+
+ }
+
+ public MediaItem[] getCoverMediaItems() {
+ int size = Math.min(MAX_NUM_COVER_ITEMS, mMediaItems.size());
+ MediaItem items[] = new MediaItem[size];
+ for (int i = 0; i < size; ++i) {
+ items[i] = mMediaItems.get(i);
+ }
+ return items;
+ }
+
+ public MediaItem getMediaItem(int index) {
+ return mMediaItems.get(index);
+ }
+
+ public int getMediaItemCount() {
+ return mMediaItems.size();
+ }
+
+ public MediaSet getSubMediaSet(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ public int getSubMediaSetCount() {
+ return 0;
+ }
+
+ public String getTitle() {
+ return mBucketTitle;
+ }
+
+ public int getTotalMediaItemCount() {
+ return mMediaItems.size();
+ }
+
+ public void setContentListener(MediaSetListener listener) {
+ mListener = listener;
+ }
+
+ private void loadMediaItemsFromDatabase() {
+ ArrayList<DatabaseMediaItem> items = new ArrayList<DatabaseMediaItem>();
+ mLoadBuffer = items;
+
+ ContentResolver resolver = mAccessor.getContentResolver();
+
+ Cursor cursor = ImageMediaItem.queryImageInBucket(resolver, mBucketId);
+ try {
+ while (cursor.moveToNext()) {
+ items.add(ImageMediaItem.load(cursor));
+ }
+ } finally {
+ cursor.close();
+ }
+
+ cursor = VideoMediaItem.queryVideoInBucket(resolver, mBucketId);
+ try {
+ while (cursor.moveToNext()) {
+ items.add(VideoMediaItem.load(cursor));
+ }
+ } finally {
+ cursor.close();
+ }
+
+ Collections.sort(items, new Comparator<DatabaseMediaItem>() {
+
+ public int compare(DatabaseMediaItem o1, DatabaseMediaItem o2) {
+ // sort items in descending order based on their taken time.
+ long result = -(o1.mDateTakenInMs - o2.mDateTakenInMs);
+ return result == 0
+ ? o1.mId - o2.mId
+ : result > 0 ? 1 : -1;
+ }
+ });
+
+ mMainHandler.sendEmptyMessage(MSG_UPDATE_BUCKET);
+ }
+
+ private void updateContent() {
+ if (mLoadBuffer == null) throw new IllegalArgumentException();
+
+ mMediaItems.clear();
+ mMediaItems.addAll(mLoadBuffer);
+ mLoadBuffer = null;
+
+ if (mListener != null) mListener.onContentChanged();
+ }
+
+ private static class MyComparator implements Comparator<BucketMediaSet> {
+
+ public int compare(BucketMediaSet s1, BucketMediaSet s2) {
+ int result = s1.mBucketTitle.compareTo(s2.mBucketTitle);
+ return result != 0 ? result : s1.mBucketId - s2.mBucketId;
+ }
+ }
+}
diff --git a/new3d/src/com/android/gallery3d/data/DatabaseMediaItem.java b/new3d/src/com/android/gallery3d/data/DatabaseMediaItem.java
index b710daa..9d922df 100644
--- a/new3d/src/com/android/gallery3d/data/DatabaseMediaItem.java
+++ b/new3d/src/com/android/gallery3d/data/DatabaseMediaItem.java
@@ -2,22 +2,17 @@
public abstract class DatabaseMediaItem extends AbstractMediaItem {
- static final int UNCONSTRAINED = -1;
- int mId;
- String mCaption;
- String mMimeType;
- double mLatitude;
- double mLongitude;
- long mDateTakenInMs;
- long mDateAddedInSec;
- long mDateModifiedInSec;
- String mFilePath;
- String mContentUri;
+ protected int mId;
+ protected String mCaption;
+ protected String mMimeType;
+ protected double mLatitude;
+ protected double mLongitude;
+ protected long mDateTakenInMs;
+ protected long mDateAddedInSec;
+ protected long mDateModifiedInSec;
- public String getMediaUri() {
- return mContentUri;
- }
+ protected String mFilePath;
public String getTitle() {
return mCaption;
diff --git a/new3d/src/com/android/gallery3d/data/ImageMediaItem.java b/new3d/src/com/android/gallery3d/data/ImageMediaItem.java
index 44c95a5..1c0e0a3 100644
--- a/new3d/src/com/android/gallery3d/data/ImageMediaItem.java
+++ b/new3d/src/com/android/gallery3d/data/ImageMediaItem.java
@@ -1,9 +1,11 @@
package com.android.gallery3d.data;
import android.content.ContentResolver;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
@@ -17,7 +19,32 @@
private static final int FULLIMAGE_TARGET_SIZE = 1024;
private static final int FULLIMAGE_MAX_NUM_PIXELS = 2 * 1024 * 1024;
- public int mRotation;
+ // Must preserve order between these indices and the order of the terms in
+ // PROJECTION_IMAGES.
+ private static final int INDEX_ID = 0;
+ private static final int INDEX_CAPTION = 1;
+ private static final int INDEX_MIME_TYPE = 2;
+ private static final int INDEX_LATITUDE = 3;
+ private static final int INDEX_LONGITUDE = 4;
+ private static final int INDEX_DATE_TAKEN = 5;
+ private static final int INDEX_DATE_ADDED = 6;
+ private static final int INDEX_DATE_MODIFIED = 7;
+ private static final int INDEX_DATA = 8;
+ private static final int INDEX_ORIENTATION = 9;
+
+ private static final String[] PROJECTION_IMAGES = {
+ ImageColumns._ID, // 0
+ ImageColumns.TITLE, // 1
+ ImageColumns.MIME_TYPE, // 2
+ ImageColumns.LATITUDE, // 3
+ ImageColumns.LONGITUDE, // 4
+ ImageColumns.DATE_TAKEN, // 5
+ ImageColumns.DATE_ADDED, // 6
+ ImageColumns.DATE_MODIFIED, // 7
+ ImageColumns.DATA, // 8
+ ImageColumns.ORIENTATION}; // 9
+
+ private int mRotation;
private final BitmapFactory.Options mOptions = new BitmapFactory.Options();
@@ -87,4 +114,33 @@
}
}
+ public static ImageMediaItem load(Cursor cursor) {
+ ImageMediaItem item = new ImageMediaItem();
+
+ item.mId = cursor.getInt(INDEX_ID);
+ item.mCaption = cursor.getString(INDEX_CAPTION);
+ item.mMimeType = cursor.getString(INDEX_MIME_TYPE);
+ item.mLatitude = cursor.getDouble(INDEX_LATITUDE);
+ item.mLongitude = cursor.getDouble(INDEX_LONGITUDE);
+ item.mDateTakenInMs = cursor.getLong(INDEX_DATE_TAKEN);
+ item.mDateAddedInSec = cursor.getLong(INDEX_DATE_ADDED);
+ item.mDateModifiedInSec = cursor.getLong(INDEX_DATE_MODIFIED);
+ item.mFilePath = cursor.getString(INDEX_DATA);
+ item.mRotation = cursor.getInt(INDEX_ORIENTATION);
+
+ return item;
+ }
+
+ public static Cursor queryImageInBucket(
+ ContentResolver resolver, int bucketId) {
+ // Build the where clause
+ StringBuilder builder = new StringBuilder(ImageColumns.BUCKET_ID);
+ builder.append(" = ").append(bucketId);
+ String whereClause = builder.toString();
+
+ return resolver.query(
+ Images.Media.EXTERNAL_CONTENT_URI,
+ PROJECTION_IMAGES, whereClause, null, null);
+ }
+
}
diff --git a/new3d/src/com/android/gallery3d/data/LocalMediaSet.java b/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
index 0a265c4..4e398ab 100644
--- a/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
@@ -4,7 +4,6 @@
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
//
@@ -20,15 +19,15 @@
private static final int MAX_NUM_COVERED_ITEMS = 4;
- private ArrayList<LocalMediaSet> mSubMediaSets =
+ private final ArrayList<LocalMediaSet> mSubMediaSets =
new ArrayList<LocalMediaSet>();
- private Map<Integer, Integer> mIdsToIndice =
+ private final Map<Integer, Integer> mIdsToIndice =
new HashMap<Integer, Integer>();
- private ArrayList<MediaItem> mMediaItems = new ArrayList<MediaItem>();
+ private final ArrayList<MediaItem> mMediaItems = new ArrayList<MediaItem>();
- private int mBucketId;
- private String mTitle;
+ private final int mBucketId;
+ private final String mTitle;
public LocalMediaSet(int bucketId, String title) {
mBucketId = bucketId;
@@ -127,4 +126,7 @@
set.printOut();
}
}
+
+ public void setContentListener(MediaSetListener listener) {
+ }
}
diff --git a/new3d/src/com/android/gallery3d/data/MediaDbAccessor.java b/new3d/src/com/android/gallery3d/data/MediaDbAccessor.java
index ef7ca94..4dc88bf 100644
--- a/new3d/src/com/android/gallery3d/data/MediaDbAccessor.java
+++ b/new3d/src/com/android/gallery3d/data/MediaDbAccessor.java
@@ -2,211 +2,61 @@
import android.content.ContentResolver;
import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Images.ImageColumns;
-import android.provider.MediaStore.Video;
-import android.provider.MediaStore.Video.VideoColumns;
-import android.util.Log;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+
+import com.android.gallery3d.ui.Util;
public class MediaDbAccessor {
+
private static final String TAG = "MediaDbAccessor";
- private static final String BASE_CONTENT_STRING_IMAGES =
- (Images.Media.EXTERNAL_CONTENT_URI).toString() + "/";
- private static final String BASE_CONTENT_STRING_VIDEOS =
- (Video.Media.EXTERNAL_CONTENT_URI).toString() + "/";
+ private static MediaDbAccessor sInstance;
- // Must preserve order between these indices and the order of the terms in
- // BUCKET_PROJECTION_IMAGES, BUCKET_PROJECTION_VIDEOS.
- // Not using SortedHashMap for efficiency reasons.
- private static final int BUCKET_ID_INDEX = 0;
- private static final int BUCKET_NAME_INDEX = 1;
- private static final String[] BUCKET_PROJECTION_IMAGES = new String[] {
- ImageColumns.BUCKET_ID, ImageColumns.BUCKET_DISPLAY_NAME };
- private static final String[] BUCKET_PROJECTION_VIDEOS = new String[] {
- VideoColumns.BUCKET_ID, VideoColumns.BUCKET_DISPLAY_NAME };
+ private final HandlerThread mThread =
+ new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
- // Must preserve order between these indices and the order of the terms in
- // INITIAL_PROJECTION_IMAGES and
- // INITIAL_PROJECTION_VIDEOS.
- private static final int MEDIA_ID_INDEX = 0;
- private static final int MEDIA_CAPTION_INDEX = 1;
- private static final int MEDIA_MIME_TYPE_INDEX = 2;
- private static final int MEDIA_LATITUDE_INDEX = 3;
- private static final int MEDIA_LONGITUDE_INDEX = 4;
- private static final int MEDIA_DATE_TAKEN_INDEX = 5;
- private static final int MEDIA_DATE_ADDED_INDEX = 6;
- private static final int MEDIA_DATE_MODIFIED_INDEX = 7;
- private static final int MEDIA_DATA_INDEX = 8;
- private static final int MEDIA_ORIENTATION = 9;
- private static final int DURATION_INDEX = 9;
- private static final int MEDIA_BUCKET_ID_INDEX = 10;
- private static final String[] PROJECTION_IMAGES = new String[] {
- ImageColumns._ID, ImageColumns.TITLE,
- ImageColumns.MIME_TYPE, ImageColumns.LATITUDE,
- ImageColumns.LONGITUDE, ImageColumns.DATE_TAKEN,
- ImageColumns.DATE_ADDED, ImageColumns.DATE_MODIFIED,
- ImageColumns.DATA, ImageColumns.ORIENTATION,
- ImageColumns.BUCKET_ID };
+ private final RootMediaSet mRootMediaSet;
+ private final Context mContext;
+ private final Looper mMainLooper;
+ private final Object mUiMonitor;
- private static final String[] PROJECTION_VIDEOS = new String[] {
- VideoColumns._ID, VideoColumns.TITLE, VideoColumns.MIME_TYPE,
- VideoColumns.LATITUDE, VideoColumns.LONGITUDE,
- VideoColumns.DATE_TAKEN, VideoColumns.DATE_ADDED,
- VideoColumns.DATE_MODIFIED, VideoColumns.DATA,
- VideoColumns.DURATION, VideoColumns.BUCKET_ID };
+ public MediaDbAccessor(Context context, Object uiMonitor) {
+ mThread.start();
+ mMainLooper = Looper.getMainLooper();
+ mContext = context;
+ mUiMonitor = Util.checkNotNull(uiMonitor);
- private static final String DEFAULT_BUCKET_SORT_ORDER = "upper(" +
- ImageColumns.BUCKET_DISPLAY_NAME + ") ASC";
- private static final String DEFAULT_IMAGE_SORT_ORDER =
- ImageColumns.DATE_TAKEN + " ASC";
- private static final String DEFAULT_VIDEO_SORT_ORDER =
- VideoColumns.DATE_TAKEN + " ASC";
-
- public static LocalMediaSet getMediaSets(Context context) {
- LocalMediaSet rootSet = new LocalMediaSet(LocalMediaSet.ROOT_SET_ID,
- "All Albums");
-
- final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().
- appendQueryParameter("distinct", "true").build();
- final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().
- appendQueryParameter("distinct", "true").build();
- final ContentResolver cr = context.getContentResolver();
-
- SortCursor sortCursor = null;
- try {
- final Cursor cursorImages = cr.query(uriImages,
- BUCKET_PROJECTION_IMAGES, null, null,
- DEFAULT_BUCKET_SORT_ORDER);
- final Cursor cursorVideos = cr.query(uriVideos,
- BUCKET_PROJECTION_VIDEOS, null, null,
- DEFAULT_BUCKET_SORT_ORDER);
- Cursor[] cursors = new Cursor[2];
- cursors[0] = cursorImages;
- cursors[1] = cursorVideos;
- sortCursor = new SortCursor(cursors,
- ImageColumns.BUCKET_DISPLAY_NAME, SortCursor.TYPE_STRING,
- true);
-
- if (sortCursor.moveToFirst()) {
- do {
- int setId = sortCursor.getInt(BUCKET_ID_INDEX);
- LocalMediaSet set =
- (LocalMediaSet) rootSet.getSubMediaSetById(setId);
- if (set == null) {
- set = new LocalMediaSet(setId,
- sortCursor.getString(BUCKET_NAME_INDEX));
- rootSet.addSubMediaSet(set);
- }
- } while (sortCursor.moveToNext());
- }
- } finally {
- if (sortCursor != null) {
- sortCursor.close();
- }
- }
- populateMediaItemsForSets(context, rootSet);
- return rootSet;
+ mRootMediaSet = new RootMediaSet(this);
}
- private static void populateMediaItemsForSets(Context context,
- LocalMediaSet rootSet) {
- final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
- final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
- final ContentResolver cr = context.getContentResolver();
-
- String whereClause = composeWhereClause(rootSet);
-
- try {
- final Cursor cursorImages = cr.query(uriImages, PROJECTION_IMAGES,
- whereClause, null, DEFAULT_IMAGE_SORT_ORDER);
- final Cursor cursorVideos = cr.query(uriVideos, PROJECTION_VIDEOS,
- whereClause, null, DEFAULT_VIDEO_SORT_ORDER);
- final Cursor[] cursors = new Cursor[2];
- cursors[0] = cursorImages;
- cursors[1] = cursorVideos;
- final SortCursor sortCursor = new SortCursor(cursors,
- ImageColumns.DATE_TAKEN, SortCursor.TYPE_NUMERIC, true);
- try {
- if (sortCursor.moveToFirst()) {
- do {
- final int setId =
- sortCursor.getInt(MEDIA_BUCKET_ID_INDEX);
- LocalMediaSet set = (LocalMediaSet)
- rootSet.getSubMediaSetById(setId);
- MediaItem item;
- final boolean isVideo =
- (sortCursor.getCurrentCursorIndex() == 1);
- if (isVideo) {
- item = createVideoMediaItemFromCursor(sortCursor,
- BASE_CONTENT_STRING_VIDEOS);
- } else {
- item = createImageMediaItemFromCursor(sortCursor,
- BASE_CONTENT_STRING_IMAGES);
- }
- Log.i(TAG, "Item to add in: " + item.getTitle());
- set.addMediaItem(item);
- } while (sortCursor.moveToNext());
- }
- } finally {
- if (sortCursor != null)
- sortCursor.close();
- }
- } catch (Exception e) {
- // If the database operation failed for any reason
- Log.e(TAG, "Failed to complete the database operation!", e);
- }
-
+ public static void initialize(Context context, Object uiMonitor) {
+ sInstance = new MediaDbAccessor(context, uiMonitor);
}
- private static VideoMediaItem createVideoMediaItemFromCursor(Cursor cursor,
- String baseUri) {
- VideoMediaItem item = new VideoMediaItem();
- populateAbstractMediaItemFromCursor(cursor, baseUri, item);
- item.mDurationInSec = cursor.getInt(DURATION_INDEX);
- return item;
+ public static MediaDbAccessor getInstance() {
+ if (sInstance == null) throw new IllegalStateException();
+ return sInstance;
}
- private static ImageMediaItem createImageMediaItemFromCursor(Cursor cursor,
- String baseUri) {
- ImageMediaItem item = new ImageMediaItem();
- populateAbstractMediaItemFromCursor(cursor, baseUri, item);
- item.mRotation = cursor.getInt(MEDIA_ORIENTATION);
- return item;
+ public MediaSet getRootMediaSets() {
+ return mRootMediaSet;
}
- private static void populateAbstractMediaItemFromCursor(Cursor cursor,
- String baseUri, DatabaseMediaItem item) {
- item.mId = cursor.getInt(MEDIA_ID_INDEX);
- item.mCaption = cursor.getString(MEDIA_CAPTION_INDEX);
- item.mMimeType = cursor.getString(MEDIA_MIME_TYPE_INDEX);
- item.mLatitude = cursor.getDouble(MEDIA_LATITUDE_INDEX);
- item.mLongitude = cursor.getDouble(MEDIA_LONGITUDE_INDEX);
- item.mDateTakenInMs = cursor.getLong(MEDIA_DATE_TAKEN_INDEX);
- item.mDateAddedInSec = cursor.getLong(MEDIA_DATE_ADDED_INDEX);
- item.mDateModifiedInSec = cursor.getLong(MEDIA_DATE_MODIFIED_INDEX);
- item.mFilePath = cursor.getString(MEDIA_DATA_INDEX);
- if (baseUri != null)
- item.mContentUri = baseUri + item.mId;
+ public ContentResolver getContentResolver() {
+ return mContext.getContentResolver();
}
- private static String composeWhereClause(LocalMediaSet rootSet) {
- int count = rootSet.getSubMediaSetCount();
- if (count <= 0) {
- return null;
- }
-
- StringBuilder whereString = new StringBuilder(
- ImageColumns.BUCKET_ID + " in (");
- for (int i = 0; i < count - 1; ++i) {
- whereString.append(((LocalMediaSet) rootSet.getSubMediaSet(i))
- .getBucketId()).append(",");
- }
- whereString.append(((LocalMediaSet) rootSet.getSubMediaSet(count-1))
- .getBucketId()).append(")");
- return whereString.toString();
+ public Looper getLooper() {
+ return mThread.getLooper();
}
+ public Looper getMainLooper() {
+ return mMainLooper;
+ }
+
+ public Object getUiMonitor() {
+ return mUiMonitor;
+ }
}
\ No newline at end of file
diff --git a/new3d/src/com/android/gallery3d/data/MediaItem.java b/new3d/src/com/android/gallery3d/data/MediaItem.java
index 68c3f4c..545cc24 100644
--- a/new3d/src/com/android/gallery3d/data/MediaItem.java
+++ b/new3d/src/com/android/gallery3d/data/MediaItem.java
@@ -18,8 +18,6 @@
public void onImageCanceled(MediaItem abstractMediaItem, int type);
}
- public String getMediaUri();
-
public String getTitle();
public Bitmap getImage(int type);
diff --git a/new3d/src/com/android/gallery3d/data/MediaSet.java b/new3d/src/com/android/gallery3d/data/MediaSet.java
index 3dc8d58..8487a81 100644
--- a/new3d/src/com/android/gallery3d/data/MediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/MediaSet.java
@@ -1,5 +1,6 @@
package com.android.gallery3d.data;
+
//
// MediaSet is a directory-like data structure.
// It contains MediaItems and sub-MediaSets.
@@ -29,4 +30,6 @@
public String getTitle();
public MediaItem[] getCoverMediaItems();
+
+ public void setContentListener(MediaSetListener listener);
}
diff --git a/new3d/src/com/android/gallery3d/data/RootMediaSet.java b/new3d/src/com/android/gallery3d/data/RootMediaSet.java
new file mode 100644
index 0000000..cad3290
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/data/RootMediaSet.java
@@ -0,0 +1,184 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.data;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.util.Log;
+
+import com.android.gallery3d.ui.SynchronizedHandler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RootMediaSet implements MediaSet{
+ private static final String TITLE = "RootSet";
+
+ private static final int MSG_LOAD_DATA = 0;
+ private static final int MSG_UPDATE_CONTENT = 0;
+
+ // Must preserve order between these indices and the order of the terms in
+ // BUCKET_PROJECTION_IMAGES, BUCKET_PROJECTION_VIDEOS.
+ // Not using SortedHashMap for efficiency reasons.
+ private static final int BUCKET_ID_INDEX = 0;
+ private static final int BUCKET_NAME_INDEX = 1;
+
+ private static final String[] PROJECTION_IMAGE_BUCKETS = {
+ ImageColumns.BUCKET_ID,
+ ImageColumns.BUCKET_DISPLAY_NAME };
+
+ private static final String[] PROJECTION_VIDEO_BUCKETS = {
+ VideoColumns.BUCKET_ID,
+ VideoColumns.BUCKET_DISPLAY_NAME };
+
+ private final MediaDbAccessor mAccessor;
+ private int mTotalCountCached = -1;
+
+ private final ArrayList<BucketMediaSet>
+ mSubsets = new ArrayList<BucketMediaSet>();
+
+ private HashMap<Integer, String> mLoadBuffer;
+
+ private final Handler mDataHandler;
+ private final Handler mMainHandler;
+
+ private MediaSetListener mListener;
+
+ public RootMediaSet(MediaDbAccessor accessor) {
+ mAccessor = accessor;
+
+ mDataHandler = new Handler(accessor.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_LOAD_DATA:
+ loadBucketsFromDatabase();
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+ };
+
+ mMainHandler = new SynchronizedHandler(
+ accessor.getUiMonitor(), accessor.getMainLooper()) {
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_CONTENT:
+ updateContent();
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+ };
+
+ mDataHandler.sendEmptyMessage(MSG_LOAD_DATA);
+ }
+
+ public MediaItem[] getCoverMediaItems() {
+ return new MediaItem[0];
+ }
+
+ public MediaItem getMediaItem(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ public synchronized int getMediaItemCount() {
+ return 0;
+ }
+
+ public synchronized MediaSet getSubMediaSet(int index) {
+ return mSubsets.get(index);
+ }
+
+ public synchronized int getSubMediaSetCount() {
+ return mSubsets.size();
+ }
+
+ public String getTitle() {
+ return TITLE;
+ }
+
+ public int getTotalMediaItemCount() {
+ if (mTotalCountCached >= 0) return mTotalCountCached;
+ int total = 0;
+ for (MediaSet subset : mSubsets) {
+ total += subset.getTotalMediaItemCount();
+ }
+ mTotalCountCached = total;
+ return total;
+ }
+
+ public void setContentListener(MediaSetListener listener) {
+ mListener = listener;
+ }
+
+ private void loadBucketsFromDatabase() {
+ ContentResolver resolver = mAccessor.getContentResolver();
+ HashMap<Integer, String> map = new HashMap<Integer, String>();
+ mLoadBuffer = map;
+
+ Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().
+ appendQueryParameter("distinct", "true").build();
+ Cursor cursor = resolver.query(
+ uriImages, PROJECTION_IMAGE_BUCKETS, null, null, null);
+ if (cursor == null) throw new NullPointerException();
+ try {
+ while (cursor.moveToNext()) {
+ Log.v("Image", cursor.getString(BUCKET_NAME_INDEX));
+ map.put(cursor.getInt(BUCKET_ID_INDEX),
+ cursor.getString(BUCKET_NAME_INDEX));
+ }
+ } finally {
+ cursor.close();
+ }
+
+ Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().
+ appendQueryParameter("distinct", "true").build();
+ cursor = resolver.query(
+ uriVideos, PROJECTION_VIDEO_BUCKETS, null, null, null);
+ if (cursor == null) throw new NullPointerException();
+ try {
+ while (cursor.moveToNext()) {
+ Log.v("Video", cursor.getString(BUCKET_ID_INDEX));
+ Log.v("Video", cursor.getString(BUCKET_NAME_INDEX));
+ map.put(cursor.getInt(BUCKET_ID_INDEX),
+ cursor.getString(BUCKET_NAME_INDEX));
+ }
+ } finally {
+ cursor.close();
+ }
+
+ mMainHandler.sendEmptyMessage(MSG_UPDATE_CONTENT);
+ }
+
+ private void updateContent() {
+ HashMap<Integer, String> map = mLoadBuffer;
+ if (map == null) throw new IllegalStateException();
+
+ for (Map.Entry<Integer, String> entry : map.entrySet()) {
+ mSubsets.add(new BucketMediaSet(
+ mAccessor, entry.getKey(), entry.getValue()));
+ }
+ mLoadBuffer = null;
+
+ Collections.sort(mSubsets, BucketMediaSet.sNameComparator);
+
+ for (BucketMediaSet mediaset : mSubsets) {
+ mediaset.invalidate();
+ }
+ if (mListener != null) mListener.onContentChanged();
+ }
+}
diff --git a/new3d/src/com/android/gallery3d/data/VideoMediaItem.java b/new3d/src/com/android/gallery3d/data/VideoMediaItem.java
index 47a61d3..2985360 100644
--- a/new3d/src/com/android/gallery3d/data/VideoMediaItem.java
+++ b/new3d/src/com/android/gallery3d/data/VideoMediaItem.java
@@ -1,12 +1,41 @@
package com.android.gallery3d.data;
import android.content.ContentResolver;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Video.VideoColumns;
public class VideoMediaItem extends DatabaseMediaItem {
private static final int MICRO_TARGET_PIXELS = 128 * 128;
+
+ // Must preserve order between these indices and the order of the terms in
+ // PROJECTION_VIDEOS.
+ private static final int INDEX_ID = 0;
+ private static final int INDEX_CAPTION = 1;
+ private static final int INDEX_MIME_TYPE = 2;
+ private static final int INDEX_LATITUDE = 3;
+ private static final int INDEX_LONGITUDE = 4;
+ private static final int INDEX_DATE_TAKEN = 5;
+ private static final int INDEX_DATE_ADDED = 6;
+ private static final int INDEX_DATE_MODIFIED = 7;
+ private static final int INDEX_DATA = 8;
+ private static final int INDEX_DURATION = 9;
+
+ private static final String[] PROJECTION_VIDEOS = new String[] {
+ VideoColumns._ID,
+ VideoColumns.TITLE,
+ VideoColumns.MIME_TYPE,
+ VideoColumns.LATITUDE,
+ VideoColumns.LONGITUDE,
+ VideoColumns.DATE_TAKEN,
+ VideoColumns.DATE_ADDED,
+ VideoColumns.DATE_MODIFIED,
+ VideoColumns.DATA,
+ VideoColumns.DURATION};
+
public int mDurationInSec;
@Override
@@ -33,4 +62,36 @@
throw new IllegalArgumentException();
}
}
+
+ public static VideoMediaItem load(Cursor cursor) {
+ VideoMediaItem item = new VideoMediaItem();
+
+ item.mId = cursor.getInt(INDEX_ID);
+ item.mCaption = cursor.getString(INDEX_CAPTION);
+ item.mMimeType = cursor.getString(INDEX_MIME_TYPE);
+ item.mLatitude = cursor.getDouble(INDEX_LATITUDE);
+ item.mLongitude = cursor.getDouble(INDEX_LONGITUDE);
+ item.mDateTakenInMs = cursor.getLong(INDEX_DATE_TAKEN);
+ item.mDateAddedInSec = cursor.getLong(INDEX_DATE_ADDED);
+ item.mDateModifiedInSec = cursor.getLong(INDEX_DATE_MODIFIED);
+ item.mFilePath = cursor.getString(INDEX_DATA);
+ item.mDurationInSec = cursor.getInt(INDEX_DURATION);
+
+ return item;
+ }
+
+ public static Cursor queryVideoInBucket(
+ ContentResolver resolver, int bucketId) {
+
+ // Build the where clause
+ StringBuilder builder = new StringBuilder(ImageColumns.BUCKET_ID);
+ builder.append(" = ").append(bucketId);
+ String whereClause = builder.toString();
+
+ return resolver.query(
+ Video.Media.EXTERNAL_CONTENT_URI,
+ PROJECTION_VIDEOS, whereClause, null, null);
+ }
+
+
}
diff --git a/new3d/src/com/android/gallery3d/ui/GLHandler.java b/new3d/src/com/android/gallery3d/ui/GLHandler.java
deleted file mode 100644
index d59f9ef..0000000
--- a/new3d/src/com/android/gallery3d/ui/GLHandler.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.android.gallery3d.ui;
-
-import android.os.Handler;
-import android.os.Message;
-
-public class GLHandler extends Handler {
-
- private final GLRootView mRootView;
-
- public GLHandler(GLRootView rootView) {
- mRootView = rootView;
- }
-
- @Override
- public void dispatchMessage(Message message) {
- synchronized (mRootView) {
- super.dispatchMessage(message);
- }
- }
-}
diff --git a/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java b/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java
index c2c9079..a048ce1 100644
--- a/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java
+++ b/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java
@@ -19,17 +19,18 @@
private static final double EXPECTED_AREA = 150 * 120;
private static final int SLOT_WIDTH = 162;
private static final int SLOT_HEIGHT = 132;
- private static final int INITIAL_CACHE_CAPACITY = 48;
+ private static final int CACHE_CAPACITY = 48;
private final Map<Integer, MyDisplayItem> mItemMap =
- new HashMap<Integer, MyDisplayItem>(INITIAL_CACHE_CAPACITY);
+ new HashMap<Integer, MyDisplayItem>(CACHE_CAPACITY);
private final LinkedHashSet<Integer> mLruSlot =
- new LinkedHashSet<Integer>(INITIAL_CACHE_CAPACITY);
+ new LinkedHashSet<Integer>(CACHE_CAPACITY);
private final NinePatchTexture mFrame;
private final MediaSet mMediaSet;
private final Texture mWaitLoadingTexture;
private final SlotView mSlotView;
+ private boolean mContentInvalidated = false;
public GridSlotAdapter(Context context, MediaSet mediaSet, SlotView slotView) {
mSlotView = slotView;
@@ -38,11 +39,13 @@
ColorTexture gray = new ColorTexture(Color.GRAY);
gray.setSize(64, 48);
mWaitLoadingTexture = gray;
+ mediaSet.setContentListener(new MyContentListener());
}
public void putSlot(int slotIndex, int x, int y, DisplayItemPanel panel) {
MyDisplayItem displayItem = mItemMap.get(slotIndex);
- if (displayItem == null) {
+
+ if (displayItem == null || mContentInvalidated) {
MediaItem item = mMediaSet.getMediaItem(slotIndex);
item.setListener(new MyMediaItemListener(slotIndex));
switch (item.requestImage(MediaItem.TYPE_MICROTHUMBNAIL)) {
@@ -57,17 +60,18 @@
}
// Remove an item if the size of mItemsetMap is no less than
- // INITIAL_CACHE_CAPACITY and there exists a slot in mLruSlot.
- if (mItemMap.size() >= INITIAL_CACHE_CAPACITY && !mLruSlot.isEmpty()) {
- Iterator<Integer> iter = mLruSlot.iterator();
- int index = iter.next();
- mItemMap.remove(index);
- mLruSlot.remove(index);
+ // CACHE_CAPACITY and there exists a slot in mLruSlot.
+ Iterator<Integer> iter = mLruSlot.iterator();
+ while (mItemMap.size() >= CACHE_CAPACITY && iter.hasNext()) {
+ mItemMap.remove(iter.next());
+ iter.remove();
}
mItemMap.put(slotIndex, displayItem);
- mLruSlot.remove(slotIndex);
}
+ // Reclaim the slot
+ mLruSlot.remove(slotIndex);
+
x += getSlotWidth() / 2;
y += getSlotHeight() / 2;
panel.putDisplayItem(displayItem, x, y, 0);
@@ -108,6 +112,16 @@
}
}
+ private void onContentChanged() {
+ mContentInvalidated = true;
+ mSlotView.notifyDataChanged();
+ mContentInvalidated = false;
+
+ for (Integer index : mLruSlot) {
+ mItemMap.remove(index);
+ }
+ }
+
private static class MyDisplayItem extends DisplayItem {
private Texture mContent;
@@ -157,7 +171,15 @@
}
public void freeSlot(int index, DisplayItemPanel panel) {
- panel.removeDisplayItem(mItemMap.get(index));
+ DisplayItem item = mItemMap.get(index);
+ panel.removeDisplayItem(item);
mLruSlot.add(index);
}
+
+ private class MyContentListener implements MediaSet.MediaSetListener {
+ public void onContentChanged() {
+ GridSlotAdapter.this.onContentChanged();
+ }
+ }
+
}
diff --git a/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java b/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java
index 067954d..541c403 100644
--- a/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java
+++ b/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java
@@ -21,7 +21,8 @@
private static final int SLOT_WIDTH = 220;
private static final int SLOT_HEIGHT = 200;
private static final int MARGIN_TO_SLOTSIDE = 10;
- private static final int INITIAL_CACHE_CAPACITY = 32;
+ private static final int CACHE_CAPACITY = 32;
+ private static final int INDEX_NONE = -1;
private final NinePatchTexture mFrame;
@@ -31,11 +32,14 @@
private final Texture mWaitLoadingTexture;
private final Map<Integer, MyDisplayItem[]> mItemsetMap =
- new HashMap<Integer, MyDisplayItem[]>(INITIAL_CACHE_CAPACITY);
+ new HashMap<Integer, MyDisplayItem[]>(CACHE_CAPACITY);
private final LinkedHashSet<Integer> mLruSlot =
- new LinkedHashSet<Integer>(INITIAL_CACHE_CAPACITY);
+ new LinkedHashSet<Integer>(CACHE_CAPACITY);
private final SlotView mSlotView;
+ private boolean mContentInvalidated = false;
+ private int mInvalidateIndex = INDEX_NONE;
+
public MediaSetSlotAdapter(
Context context, MediaSet rootSet, SlotView view) {
mRootSet = rootSet;
@@ -44,16 +48,24 @@
gray.setSize(64, 48);
mWaitLoadingTexture = gray;
mSlotView = view;
+
+ rootSet.setContentListener(new MyContentListener());
}
- public void putSlot(int slotIndex, int x, int y, DisplayItemPanel panel) {
+ public void putSlot(
+ int slotIndex, int x, int y, DisplayItemPanel panel) {
// Get displayItems from mItemsetMap or create them from MediaSet.
MyDisplayItem[] displayItems = mItemsetMap.get(slotIndex);
- if (displayItems == null) {
+ if (displayItems == null
+ || mContentInvalidated || mInvalidateIndex == slotIndex) {
displayItems = createDisplayItems(slotIndex);
+ addSlotToCache(slotIndex, displayItems);
}
+ // Reclaim the slot
+ mLruSlot.remove(slotIndex);
+
// Put displayItems to the panel.
Random random = mRandom;
int left = x + MARGIN_TO_SLOTSIDE;
@@ -72,11 +84,14 @@
int theta = random.nextInt(31) - 15;
panel.putDisplayItem(displayItems[i], itemX, y + dy, theta);
}
- panel.putDisplayItem(displayItems[0], x, y, 0);
+ if (displayItems.length > 0) {
+ panel.putDisplayItem(displayItems[0], x, y, 0);
+ }
}
private MyDisplayItem[] createDisplayItems(int slotIndex) {
MediaSet set = mRootSet.getSubMediaSet(slotIndex);
+ set.setContentListener(new SlotContentListener(slotIndex));
MediaItem[] items = set.getCoverMediaItems();
MyDisplayItem[] displayItems = new MyDisplayItem[items.length];
@@ -86,8 +101,6 @@
case MediaItem.IMAGE_READY:
Bitmap bitmap =
items[i].getImage(MediaItem.TYPE_MICROTHUMBNAIL);
- // TODO: Need to replace BitmapTexture with something else
- // whose bitmap is freeable.
displayItems[i] = new MyDisplayItem(
new BitmapTexture(bitmap), mFrame);
break;
@@ -97,7 +110,6 @@
break;
}
}
- addSlotToCache(slotIndex, displayItems);
return displayItems;
}
@@ -105,13 +117,12 @@
// Remove an itemset if the size of mItemsetMap is no less than
// INITIAL_CACHE_CAPACITY and there exists a slot in mLruSlot.
Iterator<Integer> iter = mLruSlot.iterator();
- for (int i = mItemsetMap.size() - INITIAL_CACHE_CAPACITY;
+ for (int i = mItemsetMap.size() - CACHE_CAPACITY;
i >= 0 && iter.hasNext(); --i) {
mItemsetMap.remove(iter.next());
iter.remove();
}
mItemsetMap.put(slotIndex, displayItems);
- mLruSlot.remove(slotIndex);
}
public int getSlotHeight() {
@@ -200,11 +211,65 @@
}
public void freeSlot(int index, DisplayItemPanel panel) {
- MyDisplayItem[] displayItems = mItemsetMap.get(index);
+ MyDisplayItem[] displayItems;
+ if (mContentInvalidated) {
+ displayItems = mItemsetMap.remove(index);
+ } else {
+ displayItems = mItemsetMap.get(index);
+ mLruSlot.add(index);
+ }
for (MyDisplayItem item : displayItems) {
panel.removeDisplayItem(item);
}
- mLruSlot.add(index);
+ }
+
+ private void onContentChanged() {
+ // 1. Remove the original visible itemsets from the display panel.
+ // These itemsets will be recorded in mLruSlot.
+ // 2. Add the new visible itemsets to the display panel and cache
+ // (mItemsetMap).
+ mContentInvalidated = true;
+ mSlotView.notifyDataChanged();
+ mContentInvalidated = false;
+
+ // Clean up the cache by removing all itemsets recorded in mLruSlot.
+ for (Integer index : mLruSlot) {
+ mItemsetMap.remove(index);
+ }
+ mLruSlot.clear();
+ }
+
+ private class SlotContentListener implements MediaSet.MediaSetListener {
+ private final int mSlotIndex;
+
+ public SlotContentListener(int slotIndex) {
+ mSlotIndex = slotIndex;
+ }
+
+ public void onContentChanged() {
+ // Update the corresponding itemset to the slot based on whether
+ // the slot is visible or in mLruSlot.
+ // Remove the original corresponding itemset from cache if the
+ // slot was already in mLruSlot. That is the itemset was invisible
+ // and freed before.
+ if (mLruSlot.remove(mSlotIndex)) {
+ mItemsetMap.remove(mSlotIndex);
+ } else {
+ // Refresh the corresponding items in the slot if the slot is
+ // visible. Note that only visible slots are refreshed in
+ // mSlotView.notifySlotInvalidate(mSlotIndex).
+ mInvalidateIndex = mSlotIndex;
+ mSlotView.notifySlotInvalidate(mSlotIndex);
+ mInvalidateIndex = INDEX_NONE;
+ }
+ }
+ }
+
+ private class MyContentListener implements MediaSet.MediaSetListener {
+
+ public void onContentChanged() {
+ MediaSetSlotAdapter.this.onContentChanged();
+ }
}
}
diff --git a/new3d/src/com/android/gallery3d/ui/Pathbar.java b/new3d/src/com/android/gallery3d/ui/Pathbar.java
index b92f1da..29c73f5 100644
--- a/new3d/src/com/android/gallery3d/ui/Pathbar.java
+++ b/new3d/src/com/android/gallery3d/ui/Pathbar.java
@@ -162,7 +162,7 @@
@Override
protected void onAttachToRoot(GLRootView root) {
super.onAttachToRoot(root);
- mHandler = new GLHandler(root) {
+ mHandler = new SynchronizedHandler(root) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
diff --git a/new3d/src/com/android/gallery3d/ui/SlotView.java b/new3d/src/com/android/gallery3d/ui/SlotView.java
index 115f1a9..efd0215 100644
--- a/new3d/src/com/android/gallery3d/ui/SlotView.java
+++ b/new3d/src/com/android/gallery3d/ui/SlotView.java
@@ -1,8 +1,6 @@
package com.android.gallery3d.ui;
import android.content.Context;
-import android.graphics.Rect;
-import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
@@ -24,6 +22,7 @@
private Model mModel;
private final DisplayItemPanel mPanel;
+ private int mSlotCount;
private int mVerticalGap;
private int mHorizontalGap;
private int mSlotWidth;
@@ -58,21 +57,23 @@
public void setModel(Model model) {
if (model == mModel) return;
- setVisibleRange(0, 0);
+ if (mModel != null) {
+ // free all the slot in the old model
+ setVisibleRange(0, 0);
+ }
mModel = model;
- if (model != null) initializeLayoutParams();
notifyDataChanged();
}
private void initializeLayoutParams() {
- int size = mModel.size();
+ mSlotCount = mModel.size();
mSlotWidth = mModel.getSlotWidth();
mSlotHeight = mModel.getSlotHeight();
int rowCount = (getHeight() - mVerticalGap)
/ (mVerticalGap + mSlotHeight);
if (rowCount == 0) rowCount = 1;
mRowCount = rowCount;
- mScrollLimit = ((size + rowCount - 1) / rowCount)
+ mScrollLimit = ((mSlotCount + rowCount - 1) / rowCount)
* (mHorizontalGap + mSlotWidth)
+ mHorizontalGap - getWidth();
if (mScrollLimit < 0) mScrollLimit = 0;
@@ -104,6 +105,10 @@
}
public void notifyDataChanged() {
+ // free all slots in previous data
+ setVisibleRange(0, 0);
+
+ if (mModel != null) initializeLayoutParams();
setScrollPosition(0, true);
notifyDataInvalidate();
}
@@ -143,7 +148,7 @@
int rowCount = mRowCount;
DisplayItemPanel panel = mPanel;
for (int i = columnIndex * rowCount,
- n = Math.min(mModel.size(), i + rowCount); i < n; ++i) {
+ n = Math.min(mSlotCount, i + rowCount); i < n; ++i) {
mModel.freeSlot(i, panel);
}
}
@@ -156,17 +161,28 @@
DisplayItemPanel panel = mPanel;
for (int i = columnIndex * rowCount,
- n = Math.min(mModel.size(), i + rowCount); i < n; ++i) {
+ n = Math.min(mSlotCount, i + rowCount); i < n; ++i) {
mModel.putSlot(i, x, y, panel);
y += rowHeight;
}
}
+ public void notifySlotInvalidate(int slotIndex) {
+ int columnIndex = slotIndex / mRowCount;
+ if (columnIndex >= mVisibleStart && columnIndex < mVisibleEnd) {
+ mModel.freeSlot(slotIndex, mPanel);
+ int x = columnIndex * (mHorizontalGap + mSlotWidth) + mHorizontalGap;
+ int rowIndex = slotIndex - (columnIndex * mRowCount);
+ int y = mVerticalGap + (mVerticalGap + mSlotHeight) * rowIndex;
+ mModel.putSlot(slotIndex, x, y, mPanel);
+ }
+ }
+
// start: inclusive, end: exclusive
private void setVisibleRange(int start, int end) {
if (start == mVisibleStart && end == mVisibleEnd) return;
int rowCount = mRowCount;
- if (start >= mVisibleEnd || end < mVisibleStart) {
+ if (start >= mVisibleEnd || end <= mVisibleStart) {
for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
freeSlotsInColumn(i);
}
@@ -245,7 +261,7 @@
return NOT_AT_SLOTPOSITION;
}
int index = columnIdx * mRowCount + rowIdx;
- return index >= mModel.size() ? NOT_AT_SLOTPOSITION : index;
+ return index >= mSlotCount ? NOT_AT_SLOTPOSITION : index;
}
public interface SlotTapListener {
diff --git a/new3d/src/com/android/gallery3d/ui/SlotViewMockData.java b/new3d/src/com/android/gallery3d/ui/SlotViewMockData.java
index c86c45a..850bf9b 100644
--- a/new3d/src/com/android/gallery3d/ui/SlotViewMockData.java
+++ b/new3d/src/com/android/gallery3d/ui/SlotViewMockData.java
@@ -6,6 +6,7 @@
import com.android.gallery3d.R;
import java.util.Random;
+import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
public class SlotViewMockData implements SlotView.Model {
private static final int LENGTH_LIMIT = 150;
@@ -124,4 +125,7 @@
}
}
+ public ReadLock readLock() {
+ return null;
+ }
}
diff --git a/new3d/src/com/android/gallery3d/ui/SynchronizedHandler.java b/new3d/src/com/android/gallery3d/ui/SynchronizedHandler.java
new file mode 100644
index 0000000..998997d
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/SynchronizedHandler.java
@@ -0,0 +1,26 @@
+package com.android.gallery3d.ui;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+public class SynchronizedHandler extends Handler {
+
+ private final Object mMonitor;
+
+ public SynchronizedHandler(Object monitor) {
+ mMonitor = Util.checkNotNull(monitor);
+ }
+
+ public SynchronizedHandler(Object monitor, Looper looper) {
+ super(looper);
+ mMonitor = Util.checkNotNull(monitor);
+ }
+
+ @Override
+ public void dispatchMessage(Message message) {
+ synchronized (mMonitor) {
+ super.dispatchMessage(message);
+ }
+ }
+}