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