Merge "Import translations. DO NOT MERGE" into gb-ub-photos-arches
diff --git a/Android.mk b/Android.mk
index b0ebf89..4854a5c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -18,7 +18,7 @@
LOCAL_OVERRIDES_PACKAGES := Gallery Gallery3D GalleryNew3D
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 16
LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic libjni_eglfence
diff --git a/gallerycommon/Android.mk b/gallerycommon/Android.mk
index fb81dd9..1d34147 100644
--- a/gallerycommon/Android.mk
+++ b/gallerycommon/Android.mk
@@ -22,6 +22,6 @@
LOCAL_MODULE := com.android.gallery3d.common2
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 16
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
index 85b0925..620e1ae 100644
--- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -16,10 +16,17 @@
package com.android.gallery3d.common;
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.hardware.Camera;
+import android.hardware.Camera.FaceDetectionListener;
import android.os.Build;
import android.provider.MediaStore.MediaColumns;
import android.view.View;
+import java.lang.reflect.Field;
+
public class ApiHelper {
public static interface VERSION_CODES {
// These value are copied from Build.VERSION_CODES
@@ -44,6 +51,63 @@
public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER =
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+ public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_FACTORY =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_SET_BEAM_PUSH_URIS =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_SET_DEFALT_BUFFER_SIZE = hasMethod(
+ "android.graphics.SurfaceTexture", "setDefaultBufferSize",
+ int.class, int.class);
+
+ public static final boolean HAS_RELEASE_SURFACE_TEXTURE = hasMethod(
+ "android.graphics.SurfaceTexture", "release");
+
+ public static final boolean HAS_MTP =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
+
+ public static final boolean HAS_AUTO_FOCUS_MOVE_CALLBACK =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_ACTIVITY_INVALIDATE_OPTIONS_MENU =
+ hasMethod(Activity.class, "invalidateOptionsMenu");
+
+ public static final boolean HAS_REMOTE_VIEWS_SERVICE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_INTENT_EXTRA_LOCAL_ONLY =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_SET_SYSTEM_UI_VISIBILITY =
+ hasMethod(View.class, "setSystemUiVisibility", int.class);
+
+ public static final boolean HAS_FACE_DETECTION =
+ hasClass(Camera.class, "android.hardware.Camera$FaceDetectionListener") &&
+ hasMethod(Camera.class, "setFaceDetectionListener", FaceDetectionListener.class) &&
+ hasMethod(Camera.class, "startFaceDetection") &&
+ hasMethod(Camera.class, "stopFaceDetection") &&
+ hasMethod(Camera.Parameters.class, "getMaxNumDetectedFaces");
+
+ public static final boolean HAS_GET_CAMERA_DISABLED =
+ hasMethod(DevicePolicyManager.class, "getCameraDisabled", ComponentName.class);
+
+ public static final boolean HAS_MEDIA_ACTION_SOUND =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static int getIntFieldIfExists(Class<?> klass, String fieldName,
+ Class<?> obj, int defaultVal) {
+ try {
+ Field f = klass.getDeclaredField(fieldName);
+ return f.getInt(obj);
+ } catch (Exception e) {
+ return defaultVal;
+ }
+ }
+
+ public static final boolean HAS_SET_ICON_ATTRIBUTE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
private static boolean hasField(Class<?> klass, String fieldName) {
try {
klass.getDeclaredField(fieldName);
@@ -52,4 +116,36 @@
return false;
}
}
+
+ private static boolean hasMethod(String className, String methodName,
+ Class<?>... parameterTypes) {
+ try {
+ Class<?> klass = Class.forName(className);
+ klass.getDeclaredMethod(methodName, parameterTypes);
+ return true;
+ } catch (Throwable th) {
+ return false;
+ }
+ }
+
+ private static boolean hasMethod(
+ Class<?> klass, String methodName, Class<?> ... paramTypes) {
+ try {
+ klass.getDeclaredMethod(methodName, paramTypes);
+ return true;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasClass(Class<?> klass, String className) {
+ Class<?>[] klasses = klass.getClasses();
+ for (int i = 0; i < klasses.length; ++i) {
+ if (klasses[i].getName().equals(className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
}
diff --git a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java b/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
new file mode 100644
index 0000000..9b11fe4
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.common;
+
+/**
+ * The class holds the EXIF tag names that are not available in
+ * {@link android.media.ExifInterface} prior to API level 11.
+ */
+public interface ExifTags {
+ static final String TAG_ISO = "ISOSpeedRatings";
+ static final String TAG_EXPOSURE_TIME = "ExposureTime";
+ static final String TAG_APERTURE = "FNumber";
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/OverScroller.java b/gallerycommon/src/com/android/gallery3d/common/OverScroller.java
new file mode 100644
index 0000000..1ab7953
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/OverScroller.java
@@ -0,0 +1,958 @@
+/*
+ * Copyright (C) 2010 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.common;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class encapsulates scrolling with the ability to overshoot the bounds
+ * of a scrolling operation. This class is a drop-in replacement for
+ * {@link android.widget.Scroller} in most cases.
+ */
+public class OverScroller {
+ private int mMode;
+
+ private final SplineOverScroller mScrollerX;
+ private final SplineOverScroller mScrollerY;
+
+ private Interpolator mInterpolator;
+
+ private final boolean mFlywheel;
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ /**
+ * Creates an OverScroller with a viscous fluid scroll interpolator and flywheel.
+ * @param context
+ */
+ public OverScroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Creates an OverScroller with flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ */
+ public OverScroller(Context context, Interpolator interpolator) {
+ this(context, interpolator, true);
+ }
+
+ /**
+ * Creates an OverScroller.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+ * @hide
+ */
+ public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
+ mInterpolator = interpolator;
+ mFlywheel = flywheel;
+ mScrollerX = new SplineOverScroller();
+ mScrollerY = new SplineOverScroller();
+
+ SplineOverScroller.initFromContext(context);
+ }
+
+ /**
+ * Creates an OverScroller with flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+ * behavior is no longer supported and this coefficient has no effect.
+ * !deprecated Use {!link #OverScroller(Context, Interpolator, boolean)} instead.
+ */
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY) {
+ this(context, interpolator, true);
+ }
+
+ /**
+ * Creates an OverScroller.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+ * behavior is no longer supported and this coefficient has no effect.
+ * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+ * !deprecated Use {!link OverScroller(Context, Interpolator, boolean)} instead.
+ */
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
+ this(context, interpolator, flywheel);
+ }
+
+ void setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mScrollerX.setFriction(friction);
+ mScrollerY.setFriction(friction);
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mScrollerX.mFinished && mScrollerY.mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value. Contrary to
+ * {@link #abortAnimation()}, forcing the animation to finished
+ * does NOT cause the scroller to move to the final x and y
+ * position.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mScrollerX.mFinished = mScrollerY.mFinished = finished;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mScrollerX.mCurrentPosition;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mScrollerY.mCurrentPosition;
+ }
+
+ /**
+ * Returns the absolute value of the current velocity.
+ *
+ * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
+ */
+ public float getCurrVelocity() {
+ float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
+ squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
+ return FloatMath.sqrt(squaredNorm);
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mScrollerX.mStart;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mScrollerY.mStart;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mScrollerX.mFinal;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mScrollerY.mFinal;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * This function will lie to the best of its ability.
+ */
+ @Deprecated
+ public final int getDuration() {
+ return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void extendDuration(int extend) {
+ mScrollerX.extendDuration(extend);
+ mScrollerY.extendDuration(extend);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void setFinalX(int newX) {
+ mScrollerX.setFinalPosition(newX);
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void setFinalY(int newY) {
+ mScrollerY.setFinalPosition(newY);
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true, the
+ * animation is not yet finished.
+ */
+ public boolean computeScrollOffset() {
+ if (isFinished()) {
+ return false;
+ }
+
+ switch (mMode) {
+ case SCROLL_MODE:
+ long time = AnimationUtils.currentAnimationTimeMillis();
+ // Any scroller can be used for time, since they were started
+ // together in scroll mode. We use X here.
+ final long elapsedTime = time - mScrollerX.mStartTime;
+
+ final int duration = mScrollerX.mDuration;
+ if (elapsedTime < duration) {
+ float q = (float) (elapsedTime) / duration;
+
+ if (mInterpolator == null) {
+ q = Scroller.viscousFluid(q);
+ } else {
+ q = mInterpolator.getInterpolation(q);
+ }
+
+ mScrollerX.updateScroll(q);
+ mScrollerY.updateScroll(q);
+ } else {
+ abortAnimation();
+ }
+ break;
+
+ case FLING_MODE:
+ if (!mScrollerX.mFinished) {
+ if (!mScrollerX.update()) {
+ if (!mScrollerX.continueWhenFinished()) {
+ mScrollerX.finish();
+ }
+ }
+ }
+
+ if (!mScrollerY.mFinished) {
+ if (!mScrollerY.update()) {
+ if (!mScrollerY.continueWhenFinished()) {
+ mScrollerY.finish();
+ }
+ }
+ }
+
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mScrollerX.startScroll(startX, dx, duration);
+ mScrollerY.startScroll(startY, dy, duration);
+ }
+
+ /**
+ * Call this when you want to 'spring back' into a valid coordinate range.
+ *
+ * @param startX Starting X coordinate
+ * @param startY Starting Y coordinate
+ * @param minX Minimum valid X value
+ * @param maxX Maximum valid X value
+ * @param minY Minimum valid Y value
+ * @param maxY Minimum valid Y value
+ * @return true if a springback was initiated, false if startX and startY were
+ * already within the valid range.
+ */
+ public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+ mMode = FLING_MODE;
+
+ // Make sure both methods are called.
+ final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
+ final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
+ return spingbackX || spingbackY;
+ }
+
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance traveled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use minX as
+ * a springback boundary.
+ * @param maxX Maximum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use maxX as
+ * a springback boundary.
+ * @param minY Minimum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use minY as
+ * a springback boundary.
+ * @param maxY Maximum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use maxY as
+ * a springback boundary.
+ * @param overX Overfling range. If > 0, horizontal overfling in either
+ * direction will be possible.
+ * @param overY Overfling range. If > 0, vertical overfling in either
+ * direction will be possible.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY, int overX, int overY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !isFinished()) {
+ float oldVelocityX = mScrollerX.mCurrVelocity;
+ float oldVelocityY = mScrollerY.mCurrVelocity;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mScrollerX.fling(startX, velocityX, minX, maxX, overX);
+ mScrollerY.fling(startY, velocityY, minY, maxY, overY);
+ }
+
+ /**
+ * Notify the scroller that we've reached a horizontal boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will transition the current motion and
+ * animate from startX to finalX as appropriate.
+ *
+ * @param startX Starting/current X position
+ * @param finalX Desired final X position
+ * @param overX Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalX. Absolute value - must be positive.
+ */
+ public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
+ mScrollerX.notifyEdgeReached(startX, finalX, overX);
+ }
+
+ /**
+ * Notify the scroller that we've reached a vertical boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will animate a parabolic motion from
+ * startY to finalY.
+ *
+ * @param startY Starting/current Y position
+ * @param finalY Desired final Y position
+ * @param overY Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalY. Absolute value - must be positive.
+ */
+ public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
+ mScrollerY.notifyEdgeReached(startY, finalY, overY);
+ }
+
+ /**
+ * Returns whether the current Scroller is currently returning to a valid position.
+ * Valid bounds were provided by the
+ * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
+ *
+ * One should check this value before calling
+ * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
+ * to restore a valid position will then be stopped. The caller has to take into account
+ * the fact that the started scroll will start from an overscrolled position.
+ *
+ * @return true when the current position is overscrolled and in the process of
+ * interpolating back to a valid value.
+ */
+ public boolean isOverScrolled() {
+ return ((!mScrollerX.mFinished &&
+ mScrollerX.mState != SplineOverScroller.SPLINE) ||
+ (!mScrollerY.mFinished &&
+ mScrollerY.mState != SplineOverScroller.SPLINE));
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating causes the scroller to move to the final x and y
+ * positions.
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mScrollerX.finish();
+ mScrollerY.finish();
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ *
+ * @hide
+ */
+ public int timePassed() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
+ return (int) (time - startTime);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ final int dx = mScrollerX.mFinal - mScrollerX.mStart;
+ final int dy = mScrollerY.mFinal - mScrollerY.mStart;
+ return !isFinished() && Math.signum(xvel) == Math.signum(dx) &&
+ Math.signum(yvel) == Math.signum(dy);
+ }
+
+ static class SplineOverScroller {
+ // Initial position
+ private int mStart;
+
+ // Current position
+ private int mCurrentPosition;
+
+ // Final position
+ private int mFinal;
+
+ // Initial velocity
+ private int mVelocity;
+
+ // Current velocity
+ private float mCurrVelocity;
+
+ // Constant current deceleration
+ private float mDeceleration;
+
+ // Animation starting time, in system milliseconds
+ private long mStartTime;
+
+ // Animation duration, in milliseconds
+ private int mDuration;
+
+ // Duration to complete spline component of animation
+ private int mSplineDuration;
+
+ // Distance to travel along spline animation
+ private int mSplineDistance;
+
+ // Whether the animation is currently in progress
+ private boolean mFinished;
+
+ // The allowed overshot distance before boundary is reached.
+ private int mOver;
+
+ // Fling friction
+ private float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+ // Current state of the animation.
+ private int mState = SPLINE;
+
+ // Constant gravity value, used in the deceleration phase.
+ private static final float GRAVITY = 2000.0f;
+
+ // A device specific coefficient adjusted to physical values.
+ private static float PHYSICAL_COEF;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+ private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+ private static final float START_TENSION = 0.5f;
+ private static final float END_TENSION = 1.0f;
+ private static final float P1 = START_TENSION * INFLEXION;
+ private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+ private static final int SPLINE = 0;
+ private static final int CUBIC = 1;
+ private static final int BALLISTIC = 2;
+
+ static {
+ float x_min = 0.0f;
+ float y_min = 0.0f;
+ for (int i = 0; i < NB_SAMPLES; i++) {
+ final float alpha = (float) i / NB_SAMPLES;
+
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+ if (Math.abs(tx - alpha) < 1E-5) break;
+ if (tx > alpha) x_max = x;
+ else x_min = x;
+ }
+ SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+ if (Math.abs(dy - alpha) < 1E-5) break;
+ if (dy > alpha) y_max = y;
+ else y_min = y;
+ }
+ SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+ }
+ SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+ }
+
+ static void initFromContext(Context context) {
+ final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+ PHYSICAL_COEF = SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * ppi
+ * 0.84f; // look and feel tuning
+ }
+
+ void setFriction(float friction) {
+ mFlingFriction = friction;
+ }
+
+ SplineOverScroller() {
+ mFinished = true;
+ }
+
+ void updateScroll(float q) {
+ mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+ }
+
+ /*
+ * Get a signed deceleration that will reduce the velocity.
+ */
+ static private float getDeceleration(int velocity) {
+ return velocity > 0 ? -GRAVITY : GRAVITY;
+ }
+
+ /*
+ * Modifies mDuration to the duration it takes to get from start to newFinal using the
+ * spline interpolation. The previous duration was needed to get to oldFinal.
+ */
+ private void adjustDuration(int start, int oldFinal, int newFinal) {
+ final int oldDistance = oldFinal - start;
+ final int newDistance = newFinal - start;
+ final float x = Math.abs((float) newDistance / oldDistance);
+ final int index = (int) (NB_SAMPLES * x);
+ if (index < NB_SAMPLES) {
+ final float x_inf = (float) index / NB_SAMPLES;
+ final float x_sup = (float) (index + 1) / NB_SAMPLES;
+ final float t_inf = SPLINE_TIME[index];
+ final float t_sup = SPLINE_TIME[index + 1];
+ final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
+ mDuration *= timeCoef;
+ }
+ }
+
+ void startScroll(int start, int distance, int duration) {
+ mFinished = false;
+
+ mStart = start;
+ mFinal = start + distance;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = duration;
+
+ // Unused
+ mDeceleration = 0.0f;
+ mVelocity = 0;
+ }
+
+ void finish() {
+ mCurrentPosition = mFinal;
+ // Not reset since WebView relies on this value for fast fling.
+ // TODO: restore when WebView uses the fast fling implemented in this class.
+ // mCurrVelocity = 0.0f;
+ mFinished = true;
+ }
+
+ void setFinalPosition(int position) {
+ mFinal = position;
+ mFinished = false;
+ }
+
+ void extendDuration(int extend) {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final int elapsedTime = (int) (time - mStartTime);
+ mDuration = elapsedTime + extend;
+ mFinished = false;
+ }
+
+ boolean springback(int start, int min, int max) {
+ mFinished = true;
+
+ mStart = mFinal = start;
+ mVelocity = 0;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = 0;
+
+ if (start < min) {
+ startSpringback(start, min, 0);
+ } else if (start > max) {
+ startSpringback(start, max, 0);
+ }
+
+ return !mFinished;
+ }
+
+ private void startSpringback(int start, int end, int velocity) {
+ // mStartTime has been set
+ mFinished = false;
+ mState = CUBIC;
+ mStart = start;
+ mFinal = end;
+ final int delta = start - end;
+ mDeceleration = getDeceleration(delta);
+ // TODO take velocity into account
+ mVelocity = -delta; // only sign is used
+ mOver = Math.abs(delta);
+ mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration));
+ }
+
+ void fling(int start, int velocity, int min, int max, int over) {
+ mOver = over;
+ mFinished = false;
+ mCurrVelocity = mVelocity = velocity;
+ mDuration = mSplineDuration = 0;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mCurrentPosition = mStart = start;
+
+ if (start > max || start < min) {
+ startAfterEdge(start, min, max, velocity);
+ return;
+ }
+
+ mState = SPLINE;
+ double totalDistance = 0.0;
+
+ if (velocity != 0) {
+ mDuration = mSplineDuration = getSplineFlingDuration(velocity);
+ totalDistance = getSplineFlingDistance(velocity);
+ }
+
+ mSplineDistance = (int) (totalDistance * Math.signum(velocity));
+ mFinal = start + mSplineDistance;
+
+ // Clamp to a valid final position
+ if (mFinal < min) {
+ adjustDuration(mStart, mFinal, min);
+ mFinal = min;
+ }
+
+ if (mFinal > max) {
+ adjustDuration(mStart, mFinal, max);
+ mFinal = max;
+ }
+ }
+
+ private double getSplineDeceleration(int velocity) {
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * PHYSICAL_COEF));
+ }
+
+ private double getSplineFlingDistance(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return mFlingFriction * PHYSICAL_COEF * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ }
+
+ /* Returns the duration, expressed in milliseconds */
+ private int getSplineFlingDuration(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (int) (1000.0 * Math.exp(l / decelMinusOne));
+ }
+
+ private void fitOnBounceCurve(int start, int end, int velocity) {
+ // Simulate a bounce that started from edge
+ final float durationToApex = - velocity / mDeceleration;
+ final float distanceToApex = velocity * velocity / 2.0f / Math.abs(mDeceleration);
+ final float distanceToEdge = Math.abs(end - start);
+ final float totalDuration = (float) Math.sqrt(
+ 2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration));
+ mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
+ mStart = end;
+ mVelocity = (int) (- mDeceleration * totalDuration);
+ }
+
+ private void startBounceAfterEdge(int start, int end, int velocity) {
+ mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity);
+ fitOnBounceCurve(start, end, velocity);
+ onEdgeReached();
+ }
+
+ private void startAfterEdge(int start, int min, int max, int velocity) {
+ if (start > min && start < max) {
+ Log.e("OverScroller", "startAfterEdge called from a valid position");
+ mFinished = true;
+ return;
+ }
+ final boolean positive = start > max;
+ final int edge = positive ? max : min;
+ final int overDistance = start - edge;
+ boolean keepIncreasing = overDistance * velocity >= 0;
+ if (keepIncreasing) {
+ // Will result in a bounce or a to_boundary depending on velocity.
+ startBounceAfterEdge(start, edge, velocity);
+ } else {
+ final double totalDistance = getSplineFlingDistance(velocity);
+ if (totalDistance > Math.abs(overDistance)) {
+ fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
+ } else {
+ startSpringback(start, edge, velocity);
+ }
+ }
+ }
+
+ void notifyEdgeReached(int start, int end, int over) {
+ // mState is used to detect successive notifications
+ if (mState == SPLINE) {
+ mOver = over;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ // We were in fling/scroll mode before: current velocity is such that distance to
+ // edge is increasing. This ensures that startAfterEdge will not start a new fling.
+ startAfterEdge(start, end, end, (int) mCurrVelocity);
+ }
+ }
+
+ private void onEdgeReached() {
+ // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+ float distance = mVelocity * mVelocity / (2.0f * Math.abs(mDeceleration));
+ final float sign = Math.signum(mVelocity);
+
+ if (distance > mOver) {
+ // Default deceleration is not sufficient to slow us down before boundary
+ mDeceleration = - sign * mVelocity * mVelocity / (2.0f * mOver);
+ distance = mOver;
+ }
+
+ mOver = (int) distance;
+ mState = BALLISTIC;
+ mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance);
+ mDuration = - (int) (1000.0f * mVelocity / mDeceleration);
+ }
+
+ boolean continueWhenFinished() {
+ switch (mState) {
+ case SPLINE:
+ // Duration from start to null velocity
+ if (mDuration < mSplineDuration) {
+ // If the animation was clamped, we reached the edge
+ mStart = mFinal;
+ // TODO Better compute speed when edge was reached
+ mVelocity = (int) mCurrVelocity;
+ mDeceleration = getDeceleration(mVelocity);
+ mStartTime += mDuration;
+ onEdgeReached();
+ } else {
+ // Normal stop, no need to continue
+ return false;
+ }
+ break;
+ case BALLISTIC:
+ mStartTime += mDuration;
+ startSpringback(mFinal, mStart, 0);
+ break;
+ case CUBIC:
+ return false;
+ }
+
+ update();
+ return true;
+ }
+
+ /*
+ * Update the current position and velocity for current time. Returns
+ * true if update has been done and false if animation duration has been
+ * reached.
+ */
+ boolean update() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long currentTime = time - mStartTime;
+
+ if (currentTime > mDuration) {
+ return false;
+ }
+
+ double distance = 0.0;
+ switch (mState) {
+ case SPLINE: {
+ final float t = (float) currentTime / mSplineDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ float distanceCoef = 1.f;
+ float velocityCoef = 0.f;
+ if (index < NB_SAMPLES) {
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE_POSITION[index];
+ final float d_sup = SPLINE_POSITION[index + 1];
+ velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+ distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+ }
+
+ distance = distanceCoef * mSplineDistance;
+ mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f;
+ break;
+ }
+
+ case BALLISTIC: {
+ final float t = currentTime / 1000.0f;
+ mCurrVelocity = mVelocity + mDeceleration * t;
+ distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+ break;
+ }
+
+ case CUBIC: {
+ final float t = (float) (currentTime) / mDuration;
+ final float t2 = t * t;
+ final float sign = Math.signum(mVelocity);
+ distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);
+ mCurrVelocity = sign * mOver * 6.0f * (- t + t2);
+ break;
+ }
+ }
+
+ mCurrentPosition = mStart + (int) Math.round(distance);
+
+ return true;
+ }
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/Scroller.java b/gallerycommon/src/com/android/gallery3d/common/Scroller.java
new file mode 100644
index 0000000..160f3ca
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/Scroller.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2006 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.common;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.FloatMath;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+
+/**
+ * This class encapsulates scrolling. The duration of the scroll
+ * can be passed in the constructor and specifies the maximum time that
+ * the scrolling animation should take. Past this time, the scrolling is
+ * automatically moved to its final stage and computeScrollOffset()
+ * will always return false to indicate that scrolling is over.
+ */
+public class Scroller {
+ private int mMode;
+
+ private int mStartX;
+ private int mStartY;
+ private int mFinalX;
+ private int mFinalY;
+
+ private int mMinX;
+ private int mMaxX;
+ private int mMinY;
+ private int mMaxY;
+
+ private int mCurrX;
+ private int mCurrY;
+ private long mStartTime;
+ private int mDuration;
+ private float mDurationReciprocal;
+ private float mDeltaX;
+ private float mDeltaY;
+ private boolean mFinished;
+ private Interpolator mInterpolator;
+ private boolean mFlywheel;
+
+ private float mVelocity;
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
+ private static float ALPHA = 800; // pixels / seconds
+ private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
+ private static float END_TENSION = 1.0f - START_TENSION;
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE = new float[NB_SAMPLES + 1];
+
+ private float mDeceleration;
+ private final float mPpi;
+
+ static {
+ float x_min = 0.0f;
+ for (int i = 0; i <= NB_SAMPLES; i++) {
+ final float t = (float) i / NB_SAMPLES;
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
+ if (Math.abs(tx - t) < 1E-5) break;
+ if (tx > t) x_max = x;
+ else x_min = x;
+ }
+ final float d = coef + x * x * x;
+ SPLINE[i] = d;
+ }
+ SPLINE[NB_SAMPLES] = 1.0f;
+
+ // This controls the viscous fluid effect (how much of it)
+ sViscousFluidScale = 8.0f;
+ // must be set to 1.0 (used in viscousFluid())
+ sViscousFluidNormalize = 1.0f;
+ sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+ }
+
+ private static float sViscousFluidScale;
+ private static float sViscousFluidNormalize;
+
+ /**
+ * Create a Scroller with the default duration and interpolator.
+ */
+ public Scroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
+ * be in effect for apps targeting Honeycomb or newer.
+ */
+ public Scroller(Context context, Interpolator interpolator) {
+ this(context, interpolator,
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. Specify whether or
+ * not to support progressive "flywheel" behavior in flinging.
+ */
+ public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
+ mFinished = true;
+ mInterpolator = interpolator;
+ mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
+ mFlywheel = flywheel;
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mDeceleration = computeDeceleration(friction);
+ }
+
+ private float computeDeceleration(float friction) {
+ return SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * mPpi // pixels per inch
+ * friction;
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mFinished = finished;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ */
+ public final int getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mCurrX;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mCurrY;
+ }
+
+ /**
+ * Returns the current velocity.
+ *
+ * @return The original velocity less the deceleration. Result may be
+ * negative.
+ */
+ public float getCurrVelocity() {
+ return mVelocity - mDeceleration * timePassed() / 2000.0f;
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mStartX;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mStartY;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mFinalX;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mFinalY;
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true,
+ * the animation is not yet finished. loc will be altered to provide the
+ * new location.
+ */
+ public boolean computeScrollOffset() {
+ if (mFinished) {
+ return false;
+ }
+
+ int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+
+ if (timePassed < mDuration) {
+ switch (mMode) {
+ case SCROLL_MODE:
+ float x = timePassed * mDurationReciprocal;
+
+ if (mInterpolator == null)
+ x = viscousFluid(x);
+ else
+ x = mInterpolator.getInterpolation(x);
+
+ mCurrX = mStartX + Math.round(x * mDeltaX);
+ mCurrY = mStartY + Math.round(x * mDeltaY);
+ break;
+ case FLING_MODE:
+ final float t = (float) timePassed / mDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE[index];
+ final float d_sup = SPLINE[index + 1];
+ final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
+
+ mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
+ // Pin to mMinX <= mCurrX <= mMaxX
+ mCurrX = Math.min(mCurrX, mMaxX);
+ mCurrX = Math.max(mCurrX, mMinX);
+
+ mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
+ // Pin to mMinY <= mCurrY <= mMaxY
+ mCurrY = Math.min(mCurrY, mMaxY);
+ mCurrY = Math.max(mCurrY, mMinY);
+
+ if (mCurrX == mFinalX && mCurrY == mFinalY) {
+ mFinished = true;
+ }
+
+ break;
+ }
+ }
+ else {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mFinished = false;
+ mDuration = duration;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+ mFinalX = startX + dx;
+ mFinalY = startY + dy;
+ mDeltaX = dx;
+ mDeltaY = dy;
+ mDurationReciprocal = 1.0f / (float) mDuration;
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance travelled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this
+ * point.
+ * @param maxX Maximum X value. The scroller will not scroll past this
+ * point.
+ * @param minY Minimum Y value. The scroller will not scroll past this
+ * point.
+ * @param maxY Maximum Y value. The scroller will not scroll past this
+ * point.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !mFinished) {
+ float oldVel = getCurrVelocity();
+
+ float dx = (float) (mFinalX - mStartX);
+ float dy = (float) (mFinalY - mStartY);
+ float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+
+ float ndx = dx / hyp;
+ float ndy = dy / hyp;
+
+ float oldVelocityX = ndx * oldVel;
+ float oldVelocityY = ndy * oldVel;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mFinished = false;
+
+ float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
+
+ mVelocity = velocity;
+ final double l = Math.log(START_TENSION * velocity / ALPHA);
+ mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+
+ float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
+ float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
+
+ int totalDistance =
+ (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+
+ mMinX = minX;
+ mMaxX = maxX;
+ mMinY = minY;
+ mMaxY = maxY;
+
+ mFinalX = startX + Math.round(totalDistance * coeffX);
+ // Pin to mMinX <= mFinalX <= mMaxX
+ mFinalX = Math.min(mFinalX, mMaxX);
+ mFinalX = Math.max(mFinalX, mMinX);
+
+ mFinalY = startY + Math.round(totalDistance * coeffY);
+ // Pin to mMinY <= mFinalY <= mMaxY
+ mFinalY = Math.min(mFinalY, mMaxY);
+ mFinalY = Math.max(mFinalY, mMinY);
+ }
+
+ static float viscousFluid(float x)
+ {
+ x *= sViscousFluidScale;
+ if (x < 1.0f) {
+ x -= (1.0f - (float)Math.exp(-x));
+ } else {
+ float start = 0.36787944117f; // 1/e == exp(-1)
+ x = 1.0f - (float)Math.exp(1.0f - x);
+ x = start + x * (1.0f - start);
+ }
+ x *= sViscousFluidNormalize;
+ return x;
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating cause the scroller to move to the final x and y
+ * position
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ */
+ public void extendDuration(int extend) {
+ int passed = timePassed();
+ mDuration = passed + extend;
+ mDurationReciprocal = 1.0f / mDuration;
+ mFinished = false;
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ */
+ public int timePassed() {
+ return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ */
+ public void setFinalX(int newX) {
+ mFinalX = newX;
+ mDeltaX = mFinalX - mStartX;
+ mFinished = false;
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ */
+ public void setFinalY(int newY) {
+ mFinalY = newY;
+ mDeltaY = mFinalY - mStartY;
+ mFinished = false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
+ Math.signum(yvel) == Math.signum(mFinalY - mStartY);
+ }
+}
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
index 0987ac3..6b0b106 100644
--- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java
+++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.app;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -27,11 +28,14 @@
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.data.BitmapPool;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.ui.GLRoot;
@@ -84,18 +88,42 @@
toggleStatusBarByOrientation();
}
+ private Menu mOptionsMenu;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ mOptionsMenu = menu;
+ super.onCreateOptionsMenu(menu);
+ return getStateManager().createOptionsMenu(menu);
+ }
+
+ @Override
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+ public void invalidateOptionsMenu() {
+ if (ApiHelper.HAS_ACTIVITY_INVALIDATE_OPTIONS_MENU) {
+ super.invalidateOptionsMenu();
+ } else if (mOptionsMenu != null) {
+ mOptionsMenu.clear();
+ getStateManager().createOptionsMenu(mOptionsMenu);
+ }
+ }
+
+ @Override
public Context getAndroidContext() {
return this;
}
+ @Override
public DataManager getDataManager() {
return ((GalleryApp) getApplication()).getDataManager();
}
+ @Override
public ThreadPool getThreadPool() {
return ((GalleryApp) getApplication()).getThreadPool();
}
+ @Override
public synchronized StateManager getStateManager() {
if (mStateManager == null) {
mStateManager = new StateManager(this);
@@ -103,10 +131,12 @@
return mStateManager;
}
+ @Override
public GLRoot getGLRoot() {
return mGLRootView;
}
+ @Override
public OrientationManager getOrientationManager() {
return mOrientationManager;
}
@@ -141,17 +171,27 @@
dialog.cancel();
}
};
- mAlertDialog = new AlertDialog.Builder(this)
- .setIconAttribute(android.R.attr.alertDialogIcon)
+ AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(R.string.no_external_storage_title)
.setMessage(R.string.no_external_storage)
.setNegativeButton(android.R.string.cancel, onClick)
- .setOnCancelListener(onCancel)
- .show();
+ .setOnCancelListener(onCancel);
+ if (ApiHelper.HAS_SET_ICON_ATTRIBUTE) {
+ setAlertDialogIconAttribute(builder);
+ } else {
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ }
+ mAlertDialog = builder.show();
registerReceiver(mMountReceiver, mMountFilter);
}
}
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+ private static void setAlertDialogIconAttribute(
+ AlertDialog.Builder builder) {
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ }
+
@Override
protected void onStop() {
super.onStop();
@@ -188,11 +228,16 @@
} finally {
mGLRootView.unlockRenderThread();
}
- MediaItem.getMicroThumbPool().clear();
- MediaItem.getThumbPool().clear();
+ clearBitmapPool(MediaItem.getMicroThumbPool());
+ clearBitmapPool(MediaItem.getThumbPool());
+
MediaItem.getBytesBufferPool().clear();
}
+ private static void clearBitmapPool(BitmapPool pool) {
+ if (pool != null) pool.clear();
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java
index 867cda3..c870855 100644
--- a/src/com/android/gallery3d/app/ActivityState.java
+++ b/src/com/android/gallery3d/app/ActivityState.java
@@ -27,7 +27,6 @@
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
import android.view.Window;
import android.view.WindowManager;
@@ -135,7 +134,7 @@
// should only be called by StateManager
void resume() {
- Activity activity = (Activity) mActivity;
+ AbstractGalleryActivity activity = (AbstractGalleryActivity) mActivity;
ActionBar actionBar = activity.getActionBar();
if (actionBar != null) {
if ((mFlags & FLAG_HIDE_ACTION_BAR) != 0) {
diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java
index f264697..a587624 100644
--- a/src/com/android/gallery3d/app/CropImage.java
+++ b/src/com/android/gallery3d/app/CropImage.java
@@ -33,6 +33,7 @@
import android.graphics.RectF;
import android.media.ExifInterface;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -48,6 +49,7 @@
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.ExifTags;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.LocalImage;
@@ -369,7 +371,7 @@
}
}
- @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static void setImageSize(ContentValues values, int width, int height) {
// The two fields are available since ICS but got published in JB
if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
@@ -988,11 +990,11 @@
}
// Handle some special values here
- String value = oldExif.getAttribute(ExifInterface.TAG_APERTURE);
+ String value = oldExif.getAttribute(ExifTags.TAG_APERTURE);
if (value != null) {
try {
float aperture = Float.parseFloat(value);
- newExif.setAttribute(ExifInterface.TAG_APERTURE,
+ newExif.setAttribute(ExifTags.TAG_APERTURE,
String.valueOf((int) (aperture * 10 + 0.5f)) + "/10");
} catch (NumberFormatException e) {
Log.w(TAG, "cannot parse aperture: " + value);
@@ -1001,22 +1003,22 @@
// TODO: The code is broken, need to fix the JHEAD lib
/*
- value = oldExif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
+ value = oldExif.getAttribute(ExifTags.TAG_EXPOSURE_TIME);
if (value != null) {
try {
double exposure = Double.parseDouble(value);
testToRational("test exposure", exposure);
- newExif.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, value);
+ newExif.setAttribute(ExifTags.TAG_EXPOSURE_TIME, value);
} catch (NumberFormatException e) {
Log.w(TAG, "cannot parse exposure time: " + value);
}
}
- value = oldExif.getAttribute(ExifInterface.TAG_ISO);
+ value = oldExif.getAttribute(ExifTags.TAG_ISO);
if (value != null) {
try {
int iso = Integer.parseInt(value);
- newExif.setAttribute(ExifInterface.TAG_ISO, String.valueOf(iso) + "/1");
+ newExif.setAttribute(ExifTags.TAG_ISO, String.valueOf(iso) + "/1");
} catch (NumberFormatException e) {
Log.w(TAG, "cannot parse exposure time: " + value);
}
diff --git a/src/com/android/gallery3d/app/Gallery.java b/src/com/android/gallery3d/app/Gallery.java
index 468d202..0d5770c 100644
--- a/src/com/android/gallery3d/app/Gallery.java
+++ b/src/com/android/gallery3d/app/Gallery.java
@@ -26,7 +26,6 @@
import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
-import android.view.Menu;
import android.view.Window;
import android.widget.Toast;
@@ -37,7 +36,6 @@
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.picasasource.PicasaSource;
-import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.util.GalleryUtils;
public final class Gallery extends AbstractGalleryActivity implements OnCancelListener {
@@ -232,12 +230,6 @@
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- return getStateManager().createOptionsMenu(menu);
- }
-
- @Override
protected void onResume() {
Utils.assertTrue(getStateManager().getStateCount() > 0);
super.onResume();
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java
index 98d7a64..f2dc9ad 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -29,6 +29,7 @@
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
@@ -57,6 +58,10 @@
private static final String KEY_VIDEO_POSITION = "video-position";
private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
+ // These are constants in KeyEvent, appearing on API level 11.
+ private static final int KEYCODE_MEDIA_PLAY = 126;
+ private static final int KEYCODE_MEDIA_PAUSE = 127;
+
// Copied from MediaPlaybackService in the Music Player app.
private static final String SERVICECMD = "com.android.music.musicservicecommand";
private static final String CMDNAME = "command";
@@ -178,7 +183,7 @@
}
}
- @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setOnSystemUiVisibilityChangeListener() {
if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
@@ -212,7 +217,7 @@
});
}
- @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void showSystemUi(boolean visible) {
if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
@@ -220,7 +225,7 @@
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (!visible) {
- flag |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
+ flag |= View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
mVideoView.setSystemUiVisibility(flag);
@@ -410,12 +415,12 @@
playVideo();
}
return true;
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KEYCODE_MEDIA_PAUSE:
if (mVideoView.isPlaying()) {
pauseVideo();
}
return true;
- case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KEYCODE_MEDIA_PLAY:
if (!mVideoView.isPlaying()) {
playVideo();
}
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 22b0163..3b6a770 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -321,10 +321,9 @@
}
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
private void setNfcBeamPushUris(Uri[] uris) {
- if (mNfcAdapter != null &&
- Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.JELLY_BEAN) {
+ if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
mNfcAdapter.setBeamPushUris(uris, (Activity)mActivity);
}
}
diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java
index 1ece66c..996d3f0 100644
--- a/src/com/android/gallery3d/app/Wallpaper.java
+++ b/src/com/android/gallery3d/app/Wallpaper.java
@@ -63,7 +63,7 @@
}
@SuppressWarnings("deprecation")
- @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR2)
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private Point getDefaultDisplaySize(Point size) {
Display d = getWindowManager().getDefaultDisplay();
if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
diff --git a/src/com/android/gallery3d/data/BitmapPool.java b/src/com/android/gallery3d/data/BitmapPool.java
index 0fbd84e..1151dde 100644
--- a/src/com/android/gallery3d/data/BitmapPool.java
+++ b/src/com/android/gallery3d/data/BitmapPool.java
@@ -17,14 +17,9 @@
package com.android.gallery3d.data;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapFactory.Options;
import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.ui.Log;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-import java.io.FileDescriptor;
import java.util.ArrayList;
public class BitmapPool {
@@ -94,67 +89,7 @@
mPool.clear();
}
- private Bitmap findCachedBitmap(JobContext jc,
- byte[] data, int offset, int length, Options options) {
- if (mOneSize) return getBitmap();
- DecodeUtils.decodeBounds(jc, data, offset, length, options);
- return getBitmap(options.outWidth, options.outHeight);
- }
-
- private Bitmap findCachedBitmap(JobContext jc,
- FileDescriptor fileDescriptor, Options options) {
- if (mOneSize) return getBitmap();
- DecodeUtils.decodeBounds(jc, fileDescriptor, options);
- return getBitmap(options.outWidth, options.outHeight);
- }
-
- public Bitmap decode(JobContext jc,
- byte[] data, int offset, int length, BitmapFactory.Options options) {
- if (options == null) options = new BitmapFactory.Options();
- if (options.inSampleSize < 1) options.inSampleSize = 1;
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- options.inBitmap = (options.inSampleSize == 1)
- ? findCachedBitmap(jc, data, offset, length, options) : null;
- try {
- Bitmap bitmap = DecodeUtils.decode(jc, data, offset, length, options);
- if (options.inBitmap != null && options.inBitmap != bitmap) {
- recycle(options.inBitmap);
- options.inBitmap = null;
- }
- return bitmap;
- } catch (IllegalArgumentException e) {
- if (options.inBitmap == null) throw e;
-
- Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
- recycle(options.inBitmap);
- options.inBitmap = null;
- return DecodeUtils.decode(jc, data, offset, length, options);
- }
- }
-
- // This is the same as the method above except the source data comes
- // from a file descriptor instead of a byte array.
- public Bitmap decode(JobContext jc,
- FileDescriptor fileDescriptor, Options options) {
- if (options == null) options = new BitmapFactory.Options();
- if (options.inSampleSize < 1) options.inSampleSize = 1;
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- options.inBitmap = (options.inSampleSize == 1)
- ? findCachedBitmap(jc, fileDescriptor, options) : null;
- try {
- Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
- if (options.inBitmap != null&& options.inBitmap != bitmap) {
- recycle(options.inBitmap);
- options.inBitmap = null;
- }
- return bitmap;
- } catch (IllegalArgumentException e) {
- if (options.inBitmap == null) throw e;
-
- Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
- recycle(options.inBitmap);
- options.inBitmap = null;
- return DecodeUtils.decode(jc, fileDescriptor, options);
- }
+ public boolean isOneSize() {
+ return mOneSize;
}
}
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index 8551327..9ffdc0b 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -23,6 +23,7 @@
import android.support.v4.content.LocalBroadcastManager;
import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaSet.ItemConsumer;
import com.android.gallery3d.data.MediaSource.PathId;
@@ -115,7 +116,9 @@
// the order matters, the UriSource must come last
addSource(new LocalSource(mApplication));
addSource(new PicasaSource(mApplication));
- addSource(new MtpSource(mApplication));
+ if (ApiHelper.HAS_MTP) {
+ addSource(new MtpSource(mApplication));
+ }
addSource(new ComboSource(mApplication));
addSource(new ClusterSource(mApplication));
addSource(new FilterSource(mApplication));
diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java
index e51dc3f..773af74 100644
--- a/src/com/android/gallery3d/data/DecodeUtils.java
+++ b/src/com/android/gallery3d/data/DecodeUtils.java
@@ -16,15 +16,18 @@
package com.android.gallery3d.data;
+import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.BitmapRegionDecoder;
+import android.os.Build;
import android.util.FloatMath;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.ui.Log;
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -33,7 +36,7 @@
import java.io.InputStream;
public class DecodeUtils {
- private static final String TAG = "DecodeService";
+ private static final String TAG = "DecodeUtils";
private static class DecodeCanceller implements CancelListener {
Options mOptions;
@@ -230,4 +233,78 @@
return null;
}
}
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static Bitmap decode(JobContext jc, byte[] data, int offset,
+ int length, BitmapFactory.Options options, BitmapPool pool) {
+ if (pool == null) {
+ return decode(jc, data, offset, length, options);
+ }
+
+ if (options == null) options = new BitmapFactory.Options();
+ if (options.inSampleSize < 1) options.inSampleSize = 1;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ options.inBitmap = (options.inSampleSize == 1)
+ ? findCachedBitmap(pool, jc, data, offset, length, options) : null;
+ try {
+ Bitmap bitmap = decode(jc, data, offset, length, options);
+ if (options.inBitmap != null && options.inBitmap != bitmap) {
+ pool.recycle(options.inBitmap);
+ options.inBitmap = null;
+ }
+ return bitmap;
+ } catch (IllegalArgumentException e) {
+ if (options.inBitmap == null) throw e;
+
+ Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+ pool.recycle(options.inBitmap);
+ options.inBitmap = null;
+ return decode(jc, data, offset, length, options);
+ }
+ }
+
+ // This is the same as the method above except the source data comes
+ // from a file descriptor instead of a byte array.
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static Bitmap decode(JobContext jc,
+ FileDescriptor fileDescriptor, Options options, BitmapPool pool) {
+ if (pool == null) {
+ return decode(jc, fileDescriptor, options);
+ }
+
+ if (options == null) options = new BitmapFactory.Options();
+ if (options.inSampleSize < 1) options.inSampleSize = 1;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ options.inBitmap = (options.inSampleSize == 1)
+ ? findCachedBitmap(pool, jc, fileDescriptor, options) : null;
+ try {
+ Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
+ if (options.inBitmap != null && options.inBitmap != bitmap) {
+ pool.recycle(options.inBitmap);
+ options.inBitmap = null;
+ }
+ return bitmap;
+ } catch (IllegalArgumentException e) {
+ if (options.inBitmap == null) throw e;
+
+ Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+ pool.recycle(options.inBitmap);
+ options.inBitmap = null;
+ return decode(jc, fileDescriptor, options);
+ }
+ }
+
+ private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
+ byte[] data, int offset, int length, Options options) {
+ if (pool.isOneSize()) return pool.getBitmap();
+ decodeBounds(jc, data, offset, length, options);
+ return pool.getBitmap(options.outWidth, options.outHeight);
+ }
+
+ private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
+ FileDescriptor fileDescriptor, Options options) {
+ if (pool.isOneSize()) return pool.getBitmap();
+ decodeBounds(jc, fileDescriptor, options);
+ return pool.getBitmap(options.outWidth, options.outHeight);
+ }
}
diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java
index 81660c9..b977b10 100644
--- a/src/com/android/gallery3d/data/ImageCacheRequest.java
+++ b/src/com/android/gallery3d/data/ImageCacheRequest.java
@@ -57,11 +57,13 @@
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap;
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
- bitmap = MediaItem.getMicroThumbPool().decode(jc,
- buffer.data, buffer.offset, buffer.length, options);
+ bitmap = DecodeUtils.decode(jc,
+ buffer.data, buffer.offset, buffer.length, options,
+ MediaItem.getMicroThumbPool());
} else {
- bitmap = MediaItem.getThumbPool().decode(jc,
- buffer.data, buffer.offset, buffer.length, options);
+ bitmap = DecodeUtils.decode(jc,
+ buffer.data, buffer.offset, buffer.length, options,
+ MediaItem.getThumbPool());
}
if (bitmap == null && !jc.isCancelled()) {
Log.w(TAG, "decode cached failed " + debugTag);
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index 316e324..c432ab4 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -25,6 +25,7 @@
import android.graphics.BitmapRegionDecoder;
import android.media.ExifInterface;
import android.net.Uri;
+import android.os.Build;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.MediaColumns;
@@ -85,7 +86,7 @@
updateWidthAndHeightProjection();
}
- @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static void updateWidthAndHeightProjection() {
if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
PROJECTION[INDEX_WIDTH] = MediaColumns.WIDTH;
diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java
index 9663dd0..c18365d 100644
--- a/src/com/android/gallery3d/data/MediaDetails.java
+++ b/src/com/android/gallery3d/data/MediaDetails.java
@@ -19,6 +19,7 @@
import android.media.ExifInterface;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.ExifTags;
import java.io.IOException;
import java.util.HashMap;
@@ -126,11 +127,11 @@
MediaDetails.INDEX_HEIGHT);
setExifData(details, exif, ExifInterface.TAG_MAKE, MediaDetails.INDEX_MAKE);
setExifData(details, exif, ExifInterface.TAG_MODEL, MediaDetails.INDEX_MODEL);
- setExifData(details, exif, ExifInterface.TAG_APERTURE, MediaDetails.INDEX_APERTURE);
- setExifData(details, exif, ExifInterface.TAG_ISO, MediaDetails.INDEX_ISO);
+ setExifData(details, exif, ExifTags.TAG_APERTURE, MediaDetails.INDEX_APERTURE);
+ setExifData(details, exif, ExifTags.TAG_ISO, MediaDetails.INDEX_ISO);
setExifData(details, exif, ExifInterface.TAG_WHITE_BALANCE,
MediaDetails.INDEX_WHITE_BALANCE);
- setExifData(details, exif, ExifInterface.TAG_EXPOSURE_TIME,
+ setExifData(details, exif, ExifTags.TAG_EXPOSURE_TIME,
MediaDetails.INDEX_EXPOSURE_TIME);
double data = exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, 0);
diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java
index f0f1af4..15bb176 100644
--- a/src/com/android/gallery3d/data/MediaItem.java
+++ b/src/com/android/gallery3d/data/MediaItem.java
@@ -19,6 +19,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapRegionDecoder;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.ui.ScreenNail;
import com.android.gallery3d.util.ThreadPool.Job;
@@ -42,9 +43,16 @@
private static final int BYTESBUFFE_POOL_SIZE = 4;
private static final int BYTESBUFFER_SIZE = 200 * 1024;
- private static final BitmapPool sMicroThumbPool = new BitmapPool(
- MICROTHUMBNAIL_TARGET_SIZE, MICROTHUMBNAIL_TARGET_SIZE, 16);
- private static final BitmapPool sThumbPool = new BitmapPool(4);
+ private static final BitmapPool sMicroThumbPool =
+ ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
+ ? new BitmapPool(MICROTHUMBNAIL_TARGET_SIZE, MICROTHUMBNAIL_TARGET_SIZE, 16)
+ : null;
+
+ private static final BitmapPool sThumbPool =
+ ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
+ ? new BitmapPool(4)
+ : null;
+
private static final BytesBufferPool sMicroThumbBufferPool =
new BytesBufferPool(BYTESBUFFE_POOL_SIZE, BYTESBUFFER_SIZE);
diff --git a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
index 9813902..58466bf 100644
--- a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
+++ b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.gadget;
+import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@@ -28,6 +29,7 @@
import android.widget.RemoteViews;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.gadget.WidgetDatabaseHelper.Entry;
import com.android.gallery3d.onetimeinitializer.GalleryWidgetMigrator;
@@ -50,8 +52,11 @@
@Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- // migrate gallery widgets from pre-JB releases to JB due to bucket ID change
- GalleryWidgetMigrator.migrateGalleryWidgets(context);
+
+ if (ApiHelper.HAS_REMOTE_VIEWS_SERVICE) {
+ // migrate gallery widgets from pre-JB releases to JB due to bucket ID change
+ GalleryWidgetMigrator.migrateGalleryWidgets(context);
+ }
WidgetDatabaseHelper helper = new WidgetDatabaseHelper(context);
try {
@@ -71,6 +76,7 @@
}
@SuppressWarnings("deprecation")
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
private static RemoteViews buildStackWidget(Context context, int widgetId, Entry entry) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.appwidget_main);
diff --git a/src/com/android/gallery3d/gadget/WidgetClickHandler.java b/src/com/android/gallery3d/gadget/WidgetClickHandler.java
index 35f9bec..37ee1a6 100644
--- a/src/com/android/gallery3d/gadget/WidgetClickHandler.java
+++ b/src/com/android/gallery3d/gadget/WidgetClickHandler.java
@@ -16,10 +16,12 @@
package com.android.gallery3d.gadget;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
@@ -27,6 +29,7 @@
import com.android.gallery3d.R;
import com.android.gallery3d.app.Gallery;
import com.android.gallery3d.app.PhotoPage;
+import com.android.gallery3d.common.ApiHelper;
public class WidgetClickHandler extends Activity {
private static final String TAG = "PhotoAppWidgetClickHandler";
@@ -45,20 +48,29 @@
}
@Override
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
+ // The behavior is changed in JB, refer to b/6384492 for more details
+ boolean tediousBack = Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.JELLY_BEAN;
Uri uri = getIntent().getData();
Intent intent;
if (isValidDataUri(uri)) {
intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.putExtra(PhotoPage.KEY_TREAT_BACK_AS_UP, true);
+ if (tediousBack) {
+ intent.putExtra(PhotoPage.KEY_TREAT_BACK_AS_UP, true);
+ }
} else {
Toast.makeText(this,
R.string.no_such_item, Toast.LENGTH_LONG).show();
intent = new Intent(this, Gallery.class);
}
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ if (tediousBack) {
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ }
startActivity(intent);
finish();
}
diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java
index 5717657..331e7d2 100644
--- a/src/com/android/gallery3d/gadget/WidgetConfigure.java
+++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java
@@ -29,12 +29,14 @@
import com.android.gallery3d.app.AlbumPicker;
import com.android.gallery3d.app.CropImage;
import com.android.gallery3d.app.DialogPicker;
+import com.android.gallery3d.common.ApiHelper;
public class WidgetConfigure extends Activity {
@SuppressWarnings("unused")
private static final String TAG = "WidgetConfigure";
public static final String KEY_WIDGET_TYPE = "widget-type";
+ private static final String KEY_PICKED_ITEM = "picked-item";
private static final int REQUEST_WIDGET_TYPE = 1;
private static final int REQUEST_CHOOSE_ALBUM = 2;
@@ -51,12 +53,11 @@
private static int MAX_WIDGET_SIDE = 360;
private int mAppWidgetId = -1;
- private int mWidgetType = 0;
private Uri mPickedItem;
@Override
- protected void onCreate(Bundle bundle) {
- super.onCreate(bundle);
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
mAppWidgetId = getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if (mAppWidgetId == -1) {
@@ -65,12 +66,24 @@
return;
}
- if (mWidgetType == 0) {
- Intent intent = new Intent(this, WidgetTypeChooser.class);
- startActivityForResult(intent, REQUEST_WIDGET_TYPE);
+ if (savedState == null) {
+ if (ApiHelper.HAS_REMOTE_VIEWS_SERVICE) {
+ Intent intent = new Intent(this, WidgetTypeChooser.class);
+ startActivityForResult(intent, REQUEST_WIDGET_TYPE);
+ } else { // Choose the photo type widget
+ setWidgetType(new Intent()
+ .putExtra(KEY_WIDGET_TYPE, R.id.widget_type_photo));
+ }
+ } else {
+ mPickedItem = savedState.getParcelable(KEY_PICKED_ITEM);
}
}
+ protected void onSaveInstanceStates(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_PICKED_ITEM, mPickedItem);
+ }
+
private void updateWidgetAndFinish(WidgetDatabaseHelper.Entry entry) {
AppWidgetManager manager = AppWidgetManager.getInstance(this);
RemoteViews views = PhotoAppWidgetProvider.buildWidget(this, mAppWidgetId, entry);
@@ -153,11 +166,11 @@
}
private void setWidgetType(Intent data) {
- mWidgetType = data.getIntExtra(KEY_WIDGET_TYPE, R.id.widget_type_shuffle);
- if (mWidgetType == R.id.widget_type_album) {
+ int widgetType = data.getIntExtra(KEY_WIDGET_TYPE, R.id.widget_type_shuffle);
+ if (widgetType == R.id.widget_type_album) {
Intent intent = new Intent(this, AlbumPicker.class);
startActivityForResult(intent, REQUEST_CHOOSE_ALBUM);
- } else if (mWidgetType == R.id.widget_type_shuffle) {
+ } else if (widgetType == R.id.widget_type_shuffle) {
WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this);
try {
helper.setWidget(mAppWidgetId, WidgetDatabaseHelper.TYPE_SHUFFLE, null);
diff --git a/src/com/android/gallery3d/gadget/WidgetService.java b/src/com/android/gallery3d/gadget/WidgetService.java
index eba7403..83e37a6 100644
--- a/src/com/android/gallery3d/gadget/WidgetService.java
+++ b/src/com/android/gallery3d/gadget/WidgetService.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.gadget;
+import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -25,11 +26,13 @@
import com.android.gallery3d.R;
import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
public class WidgetService extends RemoteViewsService {
@SuppressWarnings("unused")
diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java
index 2a0157a..190b2b8 100644
--- a/src/com/android/gallery3d/ui/ActionModeHandler.java
+++ b/src/com/android/gallery3d/ui/ActionModeHandler.java
@@ -227,10 +227,9 @@
return operation;
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
private void setNfcBeamPushUris(Uri[] uris) {
- if (mNfcAdapter != null &&
- Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.JELLY_BEAN) {
+ if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
mNfcAdapter.setBeamPushUris(uris, (Activity)mActivity);
}
}
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
index 6561a23..449c6f9 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -23,6 +23,7 @@
import com.android.gallery3d.app.AlbumSetDataLoader;
import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.BitmapPool;
import com.android.gallery3d.data.DataSourceType;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
@@ -423,7 +424,8 @@
@Override
protected void recycleBitmap(Bitmap bitmap) {
- MediaItem.getMicroThumbPool().recycle(bitmap);
+ BitmapPool pool = MediaItem.getMicroThumbPool();
+ if (pool != null) pool.recycle(bitmap);
}
@Override
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index deec171..d5d90a9 100644
--- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -22,6 +22,7 @@
import com.android.gallery3d.app.AlbumDataLoader;
import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.BitmapPool;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.util.Future;
@@ -277,7 +278,8 @@
@Override
protected void recycleBitmap(Bitmap bitmap) {
- MediaItem.getMicroThumbPool().recycle(bitmap);
+ BitmapPool pool = MediaItem.getMicroThumbPool();
+ if (pool != null) pool.recycle(bitmap);
}
@Override
diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java
index 5c91757..0043e0c 100644
--- a/src/com/android/gallery3d/ui/BitmapScreenNail.java
+++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java
@@ -20,6 +20,7 @@
import android.graphics.RectF;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.BitmapPool;
import com.android.gallery3d.data.MediaItem;
// This is a ScreenNail wraps a Bitmap. There are some extra functions:
@@ -70,6 +71,11 @@
mHeight = Math.round(scale * height);
}
+ private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) {
+ if (pool == null || bitmap == null) return;
+ pool.recycle(bitmap);
+ }
+
// Combines the two ScreenNails.
// Returns the used one and recycle the unused one.
public ScreenNail combine(ScreenNail other) {
@@ -88,9 +94,7 @@
mWidth = newer.mWidth;
mHeight = newer.mHeight;
if (newer.mBitmap != null) {
- if (mBitmap != null) {
- MediaItem.getThumbPool().recycle(mBitmap);
- }
+ recycleBitmap(MediaItem.getThumbPool(), mBitmap);
mBitmap = newer.mBitmap;
newer.mBitmap = null;
@@ -130,10 +134,8 @@
mTexture.recycle();
mTexture = null;
}
- if (mBitmap != null) {
- MediaItem.getThumbPool().recycle(mBitmap);
- mBitmap = null;
- }
+ recycleBitmap(MediaItem.getThumbPool(), mBitmap);
+ mBitmap = null;
}
@Override
diff --git a/src/com/android/gallery3d/ui/CustomMenu.java b/src/com/android/gallery3d/ui/CustomMenu.java
index dd8e6ab..15b3dc2 100644
--- a/src/com/android/gallery3d/ui/CustomMenu.java
+++ b/src/com/android/gallery3d/ui/CustomMenu.java
@@ -25,8 +25,6 @@
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
-import com.android.gallery3d.R;
-
import java.util.ArrayList;
public class CustomMenu implements OnMenuItemClickListener {
@@ -46,6 +44,7 @@
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mPopupMenu.setOnMenuItemClickListener(listener);
mButton.setOnClickListener(new OnClickListener() {
+ @Override
public void onClick(View v) {
mPopupMenu.show();
}
@@ -61,8 +60,6 @@
}
}
-
-
private Context mContext;
private ArrayList<DropDownMenu> mMenus;
private OnMenuItemClickListener mListener;
@@ -82,6 +79,7 @@
mListener = listener;
}
+ @Override
public boolean onMenuItemClick(MenuItem item) {
if (mListener != null) {
return mListener.onMenuItemClick(item);
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
index 478cb4f..9fd9473 100644
--- a/src/com/android/gallery3d/ui/GLRootView.java
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -21,6 +21,7 @@
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
+import android.os.Build;
import android.os.Process;
import android.os.SystemClock;
import android.util.AttributeSet;
@@ -84,9 +85,6 @@
private Matrix mCompensationMatrix = new Matrix();
private int mDisplayRotation;
- // The value which will become mCompensation in next layout.
- private int mPendingCompensation;
-
private int mFlags = FLAG_NEED_LAYOUT;
private volatile boolean mRenderRequested = false;
@@ -333,6 +331,7 @@
if (mFirstDraw) {
mFirstDraw = false;
post(new Runnable() {
+ @Override
public void run() {
View root = getRootView();
View cover = root.findViewById(R.id.gl_root_cover);
@@ -538,11 +537,13 @@
}
@Override
- @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void setLightsOutMode(boolean enabled) {
+ if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
+
int flags = 0;
if (enabled) {
- flags = SYSTEM_UI_FLAG_LOW_PROFILE;
+ flags = STATUS_BAR_HIDDEN;
if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index 6d33470..9b77d68 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -19,8 +19,8 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
-import android.widget.OverScroller;
+import com.android.gallery3d.common.OverScroller;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.RangeArray;
diff --git a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
index 5f45f16..dd9b7ce 100644
--- a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
+++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
@@ -16,13 +16,20 @@
package com.android.gallery3d.ui;
+import android.annotation.TargetApi;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
+import com.android.gallery3d.common.ApiHelper;
+
public abstract class SurfaceTextureScreenNail implements ScreenNail,
SurfaceTexture.OnFrameAvailableListener {
private static final String TAG = "SurfaceTextureScreenNail";
+ // This constant is not available in API level before 15, but it was just an
+ // oversight.
+ private static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
+
protected ExtTexture mExtTexture;
private SurfaceTexture mSurfaceTexture;
private int mWidth, mHeight;
@@ -33,16 +40,30 @@
}
public void acquireSurfaceTexture() {
- mExtTexture = new ExtTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+ mExtTexture = new ExtTexture(GL_TEXTURE_EXTERNAL_OES);
mExtTexture.setSize(mWidth, mHeight);
mSurfaceTexture = new SurfaceTexture(mExtTexture.getId());
- mSurfaceTexture.setDefaultBufferSize(mWidth, mHeight);
+ setDefaultBufferSize(mSurfaceTexture, mWidth, mHeight);
mSurfaceTexture.setOnFrameAvailableListener(this);
synchronized (this) {
mHasTexture = true;
}
}
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ private static void setDefaultBufferSize(SurfaceTexture st, int width, int height) {
+ if (ApiHelper.HAS_SET_DEFALT_BUFFER_SIZE) {
+ st.setDefaultBufferSize(width, height);
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private static void releaseSurfaceTexture(SurfaceTexture st) {
+ if (ApiHelper.HAS_RELEASE_SURFACE_TEXTURE) {
+ st.release();
+ }
+ }
+
public SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
@@ -53,7 +74,7 @@
}
mExtTexture.recycle();
mExtTexture = null;
- mSurfaceTexture.release();
+ releaseSurfaceTexture(mSurfaceTexture);
mSurfaceTexture = null;
}
diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java
index 1291ee9..05198f4 100644
--- a/src/com/android/gallery3d/util/GalleryUtils.java
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.util;
+import android.annotation.TargetApi;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -35,6 +36,7 @@
import com.android.gallery3d.R;
import com.android.gallery3d.app.PackagesMonitor;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.util.ThreadPool.CancelListener;
@@ -288,6 +290,7 @@
return durationValue;
}
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
public static int determineTypeBits(Context context, Intent intent) {
int typeBits = 0;
String type = intent.resolveType(context);
@@ -304,8 +307,10 @@
typeBits = DataManager.INCLUDE_ALL;
}
- if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) {
- typeBits |= DataManager.INCLUDE_LOCAL_ONLY;
+ if (ApiHelper.HAS_INTENT_EXTRA_LOCAL_ONLY) {
+ if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) {
+ typeBits |= DataManager.INCLUDE_LOCAL_ONLY;
+ }
}
return typeBits;