KeyguardWidgetPager.java revision 8de9e443d85df22d93898bf92cb612553313aecc
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.AnimatorListenerAdapter;
20import android.animation.AnimatorSet;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.TimeInterpolator;
24import android.appwidget.AppWidgetHostView;
25import android.appwidget.AppWidgetManager;
26import android.appwidget.AppWidgetProviderInfo;
27import android.content.Context;
28import android.os.Handler;
29import android.os.HandlerThread;
30import android.text.format.DateFormat;
31import android.util.AttributeSet;
32import android.util.Slog;
33import android.view.Gravity;
34import android.view.MotionEvent;
35import android.view.View;
36import android.view.View.OnLongClickListener;
37import android.view.ViewGroup;
38import android.view.accessibility.AccessibilityEvent;
39import android.view.accessibility.AccessibilityManager;
40import android.view.animation.DecelerateInterpolator;
41import android.widget.FrameLayout;
42import android.widget.TextClock;
43
44import com.android.internal.widget.LockPatternUtils;
45
46import java.util.ArrayList;
47import java.util.TimeZone;
48
49public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener,
50        OnLongClickListener, ChallengeLayout.OnBouncerStateChangedListener {
51
52    ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
53    private static float CAMERA_DISTANCE = 10000;
54    protected static float OVERSCROLL_MAX_ROTATION = 30;
55    private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
56
57    private static final int FLAG_HAS_LOCAL_HOUR = 0x1;
58    private static final int FLAG_HAS_LOCAL_MINUTE = 0x2;
59
60    protected KeyguardViewStateManager mViewStateManager;
61    private LockPatternUtils mLockPatternUtils;
62
63    // Related to the fading in / out background outlines
64    public static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
65    public static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
66    protected AnimatorSet mChildrenOutlineFadeAnimation;
67    protected int mScreenCenter;
68    private boolean mHasMeasure = false;
69    boolean showHintsAfterLayout = false;
70
71    private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000;
72    private static final String TAG = "KeyguardWidgetPager";
73    private boolean mCenterSmallWidgetsVertically;
74
75    private int mPage = 0;
76    private Callbacks mCallbacks;
77
78    private int mWidgetToResetAfterFadeOut;
79    protected boolean mShowingInitialHints = false;
80
81    // A temporary handle to the Add-Widget view
82    private View mAddWidgetView;
83    private int mLastWidthMeasureSpec;
84    private int mLastHeightMeasureSpec;
85
86    // Bouncer
87    private int mBouncerZoomInOutDuration = 250;
88    private float BOUNCER_SCALE_FACTOR = 0.67f;
89
90    // Background worker thread: used here for persistence, also made available to widget frames
91    private final HandlerThread mBackgroundWorkerThread;
92    private final Handler mBackgroundWorkerHandler;
93    private boolean mCameraEventInProgress;
94
95    public KeyguardWidgetPager(Context context, AttributeSet attrs) {
96        this(context, attrs, 0);
97    }
98
99    public KeyguardWidgetPager(Context context) {
100        this(null, null, 0);
101    }
102
103    public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) {
104        super(context, attrs, defStyle);
105        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
106            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
107        }
108
109        setPageSwitchListener(this);
110
111        mBackgroundWorkerThread = new HandlerThread("KeyguardWidgetPager Worker");
112        mBackgroundWorkerThread.start();
113        mBackgroundWorkerHandler = new Handler(mBackgroundWorkerThread.getLooper());
114    }
115
116    @Override
117    protected void onDetachedFromWindow() {
118        super.onDetachedFromWindow();
119
120        // Clean up the worker thread
121        mBackgroundWorkerThread.quit();
122    }
123
124    public void setViewStateManager(KeyguardViewStateManager viewStateManager) {
125        mViewStateManager = viewStateManager;
126    }
127
128    public void setLockPatternUtils(LockPatternUtils l) {
129        mLockPatternUtils = l;
130    }
131
132    @Override
133    public void onPageSwitching(View newPage, int newPageIndex) {
134        if (mViewStateManager != null) {
135            mViewStateManager.onPageSwitching(newPage, newPageIndex);
136        }
137    }
138
139    @Override
140    public void onPageSwitched(View newPage, int newPageIndex) {
141        boolean showingClock = false;
142        if (newPage instanceof ViewGroup) {
143            ViewGroup vg = (ViewGroup) newPage;
144            if (vg.getChildAt(0) instanceof KeyguardStatusView) {
145                showingClock = true;
146            }
147        }
148
149        if (newPage != null &&
150                findClockInHierarchy(newPage) == (FLAG_HAS_LOCAL_HOUR | FLAG_HAS_LOCAL_MINUTE)) {
151            showingClock = true;
152        }
153
154        // Disable the status bar clock if we're showing the default status widget
155        if (showingClock) {
156            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK);
157        } else {
158            setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK);
159        }
160
161        // Extend the display timeout if the user switches pages
162        if (mPage != newPageIndex) {
163            int oldPageIndex = mPage;
164            mPage = newPageIndex;
165            userActivity();
166            KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex);
167            if (oldWidgetPage != null) {
168                oldWidgetPage.onActive(false);
169            }
170            KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex);
171            if (newWidgetPage != null) {
172                newWidgetPage.onActive(true);
173                newWidgetPage.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
174                newWidgetPage.requestAccessibilityFocus();
175            }
176            if (mParent != null && AccessibilityManager.getInstance(mContext).isEnabled()) {
177                AccessibilityEvent event = AccessibilityEvent.obtain(
178                        AccessibilityEvent.TYPE_VIEW_SCROLLED);
179                onInitializeAccessibilityEvent(event);
180                onPopulateAccessibilityEvent(event);
181                mParent.requestSendAccessibilityEvent(this, event);
182            }
183        }
184        if (mViewStateManager != null) {
185            mViewStateManager.onPageSwitched(newPage, newPageIndex);
186        }
187    }
188
189    @Override
190    public void onPageBeginWarp() {
191        mViewStateManager.onPageBeginWarp();
192    }
193
194    @Override
195    public void onPageEndWarp() {
196        mViewStateManager.onPageEndWarp();
197    }
198
199    @Override
200    public void sendAccessibilityEvent(int eventType) {
201        if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED || isPageMoving()) {
202            super.sendAccessibilityEvent(eventType);
203        }
204    }
205
206    private void updateWidgetFramesImportantForAccessibility() {
207        final int pageCount = getPageCount();
208        for (int i = 0; i < pageCount; i++) {
209            KeyguardWidgetFrame frame = getWidgetPageAt(i);
210            updateWidgetFrameImportantForAccessibility(frame);
211        }
212    }
213
214    private void updateWidgetFrameImportantForAccessibility(KeyguardWidgetFrame frame) {
215        if (frame.getContentAlpha() <= 0) {
216            frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
217        } else {
218            frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
219        }
220    }
221
222    private void userActivity() {
223        if (mCallbacks != null) {
224            mCallbacks.onUserActivityTimeoutChanged();
225            mCallbacks.userActivity();
226        }
227    }
228
229    @Override
230    public boolean onTouchEvent(MotionEvent ev) {
231        return captureUserInteraction(ev) || super.onTouchEvent(ev);
232    }
233
234    @Override
235    public boolean onInterceptTouchEvent(MotionEvent ev) {
236        return captureUserInteraction(ev) || super.onInterceptTouchEvent(ev);
237    }
238
239    private boolean captureUserInteraction(MotionEvent ev) {
240        KeyguardWidgetFrame currentWidgetPage = getWidgetPageAt(getCurrentPage());
241        return currentWidgetPage != null && currentWidgetPage.onUserInteraction(ev);
242    }
243
244    public void showPagingFeedback() {
245        // Nothing yet.
246    }
247
248    public long getUserActivityTimeout() {
249        View page = getPageAt(mPage);
250        if (page instanceof ViewGroup) {
251            ViewGroup vg = (ViewGroup) page;
252            View view = vg.getChildAt(0);
253            if (!(view instanceof KeyguardStatusView)
254                    && !(view instanceof KeyguardMultiUserSelectorView)) {
255                return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT;
256            }
257        }
258        return -1;
259    }
260
261    public void setCallbacks(Callbacks callbacks) {
262        mCallbacks = callbacks;
263    }
264
265    public interface Callbacks {
266        public void userActivity();
267        public void onUserActivityTimeoutChanged();
268        public void onAddView(View v);
269        public void onRemoveView(View v, boolean deletePermanently);
270        public void onRemoveViewAnimationCompleted();
271    }
272
273    public void addWidget(View widget) {
274        addWidget(widget, -1);
275    }
276
277    public void onRemoveView(View v, final boolean deletePermanently) {
278        final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
279        if (mCallbacks != null) {
280            mCallbacks.onRemoveView(v, deletePermanently);
281        }
282        mBackgroundWorkerHandler.post(new Runnable() {
283            @Override
284            public void run() {
285                mLockPatternUtils.removeAppWidget(appWidgetId);
286            }
287        });
288    }
289
290    @Override
291    public void onRemoveViewAnimationCompleted() {
292        if (mCallbacks != null) {
293            mCallbacks.onRemoveViewAnimationCompleted();
294        }
295    }
296
297    public void onAddView(View v, final int index) {
298        final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
299        final int[] pagesRange = new int[mTempVisiblePagesRange.length];
300        getVisiblePages(pagesRange);
301        boundByReorderablePages(true, pagesRange);
302        if (mCallbacks != null) {
303            mCallbacks.onAddView(v);
304        }
305        // Subtract from the index to take into account pages before the reorderable
306        // pages (e.g. the "add widget" page)
307        mBackgroundWorkerHandler.post(new Runnable() {
308            @Override
309            public void run() {
310                mLockPatternUtils.addAppWidget(appWidgetId, index - pagesRange[0]);
311            }
312        });
313    }
314
315    /*
316     * We wrap widgets in a special frame which handles drawing the over scroll foreground.
317     */
318    public void addWidget(View widget, int pageIndex) {
319        KeyguardWidgetFrame frame;
320        // All views contained herein should be wrapped in a KeyguardWidgetFrame
321        if (!(widget instanceof KeyguardWidgetFrame)) {
322            frame = new KeyguardWidgetFrame(getContext());
323            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
324                    LayoutParams.MATCH_PARENT);
325            lp.gravity = Gravity.TOP;
326
327            // The framework adds a default padding to AppWidgetHostView. We don't need this padding
328            // for the Keyguard, so we override it to be 0.
329            widget.setPadding(0,  0, 0, 0);
330            frame.addView(widget, lp);
331
332            // We set whether or not this widget supports vertical resizing.
333            if (widget instanceof AppWidgetHostView) {
334                AppWidgetHostView awhv = (AppWidgetHostView) widget;
335                AppWidgetProviderInfo info = awhv.getAppWidgetInfo();
336                if ((info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
337                    frame.setWidgetLockedSmall(false);
338                } else {
339                    // Lock the widget to be small.
340                    frame.setWidgetLockedSmall(true);
341                    if (mCenterSmallWidgetsVertically) {
342                        lp.gravity = Gravity.CENTER;
343                    }
344                }
345            }
346        } else {
347            frame = (KeyguardWidgetFrame) widget;
348        }
349
350        ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams(
351                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
352        frame.setOnLongClickListener(this);
353        frame.setWorkerHandler(mBackgroundWorkerHandler);
354
355        if (pageIndex == -1) {
356            addView(frame, pageLp);
357        } else {
358            addView(frame, pageIndex, pageLp);
359        }
360
361        // Update the frame content description.
362        View content = (widget == frame) ?  frame.getContent() : widget;
363        if (content != null) {
364            String contentDescription = mContext.getString(
365                R.string.keyguard_accessibility_widget,
366                content.getContentDescription());
367            frame.setContentDescription(contentDescription);
368        }
369        updateWidgetFrameImportantForAccessibility(frame);
370    }
371
372    /**
373     * Use addWidget() instead.
374     * @deprecated
375     */
376    @Override
377    public void addView(View child, int index) {
378        enforceKeyguardWidgetFrame(child);
379        super.addView(child, index);
380    }
381
382    /**
383     * Use addWidget() instead.
384     * @deprecated
385     */
386    @Override
387    public void addView(View child, int width, int height) {
388        enforceKeyguardWidgetFrame(child);
389        super.addView(child, width, height);
390    }
391
392    /**
393     * Use addWidget() instead.
394     * @deprecated
395     */
396    @Override
397    public void addView(View child, LayoutParams params) {
398        enforceKeyguardWidgetFrame(child);
399        super.addView(child, params);
400    }
401
402    /**
403     * Use addWidget() instead.
404     * @deprecated
405     */
406    @Override
407    public void addView(View child, int index, LayoutParams params) {
408        enforceKeyguardWidgetFrame(child);
409        super.addView(child, index, params);
410    }
411
412    private void enforceKeyguardWidgetFrame(View child) {
413        if (!(child instanceof KeyguardWidgetFrame)) {
414            throw new IllegalArgumentException(
415                    "KeyguardWidgetPager children must be KeyguardWidgetFrames");
416        }
417    }
418
419    public KeyguardWidgetFrame getWidgetPageAt(int index) {
420        // This is always a valid cast as we've guarded the ability to
421        return (KeyguardWidgetFrame) getChildAt(index);
422    }
423
424    protected void onUnhandledTap(MotionEvent ev) {
425        showPagingFeedback();
426    }
427
428    @Override
429    protected void onPageBeginMoving() {
430        if (mViewStateManager != null) {
431            mViewStateManager.onPageBeginMoving();
432        }
433        if (!isReordering(false)) {
434            showOutlinesAndSidePages();
435        }
436        userActivity();
437    }
438
439    @Override
440    protected void onPageEndMoving() {
441        if (mViewStateManager != null) {
442            mViewStateManager.onPageEndMoving();
443        }
444
445        // In the reordering case, the pages will be faded appropriately on completion
446        // of the zoom in animation.
447        if (!isReordering(false)) {
448            hideOutlinesAndSidePages();
449        }
450    }
451
452    protected void enablePageContentLayers() {
453        int children = getChildCount();
454        for (int i = 0; i < children; i++) {
455            getWidgetPageAt(i).enableHardwareLayersForContent();
456        }
457    }
458
459    protected void disablePageContentLayers() {
460        int children = getChildCount();
461        for (int i = 0; i < children; i++) {
462            getWidgetPageAt(i).disableHardwareLayersForContent();
463        }
464    }
465
466    /*
467     * This interpolator emulates the rate at which the perceived scale of an object changes
468     * as its distance from a camera increases. When this interpolator is applied to a scale
469     * animation on a view, it evokes the sense that the object is shrinking due to moving away
470     * from the camera.
471     */
472    static class ZInterpolator implements TimeInterpolator {
473        private float focalLength;
474
475        public ZInterpolator(float foc) {
476            focalLength = foc;
477        }
478
479        public float getInterpolation(float input) {
480            return (1.0f - focalLength / (focalLength + input)) /
481                (1.0f - focalLength / (focalLength + 1.0f));
482        }
483    }
484
485    @Override
486    protected void overScroll(float amount) {
487        acceleratedOverScroll(amount);
488    }
489
490    float backgroundAlphaInterpolator(float r) {
491        return Math.min(1f, r);
492    }
493
494    private void updatePageAlphaValues(int screenCenter) {
495    }
496
497    public float getAlphaForPage(int screenCenter, int index, boolean showSidePages) {
498        if (getPageWarpIndex() != -1) {
499            return index == getPageWarpIndex() ? 1.0f : 0.0f;
500        }
501        if (showSidePages) {
502            return 1f;
503        } else {
504            return index == mCurrentPage ? 1.0f : 0f;
505        }
506    }
507
508    public float getOutlineAlphaForPage(int screenCenter, int index, boolean showSidePages) {
509        if (showSidePages) {
510            return getAlphaForPage(screenCenter, index, showSidePages)
511                    * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER;
512        } else {
513            return 0f;
514        }
515    }
516
517    protected boolean isOverScrollChild(int index, float scrollProgress) {
518        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
519        return (isInOverscroll && (index == 0 && scrollProgress < 0 ||
520                index == getChildCount() - 1 && scrollProgress > 0));
521    }
522
523    @Override
524    protected void screenScrolled(int screenCenter) {
525        mScreenCenter = screenCenter;
526        updatePageAlphaValues(screenCenter);
527        for (int i = 0; i < getChildCount(); i++) {
528            KeyguardWidgetFrame v = getWidgetPageAt(i);
529            if (v == mDragView) continue;
530            if (v != null) {
531                float scrollProgress = getScrollProgress(screenCenter, v, i);
532
533                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
534
535                if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) {
536                    float pivotX = v.getMeasuredWidth() / 2;
537                    float pivotY = v.getMeasuredHeight() / 2;
538                    v.setPivotX(pivotX);
539                    v.setPivotY(pivotY);
540                    v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress);
541                    v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0);
542                } else {
543                    v.setRotationY(0f);
544                    v.setOverScrollAmount(0, false);
545                }
546
547                float alpha = v.getAlpha();
548                // If the view has 0 alpha, we set it to be invisible so as to prevent
549                // it from accepting touches
550                if (alpha == 0) {
551                    v.setVisibility(INVISIBLE);
552                } else if (v.getVisibility() != VISIBLE) {
553                    v.setVisibility(VISIBLE);
554                }
555            }
556        }
557    }
558
559    public boolean isWidgetPage(int pageIndex) {
560        if (pageIndex < 0 || pageIndex >= getChildCount()) {
561            return false;
562        }
563        View v = getChildAt(pageIndex);
564        if (v != null && v instanceof KeyguardWidgetFrame) {
565            KeyguardWidgetFrame kwf = (KeyguardWidgetFrame) v;
566            return kwf.getContentAppWidgetId() != AppWidgetManager.INVALID_APPWIDGET_ID;
567        }
568        return false;
569    }
570
571    /**
572     * Returns the bounded set of pages that are re-orderable.  The range is fully inclusive.
573     */
574    @Override
575    void boundByReorderablePages(boolean isReordering, int[] range) {
576        if (isReordering) {
577            // Remove non-widget pages from the range
578            while (range[1] >= range[0] && !isWidgetPage(range[1])) {
579                range[1]--;
580            }
581            while (range[0] <= range[1] && !isWidgetPage(range[0])) {
582                range[0]++;
583            }
584        }
585    }
586
587    protected void reorderStarting() {
588        showOutlinesAndSidePages();
589    }
590
591    @Override
592    protected void onStartReordering() {
593        super.onStartReordering();
594        enablePageContentLayers();
595        reorderStarting();
596    }
597
598    @Override
599    protected void onEndReordering() {
600        super.onEndReordering();
601        hideOutlinesAndSidePages();
602    }
603
604    void showOutlinesAndSidePages() {
605        animateOutlinesAndSidePages(true);
606    }
607
608    void hideOutlinesAndSidePages() {
609        animateOutlinesAndSidePages(false);
610    }
611
612    void updateChildrenContentAlpha(float sidePageAlpha) {
613        int count = getChildCount();
614        for (int i = 0; i < count; i++) {
615            KeyguardWidgetFrame child = getWidgetPageAt(i);
616            if (i != mCurrentPage) {
617                child.setBackgroundAlpha(sidePageAlpha);
618                child.setContentAlpha(0f);
619            } else {
620                child.setBackgroundAlpha(0f);
621                child.setContentAlpha(1f);
622            }
623        }
624    }
625
626    public void showInitialPageHints() {
627        mShowingInitialHints = true;
628        updateChildrenContentAlpha(KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER);
629    }
630
631    @Override
632    void setCurrentPage(int currentPage) {
633        super.setCurrentPage(currentPage);
634        updateChildrenContentAlpha(0.0f);
635        updateWidgetFramesImportantForAccessibility();
636    }
637
638    @Override
639    public void onAttachedToWindow() {
640        super.onAttachedToWindow();
641        mHasMeasure = false;
642    }
643
644    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
645        mLastWidthMeasureSpec = widthMeasureSpec;
646        mLastHeightMeasureSpec = heightMeasureSpec;
647
648        int maxChallengeTop = -1;
649        View parent = (View) getParent();
650        boolean challengeShowing = false;
651        // Widget pages need to know where the top of the sliding challenge is so that they
652        // now how big the widget should be when the challenge is up. We compute it here and
653        // then propagate it to each of our children.
654        if (parent.getParent() instanceof SlidingChallengeLayout) {
655            SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent();
656            int top = scl.getMaxChallengeTop();
657
658            // This is a bit evil, but we need to map a coordinate relative to the SCL into a
659            // coordinate relative to our children, hence we subtract the top padding.s
660            maxChallengeTop = top - getPaddingTop();
661            challengeShowing = scl.isChallengeShowing();
662
663            int count = getChildCount();
664            for (int i = 0; i < count; i++) {
665                KeyguardWidgetFrame frame = getWidgetPageAt(i);
666                frame.setMaxChallengeTop(maxChallengeTop);
667                // On the very first measure pass, if the challenge is showing, we need to make sure
668                // that the widget on the current page is small.
669                if (challengeShowing && i == mCurrentPage && !mHasMeasure) {
670                    frame.shrinkWidget();
671                }
672            }
673        }
674        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
675        mHasMeasure = true;
676    }
677
678    void animateOutlinesAndSidePages(final boolean show) {
679        animateOutlinesAndSidePages(show, -1);
680    }
681
682    public void setWidgetToResetOnPageFadeOut(int widget) {
683        mWidgetToResetAfterFadeOut = widget;
684    }
685
686    public int getWidgetToResetOnPageFadeOut() {
687        return mWidgetToResetAfterFadeOut;
688    }
689
690    void animateOutlinesAndSidePages(final boolean show, int duration) {
691        if (mChildrenOutlineFadeAnimation != null) {
692            mChildrenOutlineFadeAnimation.cancel();
693            mChildrenOutlineFadeAnimation = null;
694        }
695        int count = getChildCount();
696        PropertyValuesHolder alpha;
697        ArrayList<Animator> anims = new ArrayList<Animator>();
698
699        if (duration == -1) {
700            duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION :
701                CHILDREN_OUTLINE_FADE_OUT_DURATION;
702        }
703
704        int curPage = getNextPage();
705        for (int i = 0; i < count; i++) {
706            float finalContentAlpha;
707            if (show) {
708                finalContentAlpha = getAlphaForPage(mScreenCenter, i, true);
709            } else if (!show && i == curPage) {
710                finalContentAlpha = 1f;
711            } else {
712                finalContentAlpha = 0f;
713            }
714            KeyguardWidgetFrame child = getWidgetPageAt(i);
715
716            alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha);
717            ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha);
718            anims.add(a);
719
720            float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i, true) : 0f;
721            child.fadeFrame(this, show, finalOutlineAlpha, duration);
722        }
723
724        mChildrenOutlineFadeAnimation = new AnimatorSet();
725        mChildrenOutlineFadeAnimation.playTogether(anims);
726
727        mChildrenOutlineFadeAnimation.setDuration(duration);
728        mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() {
729            @Override
730            public void onAnimationStart(Animator animation) {
731                if (show) {
732                    enablePageContentLayers();
733                }
734            }
735
736            @Override
737            public void onAnimationEnd(Animator animation) {
738                if (!show) {
739                    disablePageContentLayers();
740                    KeyguardWidgetFrame frame = getWidgetPageAt(mWidgetToResetAfterFadeOut);
741                    if (frame != null && !(frame == getWidgetPageAt(mCurrentPage) &&
742                            mViewStateManager.isChallengeOverlapping())) {
743                        frame.resetSize();
744                    }
745                    mWidgetToResetAfterFadeOut = -1;
746                    mShowingInitialHints = false;
747                }
748                updateWidgetFramesImportantForAccessibility();
749            }
750        });
751        mChildrenOutlineFadeAnimation.start();
752    }
753
754    @Override
755    public boolean onLongClick(View v) {
756        // Disallow long pressing to reorder if the challenge is showing
757        boolean isChallengeOverlapping = mViewStateManager.isChallengeShowing() &&
758                mViewStateManager.isChallengeOverlapping();
759        if (!isChallengeOverlapping && startReordering()) {
760            return true;
761        }
762        return false;
763    }
764
765    public void removeWidget(View view) {
766        if (view instanceof KeyguardWidgetFrame) {
767            removeView(view);
768        } else {
769            // Assume view was wrapped by a KeyguardWidgetFrame in KeyguardWidgetPager#addWidget().
770            // This supports legacy hard-coded "widgets" like KeyguardTransportControlView.
771            int pos = getWidgetPageIndex(view);
772            if (pos != -1) {
773                KeyguardWidgetFrame frame = (KeyguardWidgetFrame) getChildAt(pos);
774                frame.removeView(view);
775                removeView(frame);
776            } else {
777                Slog.w(TAG, "removeWidget() can't find:" + view);
778            }
779        }
780    }
781
782    public int getWidgetPageIndex(View view) {
783        if (view instanceof KeyguardWidgetFrame) {
784            return indexOfChild(view);
785        } else {
786            // View was wrapped by a KeyguardWidgetFrame by KeyguardWidgetPager#addWidget()
787            return indexOfChild((KeyguardWidgetFrame)view.getParent());
788        }
789    }
790
791    @Override
792    protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {
793        KeyguardWidgetFrame child = getWidgetPageAt(viewIndex);
794        child.setIsHoveringOverDeleteDropTarget(isHovering);
795    }
796
797    // ChallengeLayout.OnBouncerStateChangedListener
798    @Override
799    public void onBouncerStateChanged(boolean bouncerActive) {
800        if (bouncerActive) {
801            zoomOutToBouncer();
802        } else {
803            zoomInFromBouncer();
804        }
805    }
806
807    void setBouncerAnimationDuration(int duration) {
808        mBouncerZoomInOutDuration = duration;
809    }
810
811    // Zoom in after the bouncer is dismissed
812    void zoomInFromBouncer() {
813        if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
814            mZoomInOutAnim.cancel();
815        }
816        final View currentPage = getPageAt(getCurrentPage());
817        if (currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f) {
818            mZoomInOutAnim = new AnimatorSet();
819            mZoomInOutAnim.playTogether(
820                    ObjectAnimator.ofFloat(currentPage, "scaleX", 1f),
821                    ObjectAnimator.ofFloat(currentPage , "scaleY", 1f));
822            mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration);
823            mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f));
824            mZoomInOutAnim.start();
825        }
826        if (currentPage instanceof KeyguardWidgetFrame) {
827            ((KeyguardWidgetFrame)currentPage).onBouncerShowing(false);
828        }
829    }
830
831    // Zoom out after the bouncer is initiated
832    void zoomOutToBouncer() {
833        if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
834            mZoomInOutAnim.cancel();
835        }
836        int curPage = getCurrentPage();
837        View currentPage = getPageAt(curPage);
838        if (shouldSetTopAlignedPivotForWidget(curPage)) {
839            currentPage.setPivotY(0);
840            // Note: we are working around the issue that setting the x-pivot to the same value as it
841            //       was does not actually work.
842            currentPage.setPivotX(0);
843            currentPage.setPivotX(currentPage.getMeasuredWidth() / 2);
844        }
845        if (!(currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f)) {
846            mZoomInOutAnim = new AnimatorSet();
847            mZoomInOutAnim.playTogether(
848                    ObjectAnimator.ofFloat(currentPage, "scaleX", BOUNCER_SCALE_FACTOR),
849                    ObjectAnimator.ofFloat(currentPage, "scaleY", BOUNCER_SCALE_FACTOR));
850            mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration);
851            mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f));
852            mZoomInOutAnim.start();
853        }
854        if (currentPage instanceof KeyguardWidgetFrame) {
855            ((KeyguardWidgetFrame)currentPage).onBouncerShowing(true);
856        }
857    }
858
859    void setAddWidgetEnabled(boolean enabled) {
860        if (mAddWidgetView != null && enabled) {
861            addView(mAddWidgetView, 0);
862            // We need to force measure the PagedView so that the calls to update the scroll
863            // position below work
864            measure(mLastWidthMeasureSpec, mLastHeightMeasureSpec);
865            // Bump up the current page to account for the addition of the new page
866            setCurrentPage(mCurrentPage + 1);
867            mAddWidgetView = null;
868        } else if (mAddWidgetView == null && !enabled) {
869            View addWidget = findViewById(R.id.keyguard_add_widget);
870            if (addWidget != null) {
871                mAddWidgetView = addWidget;
872                removeView(addWidget);
873            }
874        }
875    }
876
877    boolean isAddPage(int pageIndex) {
878        View v = getChildAt(pageIndex);
879        return v != null && v.getId() == R.id.keyguard_add_widget;
880    }
881
882    boolean isCameraPage(int pageIndex) {
883        View v = getChildAt(pageIndex);
884        return v != null && v instanceof CameraWidgetFrame;
885    }
886
887    @Override
888    protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
889        return !isCameraPage(childIndex) && super.shouldSetTopAlignedPivotForWidget(childIndex);
890    }
891
892    /**
893     * Search given {@link View} hierarchy for {@link TextClock} instances that
894     * show various time components. Returns combination of
895     * {@link #FLAG_HAS_LOCAL_HOUR} and {@link #FLAG_HAS_LOCAL_MINUTE}.
896     */
897    private static int findClockInHierarchy(View view) {
898        if (view instanceof TextClock) {
899            return getClockFlags((TextClock) view);
900        } else if (view instanceof ViewGroup) {
901            int flags = 0;
902            final ViewGroup group = (ViewGroup) view;
903            final int size = group.getChildCount();
904            for (int i = 0; i < size; i++) {
905                flags |= findClockInHierarchy(group.getChildAt(i));
906            }
907            return flags;
908        } else {
909            return 0;
910        }
911    }
912
913    /**
914     * Return combination of {@link #FLAG_HAS_LOCAL_HOUR} and
915     * {@link #FLAG_HAS_LOCAL_MINUTE} describing the time represented described
916     * by the given {@link TextClock}.
917     */
918    private static int getClockFlags(TextClock clock) {
919        int flags = 0;
920
921        final String timeZone = clock.getTimeZone();
922        if (timeZone != null && !TimeZone.getDefault().equals(TimeZone.getTimeZone(timeZone))) {
923            // Ignore clocks showing another timezone
924            return 0;
925        }
926
927        final CharSequence format = clock.getFormat();
928        final char hour = clock.is24HourModeEnabled() ? DateFormat.HOUR_OF_DAY
929                : DateFormat.HOUR;
930
931        if (DateFormat.hasDesignator(format, hour)) {
932            flags |= FLAG_HAS_LOCAL_HOUR;
933        }
934        if (DateFormat.hasDesignator(format, DateFormat.MINUTE)) {
935            flags |= FLAG_HAS_LOCAL_MINUTE;
936        }
937
938        return flags;
939    }
940
941    public void handleExternalCameraEvent(MotionEvent event) {
942        beginCameraEvent();
943        int cameraPage = getPageCount() - 1;
944        boolean endWarp = false;
945        if (isCameraPage(cameraPage) || mCameraEventInProgress) {
946            switch (event.getAction()) {
947                case MotionEvent.ACTION_DOWN:
948                    // Once we start dispatching camera events, we must continue to do so
949                    // to keep event dispatch happy.
950                    mCameraEventInProgress = true;
951                    userActivity();
952                    startWarp(cameraPage);
953                    break;
954                case MotionEvent.ACTION_UP:
955                case MotionEvent.ACTION_CANCEL:
956                    mCameraEventInProgress = false;
957                    endWarp = true;
958                    break;
959            }
960            dispatchTouchEvent(event);
961            // This has to happen after the event has been handled by the real widget pager
962            if (endWarp) endWarp();
963        }
964        endCameraEvent();
965    }
966
967}
968