1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.keyguard;
17
18import android.animation.Animator;
19import android.animation.Animator.AnimatorListener;
20import android.animation.AnimatorListenerAdapter;
21import android.os.Handler;
22import android.os.Looper;
23import android.view.View;
24
25public class KeyguardViewStateManager implements
26        SlidingChallengeLayout.OnChallengeScrolledListener,
27        ChallengeLayout.OnBouncerStateChangedListener {
28
29    private static final String TAG = "KeyguardViewStateManager";
30    private KeyguardWidgetPager mKeyguardWidgetPager;
31    private ChallengeLayout mChallengeLayout;
32    private KeyguardHostView mKeyguardHostView;
33    private int[] mTmpPoint = new int[2];
34    private int[] mTmpLoc = new int[2];
35
36    private KeyguardSecurityView mKeyguardSecurityContainer;
37    private static final int SCREEN_ON_HINT_DURATION = 1000;
38    private static final int SCREEN_ON_RING_HINT_DELAY = 300;
39    private static final boolean SHOW_INITIAL_PAGE_HINTS = false;
40    Handler mMainQueue = new Handler(Looper.myLooper());
41
42    int mLastScrollState = SlidingChallengeLayout.SCROLL_STATE_IDLE;
43
44    // Paged view state
45    private int mPageListeningToSlider = -1;
46    private int mCurrentPage = -1;
47    private int mPageIndexOnPageBeginMoving = -1;
48
49    int mChallengeTop = 0;
50
51    private final AnimatorListener mPauseListener = new AnimatorListenerAdapter() {
52        public void onAnimationEnd(Animator animation) {
53            mKeyguardSecurityContainer.onPause();
54        }
55    };
56
57    private final AnimatorListener mResumeListener = new AnimatorListenerAdapter() {
58        public void onAnimationEnd(Animator animation) {
59            if (((View)mKeyguardSecurityContainer).isShown()) {
60                mKeyguardSecurityContainer.onResume(0);
61            }
62        }
63    };
64
65    public KeyguardViewStateManager(KeyguardHostView hostView) {
66        mKeyguardHostView = hostView;
67    }
68
69    public void setPagedView(KeyguardWidgetPager pagedView) {
70        mKeyguardWidgetPager = pagedView;
71        updateEdgeSwiping();
72    }
73
74    public void setChallengeLayout(ChallengeLayout layout) {
75        mChallengeLayout = layout;
76        updateEdgeSwiping();
77    }
78
79    private void updateEdgeSwiping() {
80        if (mChallengeLayout != null && mKeyguardWidgetPager != null) {
81            if (mChallengeLayout.isChallengeOverlapping()) {
82                mKeyguardWidgetPager.setOnlyAllowEdgeSwipes(true);
83            } else {
84                mKeyguardWidgetPager.setOnlyAllowEdgeSwipes(false);
85            }
86        }
87    }
88
89    public boolean isChallengeShowing() {
90        if (mChallengeLayout != null) {
91            return mChallengeLayout.isChallengeShowing();
92        }
93        return false;
94    }
95
96    public boolean isChallengeOverlapping() {
97        if (mChallengeLayout != null) {
98            return mChallengeLayout.isChallengeOverlapping();
99        }
100        return false;
101    }
102
103    public void setSecurityViewContainer(KeyguardSecurityView container) {
104        mKeyguardSecurityContainer = container;
105    }
106
107    public void showBouncer(boolean show) {
108        mChallengeLayout.showBouncer();
109    }
110
111    public boolean isBouncing() {
112        return mChallengeLayout.isBouncing();
113    }
114
115    public void fadeOutSecurity(int duration) {
116        ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration)
117                .setListener(mPauseListener);
118    }
119
120    public void fadeInSecurity(int duration) {
121        ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration)
122                .setListener(mResumeListener);
123    }
124
125    public void onPageBeginMoving() {
126        if (mChallengeLayout.isChallengeOverlapping() &&
127                mChallengeLayout instanceof SlidingChallengeLayout) {
128            SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
129            scl.fadeOutChallenge();
130            mPageIndexOnPageBeginMoving = mKeyguardWidgetPager.getCurrentPage();
131        }
132        // We use mAppWidgetToShow to show a particular widget after you add it--
133        // once the user swipes a page we clear that behavior
134        if (mKeyguardHostView != null) {
135            mKeyguardHostView.clearAppWidgetToShow();
136            mKeyguardHostView.setOnDismissAction(null);
137        }
138        if (mHideHintsRunnable != null) {
139            mMainQueue.removeCallbacks(mHideHintsRunnable);
140            mHideHintsRunnable = null;
141        }
142    }
143
144    public void onPageEndMoving() {
145        mPageIndexOnPageBeginMoving = -1;
146    }
147
148    public void onPageSwitching(View newPage, int newPageIndex) {
149        if (mKeyguardWidgetPager != null && mChallengeLayout instanceof SlidingChallengeLayout) {
150            boolean isCameraPage = newPage instanceof CameraWidgetFrame;
151            if (isCameraPage) {
152                CameraWidgetFrame camera = (CameraWidgetFrame) newPage;
153                camera.setUseFastTransition(mKeyguardWidgetPager.isWarping());
154            }
155            SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
156            scl.setChallengeInteractive(!isCameraPage);
157            final int currentFlags = mKeyguardWidgetPager.getSystemUiVisibility();
158            final int newFlags = isCameraPage ? (currentFlags | View.STATUS_BAR_DISABLE_SEARCH)
159                    : (currentFlags & ~View.STATUS_BAR_DISABLE_SEARCH);
160            mKeyguardWidgetPager.setSystemUiVisibility(newFlags);
161        }
162
163        // If the page we're settling to is the same as we started on, and the action of
164        // moving the page hid the security, we restore it immediately.
165        if (mPageIndexOnPageBeginMoving == mKeyguardWidgetPager.getNextPage() &&
166                mChallengeLayout instanceof SlidingChallengeLayout) {
167            SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
168            scl.fadeInChallenge();
169            mKeyguardWidgetPager.setWidgetToResetOnPageFadeOut(-1);
170        }
171        mPageIndexOnPageBeginMoving = -1;
172    }
173
174    public void onPageSwitched(View newPage, int newPageIndex) {
175        // Reset the previous page size and ensure the current page is sized appropriately.
176        // We only modify the page state if it is not currently under control by the slider.
177        // This prevents conflicts.
178
179        // If the page hasn't switched, don't bother with any of this
180        if (mCurrentPage == newPageIndex) return;
181
182        if (mKeyguardWidgetPager != null && mChallengeLayout != null) {
183            KeyguardWidgetFrame prevPage = mKeyguardWidgetPager.getWidgetPageAt(mCurrentPage);
184            if (prevPage != null && mCurrentPage != mPageListeningToSlider && mCurrentPage
185                    != mKeyguardWidgetPager.getWidgetToResetOnPageFadeOut()) {
186                prevPage.resetSize();
187            }
188
189            KeyguardWidgetFrame newCurPage = mKeyguardWidgetPager.getWidgetPageAt(newPageIndex);
190            boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping();
191            if (challengeOverlapping && !newCurPage.isSmall()
192                    && mPageListeningToSlider != newPageIndex) {
193                newCurPage.shrinkWidget(true);
194            }
195        }
196
197        mCurrentPage = newPageIndex;
198    }
199
200    public void onPageBeginWarp() {
201        fadeOutSecurity(SlidingChallengeLayout.CHALLENGE_FADE_OUT_DURATION);
202        View frame = mKeyguardWidgetPager.getPageAt(mKeyguardWidgetPager.getPageWarpIndex());
203        ((KeyguardWidgetFrame)frame).showFrame(this);
204    }
205
206    public void onPageEndWarp() {
207        fadeInSecurity(SlidingChallengeLayout.CHALLENGE_FADE_IN_DURATION);
208        View frame = mKeyguardWidgetPager.getPageAt(mKeyguardWidgetPager.getPageWarpIndex());
209        ((KeyguardWidgetFrame)frame).hideFrame(this);
210    }
211
212    private int getChallengeTopRelativeToFrame(KeyguardWidgetFrame frame, int top) {
213        mTmpPoint[0] = 0;
214        mTmpPoint[1] = top;
215        mapPoint((View) mChallengeLayout, frame, mTmpPoint);
216        return mTmpPoint[1];
217    }
218
219    /**
220     * Simple method to map a point from one view's coordinates to another's. Note: this method
221     * doesn't account for transforms, so if the views will be transformed, this should not be used.
222     *
223     * @param fromView The view to which the point is relative
224     * @param toView The view into which the point should be mapped
225     * @param pt The point
226     */
227    private void mapPoint(View fromView, View toView, int pt[]) {
228        fromView.getLocationInWindow(mTmpLoc);
229
230        int x = mTmpLoc[0];
231        int y = mTmpLoc[1];
232
233        toView.getLocationInWindow(mTmpLoc);
234        int vX = mTmpLoc[0];
235        int vY = mTmpLoc[1];
236
237        pt[0] += x - vX;
238        pt[1] += y - vY;
239    }
240
241    private void userActivity() {
242        if (mKeyguardHostView != null) {
243            mKeyguardHostView.onUserActivityTimeoutChanged();
244            mKeyguardHostView.userActivity();
245        }
246    }
247
248    @Override
249    public void onScrollStateChanged(int scrollState) {
250        if (mKeyguardWidgetPager == null || mChallengeLayout == null) return;
251
252        boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping();
253
254        if (scrollState == SlidingChallengeLayout.SCROLL_STATE_IDLE) {
255            KeyguardWidgetFrame frame = mKeyguardWidgetPager.getWidgetPageAt(mPageListeningToSlider);
256            if (frame == null) return;
257
258            if (!challengeOverlapping) {
259                if (!mKeyguardWidgetPager.isPageMoving()) {
260                    frame.resetSize();
261                    userActivity();
262                } else {
263                    mKeyguardWidgetPager.setWidgetToResetOnPageFadeOut(mPageListeningToSlider);
264                }
265            }
266            if (frame.isSmall()) {
267                // This is to make sure that if the scroller animation gets cut off midway
268                // that the frame doesn't stay in a partial down position.
269                frame.setFrameHeight(frame.getSmallFrameHeight());
270            }
271            if (scrollState != SlidingChallengeLayout.SCROLL_STATE_FADING) {
272                frame.hideFrame(this);
273            }
274            updateEdgeSwiping();
275
276            if (mChallengeLayout.isChallengeShowing()) {
277                mKeyguardSecurityContainer.onResume(KeyguardSecurityView.VIEW_REVEALED);
278            } else {
279                mKeyguardSecurityContainer.onPause();
280            }
281            mPageListeningToSlider = -1;
282        } else if (mLastScrollState == SlidingChallengeLayout.SCROLL_STATE_IDLE) {
283            // Whether dragging or settling, if the last state was idle, we use this signal
284            // to update the current page who will receive events from the sliding challenge.
285            // We resize the frame as appropriate.
286            mPageListeningToSlider = mKeyguardWidgetPager.getNextPage();
287            KeyguardWidgetFrame frame = mKeyguardWidgetPager.getWidgetPageAt(mPageListeningToSlider);
288            if (frame == null) return;
289
290            // Skip showing the frame and shrinking the widget if we are
291            if (!mChallengeLayout.isBouncing()) {
292                if (scrollState != SlidingChallengeLayout.SCROLL_STATE_FADING) {
293                    frame.showFrame(this);
294                }
295
296                // As soon as the security begins sliding, the widget becomes small (if it wasn't
297                // small to begin with).
298                if (!frame.isSmall()) {
299                    // We need to fetch the final page, in case the pages are in motion.
300                    mPageListeningToSlider = mKeyguardWidgetPager.getNextPage();
301                    frame.shrinkWidget(false);
302                }
303            } else {
304                if (!frame.isSmall()) {
305                    // We need to fetch the final page, in case the pages are in motion.
306                    mPageListeningToSlider = mKeyguardWidgetPager.getNextPage();
307                }
308            }
309
310            // View is on the move.  Pause the security view until it completes.
311            mKeyguardSecurityContainer.onPause();
312        }
313        mLastScrollState = scrollState;
314    }
315
316    @Override
317    public void onScrollPositionChanged(float scrollPosition, int challengeTop) {
318        mChallengeTop = challengeTop;
319        KeyguardWidgetFrame frame = mKeyguardWidgetPager.getWidgetPageAt(mPageListeningToSlider);
320        if (frame != null && mLastScrollState != SlidingChallengeLayout.SCROLL_STATE_FADING) {
321            frame.adjustFrame(getChallengeTopRelativeToFrame(frame, mChallengeTop));
322        }
323    }
324
325    private Runnable mHideHintsRunnable = new Runnable() {
326        @Override
327        public void run() {
328            if (mKeyguardWidgetPager != null) {
329                mKeyguardWidgetPager.hideOutlinesAndSidePages();
330            }
331        }
332    };
333
334    public void showUsabilityHints() {
335        mMainQueue.postDelayed( new Runnable() {
336            @Override
337            public void run() {
338                mKeyguardSecurityContainer.showUsabilityHint();
339            }
340        } , SCREEN_ON_RING_HINT_DELAY);
341        if (SHOW_INITIAL_PAGE_HINTS) {
342            mKeyguardWidgetPager.showInitialPageHints();
343        }
344        if (mHideHintsRunnable != null) {
345            mMainQueue.postDelayed(mHideHintsRunnable, SCREEN_ON_HINT_DURATION);
346        }
347    }
348
349    // ChallengeLayout.OnBouncerStateChangedListener
350    @Override
351    public void onBouncerStateChanged(boolean bouncerActive) {
352        if (bouncerActive) {
353            mKeyguardWidgetPager.zoomOutToBouncer();
354        } else {
355            mKeyguardWidgetPager.zoomInFromBouncer();
356            if (mKeyguardHostView != null) {
357                mKeyguardHostView.setOnDismissAction(null);
358            }
359        }
360    }
361}
362