PhotoView.java revision 3b4a8aeb0353fa18a2b5267b3952a80a6c6d4d13
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;
212ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Changimport android.graphics.Matrix;
22cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Changimport android.graphics.Point;
2304ac045bf8da5082bbb0bdc9ea5f9c9b5b796ad0Yuli Huangimport android.graphics.Rect;
24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.os.Message;
25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.MotionEvent;
26cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Changimport android.view.animation.AccelerateInterpolator;
27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
282b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.R;
292b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.app.GalleryActivity;
30cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Changimport com.android.gallery3d.common.Utils;
31b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.data.MediaObject;
32b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport com.android.gallery3d.util.RangeArray;
33b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
34b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Changimport java.util.Arrays;
352b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Lin
36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class PhotoView extends GLView {
37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @SuppressWarnings("unused")
38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final String TAG = "PhotoView";
39c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    private static final int PLACEHOLDER_COLOR = 0xFF222222;
40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int INVALID_SIZE = -1;
42b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public static final long INVALID_DATA_VERSION =
43b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            MediaObject.INVALID_DATA_VERSION;
44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
45c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    public static class Size {
46c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public int width;
47c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public int height;
48c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    }
49c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
50c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    public interface Model extends TileImageView.Model {
51bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public int getCurrentIndex();
52bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void moveTo(int index);
53c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
54c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Returns the size for the specified picture. If the size information is
55c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // not avaiable, width = height = 0.
56c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public void getImageSize(int offset, Size size);
57c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
58c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Returns the rotation for the specified picture.
59c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        public int getImageRotation(int offset);
60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
61b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // This amends the getScreenNail() method of TileImageView.Model to get
62b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // ScreenNail at previous (negative offset) or next (positive offset)
63b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // positions. Returns null if the specified ScreenNail is unavailable.
64b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public ScreenNail getScreenNail(int offset);
65c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
66c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        // Set this to true if we need the model to provide full images.
67b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        public void setNeedFullImage(boolean enabled);
68bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
69bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // Returns true if the item is the Camera preview.
70bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public boolean isCamera(int offset);
71d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
72d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        // Returns true if the item is a Video.
73d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        public boolean isVideo(int offset);
74b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
75b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
76bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public interface Listener {
77b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void onSingleTapUp(int x, int y);
78bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void lockOrientation();
79bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void unlockOrientation();
80bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void onFullScreenChanged(boolean full);
8161f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang        public void onActionBarAllowed(boolean allowed);
82b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
84bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Here is a graph showing the places we need to lock/unlock device
85bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // orientation:
86bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
87bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+ A  +------------+
88bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Page mode |   Camera   |<---|   Photo    |
89bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           |  [locked]  |--->| [unlocked] |
90bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+  B +------------+
91bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //                ^                  ^
92bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //                | C                | D
93bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+    +------------+
94bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           |   Camera   |    |   Photo    |
95bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Film mode |    [*]     |    |    [*]     |
96bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //           +------------+    +------------+
97bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
98bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // In Page mode, we want to lock in Camera because we don't want the system
99bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // rotation animation. We also want to unlock in Photo because we want to
100bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // show the system action bar in the right place.
101bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
102bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // We don't show action bar in Film mode, so it's fine for it to be locked
103bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // or unlocked in Film mode.
104bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    //
105bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // There are four transitions we need to check if we need to
106bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // lock/unlock. Marked as A to D above and in the code.
107bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
108b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MSG_SHOW_LOADING = 1;
109b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MSG_CANCEL_EXTRA_SCALING = 2;
110b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MSG_SWITCH_FOCUS = 3;
1112c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private static final int MSG_CAPTURE_ANIMATION_DONE = 4;
112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
113b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final long DELAY_SHOW_LOADING = 250; // 250ms;
114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_INIT = 0;
116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_TIMEOUT = 1;
117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_COMPLETE = 2;
118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int LOADING_FAIL = 3;
119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
120b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final int MOVE_THRESHOLD = 256;
121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final float SWIPE_THRESHOLD = 300f;
122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final float DEFAULT_TEXT_SIZE = 20;
124cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private static float TRANSITION_SCALE_FACTOR = 0.74f;
125d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    private static final int ICON_RATIO = 6;
1262c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
1272c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // whether we want to apply card deck effect in page mode.
128b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static final boolean CARD_EFFECT = true;
129cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
130cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Used to calculate the scaling factor for the fading animation.
131cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f);
132cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
133cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Used to calculate the alpha factor for the fading animation.
134cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private AccelerateInterpolator mAlphaInterpolator =
135cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            new AccelerateInterpolator(0.9f);
136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // We keep this many previous ScreenNails. (also this many next ScreenNails)
138b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public static final int SCREEN_NAIL_MAX = 3;
139b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // The picture entries, the valid index is from -SCREEN_NAIL_MAX to
141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // SCREEN_NAIL_MAX.
142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private final RangeArray<Picture> mPictures =
143b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            new RangeArray<Picture>(-SCREEN_NAIL_MAX, SCREEN_NAIL_MAX);
144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
145b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private final long mDataVersion[] = new long[2 * SCREEN_NAIL_MAX + 1];
146b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private final int mFromIndex[] = new int[2 * SCREEN_NAIL_MAX + 1];
147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1486575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang    private final MyGestureListener mGestureListener;
1493a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang    private final GestureRecognizer mGestureRecognizer;
150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final PositionController mPositionController;
151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
152bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Listener mListener;
153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private Model mModel;
154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private StringTexture mLoadingText;
155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private StringTexture mNoThumbnailText;
156b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private TileImageView mTileView;
157532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang    private EdgeView mEdgeView;
158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private Texture mVideoPlayIcon;
159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private ProgressSpinner mLoadingSpinner;
161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private SynchronizedHandler mHandler;
163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mLoadingState = LOADING_COMPLETE;
165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
166cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private Point mImageCenter = new Point();
167534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang    private boolean mCancelExtraScalingPending;
168b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean mFilmMode = false;
169bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private int mDisplayRotation = 0;
170bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private int mCompensation = 0;
171bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private boolean mFullScreen = true;
1722ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang    private Rect mCameraRelativeFrame = new Rect();
173bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private Rect mCameraRect = new Rect();
174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
175c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // [mPrevBound, mNextBound] is the range of index for all pictures in the
176c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // model, if we assume the index of current focused picture is 0.  So if
177c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // there are some previous pictures, mPrevBound < 0, and if there are some
178c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    // next pictures, mNextBound > 0.
179c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    private int mPrevBound;
180c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    private int mNextBound;
181c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
1822c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // This variable prevents us doing snapback until its values goes to 0. This
1832c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // happens if the user gesture is still in progress or we are in a capture
1842c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    // animation.
1852c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private int mHolding;
1862c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private static final int HOLD_TOUCH_DOWN = 1;
18718958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang    private static final int HOLD_CAPTURE_ANIMATION = 2;
1882c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public PhotoView(GalleryActivity activity) {
190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mTileView = new TileImageView(activity);
191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        addComponent(mTileView);
192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Context context = activity.getAndroidContext();
193532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        mEdgeView = new EdgeView(context);
194532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang        addComponent(mEdgeView);
195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLoadingSpinner = new ProgressSpinner(context);
196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLoadingText = StringTexture.newInstance(
197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                context.getString(R.string.loading),
198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                DEFAULT_TEXT_SIZE, Color.WHITE);
199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mNoThumbnailText = StringTexture.newInstance(
200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                context.getString(R.string.no_thumbnail),
201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                DEFAULT_TEXT_SIZE, Color.WHITE);
202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
203b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mHandler = new MyHandler(activity.getGLRoot());
204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2056575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        mGestureListener = new MyGestureListener();
2066575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        mGestureRecognizer = new GestureRecognizer(context, mGestureListener);
207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
208b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController = new PositionController(context,
209b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                new PositionController.Listener() {
210b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void invalidate() {
211b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        PhotoView.this.invalidate();
212b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
2132c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    public boolean isHolding() {
21418958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                        return mHolding != 0;
215b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
216b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void onPull(int offset, int direction) {
217b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mEdgeView.onPull(offset, direction);
218b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
219b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void onRelease() {
220b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mEdgeView.onRelease();
221b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
222b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    public void onAbsorb(int velocity, int direction) {
223b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mEdgeView.onAbsorb(velocity, direction);
224b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
225b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                });
226f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play);
227b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Arrays.fill(mDataVersion, INVALID_DATA_VERSION);
228b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
229b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (i == 0) {
230b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPictures.put(i, new FullPicture());
231cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            } else {
232b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPictures.put(i, new ScreenNailPicture(i));
233cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            }
234f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
235f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
236f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
237b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setModel(Model model) {
238b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mModel = model;
239b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mTileView.setModel(mModel);
240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
241f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
242b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    class MyHandler extends SynchronizedHandler {
243b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public MyHandler(GLRoot root) {
244b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            super(root);
245f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
246f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
247b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
248b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void handleMessage(Message message) {
249b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            switch (message.what) {
250b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case MSG_SHOW_LOADING: {
251b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    if (mLoadingState == LOADING_INIT) {
252b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // We don't need the opening animation
253b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mPositionController.setOpenAnimationRect(null);
254b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
255b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mLoadingSpinner.startAnimation();
256b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        mLoadingState = LOADING_TIMEOUT;
257b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        invalidate();
258b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
259b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
260f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                }
261b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case MSG_CANCEL_EXTRA_SCALING: {
262b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mGestureRecognizer.cancelScale();
263b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mPositionController.setExtraScalingRange(false);
264b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mCancelExtraScalingPending = false;
265b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
266b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
267b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                case MSG_SWITCH_FOCUS: {
268b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    switchFocus();
269b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
270b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
2712c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                case MSG_CAPTURE_ANIMATION_DONE: {
272bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    // message.arg1 is the offset parameter passed to
273bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    // switchWithCaptureAnimation().
274bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    captureAnimationDone(message.arg1);
2752c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                    break;
2762c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang                }
277b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                default: throw new AssertionError(message.what);
278f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
279f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
280b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    };
281f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
282f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private void updateLoadingState() {
283f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // Possible transitions of mLoadingState:
284f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //        INIT --> TIMEOUT, COMPLETE, FAIL
285f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     TIMEOUT --> COMPLETE, FAIL, INIT
286f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //    COMPLETE --> INIT
287f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //        FAIL --> INIT
28815b351a22d02e89d882fc9fe32b3f4c512080e0aChih-Chung Chang        if (mModel.getLevelCount() != 0 || mModel.getScreenNail() != null) {
289f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.removeMessages(MSG_SHOW_LOADING);
290f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mLoadingState = LOADING_COMPLETE;
291f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (mModel.isFailedToLoad()) {
292f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.removeMessages(MSG_SHOW_LOADING);
293f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mLoadingState = LOADING_FAIL;
294a7b78e224b1808895ea2c3d42ae385526dea12aaYuli Huang            // We don't want the opening animation after loading failure
295b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPositionController.setOpenAnimationRect(null);
296f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (mLoadingState != LOADING_INIT) {
297f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mLoadingState = LOADING_INIT;
298f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.removeMessages(MSG_SHOW_LOADING);
299f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHandler.sendEmptyMessageDelayed(
300f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    MSG_SHOW_LOADING, DELAY_SHOW_LOADING);
301f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
302f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
303f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
304b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
305b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Data/Image change notifications
306b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
307b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
308c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang    public void notifyDataChange(long[] versions, int prevBound, int nextBound) {
309c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        mPrevBound = prevBound;
310c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        mNextBound = nextBound;
311c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
312b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Check if the data version actually changed.
313b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean changed = false;
314b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int N = 2 * SCREEN_NAIL_MAX + 1;
315b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < N; i++) {
316b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (versions[i] != mDataVersion[i]) {
317b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                changed = true;
318b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                break;
319b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
320f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
321b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!changed) return;
322f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
323b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Create the mFromIndex array, which records the index where the picture
324b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // come from. The value Integer.MAX_VALUE means it's a new picture.
325b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < N; i++) {
326b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            long v = versions[i];
327b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (v == INVALID_DATA_VERSION) {
328b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mFromIndex[i] = Integer.MAX_VALUE;
329b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                continue;
330b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
331f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
332b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Try to find the same version number in the old array
333b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int j;
334b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            for (j = 0; j < N; j++) {
335b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if (mDataVersion[j] == v) {
336b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    break;
337b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
338f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
339b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mFromIndex[i] = (j < N) ? j - SCREEN_NAIL_MAX : Integer.MAX_VALUE;
340f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
341b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
342b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Copy the new data version
343b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = 0; i < N; i++) {
344b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mDataVersion[i] = versions[i];
345b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
346b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
347b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Move the boxes
348bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0,
349bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mModel.isCamera(0));
350b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
351b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        // Update the ScreenNails.
352b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
353b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPictures.get(i).reload();
354b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
355b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
356b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        invalidate();
357f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
358f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
359b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void notifyImageChange(int index) {
360b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPictures.get(index).reload();
361b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        invalidate();
362f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
363f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
364bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    @Override
3652ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang    protected void onLayout(
3662ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang            boolean changeSize, int left, int top, int right, int bottom) {
3672ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int w = right - left;
3682ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int h = bottom - top;
3692ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        mTileView.layout(0, 0, w, h);
3702ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        mEdgeView.layout(0, 0, w, h);
3712ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang
3722ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        GLRoot root = getGLRoot();
3732ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int displayRotation = root.getDisplayRotation();
3742ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int compensation = root.getCompensation();
3752ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        if (mDisplayRotation != displayRotation
3762ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                || mCompensation != compensation) {
3772ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang            mDisplayRotation = displayRotation;
3782ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang            mCompensation = compensation;
3792ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang
3803b4a8aeb0353fa18a2b5267b3952a80a6c6d4d13Chih-Chung Chang            // We need to change the size and rotation of the Camera ScreenNail,
3813b4a8aeb0353fa18a2b5267b3952a80a6c6d4d13Chih-Chung Chang            // but we don't want it to animate because the size doen't actually
3822ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang            // change in the eye of the user.
3832ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang            for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
3842ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                Picture p = mPictures.get(i);
3852ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                if (p.isCamera()) {
3862ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                    p.updateSize(true);
3872ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                }
388bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
389bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
390bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
391bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        updateConstrainedFrame();
392bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (changeSize) {
393bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mPositionController.setViewSize(getWidth(), getHeight());
394bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
395bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
396bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
397bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Update the constrained frame due to layout change.
398bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private void updateConstrainedFrame() {
3992ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        // Get the width and height in framework orientation because the given
4002ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        // mCameraRelativeFrame is in that coordinates.
401bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int w = getWidth();
402bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        int h = getHeight();
4032ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        if (mCompensation % 180 != 0) {
404bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int tmp = w;
405bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            w = h;
406bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            h = tmp;
407bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
4082ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int l = mCameraRelativeFrame.left;
4092ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int t = mCameraRelativeFrame.top;
4102ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int r = mCameraRelativeFrame.right;
4112ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        int b = mCameraRelativeFrame.bottom;
412bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
4132ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        // Now convert it to the coordinates we are using.
4142ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        switch (mCompensation) {
415bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 0: mCameraRect.set(l, t, r, b); break;
416bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 90: mCameraRect.set(h - b, l, h - t, r); break;
417bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 180: mCameraRect.set(w - r, h - b, w - l, h - t); break;
418bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            case 270: mCameraRect.set(t, w - r, b, w - l); break;
419bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
420bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
4212ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        Log.d(TAG, "compensation = " + mCompensation
4222ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                + ", CameraRelativeFrame = " + mCameraRelativeFrame
4232ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                + ", mCameraRect = " + mCameraRect);
424bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mPositionController.setConstrainedFrame(mCameraRect);
425bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
426bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
4272ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang    public void setCameraRelativeFrame(Rect frame) {
4282ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        mCameraRelativeFrame.set(frame);
4292ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang        updateConstrainedFrame();
430bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
431bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
432bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // Returns the rotation we need to do to the camera texture before drawing
433bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // it to the canvas, assuming the camera texture is correct when the device
434bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    // is in its natural orientation.
435bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private int getCameraRotation() {
436bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return (mCompensation - mDisplayRotation + 360) % 360;
437bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
438bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
439b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
440b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Pictures
441b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
442b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
443b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private interface Picture {
444b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void reload();
445b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        void draw(GLCanvas canvas, Rect r);
446b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        void setScreenNail(ScreenNail s);
4472c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        boolean isCamera();  // whether the picture is a camera preview
448bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        void updateSize(boolean force);  // called when mCompensation changes
449b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    };
450f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
451b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    class FullPicture implements Picture {
452b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private int mRotation;
4532c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        private boolean mIsCamera;
454d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private boolean mIsVideo;
455bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        private boolean mWasCameraCenter;
456b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
457b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void FullPicture(TileImageView tileView) {
458b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTileView = tileView;
459f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
460f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
461b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
462b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void reload() {
463b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // mImageWidth and mImageHeight will get updated
464b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mTileView.notifyModelInvalidated();
465b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
466bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mIsCamera = mModel.isCamera(0);
467d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            mIsVideo = mModel.isVideo(0);
468bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            setScreenNail(mModel.getScreenNail(0));
469bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateSize(false);
470bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateLoadingState();
471bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
472bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
473bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        @Override
474bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void updateSize(boolean force) {
475bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mIsCamera) {
476bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = getCameraRotation();
477bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
478bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = mModel.getImageRotation(0);
479bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
480bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
481c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            int w = mTileView.mImageWidth;
482c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            int h = mTileView.mImageHeight;
483c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            mPositionController.setImageSize(0,
484c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                    getRotated(mRotation, w, h),
485bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    getRotated(mRotation, h, w),
486bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                    force);
487f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
488f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
489b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
490b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void draw(GLCanvas canvas, Rect r) {
491bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            boolean isCenter = mPositionController.isCenter();
492bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
493b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mLoadingState == LOADING_COMPLETE) {
494d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                drawTileView(canvas, r);
495b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
496b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            renderMessage(canvas, r.centerX(), r.centerY());
497cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
498aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            if (mIsCamera) {
499aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                boolean full = !mFilmMode && isCenter
500aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                        && mPositionController.isAtMinimalScale();
501aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                if (full != mFullScreen) {
502aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mFullScreen = full;
503aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mListener.onFullScreenChanged(full);
504aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                }
505aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            }
506aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang
507bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // We want to have the following transitions:
5082c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // (1) Move camera preview out of its place: switch to film mode
5092c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // (2) Move camera preview into its place: switch to page mode
5102c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // The extra mWasCenter check makes sure (1) does not apply if in
5112c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            // page mode, we move _to_ the camera preview from another picture.
512bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
513bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // Holdings except touch-down prevent the transitions.
51418958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return;
515bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
516bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            boolean isCameraCenter = mIsCamera && isCenter;
517bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
518bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mWasCameraCenter && mIsCamera && !isCenter && !mFilmMode) {
51918958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                // Temporary disabled to de-emphasize filmstrip.
52018958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                // setFilmMode(true);
52118958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            } else if (!mWasCameraCenter && isCameraCenter && mFilmMode) {
522bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                setFilmMode(false);
523aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            }
524aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang
525aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang            if (isCenter && !mFilmMode) {
526aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                if (mIsCamera) {
527aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    // move into camera, lock
528aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mListener.lockOrientation();  // Transition A
529aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                } else {
530aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    // move out of camera, unlock
531aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                    mListener.unlockOrientation();  // Transition B
532aeb3062e84ed28f0e25a73f511f287ae0a3e4294Chih-Chung Chang                }
533bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
534bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
535bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mWasCameraCenter = isCameraCenter;
536cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
537cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
538b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
539b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        public void setScreenNail(ScreenNail s) {
540b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang            mTileView.setScreenNail(s);
541cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
542f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
543b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
5442c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        public boolean isCamera() {
5452c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return mIsCamera;
546f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
547f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
548d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private void drawTileView(GLCanvas canvas, Rect r) {
549ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float imageScale = mPositionController.getImageScale();
550b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int viewW = getWidth();
551b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int viewH = getHeight();
552d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            float cx = r.exactCenterX();
553d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            float cy = r.exactCenterY();
554ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float scale = 1f;  // the scaling factor due to card effect
555b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
556d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
557ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float filmRatio = mPositionController.getFilmRatio();
558ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            boolean wantsCardEffect = CARD_EFFECT && !mIsCamera
559ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    && filmRatio != 1f && !mPictures.get(-1).isCamera();
5602c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (wantsCardEffect) {
561b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // Calculate the move-out progress value.
562b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int left = r.left;
563b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                int right = r.right;
564b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float progress = calculateMoveOutProgress(left, right, viewW);
565b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = Utils.clamp(progress, -1f, 1f);
566b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
567b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // We only want to apply the fading animation if the scrolling
568b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                // movement is to the right.
569bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                if (progress < 0) {
570ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    scale = getScrollScale(progress);
571ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    float alpha = getScrollAlpha(progress);
572ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    scale = interpolate(filmRatio, scale, 1f);
573ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    alpha = interpolate(filmRatio, alpha, 1f);
574d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
575ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    imageScale *= scale;
576ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    canvas.multiplyAlpha(alpha);
577ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
578ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    float cxPage;  // the cx value in page mode
579d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                    if (right - left <= viewW) {
580b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // If the picture is narrower than the view, keep it at
581b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // the center of the view.
582ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                        cxPage = viewW / 2f;
583b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    } else {
584b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // If the picture is wider than the view (it's
585b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // zoomed-in), keep the left edge of the object align
586b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        // the the left edge of the view.
587ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                        cxPage = (right - left) * scale / 2f;
588b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    }
589ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    cx = interpolate(filmRatio, cxPage, cx);
590b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                }
591b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
592f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
593d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            // Draw the tile view.
594ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            setTileViewPosition(cx, cy, viewW, viewH, imageScale);
595d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            PhotoView.super.render(canvas);
596d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
597d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            // Draw the play video icon.
598d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            if (mIsVideo) {
599d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
600ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
601d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                drawVideoPlayIcon(canvas, s);
602d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            }
603d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
604d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            canvas.restore();
605d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        }
606d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
607d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        // Set the position of the tile view
608d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private void setTileViewPosition(float cx, float cy,
609d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                int viewW, int viewH, float scale) {
610d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            // Find out the bitmap coordinates of the center of the view
611d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int imageW = mPositionController.getImageWidth();
612d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int imageH = mPositionController.getImageHeight();
613d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f);
614d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f);
615d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
616b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int inverseX = imageW - centerX;
617b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int inverseY = imageH - centerY;
618d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int x, y;
619d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            switch (mRotation) {
620d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 0: x = centerX; y = centerY; break;
621d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 90: x = centerY; y = inverseX; break;
622d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 180: x = inverseX; y = inverseY; break;
623d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                case 270: x = inverseY; y = centerX; break;
624b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                default:
625d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang                    throw new RuntimeException(String.valueOf(mRotation));
626b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
627d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            mTileView.setPosition(x, y, scale, mRotation);
628f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
629f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
630b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private void renderMessage(GLCanvas canvas, int x, int y) {
631b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Draw the progress spinner and the text below it
632b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            //
633b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // (x, y) is where we put the center of the spinner.
634b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // s is the size of the video play icon, and we use s to layout text
635b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // because we want to keep the text at the same place when the video
636b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // play icon is shown instead of the spinner.
637b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int w = getWidth();
638b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int h = getHeight();
639d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            int s = Math.min(w, h) / ICON_RATIO;
640b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
641b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mLoadingState == LOADING_TIMEOUT) {
642b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                StringTexture m = mLoadingText;
643b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                ProgressSpinner p = mLoadingSpinner;
644b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                p.draw(canvas, x - p.getWidth() / 2, y - p.getHeight() / 2);
645b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
646b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                invalidate(); // we need to keep the spinner rotating
647b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            } else if (mLoadingState == LOADING_FAIL) {
648b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                StringTexture m = mNoThumbnailText;
649b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
650b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
651f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
652b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // Draw a debug indicator showing which picture has focus (index ==
653b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // 0).
654b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            // canvas.fillRect(x - 10, y - 10, 20, 20, 0x80FF00FF);
655f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
656f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
657f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
658b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private class ScreenNailPicture implements Picture {
659b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private int mIndex;
660b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private int mRotation;
661b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private ScreenNail mScreenNail;
662c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang        private Size mSize = new Size();
6632c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        private boolean mIsCamera;
664d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        private boolean mIsVideo;
665f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
666b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public ScreenNailPicture(int index) {
667b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mIndex = index;
668b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
669532d93caddc91a7aa33ca113adbc0b8255d498ebChih-Chung Chang
670b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
671b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void reload() {
672bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            mIsCamera = mModel.isCamera(mIndex);
673d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            mIsVideo = mModel.isVideo(mIndex);
674c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            setScreenNail(mModel.getScreenNail(mIndex));
675b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
676b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
677b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
678b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        public void draw(GLCanvas canvas, Rect r) {
679b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mScreenNail == null) {
680c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // Draw a placeholder rectange if there will be a picture in
681c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // this position.
682c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                if (mIndex >= mPrevBound && mIndex <= mNextBound) {
683c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                    canvas.fillRect(r.left, r.top, r.width(), r.height(),
684c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                            PLACEHOLDER_COLOR);
685c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                }
686b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return;
687f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
688b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (r.left >= getWidth() || r.right <= 0 ||
689b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    r.top >= getHeight() || r.bottom <= 0) {
690b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mScreenNail.noDraw();
691b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return;
692f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
693f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
694bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mIsCamera && mFullScreen != false) {
695bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mFullScreen = false;
696bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mListener.onFullScreenChanged(false);
697bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
698bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
699ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            float filmRatio = mPositionController.getFilmRatio();
700ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            boolean wantsCardEffect = CARD_EFFECT && mIndex > 0
701ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    && filmRatio != 1f && !mPictures.get(0).isCamera();
702b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int w = getWidth();
703ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            int cx = wantsCardEffect
704ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    ? (int) (interpolate(filmRatio, w / 2, r.centerX()) + 0.5f)
705ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                    : r.centerX();
706b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int cy = r.centerY();
707ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
708b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            canvas.translate(cx, cy);
7092c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (wantsCardEffect) {
710b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float progress = (float) (w / 2 - r.centerX()) / w;
711b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                progress = Utils.clamp(progress, -1, 1);
712b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float alpha = getScrollAlpha(progress);
713b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                float scale = getScrollScale(progress);
714ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                alpha = interpolate(filmRatio, alpha, 1f);
715ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang                scale = interpolate(filmRatio, scale, 1f);
716b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                canvas.multiplyAlpha(alpha);
717b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                canvas.scale(scale, scale, 1);
718b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
719b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mRotation != 0) {
720b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                canvas.rotate(mRotation, 0, 0, 1);
721b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
722ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            int drawW = getRotated(mRotation, r.width(), r.height());
723ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            int drawH = getRotated(mRotation, r.height(), r.width());
724b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mScreenNail.draw(canvas, -drawW / 2, -drawH / 2, drawW, drawH);
725d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang            if (mIsVideo) drawVideoPlayIcon(canvas, Math.min(drawW, drawH));
726b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            canvas.restore();
727b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
728f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
729b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
730b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        public void setScreenNail(ScreenNail s) {
731b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mScreenNail == s) return;
732b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mScreenNail = s;
733bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            updateSize(false);
734bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
735bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
736bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        @Override
737bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        public void updateSize(boolean force) {
738bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mIsCamera) {
739bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = getCameraRotation();
740bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
741bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mRotation = mModel.getImageRotation(mIndex);
742bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
743c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
744c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            int w = 0, h = 0;
745b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mScreenNail != null) {
746bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                w = mScreenNail.getWidth();
747bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                h = mScreenNail.getHeight();
748c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            } else if (mModel != null) {
749c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // If we don't have ScreenNail available, we can still try to
750c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                // get the size information of it.
751c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                mModel.getImageSize(mIndex, mSize);
752c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                w = mSize.width;
753c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang                h = mSize.height;
754b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
755c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang
756c3b2d478f9032a8decf5c6254a238fc49e41b72cChih-Chung Chang            if (w != 0 && h != 0)  {
757b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPositionController.setImageSize(mIndex,
758b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        getRotated(mRotation, w, h),
759bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                        getRotated(mRotation, h, w),
760bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                        force);
761b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
762f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
763b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
764b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        @Override
7652c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        public boolean isCamera() {
7662c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return mIsCamera;
767b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
768b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
769b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
770d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    // Draw the video play icon (in the place where the spinner was)
771d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    private void drawVideoPlayIcon(GLCanvas canvas, int side) {
772d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        int s = side / ICON_RATIO;
773d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        // Draw the video play icon at the center
774d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang        mVideoPlayIcon.draw(canvas, -s / 2, -s / 2, s, s);
775d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang    }
776d9355113da391f8bbddef1d2a2126ce6edc72291Chih-Chung Chang
777b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static int getRotated(int degree, int original, int theother) {
778b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return (degree % 180 == 0) ? original : theother;
779b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
780b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
781b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
782b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Gestures Handling
783b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
784b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
785b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    @Override
786b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    protected boolean onTouch(MotionEvent event) {
787b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mGestureRecognizer.onTouchEvent(event);
788b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
789f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
790f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
7913a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang    private class MyGestureListener implements GestureRecognizer.Listener {
7923a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        private boolean mIgnoreUpEvent = false;
793099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang        // If we can change mode for this scale gesture.
794099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang        private boolean mCanChangeMode;
79518958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang        // If we have changed the film mode in this scaling gesture.
796b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private boolean mModeChanged;
79733f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        // If this scaling gesture should be ignored.
79833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        private boolean mIgnoreScalingGesture;
79917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        // whether the down action happened while the view is scrolling.
80017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        private boolean mDownInScrolling;
8016575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        // If we should ignore all gestures other than onSingleTapUp.
8026575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        private boolean mIgnoreSwipingGesture;
803b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
8043a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        @Override
8053a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onSingleTapUp(float x, float y) {
806ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            // We do this in addition to onUp() because we want the snapback of
807ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            // setFilmMode to happen.
808ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang            mHolding &= ~HOLD_TOUCH_DOWN;
809ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
81017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (mFilmMode && !mDownInScrolling) {
81117ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                switchToHitPicture((int) (x + 0.5f), (int) (y + 0.5f));
812b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                setFilmMode(false);
81317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mIgnoreUpEvent = true;
814b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return true;
815b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
816b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
817bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mListener != null) {
8182ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                // Do the inverse transform of the touch coordinates.
8192ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                Matrix m = getGLRoot().getCompensationMatrix();
8202ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                Matrix inv = new Matrix();
8212ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                m.invert(inv);
8222ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                float[] pts = new float[] {x, y};
8232ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                inv.mapPoints(pts);
8242ef46ed28b28b355d7f3f1432c7b1196b832a859Chih-Chung Chang                mListener.onSingleTapUp((int) (pts[0] + 0.5f), (int) (pts[1] + 0.5f));
8253a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            }
8263a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            return true;
8273a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        }
8283a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang
8293a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        @Override
8303a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onDoubleTap(float x, float y) {
8316575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
83261f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            if (mPictures.get(0).isCamera()) return false;
8333a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            PositionController controller = mPositionController;
834b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            float scale = controller.getImageScale();
8353a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            // onDoubleTap happened on the second ACTION_DOWN.
8363a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            // We need to ignore the next UP event.
8373a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            mIgnoreUpEvent = true;
8383a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            if (scale <= 1.0f || controller.isAtMinimalScale()) {
8393a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                controller.zoomIn(x, y, Math.max(1.5f, scale * 1.5f));
8403a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            } else {
8413a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                controller.resetToFullView();
8423a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            }
8433a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            return true;
8443a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        }
845f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
846f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8473a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onScroll(float dx, float dy) {
8486575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
849b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPositionController.startScroll(-dx, -dy);
850f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
851f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
852f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
853f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8543a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onFling(float velocityX, float velocityY) {
8556575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
8562ce3c3bfe08fff5aee58007cc8ba8f4a50861ae2Yuli Huang            if (swipeImages(velocityX, velocityY)) {
857b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mIgnoreUpEvent = true;
858b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang            } else if (mPositionController.fling(velocityX, velocityY)) {
859b3aab90bb37aa9cc60be32e05678ee55d6575ee8Chih-Chung Chang                mIgnoreUpEvent = true;
860f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
861f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
862f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
863f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
864f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8653a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onScaleBegin(float focusX, float focusY) {
8666575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
86733f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // We ignore the scaling gesture if it is a camera preview.
86833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            mIgnoreScalingGesture = mPictures.get(0).isCamera();
86933f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            if (mIgnoreScalingGesture) {
87033f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang                return true;
87133f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            }
8723a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            mPositionController.beginScale(focusX, focusY);
873099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang            // We can change mode if we are in film mode, or we are in page
874099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang            // mode and at minimal scale.
875099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang            mCanChangeMode = mFilmMode
876099989b310d84fe13eff0cdf2902bb3fb0bcbd14Chih-Chung Chang                    || mPositionController.isAtMinimalScale();
877b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mModeChanged = false;
878f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
879f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
880f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
881f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
8823a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public boolean onScale(float focusX, float focusY, float scale) {
8836575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return true;
8846575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreScalingGesture) return true;
88518958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (mModeChanged) return true;
886b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (Float.isNaN(scale) || Float.isInfinite(scale)) return false;
88733f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
88833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // We wait for the scale change accumulated to a large enough change
88933f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // before reacting to it. Otherwise we may mistakenly treat a
89033f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            // zoom-in gesture as zoom-out or vice versa.
89118958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (scale > 0.99f && scale < 1.01f) return false;
89233f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
893b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int outOfRange = mPositionController.scaleBy(scale, focusX, focusY);
894b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
89518958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            // If mode changes, we treat this scaling gesture has ended.
89618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (mCanChangeMode) {
897b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                if ((outOfRange < 0 && !mFilmMode) ||
898b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        (outOfRange > 0 && mFilmMode)) {
89918958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    stopExtraScalingIfNeeded();
90018958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang
90118958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    // Removing the touch down flag allows snapback to happen
90233f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang                    // for film mode change.
90318958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    mHolding &= ~HOLD_TOUCH_DOWN;
904b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    setFilmMode(!mFilmMode);
90518958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang
90618958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    // We need to call onScaleEnd() before setting mModeChanged
90718958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    // to true.
90818958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang                    onScaleEnd();
909b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    mModeChanged = true;
910b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    return true;
911534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang                }
912b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang           }
913b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
91418958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            if (outOfRange != 0) {
915b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                startExtraScalingIfNeeded();
916534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang            } else {
917b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                stopExtraScalingIfNeeded();
918534b12fd804610dd67b8109bc08ba76f31afb33eChih-Chung Chang            }
919f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
920f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
921f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
92233f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        @Override
92333f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        public void onScaleEnd() {
9246575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return;
9256575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreScalingGesture) return;
92633f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            if (mModeChanged) return;
92733f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang            mPositionController.endScale();
92833f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        }
92933f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
930b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private void startExtraScalingIfNeeded() {
931b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (!mCancelExtraScalingPending) {
932b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mHandler.sendEmptyMessageDelayed(
933b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                        MSG_CANCEL_EXTRA_SCALING, 700);
934b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPositionController.setExtraScalingRange(true);
935b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCancelExtraScalingPending = true;
936b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
937b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
938b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
939b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        private void stopExtraScalingIfNeeded() {
940b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (mCancelExtraScalingPending) {
941b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
942b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mPositionController.setExtraScalingRange(false);
943b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                mCancelExtraScalingPending = false;
944b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
945b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
946b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
947f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
9483a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public void onDown() {
9496575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return;
9506575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
9512c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mHolding |= HOLD_TOUCH_DOWN;
95217ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
95317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (mFilmMode && mPositionController.isScrolling()) {
95417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mDownInScrolling = true;
95517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mPositionController.stopScrolling();
95617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            } else {
95717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                mDownInScrolling = false;
95817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            }
9593a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        }
9603a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang
9613a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        @Override
9623a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang        public void onUp() {
9636575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            if (mIgnoreSwipingGesture) return;
9646575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
96518958c51f1412d959d52500ceefc46f987d035f3Chih-Chung Chang            mHolding &= ~HOLD_TOUCH_DOWN;
9663a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            mEdgeView.onRelease();
9673a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang
9683a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            if (mIgnoreUpEvent) {
9693a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                mIgnoreUpEvent = false;
9703a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang                return;
9713a02809c97669a157cf45bfd61d45272110d4091Chih-Chung Chang            }
972b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
9732c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            snapback();
974f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
9756575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
9766575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        public void setSwipingEnabled(boolean enabled) {
9776575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang            mIgnoreSwipingGesture = !enabled;
9786575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        }
9796575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang    }
9806575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang
9816575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang    public void setSwipingEnabled(boolean enabled) {
9826575794a9c09f22d5721e212c093e0a2df376d0cChih-Chung Chang        mGestureListener.setSwipingEnabled(enabled);
983f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
984f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
985b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void setFilmMode(boolean enabled) {
986b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode == enabled) return;
987b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mFilmMode = enabled;
988b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.setFilmMode(mFilmMode);
989b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang        mModel.setNeedFullImage(!enabled);
99061f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang        mListener.onActionBarAllowed(!enabled);
991bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
992bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        // If we leave filmstrip mode, we should lock/unlock
993bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (!enabled) {
994bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (mPictures.get(0).isCamera()) {
995bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mListener.lockOrientation();  // Transition C
996bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
997bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                mListener.unlockOrientation();  // Transition D
998bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
999bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
1000bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    }
1001bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang
1002bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public boolean getFilmMode() {
1003bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        return mFilmMode;
1004f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1005f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1006b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1007b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Framework events
1008b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1009b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1010b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void pause() {
1011b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.skipAnimation();
1012b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mTileView.freeTextures();
1013b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
1014b8be1e0ad76b6abc0da7ead39f7a9811195d001eChih-Chung Chang            mPictures.get(i).setScreenNail(null);
1015b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1016f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1017f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1018b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void resume() {
1019b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mTileView.prepareTextures();
1020f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1021f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
102233f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang    // move to the camera preview and show controls after resume
102333f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang    public void resetToFirstPicture() {
102433f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        mModel.moveTo(0);
102533f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang        setFilmMode(false);
102633f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang    }
102733f8567dd5003e4bb342683f3768ab7552648b02Chih-Chung Chang
1028b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1029b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Rendering
1030b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1031b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1032b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    @Override
1033b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    protected void render(GLCanvas canvas) {
1034ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        float filmRatio = mPositionController.getFilmRatio();
1035ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1036ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // Draw next photos. In page mode, we draw only one next photo.
1037ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        int lastPhoto = (filmRatio == 0f) ? 1 : SCREEN_NAIL_MAX;
1038ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        for (int i = lastPhoto; i > 0; i--) {
1039b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect r = mPositionController.getPosition(i);
1040b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPictures.get(i).draw(canvas, r);
1041b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1042b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1043b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Draw current photo
1044b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPictures.get(0).draw(canvas, mPositionController.getPosition(0));
1045b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1046ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        // Draw previous photos. In page mode, we draw only one previous photo.
1047ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        lastPhoto = (filmRatio == 0f) ? -1: -SCREEN_NAIL_MAX;
1048ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        for (int i = -1; i >= lastPhoto; i--) {
1049b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect r = mPositionController.getPosition(i);
1050b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mPictures.get(i).draw(canvas, r);
1051b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1052b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1053b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.advanceAnimation();
1054b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        checkFocusSwitching();
1055ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang    }
1056ec4125492f17130f65e49160a17bd437e01128a7Chih-Chung Chang
1057b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1058b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Film mode focus switching
1059b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1060f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1061b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Runs in GL thread.
1062b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void checkFocusSwitching() {
1063b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!mFilmMode) return;
1064b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mHandler.hasMessages(MSG_SWITCH_FOCUS)) return;
1065b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (switchPosition() != 0) {
1066b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            mHandler.sendEmptyMessage(MSG_SWITCH_FOCUS);
1067f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1068f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1069f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1070b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Runs in main thread.
1071b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void switchFocus() {
10722c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mHolding != 0) return;
1073b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        switch (switchPosition()) {
1074b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            case -1:
1075b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                switchToPrevImage();
1076b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                break;
1077b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            case 1:
1078b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                switchToNextImage();
1079b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                break;
1080b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1081f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1082f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1083b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // Returns -1 if we should switch focus to the previous picture, +1 if we
1084b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    // should switch to the next, 0 otherwise.
1085b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private int switchPosition() {
1086b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect curr = mPositionController.getPosition(0);
1087b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int center = getWidth() / 2;
1088f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
10892c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (curr.left > center && mPrevBound < 0) {
1090b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect prev = mPositionController.getPosition(-1);
1091b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int currDist = curr.left - center;
1092b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int prevDist = center - prev.right;
1093b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (prevDist < currDist) {
1094b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return -1;
1095b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
10962c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else if (curr.right < center && mNextBound > 0) {
1097b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            Rect next = mPositionController.getPosition(1);
1098b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int currDist = center - curr.right;
1099b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            int nextDist = next.left - center;
1100b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if (nextDist < currDist) {
1101b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return 1;
1102b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            }
1103b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        }
1104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1105b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return 0;
1106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
110817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    // Switch to the previous or next picture if the hit position is inside
110917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    // one of their boxes. This runs in main thread.
111017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    private void switchToHitPicture(int x, int y) {
111117ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        if (mPrevBound < 0) {
111217ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            Rect r = mPositionController.getPosition(-1);
111317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (r.right >= x) {
111417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                slideToPrevPicture();
111517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                return;
111617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            }
111717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        }
111817ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
111917ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        if (mNextBound > 0) {
112017ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            Rect r = mPositionController.getPosition(1);
112117ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            if (r.left <= x) {
112217ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                slideToNextPicture();
112317ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang                return;
112417ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang            }
112517ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang        }
112617ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang    }
112717ffedda6e3ed57b38afbb775594cf30d83fc652Chih-Chung Chang
1128b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1129b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Page mode focus switching
1130b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //
1131b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  We slide image to the next one or the previous one in two cases: 1: If
1132b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  the user did a fling gesture with enough velocity.  2 If the user has
1133b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  moved the picture a lot.
1134b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1136b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean swipeImages(float velocityX, float velocityY) {
1137b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) return false;
1138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1139b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // Avoid swiping images if we're possibly flinging to view the
1140b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // zoomed in picture vertically.
1141b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        PositionController controller = mPositionController;
1142b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        boolean isMinimal = controller.isAtMinimalScale();
1143b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int edges = controller.getImageAtEdges();
1144b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (!isMinimal && Math.abs(velocityY) > Math.abs(velocityX))
1145b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            if ((edges & PositionController.IMAGE_AT_TOP_EDGE) == 0
1146b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                    || (edges & PositionController.IMAGE_AT_BOTTOM_EDGE) == 0)
1147b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                return false;
1148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1149b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If we are at the edge of the current photo and the sweeping velocity
1150b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // exceeds the threshold, slide to the next / previous image.
1151b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (velocityX < -SWIPE_THRESHOLD && (isMinimal
1152b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                || (edges & PositionController.IMAGE_AT_RIGHT_EDGE) != 0)) {
1153b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToNextPicture();
1154b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else if (velocityX > SWIPE_THRESHOLD && (isMinimal
1155b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang                || (edges & PositionController.IMAGE_AT_LEFT_EDGE) != 0)) {
1156b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToPrevPicture();
1157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1159b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return false;
1160b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1161b29a27f475a2c449abdda8d4e03d30914feed8c6Chih-Chung Chang
11622c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private void snapback() {
11632c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mHolding != 0) return;
11642c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (!snapToNeighborImage()) {
11652c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mPositionController.snapback();
11662c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        }
11672c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
11682c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
1169b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean snapToNeighborImage() {
1170b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (mFilmMode) return false;
1171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1172b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        Rect r = mPositionController.getPosition(0);
1173b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int viewW = getWidth();
1174b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        int threshold = MOVE_THRESHOLD + gapToSide(r.width(), viewW);
1175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1176b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        // If we have moved the picture a lot, switching.
1177b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        if (viewW - r.right > threshold) {
1178b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToNextPicture();
1179b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        } else if (r.left > threshold) {
1180b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang            return slideToPrevPicture();
1181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1183b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return false;
1184b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1186b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean slideToNextPicture() {
11872c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mNextBound <= 0) return false;
1188b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        switchToNextImage();
11892c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mPositionController.startHorizontalSlide();
1190b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
1191b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1192676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
1193b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private boolean slideToPrevPicture() {
11942c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mPrevBound >= 0) return false;
1195b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        switchToPrevImage();
11962c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mPositionController.startHorizontalSlide();
1197b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return true;
1198b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1199676170e619ad59ea97d04e0edcd62b1500304845Chih-Chung Chang
1200b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private static int gapToSide(int imageWidth, int viewWidth) {
1201b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        return Math.max(0, (viewWidth - imageWidth) / 2);
1202b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1204b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1205b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Focus switching
1206b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1208b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void switchToNextImage() {
1209bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mModel.moveTo(mModel.getCurrentIndex() + 1);
1210b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
121115b351a22d02e89d882fc9fe32b3f4c512080e0aChih-Chung Chang
1212b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    private void switchToPrevImage() {
1213bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mModel.moveTo(mModel.getCurrentIndex() - 1);
1214b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    }
1215cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1216160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang    private void switchToFirstImage() {
1217bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mModel.moveTo(0);
1218160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang    }
1219160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang
1220b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1221b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Opening Animation
1222b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1223b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1224b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    public void setOpenAnimationRect(Rect rect) {
1225b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang        mPositionController.setOpenAnimationRect(rect);
1226cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
122715b351a22d02e89d882fc9fe32b3f4c512080e0aChih-Chung Chang
1228b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
12292c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    //  Capture Animation
12302c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
12312c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
12322c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    public boolean switchWithCaptureAnimation(int offset) {
12332c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        GLRoot root = getGLRoot();
12342c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        root.lockRenderThread();
12352c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        try {
12362c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return switchWithCaptureAnimationLocked(offset);
12372c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } finally {
12382c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            root.unlockRenderThread();
12392c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        }
12402c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
12412c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
12422c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    private boolean switchWithCaptureAnimationLocked(int offset) {
12432c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (mHolding != 0) return true;
12442c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        if (offset == 1) {
12452c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mNextBound <= 0) return false;
124661f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            // Temporary disable action bar until the capture animation is done.
124761f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            if (!mFilmMode) mListener.onActionBarAllowed(false);
12482c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            switchToNextImage();
12492c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mPositionController.startCaptureAnimationSlide(-1);
12502c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else if (offset == -1) {
12512c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            if (mPrevBound >= 0) return false;
1252160e6d776daab93610b3d12413ad9ff2dd867d8bChih-Chung Chang            switchToFirstImage();
12532c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            mPositionController.startCaptureAnimationSlide(1);
12542c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        } else {
12552c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang            return false;
12562c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        }
12572c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mHolding |= HOLD_CAPTURE_ANIMATION;
1258bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0);
1259bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mHandler.sendMessageDelayed(m, 800);
12602c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        return true;
12612c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
12622c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
1263bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    private void captureAnimationDone(int offset) {
12642c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        mHolding &= ~HOLD_CAPTURE_ANIMATION;
1265bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        if (offset == 1) {
1266bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            // move out of camera, unlock
126761f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            if (!mFilmMode) {
126861f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang                // Now the capture animation is done, enable the action bar.
126961f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang                mListener.onActionBarAllowed(true);
127061f94714c3702115d2f89bb5f8829697be0c3680Chih-Chung Chang            }
1271bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        }
12722c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang        snapback();
12732c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    }
12742c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang
12752c6173822a612597c79be41b126367ddfbb5d518Chih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1276b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Card deck effect calculation
1277b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1278b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang
1279cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Returns the scrolling progress value for an object moving out of a
1280cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // view. The progress value measures how much the object has moving out of
1281cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // the view. The object currently displays in [left, right), and the view is
1282cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // at [0, viewWidth].
1283cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    //
1284cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // The returned value is negative when the object is moving right, and
1285cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // positive when the object is moving left. The value goes to -1 or 1 when
1286cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // the object just moves out of the view completely. The value is 0 if the
1287cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // object currently fills the view.
1288cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private static float calculateMoveOutProgress(int left, int right,
1289cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            int viewWidth) {
1290cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // w = object width
1291cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // viewWidth = view width
1292cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        int w = right - left;
1293cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1294cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // If the object width is smaller than the view width,
1295cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //      |....view....|
1296cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //                   |<-->|      progress = -1 when left = viewWidth
1297bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        //          |<-->|               progress = 0 when left = viewWidth / 2 - w / 2
1298cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // |<-->|                        progress = 1 when left = -w
1299cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        if (w < viewWidth) {
1300bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            int zx = viewWidth / 2 - w / 2;
1301bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            if (left > zx) {
1302bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                return -(left - zx) / (float) (viewWidth - zx);  // progress = (0, -1]
1303bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            } else {
1304bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang                return (left - zx) / (float) (-w - zx);  // progress = [0, 1]
1305bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang            }
1306cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1307cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1308cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // If the object width is larger than the view width,
1309cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //             |..view..|
1310cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //                      |<--------->| progress = -1 when left = viewWidth
1311cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //             |<--------->|          progress = 0 between left = 0
1312cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        //          |<--------->|                          and right = viewWidth
1313cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        // |<--------->|                      progress = 1 when right = 0
1314cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        if (left > 0) {
1315cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            return -left / (float) viewWidth;
1316cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1317cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1318cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        if (right < viewWidth) {
1319cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            return (viewWidth - right) / (float) viewWidth;
1320cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1321cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1322cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        return 0;
1323cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
1324cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1325cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Maps a scrolling progress value to the alpha factor in the fading
1326cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // animation.
1327cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private float getScrollAlpha(float scrollProgress) {
1328cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        return scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
1329cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                     1 - Math.abs(scrollProgress)) : 1.0f;
1330cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
1331cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1332cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // Maps a scrolling progress value to the scaling factor in the fading
1333cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // animation.
1334cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private float getScrollScale(float scrollProgress) {
1335cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        float interpolatedProgress = mScaleInterpolator.getInterpolation(
1336cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                Math.abs(scrollProgress));
1337cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        float scale = (1 - interpolatedProgress) +
1338cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                interpolatedProgress * TRANSITION_SCALE_FACTOR;
1339cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        return scale;
1340cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    }
1341cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1342cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1343cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // This interpolator emulates the rate at which the perceived scale of an
1344cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // object changes as its distance from a camera increases. When this
1345cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // interpolator is applied to a scale animation on a view, it evokes the
1346cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    // sense that the object is shrinking due to moving away from the camera.
1347cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang    private static class ZInterpolator {
1348cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        private float focalLength;
1349cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1350cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        public ZInterpolator(float foc) {
1351cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            focalLength = foc;
1352cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        }
1353cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang
1354cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang        public float getInterpolation(float input) {
1355cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang            return (1.0f - focalLength / (focalLength + input)) /
1356cb4fb7c19f20405fb5e08513e6297dffce824118Chih-Chung Chang                (1.0f - focalLength / (focalLength + 1.0f));
1357f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
1358f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1359f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1360ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    // Returns an interpolated value for the page/film transition.
1361ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    // When ratio = 0, the result is from.
1362ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    // When ratio = 1, the result is to.
1363ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    private static float interpolate(float ratio, float from, float to) {
1364ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang        return from + (to - from) * ratio * ratio;
1365ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang    }
1366ba12eae90b5b1a80ee002aa0df8c5c5189c4faa3Chih-Chung Chang
1367b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1368b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    //  Simple public utilities
1369b7ec5534c7b539be2397c27cfa5e8b992974c12dChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
1370f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1371bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang    public void setListener(Listener listener) {
1372bd141b5a51c96f6fcaddfa547f0928ce69cf0755Chih-Chung Chang        mListener = listener;
1373f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
1374f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin}
1375