PhotoView.java revision ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3
1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/*
2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2010 The Android Open Source Project
3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License");
5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License.
6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at
7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *      http://www.apache.org/licenses/LICENSE-2.0
9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software
11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS,
12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and
14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License.
15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */
16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.ui;
18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.Context;
20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.graphics.Color;
21cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Changimport android.graphics.Point;
2204ac045bf8da5082bbb0bdc9ea5f9c9b5b796ad0Yuli Huangimport android.graphics.Rect;
23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.os.Message;
24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.MotionEvent;
25cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Changimport android.view.animation.AccelerateInterpolator;
26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
272b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.R;
282b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.app.GalleryActivity;
29cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Changimport com.android.gallery3d.common.Utils;
30b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.data.MediaObject;
31b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.util.RangeArray;
32b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
33b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport java.util.Arrays;
342b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Lin
35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class PhotoView extends GLView {
36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @SuppressWarnings("unused")
37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final String TAG = "PhotoView";
38c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    private static final int PLACEHOLDER_COLOR = 0xFF222222;
39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int INVALID_SIZE = -1;
41b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public static final long INVALID_DATA_VERSION =
42b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            MediaObject.INVALID_DATA_VERSION;
43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
44c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    public static class Size {
45c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public int width;
46c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public int height;
47c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    }
48c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
49c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    public interface Model extends TileImageView.Model {
50bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int getCurrentIndex();
51bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void moveTo(int index);
52c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
53c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Returns the size for the specified picture. If the size information is
54c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // not avaiable, width = height = 0.
55c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public void getImageSize(int offset, Size size);
56c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
57c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Returns the rotation for the specified picture.
58c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public int getImageRotation(int offset);
59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
60b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // This amends the getScreenNail() method of TileImageView.Model to get
61b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // ScreenNail at previous (negative offset) or next (positive offset)
62b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // positions. Returns null if the specified ScreenNail is unavailable.
63b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public ScreenNail getScreenNail(int offset);
64c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
65c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Set this to true if we need the model to provide full images.
66b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        public void setNeedFullImage(boolean enabled);
67bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
68bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // Returns true if the item is the Camera preview.
69bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public boolean isCamera(int offset);
70d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
71d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        // Returns true if the item is a Video.
72d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        public boolean isVideo(int offset);
73b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
74b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
75bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public interface Listener {
76b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void onSingleTapUp(int x, int y);
77bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void lockOrientation();
78bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void unlockOrientation();
79bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void onFullScreenChanged(boolean full);
8061f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang        public void onActionBarAllowed(boolean allowed);
81b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
83bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Here is a graph showing the places we need to lock/unlock device
84bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // orientation:
85bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
86bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+ A  +------------+
87bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Page mode |   Camera   |<---|   Photo    |
88bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           |  [locked]  |--->| [unlocked] |
89bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+  B +------------+
90bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //                ^                  ^
91bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //                | C                | D
92bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+    +------------+
93bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           |   Camera   |    |   Photo    |
94bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Film mode |    [*]     |    |    [*]     |
95bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+    +------------+
96bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
97bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // In Page mode, we want to lock in Camera because we don't want the system
98bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // rotation animation. We also want to unlock in Photo because we want to
99bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // show the system action bar in the right place.
100bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
101bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // We don't show action bar in Film mode, so it's fine for it to be locked
102bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // or unlocked in Film mode.
103bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
104bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // There are four transitions we need to check if we need to
105bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // lock/unlock. Marked as A to D above and in the code.
106bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
107b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MSG_SHOW_LOADING = 1;
108b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MSG_CANCEL_EXTRA_SCALING = 2;
109b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MSG_SWITCH_FOCUS = 3;
1102c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private static final int MSG_CAPTURE_ANIMATION_DONE = 4;
111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
112b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final long DELAY_SHOW_LOADING = 250; // 250ms;
113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_INIT = 0;
115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_TIMEOUT = 1;
116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_COMPLETE = 2;
117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_FAIL = 3;
118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
119b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MOVE_THRESHOLD = 256;
120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final float SWIPE_THRESHOLD = 300f;
121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final float DEFAULT_TEXT_SIZE = 20;
123cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private static float TRANSITION_SCALE_FACTOR = 0.74f;
124d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    private static final int ICON_RATIO = 6;
1252c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
1262c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // whether we want to apply card deck effect in page mode.
127b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final boolean CARD_EFFECT = true;
128cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
129cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Used to calculate the scaling factor for the fading animation.
130cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f);
131cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
132cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Used to calculate the alpha factor for the fading animation.
133cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private AccelerateInterpolator mAlphaInterpolator =
134cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            new AccelerateInterpolator(0.9f);
135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
136b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // We keep this many previous ScreenNails. (also this many next ScreenNails)
137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public static final int SCREEN_NAIL_MAX = 3;
138b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
139b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The picture entries, the valid index is from -SCREEN_NAIL_MAX to
140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // SCREEN_NAIL_MAX.
141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private final RangeArray<Picture> mPictures =
142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            new RangeArray<Picture>(-SCREEN_NAIL_MAX, SCREEN_NAIL_MAX);
143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
144b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private final long mDataVersion[] = new long[2 * SCREEN_NAIL_MAX + 1];
145b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private final int mFromIndex[] = new int[2 * SCREEN_NAIL_MAX + 1];
146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1476575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang    private final MyGestureListener mGestureListener;
1483a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang    private final GestureRecognizer mGestureRecognizer;
149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final PositionController mPositionController;
150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
151bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Listener mListener;
152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private Model mModel;
153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private StringTexture mLoadingText;
154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private StringTexture mNoThumbnailText;
155b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private TileImageView mTileView;
156532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    private EdgeView mEdgeView;
157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private Texture mVideoPlayIcon;
158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private ProgressSpinner mLoadingSpinner;
160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private SynchronizedHandler mHandler;
162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mLoadingState = LOADING_COMPLETE;
164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
165cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private Point mImageCenter = new Point();
166534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    private boolean mCancelExtraScalingPending;
167b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean mFilmMode = false;
168bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private int mDisplayRotation = 0;
169bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private int mCompensation = 0;
170bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private boolean mFullScreen = true;
171bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Rect mCameraNaturalFrame = new Rect();
172bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Rect mCameraRect = new Rect();
173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
174c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // [mPrevBound, mNextBound] is the range of index for all pictures in the
175c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // model, if we assume the index of current focused picture is 0.  So if
176c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // there are some previous pictures, mPrevBound < 0, and if there are some
177c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // next pictures, mNextBound > 0.
178c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    private int mPrevBound;
179c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    private int mNextBound;
180c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
1812c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // This variable prevents us doing snapback until its values goes to 0. This
1822c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // happens if the user gesture is still in progress or we are in a capture
1832c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // animation.
1842c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private int mHolding;
1852c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private static final int HOLD_TOUCH_DOWN = 1;
18618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang    private static final int HOLD_CAPTURE_ANIMATION = 2;
1872c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public PhotoView(GalleryActivity activity) {
189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mTileView = new TileImageView(activity);
190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        addComponent(mTileView);
191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Context context = activity.getAndroidContext();
192532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        mEdgeView = new EdgeView(context);
193532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        addComponent(mEdgeView);
194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLoadingSpinner = new ProgressSpinner(context);
195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLoadingText = StringTexture.newInstance(
196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                context.getString(R.string.loading),
197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                DEFAULT_TEXT_SIZE, Color.WHITE);
198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mNoThumbnailText = StringTexture.newInstance(
199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                context.getString(R.string.no_thumbnail),
200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                DEFAULT_TEXT_SIZE, Color.WHITE);
201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mHandler = new MyHandler(activity.getGLRoot());
203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2046575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        mGestureListener = new MyGestureListener();
2056575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        mGestureRecognizer = new GestureRecognizer(context, mGestureListener);
206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
207b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController = new PositionController(context,
208b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                new PositionController.Listener() {
209b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void invalidate() {
210b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        PhotoView.this.invalidate();
211b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
2122c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    public boolean isHolding() {
21318958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                        return mHolding != 0;
214b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
215b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void onPull(int offset, int direction) {
216b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mEdgeView.onPull(offset, direction);
217b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
218b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void onRelease() {
219b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mEdgeView.onRelease();
220b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
221b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void onAbsorb(int velocity, int direction) {
222b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mEdgeView.onAbsorb(velocity, direction);
223b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
224b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                });
225f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play);
226b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Arrays.fill(mDataVersion, INVALID_DATA_VERSION);
227b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
228b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (i == 0) {
229b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPictures.put(i, new FullPicture());
230cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            } else {
231b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPictures.put(i, new ScreenNailPicture(i));
232cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            }
233f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
234f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
235f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
236b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setModel(Model model) {
237b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mModel = model;
238b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mTileView.setModel(mModel);
239f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
241b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    class MyHandler extends SynchronizedHandler {
242b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public MyHandler(GLRoot root) {
243b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            super(root);
244f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
245f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
246b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
247b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void handleMessage(Message message) {
248b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            switch (message.what) {
249b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case MSG_SHOW_LOADING: {
250b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    if (mLoadingState == LOADING_INIT) {
251b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // We don't need the opening animation
252b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mPositionController.setOpenAnimationRect(null);
253b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
254b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mLoadingSpinner.startAnimation();
255b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mLoadingState = LOADING_TIMEOUT;
256b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        invalidate();
257b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
258b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
259f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                }
260b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case MSG_CANCEL_EXTRA_SCALING: {
261b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mGestureRecognizer.cancelScale();
262b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mPositionController.setExtraScalingRange(false);
263b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mCancelExtraScalingPending = false;
264b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
265b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
266b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case MSG_SWITCH_FOCUS: {
267b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    switchFocus();
268b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
269b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
2702c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                case MSG_CAPTURE_ANIMATION_DONE: {
271bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    // message.arg1 is the offset parameter passed to
272bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    // switchWithCaptureAnimation().
273bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    captureAnimationDone(message.arg1);
2742c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    break;
2752c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
276b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                default: throw new AssertionError(message.what);
277f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
278f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
279b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    };
280f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
281f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private void updateLoadingState() {
282f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // Possible transitions of mLoadingState:
283f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //        INIT --> TIMEOUT, COMPLETE, FAIL
284f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     TIMEOUT --> COMPLETE, FAIL, INIT
285f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //    COMPLETE --> INIT
286f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //        FAIL --> INIT
28715b351a22d02e89d882fc9fe32b3f4c512080e0aChih-Chung Chang        if (mModel.getLevelCount() != 0 || mModel.getScreenNail() != null) {
288f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.removeMessages(MSG_SHOW_LOADING);
289f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mLoadingState = LOADING_COMPLETE;
290f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (mModel.isFailedToLoad()) {
291f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.removeMessages(MSG_SHOW_LOADING);
292f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mLoadingState = LOADING_FAIL;
293a7b78e224b1808895ea2c3d42ae385526dea12aaYuli Huang            // We don't want the opening animation after loading failure
294b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPositionController.setOpenAnimationRect(null);
295f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (mLoadingState != LOADING_INIT) {
296f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mLoadingState = LOADING_INIT;
297f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.removeMessages(MSG_SHOW_LOADING);
298f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.sendEmptyMessageDelayed(
299f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    MSG_SHOW_LOADING, DELAY_SHOW_LOADING);
300f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
301f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
302f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
303b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
304b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Data/Image change notifications
305b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
306b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
307c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    public void notifyDataChange(long[] versions, int prevBound, int nextBound) {
308c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        mPrevBound = prevBound;
309c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        mNextBound = nextBound;
310c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
311b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Check if the data version actually changed.
312b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
313b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int N = 2 * SCREEN_NAIL_MAX + 1;
314b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < N; i++) {
315b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (versions[i] != mDataVersion[i]) {
316b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                changed = true;
317b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                break;
318b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
319f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
320b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!changed) return;
321f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
322b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Create the mFromIndex array, which records the index where the picture
323b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // come from. The value Integer.MAX_VALUE means it's a new picture.
324b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < N; i++) {
325b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            long v = versions[i];
326b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (v == INVALID_DATA_VERSION) {
327b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mFromIndex[i] = Integer.MAX_VALUE;
328b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                continue;
329b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
330f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
331b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Try to find the same version number in the old array
332b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j;
333b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            for (j = 0; j < N; j++) {
334b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (mDataVersion[j] == v) {
335b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
336b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
337f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
338b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromIndex[i] = (j < N) ? j - SCREEN_NAIL_MAX : Integer.MAX_VALUE;
339f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
340b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
341b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Copy the new data version
342b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < N; i++) {
343b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mDataVersion[i] = versions[i];
344b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
345b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
346b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Move the boxes
347bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0,
348bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mModel.isCamera(0));
349b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
350b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        // Update the ScreenNails.
351b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
352b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPictures.get(i).reload();
353b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
354b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
355b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        invalidate();
356f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
357f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
358b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void notifyImageChange(int index) {
359b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPictures.get(index).reload();
360b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        invalidate();
361f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
362f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
363bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    @Override
364bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    protected void onOrient(int displayRotation, int compensation) {
365bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // onLayout will be called soon. We need to change the size and rotation
366bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // of the Camera ScreenNail, but we don't want it start moving because
367bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // the view size will be changed soon.
368bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mDisplayRotation = displayRotation;
369bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mCompensation = compensation;
370bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
371bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            Picture p = mPictures.get(i);
372bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (p.isCamera()) {
373bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                p.updateSize(true);
374bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
375bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
376bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
377bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
378bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    @Override
379bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    protected void onLayout(
380bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            boolean changeSize, int left, int top, int right, int bottom) {
381bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mTileView.layout(left, top, right, bottom);
382bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mEdgeView.layout(left, top, right, bottom);
383bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        updateConstrainedFrame();
384bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (changeSize) {
385bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPositionController.setViewSize(getWidth(), getHeight());
386bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
387bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
388bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
389bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Update the constrained frame due to layout change.
390bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private void updateConstrainedFrame() {
391bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int w = getWidth();
392bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int h = getHeight();
393bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int rotation = getCameraRotation();
394bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (rotation % 180 != 0) {
395bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int tmp = w;
396bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            w = h;
397bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            h = tmp;
398bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
399bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
400bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int l = mCameraNaturalFrame.left;
401bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int t = mCameraNaturalFrame.top;
402bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int r = mCameraNaturalFrame.right;
403bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int b = mCameraNaturalFrame.bottom;
404bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
405bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        switch (rotation) {
406bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 0: mCameraRect.set(l, t, r, b); break;
407bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 90: mCameraRect.set(h - b, l, h - t, r); break;
408bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 180: mCameraRect.set(w - r, h - b, w - l, h - t); break;
409bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 270: mCameraRect.set(t, w - r, b, w - l); break;
410bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
411bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
412bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPositionController.setConstrainedFrame(mCameraRect);
413bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
414bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
415bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void setCameraNaturalFrame(Rect frame) {
416bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mCameraNaturalFrame.set(frame);
417bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
418bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
419bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Returns the rotation we need to do to the camera texture before drawing
420bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // it to the canvas, assuming the camera texture is correct when the device
421bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // is in its natural orientation.
422bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private int getCameraRotation() {
423bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return (mCompensation - mDisplayRotation + 360) % 360;
424bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
425bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
426b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
427b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Pictures
428b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
429b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
430b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private interface Picture {
431b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void reload();
432b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void draw(GLCanvas canvas, Rect r);
433b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        void setScreenNail(ScreenNail s);
4342c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        boolean isCamera();  // whether the picture is a camera preview
435bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        void updateSize(boolean force);  // called when mCompensation changes
436b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    };
437f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
438b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    class FullPicture implements Picture {
439b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private int mRotation;
4402c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        private boolean mIsCamera;
441d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private boolean mIsVideo;
442bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        private boolean mWasCameraCenter;
443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void FullPicture(TileImageView tileView) {
445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTileView = tileView;
446f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
447f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
448b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void reload() {
450b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // mImageWidth and mImageHeight will get updated
451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTileView.notifyModelInvalidated();
452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
453bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mIsCamera = mModel.isCamera(0);
454d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            mIsVideo = mModel.isVideo(0);
455bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            setScreenNail(mModel.getScreenNail(0));
456bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateSize(false);
457bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateLoadingState();
458bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
459bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
460bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        @Override
461bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void updateSize(boolean force) {
462bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mIsCamera) {
463bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = getCameraRotation();
464bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
465bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = mModel.getImageRotation(0);
466bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
467bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
468c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            int w = mTileView.mImageWidth;
469c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            int h = mTileView.mImageHeight;
470c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            mPositionController.setImageSize(0,
471c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                    getRotated(mRotation, w, h),
472bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    getRotated(mRotation, h, w),
473bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    force);
474f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
475f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
476b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
477b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void draw(GLCanvas canvas, Rect r) {
478bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            boolean isCenter = mPositionController.isCenter();
479bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
480b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mLoadingState == LOADING_COMPLETE) {
481d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                drawTileView(canvas, r);
482b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
483b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            renderMessage(canvas, r.centerX(), r.centerY());
484cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
485aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            if (mIsCamera) {
486aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                boolean full = !mFilmMode && isCenter
487aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                        && mPositionController.isAtMinimalScale();
488aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                if (full != mFullScreen) {
489aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mFullScreen = full;
490aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mListener.onFullScreenChanged(full);
491aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                }
492aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            }
493aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang
494bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // We want to have the following transitions:
4952c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // (1) Move camera preview out of its place: switch to film mode
4962c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // (2) Move camera preview into its place: switch to page mode
4972c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // The extra mWasCenter check makes sure (1) does not apply if in
4982c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // page mode, we move _to_ the camera preview from another picture.
499bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
500bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // Holdings except touch-down prevent the transitions.
50118958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return;
502bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
503bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            boolean isCameraCenter = mIsCamera && isCenter;
504bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
505bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mWasCameraCenter && mIsCamera && !isCenter && !mFilmMode) {
50618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                // Temporary disabled to de-emphasize filmstrip.
50718958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                // setFilmMode(true);
50818958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            } else if (!mWasCameraCenter && isCameraCenter && mFilmMode) {
509bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                setFilmMode(false);
510aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            }
511aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang
512aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            if (isCenter && !mFilmMode) {
513aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                if (mIsCamera) {
514aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    // move into camera, lock
515aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mListener.lockOrientation();  // Transition A
516aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                } else {
517aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    // move out of camera, unlock
518aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mListener.unlockOrientation();  // Transition B
519aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                }
520bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
521bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
522bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mWasCameraCenter = isCameraCenter;
523cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
524cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
525b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
526b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        public void setScreenNail(ScreenNail s) {
527b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang            mTileView.setScreenNail(s);
528cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
529f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
530b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
5312c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        public boolean isCamera() {
5322c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return mIsCamera;
533f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
534f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
535d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private void drawTileView(GLCanvas canvas, Rect r) {
536ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float imageScale = mPositionController.getImageScale();
537b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int viewW = getWidth();
538b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int viewH = getHeight();
539d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            float cx = r.exactCenterX();
540d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            float cy = r.exactCenterY();
541ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float scale = 1f;  // the scaling factor due to card effect
542b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
543d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
544ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float filmRatio = mPositionController.getFilmRatio();
545ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            boolean wantsCardEffect = CARD_EFFECT && !mIsCamera
546ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    && filmRatio != 1f && !mPictures.get(-1).isCamera();
5472c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (wantsCardEffect) {
548b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // Calculate the move-out progress value.
549b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int left = r.left;
550b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int right = r.right;
551b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float progress = calculateMoveOutProgress(left, right, viewW);
552b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = Utils.clamp(progress, -1f, 1f);
553b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
554b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // We only want to apply the fading animation if the scrolling
555b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // movement is to the right.
556bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (progress < 0) {
557ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    scale = getScrollScale(progress);
558ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    float alpha = getScrollAlpha(progress);
559ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    scale = interpolate(filmRatio, scale, 1f);
560ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    alpha = interpolate(filmRatio, alpha, 1f);
561d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
562ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    imageScale *= scale;
563ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    canvas.multiplyAlpha(alpha);
564ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
565ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    float cxPage;  // the cx value in page mode
566d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                    if (right - left <= viewW) {
567b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // If the picture is narrower than the view, keep it at
568b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // the center of the view.
569ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                        cxPage = viewW / 2f;
570b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    } else {
571b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // If the picture is wider than the view (it's
572b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // zoomed-in), keep the left edge of the object align
573b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // the the left edge of the view.
574ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                        cxPage = (right - left) * scale / 2f;
575b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
576ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    cx = interpolate(filmRatio, cxPage, cx);
577b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
578b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
579f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
580d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            // Draw the tile view.
581ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            setTileViewPosition(cx, cy, viewW, viewH, imageScale);
582d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            PhotoView.super.render(canvas);
583d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
584d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            // Draw the play video icon.
585d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            if (mIsVideo) {
586d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
587ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
588d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                drawVideoPlayIcon(canvas, s);
589d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            }
590d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
591d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            canvas.restore();
592d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        }
593d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
594d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        // Set the position of the tile view
595d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private void setTileViewPosition(float cx, float cy,
596d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                int viewW, int viewH, float scale) {
597d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            // Find out the bitmap coordinates of the center of the view
598d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int imageW = mPositionController.getImageWidth();
599d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int imageH = mPositionController.getImageHeight();
600d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f);
601d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f);
602d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
603b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int inverseX = imageW - centerX;
604b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int inverseY = imageH - centerY;
605d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int x, y;
606d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            switch (mRotation) {
607d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 0: x = centerX; y = centerY; break;
608d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 90: x = centerY; y = inverseX; break;
609d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 180: x = inverseX; y = inverseY; break;
610d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 270: x = inverseY; y = centerX; break;
611b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                default:
612d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                    throw new RuntimeException(String.valueOf(mRotation));
613b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
614d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            mTileView.setPosition(x, y, scale, mRotation);
615f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
616f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
617b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private void renderMessage(GLCanvas canvas, int x, int y) {
618b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Draw the progress spinner and the text below it
619b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            //
620b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // (x, y) is where we put the center of the spinner.
621b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // s is the size of the video play icon, and we use s to layout text
622b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // because we want to keep the text at the same place when the video
623b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // play icon is shown instead of the spinner.
624b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int w = getWidth();
625b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int h = getHeight();
626d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int s = Math.min(w, h) / ICON_RATIO;
627b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
628b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mLoadingState == LOADING_TIMEOUT) {
629b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                StringTexture m = mLoadingText;
630b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                ProgressSpinner p = mLoadingSpinner;
631b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                p.draw(canvas, x - p.getWidth() / 2, y - p.getHeight() / 2);
632b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
633b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                invalidate(); // we need to keep the spinner rotating
634b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (mLoadingState == LOADING_FAIL) {
635b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                StringTexture m = mNoThumbnailText;
636b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
637b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
638f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
639b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Draw a debug indicator showing which picture has focus (index ==
640b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // 0).
641b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // canvas.fillRect(x - 10, y - 10, 20, 20, 0x80FF00FF);
642f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
643f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
644f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
645b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class ScreenNailPicture implements Picture {
646b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private int mIndex;
647b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private int mRotation;
648b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private ScreenNail mScreenNail;
649c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        private Size mSize = new Size();
6502c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        private boolean mIsCamera;
651d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private boolean mIsVideo;
652f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
653b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public ScreenNailPicture(int index) {
654b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mIndex = index;
655b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
656532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
657b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
658b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void reload() {
659bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mIsCamera = mModel.isCamera(mIndex);
660d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            mIsVideo = mModel.isVideo(mIndex);
661c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            setScreenNail(mModel.getScreenNail(mIndex));
662b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
663b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
664b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
665b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void draw(GLCanvas canvas, Rect r) {
666b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mScreenNail == null) {
667c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // Draw a placeholder rectange if there will be a picture in
668c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // this position.
669c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                if (mIndex >= mPrevBound && mIndex <= mNextBound) {
670c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                    canvas.fillRect(r.left, r.top, r.width(), r.height(),
671c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                            PLACEHOLDER_COLOR);
672c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                }
673b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return;
674f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
675b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (r.left >= getWidth() || r.right <= 0 ||
676b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    r.top >= getHeight() || r.bottom <= 0) {
677b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mScreenNail.noDraw();
678b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return;
679f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
680f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
681bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mIsCamera && mFullScreen != false) {
682bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mFullScreen = false;
683bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mListener.onFullScreenChanged(false);
684bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
685bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
686ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float filmRatio = mPositionController.getFilmRatio();
687ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            boolean wantsCardEffect = CARD_EFFECT && mIndex > 0
688ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    && filmRatio != 1f && !mPictures.get(0).isCamera();
689b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int w = getWidth();
690ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            int cx = wantsCardEffect
691ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    ? (int) (interpolate(filmRatio, w / 2, r.centerX()) + 0.5f)
692ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    : r.centerX();
693b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int cy = r.centerY();
694ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
695b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            canvas.translate(cx, cy);
6962c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (wantsCardEffect) {
697b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float progress = (float) (w / 2 - r.centerX()) / w;
698b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = Utils.clamp(progress, -1, 1);
699b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float alpha = getScrollAlpha(progress);
700b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scale = getScrollScale(progress);
701ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                alpha = interpolate(filmRatio, alpha, 1f);
702ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                scale = interpolate(filmRatio, scale, 1f);
703b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                canvas.multiplyAlpha(alpha);
704b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                canvas.scale(scale, scale, 1);
705b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
706b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mRotation != 0) {
707b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                canvas.rotate(mRotation, 0, 0, 1);
708b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
709ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            int drawW = getRotated(mRotation, r.width(), r.height());
710ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            int drawH = getRotated(mRotation, r.height(), r.width());
711b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mScreenNail.draw(canvas, -drawW / 2, -drawH / 2, drawW, drawH);
712d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            if (mIsVideo) drawVideoPlayIcon(canvas, Math.min(drawW, drawH));
713b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            canvas.restore();
714b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
715f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
716b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
717b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        public void setScreenNail(ScreenNail s) {
718b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mScreenNail == s) return;
719b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mScreenNail = s;
720bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateSize(false);
721bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
722bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
723bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        @Override
724bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void updateSize(boolean force) {
725bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mIsCamera) {
726bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = getCameraRotation();
727bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
728bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = mModel.getImageRotation(mIndex);
729bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
730c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
731c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            int w = 0, h = 0;
732b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mScreenNail != null) {
733bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                w = mScreenNail.getWidth();
734bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                h = mScreenNail.getHeight();
735c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            } else if (mModel != null) {
736c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // If we don't have ScreenNail available, we can still try to
737c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // get the size information of it.
738c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                mModel.getImageSize(mIndex, mSize);
739c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                w = mSize.width;
740c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                h = mSize.height;
741b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
742c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
743c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            if (w != 0 && h != 0)  {
744b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPositionController.setImageSize(mIndex,
745b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        getRotated(mRotation, w, h),
746bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                        getRotated(mRotation, h, w),
747bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                        force);
748b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
749f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
750b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
751b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
7522c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        public boolean isCamera() {
7532c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return mIsCamera;
754b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
755b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
756b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
757d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    // Draw the video play icon (in the place where the spinner was)
758d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    private void drawVideoPlayIcon(GLCanvas canvas, int side) {
759d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        int s = side / ICON_RATIO;
760d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        // Draw the video play icon at the center
761d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        mVideoPlayIcon.draw(canvas, -s / 2, -s / 2, s, s);
762d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    }
763d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
764b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static int getRotated(int degree, int original, int theother) {
765b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (degree % 180 == 0) ? original : theother;
766b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
767b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
768b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
769b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Gestures Handling
770b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
771b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
772b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    @Override
773b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    protected boolean onTouch(MotionEvent event) {
774b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mGestureRecognizer.onTouchEvent(event);
775b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
776f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
777f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
7783a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang    private class MyGestureListener implements GestureRecognizer.Listener {
7793a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        private boolean mIgnoreUpEvent = false;
780099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang        // If we can change mode for this scale gesture.
781099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang        private boolean mCanChangeMode;
78218958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang        // If we have changed the film mode in this scaling gesture.
783b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean mModeChanged;
78433f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        // If this scaling gesture should be ignored.
78533f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        private boolean mIgnoreScalingGesture;
78617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        // whether the down action happened while the view is scrolling.
78717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        private boolean mDownInScrolling;
7886575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        // If we should ignore all gestures other than onSingleTapUp.
7896575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        private boolean mIgnoreSwipingGesture;
790b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
7913a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        @Override
7923a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onSingleTapUp(float x, float y) {
793ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            // We do this in addition to onUp() because we want the snapback of
794ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            // setFilmMode to happen.
795ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mHolding &= ~HOLD_TOUCH_DOWN;
796ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
79717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (mFilmMode && !mDownInScrolling) {
79817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                switchToHitPicture((int) (x + 0.5f), (int) (y + 0.5f));
799b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                setFilmMode(false);
80017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mIgnoreUpEvent = true;
801b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
802b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
803b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
804bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mListener != null) {
805bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mListener.onSingleTapUp((int) x, (int) y);
8063a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            }
8073a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            return true;
8083a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        }
8093a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang
8103a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        @Override
8113a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onDoubleTap(float x, float y) {
8126575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
81361f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            if (mPictures.get(0).isCamera()) return false;
8143a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            PositionController controller = mPositionController;
815b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale = controller.getImageScale();
8163a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            // onDoubleTap happened on the second ACTION_DOWN.
8173a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            // We need to ignore the next UP event.
8183a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            mIgnoreUpEvent = true;
8193a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            if (scale <= 1.0f || controller.isAtMinimalScale()) {
8203a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                controller.zoomIn(x, y, Math.max(1.5f, scale * 1.5f));
8213a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            } else {
8223a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                controller.resetToFullView();
8233a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            }
8243a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            return true;
8253a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        }
826f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
827f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8283a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onScroll(float dx, float dy) {
8296575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
830b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPositionController.startScroll(-dx, -dy);
831f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
832f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
833f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
834f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8353a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onFling(float velocityX, float velocityY) {
8366575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
8372ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            if (swipeImages(velocityX, velocityY)) {
838b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mIgnoreUpEvent = true;
839b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            } else if (mPositionController.fling(velocityX, velocityY)) {
840b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mIgnoreUpEvent = true;
841f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
842f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
843f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
844f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
845f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8463a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onScaleBegin(float focusX, float focusY) {
8476575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
84833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // We ignore the scaling gesture if it is a camera preview.
84933f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            mIgnoreScalingGesture = mPictures.get(0).isCamera();
85033f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            if (mIgnoreScalingGesture) {
85133f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang                return true;
85233f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            }
8533a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            mPositionController.beginScale(focusX, focusY);
854099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang            // We can change mode if we are in film mode, or we are in page
855099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang            // mode and at minimal scale.
856099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang            mCanChangeMode = mFilmMode
857099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang                    || mPositionController.isAtMinimalScale();
858b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mModeChanged = false;
859f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
860f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
861f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
862f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8633a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onScale(float focusX, float focusY, float scale) {
8646575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
8656575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreScalingGesture) return true;
86618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (mModeChanged) return true;
867b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (Float.isNaN(scale) || Float.isInfinite(scale)) return false;
86833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
86933f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // We wait for the scale change accumulated to a large enough change
87033f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // before reacting to it. Otherwise we may mistakenly treat a
87133f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // zoom-in gesture as zoom-out or vice versa.
87218958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (scale > 0.99f && scale < 1.01f) return false;
87333f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
874b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int outOfRange = mPositionController.scaleBy(scale, focusX, focusY);
875b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
87618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            // If mode changes, we treat this scaling gesture has ended.
87718958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (mCanChangeMode) {
878b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if ((outOfRange < 0 && !mFilmMode) ||
879b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        (outOfRange > 0 && mFilmMode)) {
88018958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    stopExtraScalingIfNeeded();
88118958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang
88218958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    // Removing the touch down flag allows snapback to happen
88333f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang                    // for film mode change.
88418958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    mHolding &= ~HOLD_TOUCH_DOWN;
885b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    setFilmMode(!mFilmMode);
88618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang
88718958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    // We need to call onScaleEnd() before setting mModeChanged
88818958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    // to true.
88918958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    onScaleEnd();
890b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mModeChanged = true;
891b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    return true;
892534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang                }
893b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang           }
894b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
89518958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (outOfRange != 0) {
896b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                startExtraScalingIfNeeded();
897534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang            } else {
898b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                stopExtraScalingIfNeeded();
899534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang            }
900f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
901f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
902f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
90333f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        @Override
90433f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        public void onScaleEnd() {
9056575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return;
9066575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreScalingGesture) return;
90733f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            if (mModeChanged) return;
90833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            mPositionController.endScale();
90933f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        }
91033f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
911b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private void startExtraScalingIfNeeded() {
912b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (!mCancelExtraScalingPending) {
913b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mHandler.sendEmptyMessageDelayed(
914b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        MSG_CANCEL_EXTRA_SCALING, 700);
915b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPositionController.setExtraScalingRange(true);
916b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCancelExtraScalingPending = true;
917b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
918b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
919b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
920b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private void stopExtraScalingIfNeeded() {
921b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mCancelExtraScalingPending) {
922b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
923b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPositionController.setExtraScalingRange(false);
924b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCancelExtraScalingPending = false;
925b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
926b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
927b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
928f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
9293a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public void onDown() {
9306575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return;
9316575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
9322c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mHolding |= HOLD_TOUCH_DOWN;
93317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
93417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (mFilmMode && mPositionController.isScrolling()) {
93517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mDownInScrolling = true;
93617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mPositionController.stopScrolling();
93717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            } else {
93817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mDownInScrolling = false;
93917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            }
9403a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        }
9413a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang
9423a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        @Override
9433a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public void onUp() {
9446575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return;
9456575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
94618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            mHolding &= ~HOLD_TOUCH_DOWN;
9473a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            mEdgeView.onRelease();
9483a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang
9493a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            if (mIgnoreUpEvent) {
9503a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                mIgnoreUpEvent = false;
9513a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                return;
9523a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            }
953b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
9542c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            snapback();
955f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
9566575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
9576575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        public void setSwipingEnabled(boolean enabled) {
9586575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            mIgnoreSwipingGesture = !enabled;
9596575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        }
9606575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang    }
9616575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
9626575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang    public void setSwipingEnabled(boolean enabled) {
9636575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        mGestureListener.setSwipingEnabled(enabled);
964f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
965f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
966b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void setFilmMode(boolean enabled) {
967b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode == enabled) return;
968b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmMode = enabled;
969b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.setFilmMode(mFilmMode);
970b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        mModel.setNeedFullImage(!enabled);
97161f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang        mListener.onActionBarAllowed(!enabled);
972bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
973bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // If we leave filmstrip mode, we should lock/unlock
974bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (!enabled) {
975bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mPictures.get(0).isCamera()) {
976bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mListener.lockOrientation();  // Transition C
977bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
978bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mListener.unlockOrientation();  // Transition D
979bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
980bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
981bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
982bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
983bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public boolean getFilmMode() {
984bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return mFilmMode;
985f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
986f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
987b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
988b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Framework events
989b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
990b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
991b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void pause() {
992b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.skipAnimation();
993b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mTileView.freeTextures();
994b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
995b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang            mPictures.get(i).setScreenNail(null);
996b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
997f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
998f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
999b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void resume() {
1000b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mTileView.prepareTextures();
1001f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1002f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
100333f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang    // move to the camera preview and show controls after resume
100433f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang    public void resetToFirstPicture() {
100533f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        mModel.moveTo(0);
100633f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        setFilmMode(false);
100733f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang    }
100833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
1009b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1010b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Rendering
1011b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1012b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1013b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    @Override
1014b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    protected void render(GLCanvas canvas) {
1015ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        float filmRatio = mPositionController.getFilmRatio();
1016ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1017ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // Draw next photos. In page mode, we draw only one next photo.
1018ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        int lastPhoto = (filmRatio == 0f) ? 1 : SCREEN_NAIL_MAX;
1019ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        for (int i = lastPhoto; i > 0; i--) {
1020b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect r = mPositionController.getPosition(i);
1021b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPictures.get(i).draw(canvas, r);
1022b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1023b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1024b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Draw current photo
1025b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPictures.get(0).draw(canvas, mPositionController.getPosition(0));
1026b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1027ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // Draw previous photos. In page mode, we draw only one previous photo.
1028ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        lastPhoto = (filmRatio == 0f) ? -1: -SCREEN_NAIL_MAX;
1029ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        for (int i = -1; i >= lastPhoto; i--) {
1030b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect r = mPositionController.getPosition(i);
1031b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPictures.get(i).draw(canvas, r);
1032b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1033b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1034b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.advanceAnimation();
1035b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        checkFocusSwitching();
1036ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1037ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1038b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1039b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Film mode focus switching
1040b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1041f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1042b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Runs in GL thread.
1043b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void checkFocusSwitching() {
1044b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!mFilmMode) return;
1045b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mHandler.hasMessages(MSG_SWITCH_FOCUS)) return;
1046b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (switchPosition() != 0) {
1047b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mHandler.sendEmptyMessage(MSG_SWITCH_FOCUS);
1048f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1049f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1050f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1051b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Runs in main thread.
1052b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void switchFocus() {
10532c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mHolding != 0) return;
1054b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        switch (switchPosition()) {
1055b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            case -1:
1056b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                switchToPrevImage();
1057b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                break;
1058b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            case 1:
1059b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                switchToNextImage();
1060b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                break;
1061b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1062f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1063f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1064b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns -1 if we should switch focus to the previous picture, +1 if we
1065b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // should switch to the next, 0 otherwise.
1066b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int switchPosition() {
1067b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect curr = mPositionController.getPosition(0);
1068b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int center = getWidth() / 2;
1069f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
10702c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (curr.left > center && mPrevBound < 0) {
1071b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect prev = mPositionController.getPosition(-1);
1072b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int currDist = curr.left - center;
1073b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int prevDist = center - prev.right;
1074b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (prevDist < currDist) {
1075b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return -1;
1076b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
10772c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else if (curr.right < center && mNextBound > 0) {
1078b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect next = mPositionController.getPosition(1);
1079b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int currDist = center - curr.right;
1080b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int nextDist = next.left - center;
1081b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (nextDist < currDist) {
1082b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return 1;
1083b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1084b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1085f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1086b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return 0;
1087f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1088f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
108917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    // Switch to the previous or next picture if the hit position is inside
109017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    // one of their boxes. This runs in main thread.
109117ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    private void switchToHitPicture(int x, int y) {
109217ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        if (mPrevBound < 0) {
109317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            Rect r = mPositionController.getPosition(-1);
109417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (r.right >= x) {
109517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                slideToPrevPicture();
109617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                return;
109717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            }
109817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        }
109917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
110017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        if (mNextBound > 0) {
110117ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            Rect r = mPositionController.getPosition(1);
110217ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (r.left <= x) {
110317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                slideToNextPicture();
110417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                return;
110517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            }
110617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        }
110717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    }
110817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
1109b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1110b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Page mode focus switching
1111b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
1112b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  We slide image to the next one or the previous one in two cases: 1: If
1113b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  the user did a fling gesture with enough velocity.  2 If the user has
1114b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  moved the picture a lot.
1115b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1117b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean swipeImages(float velocityX, float velocityY) {
1118b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) return false;
1119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1120b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Avoid swiping images if we're possibly flinging to view the
1121b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // zoomed in picture vertically.
1122b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        PositionController controller = mPositionController;
1123b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean isMinimal = controller.isAtMinimalScale();
1124b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int edges = controller.getImageAtEdges();
1125b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!isMinimal && Math.abs(velocityY) > Math.abs(velocityX))
1126b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if ((edges & PositionController.IMAGE_AT_TOP_EDGE) == 0
1127b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    || (edges & PositionController.IMAGE_AT_BOTTOM_EDGE) == 0)
1128b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1130b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If we are at the edge of the current photo and the sweeping velocity
1131b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // exceeds the threshold, slide to the next / previous image.
1132b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (velocityX < -SWIPE_THRESHOLD && (isMinimal
1133b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                || (edges & PositionController.IMAGE_AT_RIGHT_EDGE) != 0)) {
1134b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToNextPicture();
1135b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else if (velocityX > SWIPE_THRESHOLD && (isMinimal
1136b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                || (edges & PositionController.IMAGE_AT_LEFT_EDGE) != 0)) {
1137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToPrevPicture();
1138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return false;
1141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1142b29a27f475a2c449abdda8d4e03d30914feed8c6Chih-Chung Chang
11432c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private void snapback() {
11442c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mHolding != 0) return;
11452c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (!snapToNeighborImage()) {
11462c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mPositionController.snapback();
11472c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        }
11482c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
11492c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
1150b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean snapToNeighborImage() {
1151b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) return false;
1152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1153b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mPositionController.getPosition(0);
1154b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int viewW = getWidth();
1155b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int threshold = MOVE_THRESHOLD + gapToSide(r.width(), viewW);
1156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1157b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If we have moved the picture a lot, switching.
1158b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewW - r.right > threshold) {
1159b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToNextPicture();
1160b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else if (r.left > threshold) {
1161b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToPrevPicture();
1162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1164b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return false;
1165b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1167b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean slideToNextPicture() {
11682c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mNextBound <= 0) return false;
1169b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        switchToNextImage();
11702c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mPositionController.startHorizontalSlide();
1171b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
1172b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1173676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
1174b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean slideToPrevPicture() {
11752c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mPrevBound >= 0) return false;
1176b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        switchToPrevImage();
11772c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mPositionController.startHorizontalSlide();
1178b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
1179b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1180676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
1181b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static int gapToSide(int imageWidth, int viewWidth) {
1182b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return Math.max(0, (viewWidth - imageWidth) / 2);
1183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1185b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1186b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Focus switching
1187b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1189b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void switchToNextImage() {
1190bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mModel.moveTo(mModel.getCurrentIndex() + 1);
1191b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
119215b351a22d02e89d882fc9fe32b3f4c512080e0aChih-Chung Chang
1193b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void switchToPrevImage() {
1194bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mModel.moveTo(mModel.getCurrentIndex() - 1);
1195b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1196cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1197160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang    private void switchToFirstImage() {
1198bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mModel.moveTo(0);
1199160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang    }
1200160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang
1201b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Opening Animation
1203b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1204b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1205b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setOpenAnimationRect(Rect rect) {
1206b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.setOpenAnimationRect(rect);
1207cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
120815b351a22d02e89d882fc9fe32b3f4c512080e0aChih-Chung Chang
1209b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
12102c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    //  Capture Animation
12112c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
12122c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
12132c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public boolean switchWithCaptureAnimation(int offset) {
12142c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        GLRoot root = getGLRoot();
12152c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        root.lockRenderThread();
12162c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        try {
12172c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return switchWithCaptureAnimationLocked(offset);
12182c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } finally {
12192c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            root.unlockRenderThread();
12202c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        }
12212c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
12222c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
12232c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private boolean switchWithCaptureAnimationLocked(int offset) {
12242c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mHolding != 0) return true;
12252c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (offset == 1) {
12262c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mNextBound <= 0) return false;
122761f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            // Temporary disable action bar until the capture animation is done.
122861f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            if (!mFilmMode) mListener.onActionBarAllowed(false);
12292c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            switchToNextImage();
12302c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mPositionController.startCaptureAnimationSlide(-1);
12312c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else if (offset == -1) {
12322c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mPrevBound >= 0) return false;
1233160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang            switchToFirstImage();
12342c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mPositionController.startCaptureAnimationSlide(1);
12352c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else {
12362c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return false;
12372c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        }
12382c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mHolding |= HOLD_CAPTURE_ANIMATION;
1239bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0);
1240bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mHandler.sendMessageDelayed(m, 800);
12412c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        return true;
12422c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
12432c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
1244bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private void captureAnimationDone(int offset) {
12452c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mHolding &= ~HOLD_CAPTURE_ANIMATION;
1246bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (offset == 1) {
1247bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // move out of camera, unlock
124861f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            if (!mFilmMode) {
124961f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang                // Now the capture animation is done, enable the action bar.
125061f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang                mListener.onActionBarAllowed(true);
125161f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            }
1252bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
12532c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        snapback();
12542c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
12552c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
12562c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1257b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Card deck effect calculation
1258b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1259b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1260cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Returns the scrolling progress value for an object moving out of a
1261cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // view. The progress value measures how much the object has moving out of
1262cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // the view. The object currently displays in [left, right), and the view is
1263cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // at [0, viewWidth].
1264cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    //
1265cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // The returned value is negative when the object is moving right, and
1266cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // positive when the object is moving left. The value goes to -1 or 1 when
1267cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // the object just moves out of the view completely. The value is 0 if the
1268cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // object currently fills the view.
1269cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private static float calculateMoveOutProgress(int left, int right,
1270cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            int viewWidth) {
1271cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // w = object width
1272cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // viewWidth = view width
1273cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        int w = right - left;
1274cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1275cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // If the object width is smaller than the view width,
1276cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //      |....view....|
1277cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //                   |<-->|      progress = -1 when left = viewWidth
1278bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        //          |<-->|               progress = 0 when left = viewWidth / 2 - w / 2
1279cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // |<-->|                        progress = 1 when left = -w
1280cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        if (w < viewWidth) {
1281bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int zx = viewWidth / 2 - w / 2;
1282bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (left > zx) {
1283bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                return -(left - zx) / (float) (viewWidth - zx);  // progress = (0, -1]
1284bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
1285bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                return (left - zx) / (float) (-w - zx);  // progress = [0, 1]
1286bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1287cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1288cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1289cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // If the object width is larger than the view width,
1290cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //             |..view..|
1291cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //                      |<--------->| progress = -1 when left = viewWidth
1292cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //             |<--------->|          progress = 0 between left = 0
1293cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //          |<--------->|                          and right = viewWidth
1294cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // |<--------->|                      progress = 1 when right = 0
1295cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        if (left > 0) {
1296cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            return -left / (float) viewWidth;
1297cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1298cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1299cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        if (right < viewWidth) {
1300cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            return (viewWidth - right) / (float) viewWidth;
1301cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1302cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1303cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        return 0;
1304cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
1305cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1306cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Maps a scrolling progress value to the alpha factor in the fading
1307cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // animation.
1308cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private float getScrollAlpha(float scrollProgress) {
1309cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        return scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
1310cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                     1 - Math.abs(scrollProgress)) : 1.0f;
1311cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
1312cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1313cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Maps a scrolling progress value to the scaling factor in the fading
1314cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // animation.
1315cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private float getScrollScale(float scrollProgress) {
1316cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        float interpolatedProgress = mScaleInterpolator.getInterpolation(
1317cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                Math.abs(scrollProgress));
1318cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        float scale = (1 - interpolatedProgress) +
1319cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                interpolatedProgress * TRANSITION_SCALE_FACTOR;
1320cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        return scale;
1321cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
1322cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1323cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1324cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // This interpolator emulates the rate at which the perceived scale of an
1325cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // object changes as its distance from a camera increases. When this
1326cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // interpolator is applied to a scale animation on a view, it evokes the
1327cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // sense that the object is shrinking due to moving away from the camera.
1328cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private static class ZInterpolator {
1329cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        private float focalLength;
1330cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1331cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        public ZInterpolator(float foc) {
1332cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            focalLength = foc;
1333cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1334cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1335cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        public float getInterpolation(float input) {
1336cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            return (1.0f - focalLength / (focalLength + input)) /
1337cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                (1.0f - focalLength / (focalLength + 1.0f));
1338f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1339f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1340f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1341ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    // Returns an interpolated value for the page/film transition.
1342ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    // When ratio = 0, the result is from.
1343ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    // When ratio = 1, the result is to.
1344ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private static float interpolate(float ratio, float from, float to) {
1345ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        return from + (to - from) * ratio * ratio;
1346ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    }
1347ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1348b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1349b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Simple public utilities
1350b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1351f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1352bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void setListener(Listener listener) {
1353bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mListener = listener;
1354f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1355f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin}
1356