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