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