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