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