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