KeyguardWidgetPager.java revision 6cf53bb3a94ec5a6eebc96a315dac32d042f1945
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.AppWidgetProviderInfo;
26import android.content.Context;
27import android.content.res.Resources;
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.widget.FrameLayout;
40
41import com.android.internal.R;
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 boolean mCameraWidgetEnabled;
73
74    private int mWidgetToResetAfterFadeOut;
75
76    // Bouncer
77    protected int BOUNCER_ZOOM_IN_OUT_DURATION = 250;
78    private float BOUNCER_SCALE_FACTOR = 0.67f;
79
80    // Background worker thread: used here for persistence, also made available to widget frames
81    private final HandlerThread mBackgroundWorkerThread;
82    private final Handler mBackgroundWorkerHandler;
83
84    public KeyguardWidgetPager(Context context, AttributeSet attrs) {
85        this(context, attrs, 0);
86    }
87
88    public KeyguardWidgetPager(Context context) {
89        this(null, null, 0);
90    }
91
92    public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) {
93        super(context, attrs, defStyle);
94        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
95            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
96        }
97
98        setPageSwitchListener(this);
99
100        Resources r = getResources();
101        mCameraWidgetEnabled = r.getBoolean(R.bool.kg_enable_camera_default_widget);
102        mCenterSmallWidgetsVertically =
103                r.getBoolean(com.android.internal.R.bool.kg_center_small_widgets_vertically);
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 userActivity() {
184        if (mCallbacks != null) {
185            mCallbacks.onUserActivityTimeoutChanged();
186            mCallbacks.userActivity();
187        }
188    }
189
190    @Override
191    public boolean onTouchEvent(MotionEvent ev) {
192        return captureUserInteraction(ev) || super.onTouchEvent(ev);
193    }
194
195    @Override
196    public boolean onInterceptTouchEvent(MotionEvent ev) {
197        return captureUserInteraction(ev) || super.onInterceptTouchEvent(ev);
198    }
199
200    private boolean captureUserInteraction(MotionEvent ev) {
201        KeyguardWidgetFrame currentWidgetPage = getWidgetPageAt(getCurrentPage());
202        return currentWidgetPage != null && currentWidgetPage.onUserInteraction(ev);
203    }
204
205    public void showPagingFeedback() {
206        // Nothing yet.
207    }
208
209    public long getUserActivityTimeout() {
210        View page = getPageAt(mPage);
211        if (page instanceof ViewGroup) {
212            ViewGroup vg = (ViewGroup) page;
213            View view = vg.getChildAt(0);
214            if (!(view instanceof KeyguardStatusView)
215                    && !(view instanceof KeyguardMultiUserSelectorView)) {
216                return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT;
217            }
218        }
219        return -1;
220    }
221
222    public void setCallbacks(Callbacks callbacks) {
223        mCallbacks = callbacks;
224    }
225
226    public interface Callbacks {
227        public void userActivity();
228        public void onUserActivityTimeoutChanged();
229    }
230
231    public void addWidget(View widget) {
232        addWidget(widget, -1);
233    }
234
235
236    public void onRemoveView(View v) {
237        final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
238        mBackgroundWorkerHandler.post(new Runnable() {
239            @Override
240            public void run() {
241                mLockPatternUtils.removeAppWidget(appWidgetId);
242            }
243        });
244    }
245
246    public void onAddView(View v, final int index) {
247        final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
248        final int[] pagesRange = new int[mTempVisiblePagesRange.length];
249        getVisiblePages(pagesRange);
250        boundByReorderablePages(true, pagesRange);
251        // Subtract from the index to take into account pages before the reorderable
252        // pages (e.g. the "add widget" page)
253        mBackgroundWorkerHandler.post(new Runnable() {
254            @Override
255            public void run() {
256                mLockPatternUtils.addAppWidget(appWidgetId, index - pagesRange[0]);
257            }
258        });
259    }
260
261    /*
262     * We wrap widgets in a special frame which handles drawing the over scroll foreground.
263     */
264    public void addWidget(View widget, int pageIndex) {
265        KeyguardWidgetFrame frame;
266        // All views contained herein should be wrapped in a KeyguardWidgetFrame
267        if (!(widget instanceof KeyguardWidgetFrame)) {
268            frame = new KeyguardWidgetFrame(getContext());
269            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
270                    LayoutParams.MATCH_PARENT);
271            lp.gravity = Gravity.TOP;
272
273            // The framework adds a default padding to AppWidgetHostView. We don't need this padding
274            // for the Keyguard, so we override it to be 0.
275            widget.setPadding(0,  0, 0, 0);
276            frame.addView(widget, lp);
277
278            // We set whether or not this widget supports vertical resizing.
279            if (widget instanceof AppWidgetHostView) {
280                AppWidgetHostView awhv = (AppWidgetHostView) widget;
281                AppWidgetProviderInfo info = awhv.getAppWidgetInfo();
282                if ((info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
283                    frame.setWidgetLockedSmall(false);
284                } else {
285                    // Lock the widget to be small.
286                    frame.setWidgetLockedSmall(true);
287                    if (mCenterSmallWidgetsVertically) {
288                        lp.gravity = Gravity.CENTER;
289                    }
290                }
291            }
292        } else {
293            frame = (KeyguardWidgetFrame) widget;
294        }
295
296        ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams(
297                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
298        frame.setOnLongClickListener(this);
299        frame.setWorkerHandler(mBackgroundWorkerHandler);
300
301        if (pageIndex == -1) {
302            addView(frame, pageLp);
303        } else {
304            addView(frame, pageIndex, pageLp);
305        }
306
307        // Update the frame content description.
308        View content = (widget == frame) ?  frame.getContent() : widget;
309        if (content != null) {
310            String contentDescription = mContext.getString(
311                com.android.internal.R.string.keyguard_accessibility_widget,
312                content.getContentDescription());
313            frame.setContentDescription(contentDescription);
314        }
315    }
316
317    /**
318     * Use addWidget() instead.
319     * @deprecated
320     */
321    @Override
322    public void addView(View child, int index) {
323        enforceKeyguardWidgetFrame(child);
324        super.addView(child, index);
325    }
326
327    /**
328     * Use addWidget() instead.
329     * @deprecated
330     */
331    @Override
332    public void addView(View child, int width, int height) {
333        enforceKeyguardWidgetFrame(child);
334        super.addView(child, width, height);
335    }
336
337    /**
338     * Use addWidget() instead.
339     * @deprecated
340     */
341    @Override
342    public void addView(View child, LayoutParams params) {
343        enforceKeyguardWidgetFrame(child);
344        super.addView(child, params);
345    }
346
347    /**
348     * Use addWidget() instead.
349     * @deprecated
350     */
351    @Override
352    public void addView(View child, int index, LayoutParams params) {
353        enforceKeyguardWidgetFrame(child);
354        super.addView(child, index, params);
355    }
356
357    private void enforceKeyguardWidgetFrame(View child) {
358        if (!(child instanceof KeyguardWidgetFrame)) {
359            throw new IllegalArgumentException(
360                    "KeyguardWidgetPager children must be KeyguardWidgetFrames");
361        }
362    }
363
364    public KeyguardWidgetFrame getWidgetPageAt(int index) {
365        // This is always a valid cast as we've guarded the ability to
366        return (KeyguardWidgetFrame) getChildAt(index);
367    }
368
369    protected void onUnhandledTap(MotionEvent ev) {
370        showPagingFeedback();
371    }
372
373    @Override
374    protected void onPageBeginMoving() {
375        if (mViewStateManager != null) {
376            mViewStateManager.onPageBeginMoving();
377        }
378        if (!isReordering(false)) {
379            showOutlinesAndSidePages();
380        }
381        userActivity();
382    }
383
384    @Override
385    protected void onPageEndMoving() {
386        if (mViewStateManager != null) {
387            mViewStateManager.onPageEndMoving();
388        }
389
390        // In the reordering case, the pages will be faded appropriately on completion
391        // of the zoom in animation.
392        if (!isReordering(false)) {
393            hideOutlinesAndSidePages();
394        }
395    }
396
397    protected void enablePageContentLayers() {
398        int children = getChildCount();
399        for (int i = 0; i < children; i++) {
400            getWidgetPageAt(i).enableHardwareLayersForContent();
401        }
402    }
403
404    protected void disablePageContentLayers() {
405        int children = getChildCount();
406        for (int i = 0; i < children; i++) {
407            getWidgetPageAt(i).disableHardwareLayersForContent();
408        }
409    }
410
411    /*
412     * This interpolator emulates the rate at which the perceived scale of an object changes
413     * as its distance from a camera increases. When this interpolator is applied to a scale
414     * animation on a view, it evokes the sense that the object is shrinking due to moving away
415     * from the camera.
416     */
417    static class ZInterpolator implements TimeInterpolator {
418        private float focalLength;
419
420        public ZInterpolator(float foc) {
421            focalLength = foc;
422        }
423
424        public float getInterpolation(float input) {
425            return (1.0f - focalLength / (focalLength + input)) /
426                (1.0f - focalLength / (focalLength + 1.0f));
427        }
428    }
429
430    @Override
431    protected void overScroll(float amount) {
432        acceleratedOverScroll(amount);
433    }
434
435    float backgroundAlphaInterpolator(float r) {
436        return Math.min(1f, r);
437    }
438
439    private void updatePageAlphaValues(int screenCenter) {
440    }
441
442    public float getAlphaForPage(int screenCenter, int index) {
443        return 1f;
444    }
445
446    public float getOutlineAlphaForPage(int screenCenter, int index) {
447        return getAlphaForPage(screenCenter, index) * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER;
448    }
449
450    protected boolean isOverScrollChild(int index, float scrollProgress) {
451        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
452        return (isInOverscroll && (index == 0 && scrollProgress < 0 ||
453                index == getChildCount() - 1 && scrollProgress > 0));
454    }
455
456    @Override
457    protected void screenScrolled(int screenCenter) {
458        mScreenCenter = screenCenter;
459        updatePageAlphaValues(screenCenter);
460        for (int i = 0; i < getChildCount(); i++) {
461            KeyguardWidgetFrame v = getWidgetPageAt(i);
462            if (v == mDragView) continue;
463            if (v != null) {
464                float scrollProgress = getScrollProgress(screenCenter, v, i);
465
466                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
467
468                if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) {
469                    v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress);
470                    v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0);
471                } else {
472                    v.setRotationY(0f);
473                    v.setOverScrollAmount(0, false);
474                }
475
476                float alpha = v.getAlpha();
477                // If the view has 0 alpha, we set it to be invisible so as to prevent
478                // it from accepting touches
479                if (alpha == 0) {
480                    v.setVisibility(INVISIBLE);
481                } else if (v.getVisibility() != VISIBLE) {
482                    v.setVisibility(VISIBLE);
483                }
484            }
485        }
486    }
487
488    @Override
489    void boundByReorderablePages(boolean isReordering, int[] range) {
490        if (isReordering) {
491            if (isAddWidgetPageVisible()) {
492                range[0]++;
493            }
494            if (isMusicWidgetVisible()) {
495                range[1]--;
496            }
497            if (isCameraWidgetVisible()) {
498                range[1]--;
499            }
500        }
501    }
502
503    /*
504     * Special widgets
505     */
506    boolean isAddWidgetPageVisible() {
507        // TODO: Make proper test once we decide whether the add-page is always showing
508        return true;
509    }
510    boolean isMusicWidgetVisible() {
511        return mViewStateManager.getTransportState() != KeyguardViewStateManager.TRANSPORT_GONE;
512    }
513    boolean isCameraWidgetVisible() {
514        return mCameraWidgetEnabled;
515    }
516
517    protected void reorderStarting() {
518        showOutlinesAndSidePages();
519    }
520
521    @Override
522    protected void onStartReordering() {
523        super.onStartReordering();
524        enablePageContentLayers();
525        reorderStarting();
526    }
527
528    @Override
529    protected void onEndReordering() {
530        super.onEndReordering();
531        hideOutlinesAndSidePages();
532    }
533
534    void showOutlinesAndSidePages() {
535        animateOutlinesAndSidePages(true);
536    }
537
538    void hideOutlinesAndSidePages() {
539        animateOutlinesAndSidePages(false);
540    }
541
542    public void showInitialPageHints() {
543        int count = getChildCount();
544        for (int i = 0; i < count; i++) {
545            KeyguardWidgetFrame child = getWidgetPageAt(i);
546            if (i != mCurrentPage) {
547                child.fadeFrame(this, true, KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER,
548                        CHILDREN_OUTLINE_FADE_IN_DURATION);
549                child.setContentAlpha(0f);
550            } else {
551                child.setBackgroundAlpha(0f);
552                child.setContentAlpha(1f);
553            }
554        }
555    }
556
557    public void showSidePageHints() {
558        animateOutlinesAndSidePages(true, -1);
559    }
560
561    @Override
562    public void onAttachedToWindow() {
563        super.onAttachedToWindow();
564        mHasMeasure = false;
565    }
566
567    @Override
568    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
569        super.onLayout(changed, left, top, right, bottom);
570    }
571
572    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
573        int maxChallengeTop = -1;
574        View parent = (View) getParent();
575        boolean challengeShowing = false;
576        // Widget pages need to know where the top of the sliding challenge is so that they
577        // now how big the widget should be when the challenge is up. We compute it here and
578        // then propagate it to each of our children.
579        if (parent.getParent() instanceof SlidingChallengeLayout) {
580            SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent();
581            int top = scl.getMaxChallengeTop();
582
583            // This is a bit evil, but we need to map a coordinate relative to the SCL into a
584            // coordinate relative to our children, hence we subtract the top padding.s
585            maxChallengeTop = top - getPaddingTop();
586            challengeShowing = scl.isChallengeShowing();
587
588            int count = getChildCount();
589            for (int i = 0; i < count; i++) {
590                KeyguardWidgetFrame frame = getWidgetPageAt(i);
591                frame.setMaxChallengeTop(maxChallengeTop);
592                // On the very first measure pass, if the challenge is showing, we need to make sure
593                // that the widget on the current page is small.
594                if (challengeShowing && i == mCurrentPage && !mHasMeasure) {
595                    frame.shrinkWidget();
596                }
597            }
598        }
599        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
600        mHasMeasure = true;
601    }
602
603    void animateOutlinesAndSidePages(final boolean show) {
604        animateOutlinesAndSidePages(show, -1);
605    }
606
607    public void setWidgetToResetOnPageFadeOut(int widget) {
608        mWidgetToResetAfterFadeOut = widget;
609    }
610
611    public int getWidgetToResetOnPageFadeOut() {
612        return mWidgetToResetAfterFadeOut;
613    }
614
615    void animateOutlinesAndSidePages(final boolean show, int duration) {
616        if (mChildrenOutlineFadeAnimation != null) {
617            mChildrenOutlineFadeAnimation.cancel();
618            mChildrenOutlineFadeAnimation = null;
619        }
620        int count = getChildCount();
621        PropertyValuesHolder alpha;
622        ArrayList<Animator> anims = new ArrayList<Animator>();
623
624        if (duration == -1) {
625            duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION :
626                CHILDREN_OUTLINE_FADE_OUT_DURATION;
627        }
628
629        int curPage = getNextPage();
630        for (int i = 0; i < count; i++) {
631            float finalContentAlpha;
632            if (show) {
633                finalContentAlpha = getAlphaForPage(mScreenCenter, i);
634            } else if (!show && i == curPage) {
635                finalContentAlpha = 1f;
636            } else {
637                finalContentAlpha = 0f;
638            }
639            KeyguardWidgetFrame child = getWidgetPageAt(i);
640
641            alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha);
642            ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha);
643            anims.add(a);
644
645            float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i) : 0f;
646            child.fadeFrame(this, show, finalOutlineAlpha, duration);
647        }
648
649        mChildrenOutlineFadeAnimation = new AnimatorSet();
650        mChildrenOutlineFadeAnimation.playTogether(anims);
651
652        mChildrenOutlineFadeAnimation.setDuration(duration);
653        mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() {
654            @Override
655            public void onAnimationStart(Animator animation) {
656                if (show) {
657                    enablePageContentLayers();
658                }
659            }
660
661            @Override
662            public void onAnimationEnd(Animator animation) {
663                if (!show) {
664                    disablePageContentLayers();
665                    KeyguardWidgetFrame frame = getWidgetPageAt(mWidgetToResetAfterFadeOut);
666                    if (frame != null && !(frame == getWidgetPageAt(mCurrentPage) &&
667                            mViewStateManager.isChallengeOverlapping())) {
668                        frame.resetSize();
669                    }
670                    mWidgetToResetAfterFadeOut = -1;
671                }
672            }
673        });
674        mChildrenOutlineFadeAnimation.start();
675    }
676
677    @Override
678    public boolean onLongClick(View v) {
679        // Disallow long pressing to reorder if the challenge is showing
680        boolean isChallengeOverlapping = mViewStateManager.isChallengeShowing() &&
681                mViewStateManager.isChallengeOverlapping();
682        if (!isChallengeOverlapping && startReordering()) {
683            return true;
684        }
685        return false;
686    }
687
688    public void removeWidget(View view) {
689        if (view instanceof KeyguardWidgetFrame) {
690            removeView(view);
691        } else {
692            // Assume view was wrapped by a KeyguardWidgetFrame in KeyguardWidgetPager#addWidget().
693            // This supports legacy hard-coded "widgets" like KeyguardTransportControlView.
694            int pos = getWidgetPageIndex(view);
695            if (pos != -1) {
696                KeyguardWidgetFrame frame = (KeyguardWidgetFrame) getChildAt(pos);
697                frame.removeView(view);
698                removeView(frame);
699            } else {
700                Slog.w(TAG, "removeWidget() can't find:" + view);
701            }
702        }
703    }
704
705    public int getWidgetPageIndex(View view) {
706        if (view instanceof KeyguardWidgetFrame) {
707            return indexOfChild(view);
708        } else {
709            // View was wrapped by a KeyguardWidgetFrame by KeyguardWidgetPager#addWidget()
710            return indexOfChild((KeyguardWidgetFrame)view.getParent());
711        }
712    }
713
714    @Override
715    protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {
716        KeyguardWidgetFrame child = getWidgetPageAt(viewIndex);
717        child.setIsHoveringOverDeleteDropTarget(isHovering);
718    }
719
720    // ChallengeLayout.OnBouncerStateChangedListener
721    @Override
722    public void onBouncerStateChanged(boolean bouncerActive) {
723        if (bouncerActive) {
724            zoomOutToBouncer();
725        } else {
726            zoomInFromBouncer();
727        }
728    }
729
730    // Zoom in after the bouncer is dismissed
731    void zoomInFromBouncer() {
732        if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
733            mZoomInOutAnim.cancel();
734        }
735        final View currentPage = getPageAt(getCurrentPage());
736        if (currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f) {
737            mZoomInOutAnim = new AnimatorSet();
738            mZoomInOutAnim.setDuration(BOUNCER_ZOOM_IN_OUT_DURATION);
739            mZoomInOutAnim.playTogether(
740                    ObjectAnimator.ofFloat(currentPage, "scaleX", 1f),
741                    ObjectAnimator.ofFloat(currentPage , "scaleY", 1f));
742            mZoomInOutAnim.start();
743        }
744    }
745
746    // Zoom out after the bouncer is initiated
747    void zoomOutToBouncer() {
748        if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
749            mZoomInOutAnim.cancel();
750        }
751        View currentPage = getPageAt(getCurrentPage());
752        if (!(currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f)) {
753            mZoomInOutAnim = new AnimatorSet();
754            mZoomInOutAnim.setDuration(BOUNCER_ZOOM_IN_OUT_DURATION);
755            mZoomInOutAnim.playTogether(
756                    ObjectAnimator.ofFloat(currentPage, "scaleX", BOUNCER_SCALE_FACTOR),
757                    ObjectAnimator.ofFloat(currentPage, "scaleY", BOUNCER_SCALE_FACTOR));
758            mZoomInOutAnim.start();
759        }
760    }
761
762    boolean isAddPage(int pageIndex) {
763        View v = getChildAt(pageIndex);
764        return v != null && v.getId() == R.id.keyguard_add_widget;
765    }
766
767    boolean isCameraPage(int pageIndex) {
768        View v = getChildAt(pageIndex);
769        return v != null && v instanceof CameraWidgetFrame;
770    }
771
772    @Override
773    protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
774        return !isCameraPage(childIndex) && super.shouldSetTopAlignedPivotForWidget(childIndex);
775    }
776}
777