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