diff options
| author | Ed Heyl <ed@google.com> | 2012-07-13 10:42:03 -0700 |
|---|---|---|
| committer | Ed Heyl <ed@google.com> | 2012-07-13 10:42:03 -0700 |
| commit | c8626d95cdea9b647231f349cec6b41b93dea99e (patch) | |
| tree | 707e3418d23bc1b2d747cb936ab31952e6528e3c | |
| parent | d79a78abc7a35adfe7437f1664de525349da2e02 (diff) | |
| parent | 840ae396bac4ff8ff34e88c8d2a1773cb112a4b6 (diff) | |
| download | android_packages_apps_Gallery2-c8626d95cdea9b647231f349cec6b41b93dea99e.tar.gz android_packages_apps_Gallery2-c8626d95cdea9b647231f349cec6b41b93dea99e.tar.bz2 android_packages_apps_Gallery2-c8626d95cdea9b647231f349cec6b41b93dea99e.zip | |
Merge remote-tracking branch 'goog/master' into gb-ub-photos-arches
31 files changed, 1874 insertions, 153 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java index 85b092576..620e1ae7a 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 class ApiHelper { 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 @@ public class ApiHelper { 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 000000000..9b11fe416 --- /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 000000000..1ab7953d2 --- /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 000000000..160f3ca43 --- /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 0987ac39f..6b0b106aa 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.Intent; 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 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity 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 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity return mStateManager; } + @Override public GLRoot getGLRoot() { return mGLRootView; } + @Override public OrientationManager getOrientationManager() { return mOrientationManager; } @@ -141,17 +171,27 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity 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 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity } 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 867cda3e5..c870855c9 100644 --- a/src/com/android/gallery3d/app/ActivityState.java +++ b/src/com/android/gallery3d/app/ActivityState.java @@ -27,7 +27,6 @@ import android.os.BatteryManager; 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 @@ abstract public class ActivityState { // 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 f2646978a..a587624ed 100644 --- a/src/com/android/gallery3d/app/CropImage.java +++ b/src/com/android/gallery3d/app/CropImage.java @@ -33,6 +33,7 @@ import android.graphics.Rect; 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 android.widget.Toast; 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 @@ public class CropImage extends AbstractGalleryActivity { } } - @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 @@ public class CropImage extends AbstractGalleryActivity { } // 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 @@ public class CropImage extends AbstractGalleryActivity { // 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 468d202d4..0d5770cd5 100644 --- a/src/com/android/gallery3d/app/Gallery.java +++ b/src/com/android/gallery3d/app/Gallery.java @@ -26,7 +26,6 @@ import android.database.Cursor; 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.MediaItem; 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 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi } @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 98d7a6434..f2dc9ad20 100644 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -29,6 +29,7 @@ import android.graphics.Color; 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 @@ public class MoviePlayer implements 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 @@ public class MoviePlayer implements } } - @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 @@ public class MoviePlayer implements }); } - @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 @@ public class MoviePlayer implements | 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 @@ public class MoviePlayer implements 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 22b01637e..3b6a7704b 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -321,10 +321,9 @@ public class PhotoPage extends ActivityState implements } } - @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 1ece66c49..996d3f080 100644 --- a/src/com/android/gallery3d/app/Wallpaper.java +++ b/src/com/android/gallery3d/app/Wallpaper.java @@ -63,7 +63,7 @@ public class Wallpaper extends Activity { } @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 0fbd84ef5..1151dded7 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 @@ public class BitmapPool { 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 85513279c..9ffdc0b50 100644 --- a/src/com/android/gallery3d/data/DataManager.java +++ b/src/com/android/gallery3d/data/DataManager.java @@ -23,6 +23,7 @@ import android.os.Handler; 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 @@ public class DataManager { // 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 e51dc3fa9..773af74bb 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.FileInputStream; 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 @@ public class DecodeUtils { 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 81660c915..b977b1016 100644 --- a/src/com/android/gallery3d/data/ImageCacheRequest.java +++ b/src/com/android/gallery3d/data/ImageCacheRequest.java @@ -57,11 +57,13 @@ abstract class ImageCacheRequest implements Job<Bitmap> { 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 316e32469..c432ab44d 100644 --- a/src/com/android/gallery3d/data/LocalImage.java +++ b/src/com/android/gallery3d/data/LocalImage.java @@ -25,6 +25,7 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.media.ExifInterface; import android.net.Uri; +import android.os.Build; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.ImageColumns; import android.provider.MediaStore.MediaColumns; @@ -85,7 +86,7 @@ public class LocalImage extends LocalMediaItem { 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 9663dd0e6..c18365df0 100644 --- a/src/com/android/gallery3d/data/MediaDetails.java +++ b/src/com/android/gallery3d/data/MediaDetails.java @@ -19,6 +19,7 @@ package com.android.gallery3d.data; 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 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { 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 f0f1af434..15bb17623 100644 --- a/src/com/android/gallery3d/data/MediaItem.java +++ b/src/com/android/gallery3d/data/MediaItem.java @@ -19,6 +19,7 @@ package com.android.gallery3d.data; 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 @@ public abstract class MediaItem extends MediaObject { 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 98139026e..58466bf01 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.util.Log; 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 @@ public class PhotoAppWidgetProvider extends AppWidgetProvider { @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 @@ public class PhotoAppWidgetProvider extends AppWidgetProvider { } @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 35f9bec36..37ee1a651 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 android.widget.Toast; 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 @@ public class WidgetClickHandler extends Activity { } @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 5717657c2..331e7d2c4 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.R; 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 @@ public class WidgetConfigure extends Activity { 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 @@ public class WidgetConfigure extends Activity { 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 @@ public class WidgetConfigure extends Activity { } 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 eba7403c9..83e37a630 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 android.widget.RemoteViewsService; 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 2a0157a4b..190b2b8c6 100644 --- a/src/com/android/gallery3d/ui/ActionModeHandler.java +++ b/src/com/android/gallery3d/ui/ActionModeHandler.java @@ -227,10 +227,9 @@ public class ActionModeHandler implements ActionMode.Callback { 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 6561a23f3..449c6f9d3 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.R; 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 @@ public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener { @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 deec1712d..d5d90a9ea 100644 --- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java +++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java @@ -22,6 +22,7 @@ import android.os.Message; 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 @@ public class AlbumSlidingWindow implements AlbumDataLoader.DataListener { @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 5c917570d..0043e0cbe 100644 --- a/src/com/android/gallery3d/ui/BitmapScreenNail.java +++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java @@ -20,6 +20,7 @@ import android.graphics.Bitmap; 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 @@ public class BitmapScreenNail implements ScreenNail { 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 @@ public class BitmapScreenNail implements ScreenNail { 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 @@ public class BitmapScreenNail implements ScreenNail { 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 dd8e6abc4..15b3dc2c2 100644 --- a/src/com/android/gallery3d/ui/CustomMenu.java +++ b/src/com/android/gallery3d/ui/CustomMenu.java @@ -25,8 +25,6 @@ import android.widget.Button; 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 @@ public class CustomMenu implements OnMenuItemClickListener { mPopupMenu.getMenuInflater().inflate(menuId, mMenu); mPopupMenu.setOnMenuItemClickListener(listener); mButton.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) { mPopupMenu.show(); } @@ -61,8 +60,6 @@ public class CustomMenu implements OnMenuItemClickListener { } } - - private Context mContext; private ArrayList<DropDownMenu> mMenus; private OnMenuItemClickListener mListener; @@ -82,6 +79,7 @@ public class CustomMenu implements OnMenuItemClickListener { 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 478cb4f87..9fd9473dc 100644 --- a/src/com/android/gallery3d/ui/GLRootView.java +++ b/src/com/android/gallery3d/ui/GLRootView.java @@ -21,6 +21,7 @@ import android.content.Context; 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 @@ public class GLRootView extends GLSurfaceView 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 @@ public class GLRootView extends GLSurfaceView 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 @@ public class GLRootView extends GLSurfaceView } @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 6d334709d..9b77d6814 100644 --- a/src/com/android/gallery3d/ui/PositionController.java +++ b/src/com/android/gallery3d/ui/PositionController.java @@ -19,8 +19,8 @@ package com.android.gallery3d.ui; 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 5f45f1683..dd9b7ce1f 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 abstract class SurfaceTextureScreenNail implements ScreenNail, } 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 @@ public abstract class SurfaceTextureScreenNail implements ScreenNail, } 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 1291ee9f7..05198f41b 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 android.view.WindowManager; 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 @@ public class GalleryUtils { 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 @@ public class GalleryUtils { 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; |
