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