summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java3
-rw-r--r--src/com/android/gallery3d/app/PhotoDataAdapter.java8
-rw-r--r--src/com/android/gallery3d/app/PhotoPage.java39
-rw-r--r--src/com/android/gallery3d/data/DecodeUtils.java25
-rw-r--r--src/com/android/gallery3d/data/LocalImage.java22
-rw-r--r--src/com/android/gallery3d/ui/AlbumSlidingWindow.java7
-rw-r--r--src/com/android/gallery3d/ui/FilmStripView.java59
-rw-r--r--src/com/android/gallery3d/ui/GLView.java7
-rw-r--r--src/com/android/gallery3d/util/JobLimiter.java163
9 files changed, 264 insertions, 69 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java b/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
index 29f86e056..ea98c6c19 100644
--- a/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
+++ b/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
@@ -298,8 +298,5 @@ public class BitmapUtils {
ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
bitmap.compress(CompressFormat.JPEG, quality, baos);
return baos.toByteArray();
-
}
-
-
}
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index c05c89a0d..78afe7ee7 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -142,6 +142,7 @@ public class PhotoDataAdapter implements PhotoPage.Model {
private boolean mIsActive;
public interface DataListener extends LoadingListener {
+ public void onPhotoAvailable(long version, boolean fullImage);
public void onPhotoChanged(int index, Path item);
}
@@ -224,6 +225,9 @@ public class PhotoDataAdapter implements PhotoPage.Model {
if (entry.screenNail == null) {
entry.failToLoad = true;
} else {
+ if (mDataListener != null) {
+ mDataListener.onPhotoAvailable(version, false);
+ }
for (int i = -1; i <=1; ++i) {
if (version == getVersion(mCurrentIndex + i)) {
if (i == 0) updateTileProvider(entry);
@@ -244,6 +248,9 @@ public class PhotoDataAdapter implements PhotoPage.Model {
entry.fullImageTask = null;
entry.fullImage = future.get();
if (entry.fullImage != null) {
+ if (mDataListener != null) {
+ mDataListener.onPhotoAvailable(version, true);
+ }
if (version == getVersion(mCurrentIndex)) {
updateTileProvider(entry);
mPhotoView.notifyImageInvalidated(0);
@@ -644,6 +651,7 @@ public class PhotoDataAdapter implements PhotoPage.Model {
@Override
public UpdateInfo call() throws Exception {
+ // TODO: Try to load some data in first update
UpdateInfo info = new UpdateInfo();
info.version = mSourceVersion;
info.reloadContent = needContentReload();
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 44f299ef5..3a0bcd23f 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -94,7 +94,7 @@ public class PhotoPage extends ActivityState
private Intent mResultIntent = new Intent();
private int mCurrentIndex = 0;
private Handler mHandler;
- private boolean mShowBars;
+ private boolean mShowBars = true;
private ActionBar mActionBar;
private MyMenuVisibilityListener mMenuVisibilityListener;
private boolean mIsMenuVisible;
@@ -148,6 +148,22 @@ public class PhotoPage extends ActivityState
}
};
+ private void initFilmStripView() {
+ Config.PhotoPage config = Config.PhotoPage.get((Context) mActivity);
+ mFilmStripView = new FilmStripView(mActivity, mMediaSet,
+ config.filmstripTopMargin, config.filmstripMidMargin, config.filmstripBottomMargin,
+ config.filmstripContentSize, config.filmstripThumbSize, config.filmstripBarSize,
+ config.filmstripGripSize, config.filmstripGripWidth);
+ mRootPane.addComponent(mFilmStripView);
+ mFilmStripView.setListener(this);
+ mFilmStripView.setUserInteractionListener(this);
+ mFilmStripView.setFocusIndex(mCurrentIndex);
+ mFilmStripView.setStartIndex(mCurrentIndex);
+ mRootPane.requestLayout();
+ if (mIsActive) mFilmStripView.resume();
+ if (!mShowBars) mFilmStripView.setVisibility(GLView.INVISIBLE);
+ }
+
@Override
public void onCreate(Bundle data, Bundle restoreState) {
mActionBar = ((Activity) mActivity).getActionBar();
@@ -175,25 +191,14 @@ public class PhotoPage extends ActivityState
mModel = pda;
mPhotoView.setModel(mModel);
- Config.PhotoPage config = Config.PhotoPage.get((Context) mActivity);
-
- mFilmStripView = new FilmStripView(mActivity, mMediaSet,
- config.filmstripTopMargin, config.filmstripMidMargin, config.filmstripBottomMargin,
- config.filmstripContentSize, config.filmstripThumbSize, config.filmstripBarSize,
- config.filmstripGripSize, config.filmstripGripWidth);
- mRootPane.addComponent(mFilmStripView);
- mFilmStripView.setListener(this);
- mFilmStripView.setUserInteractionListener(this);
- mFilmStripView.setFocusIndex(mCurrentIndex);
- mFilmStripView.setStartIndex(mCurrentIndex);
-
mResultIntent.putExtra(KEY_INDEX_HINT, mCurrentIndex);
setStateResult(Activity.RESULT_OK, mResultIntent);
pda.setDataListener(new PhotoDataAdapter.DataListener() {
+ @Override
public void onPhotoChanged(int index, Path item) {
- mFilmStripView.setFocusIndex(index);
+ if (mFilmStripView != null) mFilmStripView.setFocusIndex(index);
mCurrentIndex = index;
mResultIntent.putExtra(KEY_INDEX_HINT, index);
if (item != null) {
@@ -217,11 +222,15 @@ public class PhotoPage extends ActivityState
}
}
-
@Override
public void onLoadingStarted() {
GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
}
+
+ @Override
+ public void onPhotoAvailable(long version, boolean fullImage) {
+ if (mFilmStripView == null) initFilmStripView();
+ }
});
} else {
// Get default media set by the URI
diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java
index da2d3e0ee..d2b4ebc4e 100644
--- a/src/com/android/gallery3d/data/DecodeUtils.java
+++ b/src/com/android/gallery3d/data/DecodeUtils.java
@@ -106,6 +106,31 @@ public class DecodeUtils {
BitmapFactory.decodeFileDescriptor(fd, null, options));
}
+ /**
+ * Decodes the bitmap from the given byte array if the image size is larger than the given
+ * requirement.
+ *
+ * Note: The returned image may be resized down. However, both width and heigh must be
+ * larger than the <code>targetSize</code>.
+ */
+ public static Bitmap requestDecodeIfBigEnough(JobContext jc, byte[] data,
+ Options options, int targetSize) {
+ if (options == null) options = new Options();
+ jc.setCancelListener(new DecodeCanceller(options));
+
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(data, 0, data.length, options);
+ if (jc.isCancelled()) return null;
+ if (options.outWidth < targetSize || options.outHeight < targetSize) {
+ return null;
+ }
+ options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
+ options.outWidth, options.outHeight, targetSize);
+ options.inJustDecodeBounds = false;
+ return ensureGLCompatibleBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length, options));
+ }
+
public static Bitmap requestDecode(JobContext jc,
FileDescriptor fileDescriptor, Rect paddings, Options options) {
if (options == null) options = new Options();
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index f3dedf037..7ab04c568 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -18,10 +18,10 @@ package com.android.gallery3d.data;
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.util.UpdateHelper;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
+import com.android.gallery3d.util.UpdateHelper;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -33,6 +33,7 @@ import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
+import android.util.Log;
import java.io.File;
import java.io.IOException;
@@ -158,6 +159,25 @@ public class LocalImage extends LocalMediaItem {
public Bitmap onDecodeOriginal(JobContext jc, int type) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+ // try to decode from JPEG EXIF
+ if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
+ ExifInterface exif = null;
+ 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);
+ }
+ if (thumbData != null) {
+ Bitmap bitmap = DecodeUtils.requestDecodeIfBigEnough(
+ jc, thumbData, options, getTargetSize(type));
+ if (bitmap != null) return bitmap;
+ }
+ }
return DecodeUtils.requestDecode(
jc, mLocalFilePath, options, getTargetSize(type));
}
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index 9e44bd1d2..9b410e9a8 100644
--- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -24,7 +24,7 @@ import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.FutureListener;
-import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.JobLimiter;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -39,6 +39,7 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
private static final int MSG_LOAD_BITMAP_DONE = 0;
private static final int MSG_UPDATE_SLOT = 1;
private static final int MIN_THUMB_SIZE = 100;
+ private static final int JOB_LIMIT = 2;
public static interface Listener {
public void onSizeChanged(int size);
@@ -64,7 +65,7 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
private SelectionDrawer mSelectionDrawer;
private SynchronizedHandler mHandler;
- private ThreadPool mThreadPool;
+ private JobLimiter mThreadPool;
private int mSlotWidth, mSlotHeight;
private int mActiveRequestCount = 0;
@@ -103,7 +104,7 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
}
};
- mThreadPool = activity.getThreadPool();
+ mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT);
}
public void setSelectionDrawer(SelectionDrawer drawer) {
diff --git a/src/com/android/gallery3d/ui/FilmStripView.java b/src/com/android/gallery3d/ui/FilmStripView.java
index 8d28f2c7b..c53e1aedf 100644
--- a/src/com/android/gallery3d/ui/FilmStripView.java
+++ b/src/com/android/gallery3d/ui/FilmStripView.java
@@ -18,7 +18,6 @@ package com.android.gallery3d.ui;
import com.android.gallery3d.R;
import com.android.gallery3d.anim.AlphaAnimation;
-import com.android.gallery3d.anim.CanvasAnimation;
import com.android.gallery3d.app.AlbumDataAdapter;
import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.data.MediaSet;
@@ -46,8 +45,6 @@ public class FilmStripView extends GLView implements SlotView.Listener,
private StripDrawer mStripDrawer;
private Listener mListener;
private UserInteractionListener mUIListener;
- private boolean mFilmStripVisible;
- private CanvasAnimation mFilmStripAnimation;
private NinePatchTexture mBackgroundTexture;
// The layout of FileStripView is
@@ -90,7 +87,6 @@ public class FilmStripView extends GLView implements SlotView.Listener,
mAlbumView.setModel(mAlbumDataAdapter);
mBackgroundTexture = new NinePatchTexture(activity.getAndroidContext(),
R.drawable.navstrip_translucent);
- mFilmStripVisible = true;
}
public void setListener(Listener listener) {
@@ -101,25 +97,18 @@ public class FilmStripView extends GLView implements SlotView.Listener,
mUIListener = listener;
}
- private void setFilmStripVisible(boolean visible) {
- if (mFilmStripVisible == visible) return;
- mFilmStripVisible = visible;
- if (!visible) {
- mFilmStripAnimation = new AlphaAnimation(1, 0);
- mFilmStripAnimation.setDuration(HIDE_ANIMATION_DURATION);
- mFilmStripAnimation.start();
- } else {
- mFilmStripAnimation = null;
- }
- invalidate();
- }
-
public void show() {
- setFilmStripVisible(true);
+ if (getVisibility() == GLView.VISIBLE) return;
+ startAnimation(null);
+ setVisibility(GLView.VISIBLE);
}
public void hide() {
- setFilmStripVisible(false);
+ if (getVisibility() == GLView.INVISIBLE) return;
+ AlphaAnimation animation = new AlphaAnimation(1, 0);
+ animation.setDuration(HIDE_ANIMATION_DURATION);
+ startAnimation(animation);
+ setVisibility(GLView.INVISIBLE);
}
@Override
@@ -159,10 +148,6 @@ public class FilmStripView extends GLView implements SlotView.Listener,
@Override
protected boolean dispatchTouchEvent(MotionEvent event) {
- if (!mFilmStripVisible && mFilmStripAnimation == null) {
- return false;
- }
-
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
@@ -179,63 +164,49 @@ public class FilmStripView extends GLView implements SlotView.Listener,
@Override
protected void render(GLCanvas canvas) {
- CanvasAnimation anim = mFilmStripAnimation;
- if (anim == null && !mFilmStripVisible) return;
-
- boolean needRestore = false;
- if (anim != null) {
- needRestore = true;
- canvas.save(anim.getCanvasSaveFlags());
- long now = canvas.currentAnimationTimeMillis();
- boolean more = anim.calculate(now);
- anim.apply(canvas);
- if (more) {
- invalidate();
- } else {
- mFilmStripAnimation = null;
- }
- }
-
mBackgroundTexture.draw(canvas, 0, 0, getWidth(), getHeight());
super.render(canvas);
-
- if (needRestore) {
- canvas.restore();
- }
}
// Called by AlbumView
+ @Override
public void onSingleTapUp(int slotIndex) {
mAlbumView.setFocusIndex(slotIndex);
mListener.onSlotSelected(slotIndex);
}
// Called by AlbumView
+ @Override
public void onLongTap(int slotIndex) {
onSingleTapUp(slotIndex);
}
// Called by AlbumView
+ @Override
public void onUserInteractionBegin() {
mUIListener.onUserInteractionBegin();
}
// Called by AlbumView
+ @Override
public void onUserInteractionEnd() {
mUIListener.onUserInteractionEnd();
}
// Called by AlbumView
+ @Override
public void onUserInteraction() {
mUIListener.onUserInteraction();
}
// Called by AlbumView
+ @Override
public void onScrollPositionChanged(int position, int total) {
mScrollBarView.setContentPosition(position, total);
}
// Called by ScrollBarView
+ @Override
public void onScrollBarPositionChanged(int position) {
mAlbumView.setScrollPosition(position);
}
diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java
index c59327831..7491a6ffb 100644
--- a/src/com/android/gallery3d/ui/GLView.java
+++ b/src/com/android/gallery3d/ui/GLView.java
@@ -76,10 +76,11 @@ public class GLView {
public void startAnimation(CanvasAnimation animation) {
GLRoot root = getGLRoot();
if (root == null) throw new IllegalStateException();
-
mAnimation = animation;
- mAnimation.start();
- root.registerLaunchedAnimation(mAnimation);
+ if (mAnimation != null) {
+ mAnimation.start();
+ root.registerLaunchedAnimation(mAnimation);
+ }
invalidate();
}
diff --git a/src/com/android/gallery3d/util/JobLimiter.java b/src/com/android/gallery3d/util/JobLimiter.java
new file mode 100644
index 000000000..5abdfd80b
--- /dev/null
+++ b/src/com/android/gallery3d/util/JobLimiter.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2011 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.gallery3d.util;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import java.util.LinkedList;
+
+// Limit the number of concurrent jobs that has been submitted into a ThreadPool
+@SuppressWarnings("rawtypes")
+public class JobLimiter implements FutureListener {
+ private static final String TAG = "JobLimiter";
+
+ // State Transition:
+ // INIT -> DONE, CANCELLED
+ // DONE -> CANCELLED
+ private static final int STATE_INIT = 0;
+ private static final int STATE_DONE = 1;
+ private static final int STATE_CANCELLED = 2;
+
+ private final LinkedList<JobWrapper<?>> mJobs = new LinkedList<JobWrapper<?>>();
+ private final ThreadPool mPool;
+ private int mLimit;
+
+ private static class JobWrapper<T> implements Future<T>, Job<T> {
+ private int mState = STATE_INIT;
+ private Job<T> mJob;
+ private Future<T> mDelegate;
+ private FutureListener<T> mListener;
+ private T mResult;
+
+ public JobWrapper(Job<T> job, FutureListener<T> listener) {
+ mJob = job;
+ mListener = listener;
+ }
+
+ public synchronized void setFuture(Future<T> future) {
+ if (mState != STATE_INIT) return;
+ mDelegate = future;
+ }
+
+ @Override
+ public void cancel() {
+ FutureListener<T> listener = null;
+ synchronized (this) {
+ if (mState != STATE_DONE) {
+ listener = mListener;
+ mJob = null;
+ mListener = null;
+ if (mDelegate != null) {
+ mDelegate.cancel();
+ mDelegate = null;
+ }
+ }
+ mState = STATE_CANCELLED;
+ mResult = null;
+ notifyAll();
+ }
+ if (listener != null) listener.onFutureDone(this);
+ }
+
+ @Override
+ public synchronized boolean isCancelled() {
+ return mState == STATE_CANCELLED;
+ }
+
+ @Override
+ public boolean isDone() {
+ // Both CANCELLED AND DONE is considered as done
+ return mState != STATE_INIT;
+ }
+
+ @Override
+ public synchronized T get() {
+ while (mState == STATE_INIT) {
+ // handle the interrupted exception of wait()
+ Utils.waitWithoutInterrupt(this);
+ }
+ return mResult;
+ }
+
+ @Override
+ public void waitDone() {
+ get();
+ }
+
+ @Override
+ public T run(JobContext jc) {
+ Job<T> job = null;
+ synchronized (this) {
+ if (mState == STATE_CANCELLED) return null;
+ job = mJob;
+ }
+ T result = null;
+ try {
+ result = job.run(jc);
+ } catch (Throwable t) {
+ Log.w(TAG, "error executing job: " + job, t);
+ }
+ FutureListener<T> listener = null;
+ synchronized (this) {
+ if (mState == STATE_CANCELLED) return null;
+ mState = STATE_DONE;
+ listener = mListener;
+ mListener = null;
+ mJob = null;
+ mResult = result;
+ notifyAll();
+ }
+ if (listener != null) listener.onFutureDone(this);
+ return result;
+ }
+ }
+
+ public JobLimiter(ThreadPool pool, int limit) {
+ mPool = Utils.checkNotNull(pool);
+ mLimit = limit;
+ }
+
+ public synchronized <T> Future<T> submit(Job<T> job, FutureListener<T> listener) {
+ JobWrapper<T> future = new JobWrapper<T>(Utils.checkNotNull(job), listener);
+ mJobs.addLast(future);
+ submitTasksIfAllowed();
+ return future;
+ }
+
+ public <T> Future<T> submit(Job<T> job) {
+ return submit(job, null);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private void submitTasksIfAllowed() {
+ while (mLimit > 0 && !mJobs.isEmpty()) {
+ JobWrapper wrapper = mJobs.removeFirst();
+ if (!wrapper.isCancelled()) {
+ --mLimit;
+ wrapper.setFuture(mPool.submit(wrapper, this));
+ }
+ }
+ }
+
+ @Override
+ public synchronized void onFutureDone(Future future) {
+ ++mLimit;
+ submitTasksIfAllowed();
+ }
+}