diff options
-rw-r--r-- | gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java | 3 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/PhotoDataAdapter.java | 8 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/PhotoPage.java | 39 | ||||
-rw-r--r-- | src/com/android/gallery3d/data/DecodeUtils.java | 25 | ||||
-rw-r--r-- | src/com/android/gallery3d/data/LocalImage.java | 22 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/AlbumSlidingWindow.java | 7 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/FilmStripView.java | 59 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/GLView.java | 7 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/JobLimiter.java | 163 |
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(); + } +} |