KeyguardWidgetPager.java revision 70009e426a39cc2940d264c4fb87a4402c60b0ff
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.internal.policy.impl.keyguard;
17
18import android.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.AnimatorSet;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.TimeInterpolator;
24import android.appwidget.AppWidgetHostView;
25import android.content.Context;
26import android.content.res.Resources;
27import android.os.Handler;
28import android.os.HandlerThread;
29import android.util.AttributeSet;
30import android.view.Gravity;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.View.OnLongClickListener;
34import android.view.ViewGroup;
35import android.widget.FrameLayout;
36
37import com.android.internal.R;
38import com.android.internal.widget.LockPatternUtils;
39
40import java.util.ArrayList;
41
42public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener,
43        OnLongClickListener {
44
45    ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
46    private static float CAMERA_DISTANCE = 10000;
47    protected static float OVERSCROLL_MAX_ROTATION = 30;
48    private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
49
50    protected KeyguardViewStateManager mViewStateManager;
51    private LockPatternUtils mLockPatternUtils;
52
53    // Related to the fading in / out background outlines
54    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
55    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 75;
56    protected AnimatorSet mChildrenOutlineFadeAnimation;
57    private float mChildrenOutlineAlpha = 0;
58    private float mSidePagesAlpha = 1f;
59    protected int mScreenCenter;
60    private boolean mHasLayout = false;
61    private boolean mHasMeasure = false;
62    boolean showHintsAfterLayout = false;
63
64    private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000;
65
66    private int mPage = 0;
67    private Callbacks mCallbacks;
68
69    private boolean mCameraWidgetEnabled;
70
71    // Background threads to deal with persistence
72    private HandlerThread mBgPersistenceWorkerThread;
73    private Handler mBgPersistenceWorkerHandler;
74
75    public KeyguardWidgetPager(Context context, AttributeSet attrs) {
76        this(context, attrs, 0);
77    }
78
79    public KeyguardWidgetPager(Context context) {
80        this(null, null, 0);
81    }
82
83    public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) {
84        super(context, attrs, defStyle);
85        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
86            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
87        }
88
89        setPageSwitchListener(this);
90
91        Resources r = getResources();
92        mCameraWidgetEnabled = r.getBoolean(R.bool.kg_enable_camera_default_widget);
93        mBgPersistenceWorkerThread = new HandlerThread("KeyguardWidgetPager Persistence");
94        mBgPersistenceWorkerThread.start();
95        mBgPersistenceWorkerHandler = new Handler(mBgPersistenceWorkerThread.getLooper());
96    }
97
98    public void setViewStateManager(KeyguardViewStateManager viewStateManager) {
99        mViewStateManager = viewStateManager;
100    }
101
102    public void setLockPatternUtils(LockPatternUtils l) {
103        mLockPatternUtils = l;
104    }
105
106    @Override
107    public void onPageSwitch(View newPage, int newPageIndex) {
108        boolean showingStatusWidget = false;
109        if (newPage instanceof ViewGroup) {
110            ViewGroup vg = (ViewGroup) newPage;
111            if (vg.getChildAt(0) instanceof KeyguardStatusView) {
112                showingStatusWidget = true;
113            }
114        }
115
116        // Disable the status bar clock if we're showing the default status widget
117        if (showingStatusWidget) {
118            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK);
119        } else {
120            setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK);
121        }
122
123        // Extend the display timeout if the user switches pages
124        if (mPage != newPageIndex) {
125            int oldPageIndex = mPage;
126            mPage = newPageIndex;
127            userActivity();
128            KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex);
129            if (oldWidgetPage != null) {
130                oldWidgetPage.onActive(false);
131            }
132            KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex);
133            if (newWidgetPage != null) {
134                newWidgetPage.onActive(true);
135            }
136        }
137        if (mViewStateManager != null) {
138            mViewStateManager.onPageSwitch(newPage, newPageIndex);
139        }
140    }
141
142    private void userActivity() {
143        if (mCallbacks != null) {
144            mCallbacks.onUserActivityTimeoutChanged();
145            mCallbacks.userActivity();
146        }
147    }
148
149    @Override
150    public boolean onTouchEvent(MotionEvent ev) {
151        KeyguardWidgetFrame currentWidgetPage = getWidgetPageAt(getCurrentPage());
152        if (currentWidgetPage != null && currentWidgetPage.onUserInteraction(ev.getAction())) {
153            return true;
154        }
155        return super.onTouchEvent(ev);
156    }
157
158    public void showPagingFeedback() {
159        // Nothing yet.
160    }
161
162    public long getUserActivityTimeout() {
163        View page = getPageAt(mPage);
164        if (page instanceof ViewGroup) {
165            ViewGroup vg = (ViewGroup) page;
166            View view = vg.getChildAt(0);
167            if (!(view instanceof KeyguardStatusView)
168                    && !(view instanceof KeyguardMultiUserSelectorView)) {
169                return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT;
170            }
171        }
172        return -1;
173    }
174
175    public void setCallbacks(Callbacks callbacks) {
176        mCallbacks = callbacks;
177    }
178
179    public interface Callbacks {
180        public void userActivity();
181        public void onUserActivityTimeoutChanged();
182    }
183
184    public void addWidget(View widget) {
185        addWidget(widget, -1);
186    }
187
188
189    public void onRemoveView(View v) {
190        final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
191        mBgPersistenceWorkerHandler.post(new Runnable() {
192            @Override
193            public void run() {
194                mLockPatternUtils.removeAppWidget(appWidgetId);
195            }
196        });
197    }
198
199    public void onAddView(View v, final int index) {
200        final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
201        final int[] pagesRange = new int[mTempVisiblePagesRange.length];
202        getVisiblePages(pagesRange);
203        boundByReorderablePages(true, pagesRange);
204        // Subtract from the index to take into account pages before the reorderable
205        // pages (e.g. the "add widget" page)
206        mBgPersistenceWorkerHandler.post(new Runnable() {
207            @Override
208            public void run() {
209                mLockPatternUtils.addAppWidget(appWidgetId, index - pagesRange[0]);
210            }
211        });
212    }
213
214    /*
215     * We wrap widgets in a special frame which handles drawing the over scroll foreground.
216     */
217    public void addWidget(View widget, int pageIndex) {
218        KeyguardWidgetFrame frame;
219        // All views contained herein should be wrapped in a KeyguardWidgetFrame
220        if (!(widget instanceof KeyguardWidgetFrame)) {
221            frame = new KeyguardWidgetFrame(getContext());
222            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
223                    LayoutParams.MATCH_PARENT);
224            lp.gravity = Gravity.TOP;
225            // The framework adds a default padding to AppWidgetHostView. We don't need this padding
226            // for the Keyguard, so we override it to be 0.
227            widget.setPadding(0,  0, 0, 0);
228            if (widget instanceof AppWidgetHostView) {
229                AppWidgetHostView awhv = (AppWidgetHostView) widget;
230                widget.setContentDescription(awhv.getAppWidgetInfo().label);
231            }
232            frame.addView(widget, lp);
233        } else {
234            frame = (KeyguardWidgetFrame) widget;
235        }
236
237        ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams(
238                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
239        frame.setOnLongClickListener(this);
240
241        if (pageIndex == -1) {
242            addView(frame, pageLp);
243        } else {
244            addView(frame, pageIndex, pageLp);
245        }
246    }
247
248    // We enforce that all children are KeyguardWidgetFrames
249    @Override
250    public void addView(View child, int index) {
251        enforceKeyguardWidgetFrame(child);
252        super.addView(child, index);
253    }
254
255    @Override
256    public void addView(View child, int width, int height) {
257        enforceKeyguardWidgetFrame(child);
258        super.addView(child, width, height);
259    }
260
261    @Override
262    public void addView(View child, LayoutParams params) {
263        enforceKeyguardWidgetFrame(child);
264        super.addView(child, params);
265    }
266
267    @Override
268    public void addView(View child, int index, LayoutParams params) {
269        enforceKeyguardWidgetFrame(child);
270        super.addView(child, index, params);
271    }
272
273    private void enforceKeyguardWidgetFrame(View child) {
274        if (!(child instanceof KeyguardWidgetFrame)) {
275            throw new IllegalArgumentException(
276                    "KeyguardWidgetPager children must be KeyguardWidgetFrames");
277        }
278    }
279
280    public KeyguardWidgetFrame getWidgetPageAt(int index) {
281        // This is always a valid cast as we've guarded the ability to
282        return (KeyguardWidgetFrame) getChildAt(index);
283    }
284
285    protected void onUnhandledTap(MotionEvent ev) {
286        showPagingFeedback();
287    }
288
289    @Override
290    protected void onPageBeginMoving() {
291        if (mViewStateManager != null) {
292            mViewStateManager.onPageBeginMoving();
293        }
294        if (!isReordering(false)) {
295            showOutlinesAndSidePages();
296        }
297        userActivity();
298    }
299
300    @Override
301    protected void onPageEndMoving() {
302        if (mViewStateManager != null) {
303            mViewStateManager.onPageEndMoving();
304        }
305
306        // In the reordering case, the pages will be faded appropriately on completion
307        // of the zoom in animation.
308        if (!isReordering(false)) {
309            hideOutlinesAndSidePages();
310        }
311    }
312
313    protected void enablePageLayers() {
314        int children = getChildCount();
315        for (int i = 0; i < children; i++) {
316            getWidgetPageAt(i).enableHardwareLayersForContent();
317        }
318    }
319
320    protected void disablePageLayers() {
321        int children = getChildCount();
322        for (int i = 0; i < children; i++) {
323            getWidgetPageAt(i).disableHardwareLayersForContent();
324        }
325    }
326
327    /*
328     * This interpolator emulates the rate at which the perceived scale of an object changes
329     * as its distance from a camera increases. When this interpolator is applied to a scale
330     * animation on a view, it evokes the sense that the object is shrinking due to moving away
331     * from the camera.
332     */
333    static class ZInterpolator implements TimeInterpolator {
334        private float focalLength;
335
336        public ZInterpolator(float foc) {
337            focalLength = foc;
338        }
339
340        public float getInterpolation(float input) {
341            return (1.0f - focalLength / (focalLength + input)) /
342                (1.0f - focalLength / (focalLength + 1.0f));
343        }
344    }
345
346    @Override
347    public String getCurrentPageDescription() {
348        final int nextPageIndex = getNextPage();
349        if (nextPageIndex >= 0 && nextPageIndex < getChildCount()) {
350            KeyguardWidgetFrame frame = getWidgetPageAt(nextPageIndex);
351            CharSequence title = frame.getChildAt(0).getContentDescription();
352            if (title == null) {
353                title = "";
354            }
355            return mContext.getString(
356                    com.android.internal.R.string.keyguard_accessibility_widget_changed,
357                    title, nextPageIndex + 1, getChildCount());
358        }
359        return super.getCurrentPageDescription();
360    }
361
362    @Override
363    protected void overScroll(float amount) {
364        acceleratedOverScroll(amount);
365    }
366
367    float backgroundAlphaInterpolator(float r) {
368        return Math.min(1f, r);
369    }
370
371    private void updatePageAlphaValues(int screenCenter) {
372    }
373
374    public float getAlphaForPage(int screenCenter, int index) {
375        return 1f;
376    }
377
378    public float getOutlineAlphaForPage(int screenCenter, int index) {
379        return getAlphaForPage(screenCenter, index) * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER;
380    }
381
382    protected boolean isOverScrollChild(int index, float scrollProgress) {
383        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
384        return (isInOverscroll && (index == 0 && scrollProgress < 0 ||
385                index == getChildCount() - 1 && scrollProgress > 0));
386    }
387
388    @Override
389    protected void screenScrolled(int screenCenter) {
390        mScreenCenter = screenCenter;
391        updatePageAlphaValues(screenCenter);
392        for (int i = 0; i < getChildCount(); i++) {
393            KeyguardWidgetFrame v = getWidgetPageAt(i);
394            if (v == mDragView) continue;
395            if (v != null) {
396                float scrollProgress = getScrollProgress(screenCenter, v, i);
397
398                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
399
400                if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) {
401                    v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress);
402                    v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0);
403                } else {
404                    v.setRotationY(0f);
405                    v.setOverScrollAmount(0, false);
406                }
407
408                float alpha = v.getAlpha();
409                // If the view has 0 alpha, we set it to be invisible so as to prevent
410                // it from accepting touches
411                if (alpha == 0) {
412                    v.setVisibility(INVISIBLE);
413                } else if (v.getVisibility() != VISIBLE) {
414                    v.setVisibility(VISIBLE);
415                }
416            }
417        }
418    }
419
420    @Override
421    void boundByReorderablePages(boolean isReordering, int[] range) {
422        if (isReordering) {
423            if (isAddWidgetPageVisible()) {
424                range[0]++;
425            }
426            if (isMusicWidgetVisible()) {
427                range[1]--;
428            }
429            if (isCameraWidgetVisible()) {
430                range[1]--;
431            }
432        }
433    }
434
435    /*
436     * Special widgets
437     */
438    boolean isAddWidgetPageVisible() {
439        // TODO: Make proper test once we decide whether the add-page is always showing
440        return true;
441    }
442    boolean isMusicWidgetVisible() {
443        // TODO: Make proper test once we have music in the list
444        return false;
445    }
446    boolean isCameraWidgetVisible() {
447        return mCameraWidgetEnabled;
448    }
449
450    protected void reorderStarting() {
451        showOutlinesAndSidePages();
452    }
453
454    @Override
455    protected void onStartReordering() {
456        super.onStartReordering();
457        enablePageLayers();
458        reorderStarting();
459    }
460
461    @Override
462    protected void onEndReordering() {
463        super.onEndReordering();
464        hideOutlinesAndSidePages();
465    }
466
467    void showOutlinesAndSidePages() {
468        animateOutlinesAndSidePages(true);
469    }
470
471    void hideOutlinesAndSidePages() {
472        animateOutlinesAndSidePages(false);
473    }
474
475    public void showInitialPageHints() {
476        if (mHasLayout) {
477            showOutlinesAndSidePages();
478        } else {
479            // The layout hints depend on layout being run once
480            showHintsAfterLayout = true;
481        }
482    }
483
484    @Override
485    public void onAttachedToWindow() {
486        super.onAttachedToWindow();
487        mHasMeasure = false;
488        mHasLayout = false;
489    }
490
491    @Override
492    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
493        super.onLayout(changed, left, top, right, bottom);
494        if (showHintsAfterLayout) {
495            post(new Runnable() {
496                @Override
497                public void run() {
498                    showOutlinesAndSidePages();
499                }
500            });
501            showHintsAfterLayout = false;
502        }
503        mHasLayout = true;
504    }
505
506    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
507        int maxChallengeTop = -1;
508        View parent = (View) getParent();
509        boolean challengeShowing = false;
510        // Widget pages need to know where the top of the sliding challenge is so that they
511        // now how big the widget should be when the challenge is up. We compute it here and
512        // then propagate it to each of our children.
513        if (parent.getParent() instanceof SlidingChallengeLayout) {
514            SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent();
515            int top = scl.getMaxChallengeTop();
516
517            // This is a bit evil, but we need to map a coordinate relative to the SCL into a
518            // coordinate relative to our children, hence we subtract the top padding.s
519            maxChallengeTop = top - getPaddingTop();
520            challengeShowing = scl.isChallengeShowing();
521        }
522
523        int count = getChildCount();
524        for (int i = 0; i < count; i++) {
525            KeyguardWidgetFrame frame = getWidgetPageAt(i);
526            frame.setMaxChallengeTop(maxChallengeTop);
527
528            // On the very first measure pass, if the challenge is showing, we need to make sure
529            // that the widget on the current page is small.
530            if (challengeShowing && i == mCurrentPage && !mHasMeasure) {
531                frame.shrinkWidget();
532            }
533        }
534        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
535    }
536
537    void animateOutlinesAndSidePages(final boolean show) {
538        animateOutlinesAndSidePages(show, -1);
539    }
540
541    void animateOutlinesAndSidePages(final boolean show, int duration) {
542        if (mChildrenOutlineFadeAnimation != null) {
543            mChildrenOutlineFadeAnimation.cancel();
544            mChildrenOutlineFadeAnimation = null;
545        }
546        int count = getChildCount();
547        PropertyValuesHolder alpha;
548        ArrayList<Animator> anims = new ArrayList<Animator>();
549
550        if (duration == -1) {
551            duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION :
552                CHILDREN_OUTLINE_FADE_OUT_DURATION;
553        }
554
555        int curPage = getNextPage();
556        for (int i = 0; i < count; i++) {
557            float finalContentAlpha;
558            if (show) {
559                finalContentAlpha = getAlphaForPage(mScreenCenter, i);
560            } else if (!show && i == curPage) {
561                finalContentAlpha = 1f;
562            } else {
563                finalContentAlpha = 0f;
564            }
565            KeyguardWidgetFrame child = getWidgetPageAt(i);
566            alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha);
567            ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha);
568            anims.add(a);
569
570            float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i) : 0f;
571            child.fadeFrame(this, show, finalOutlineAlpha, duration);
572        }
573
574        mChildrenOutlineFadeAnimation = new AnimatorSet();
575        mChildrenOutlineFadeAnimation.playTogether(anims);
576
577        mChildrenOutlineFadeAnimation.setDuration(duration);
578        mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() {
579            @Override
580            public void onAnimationStart(Animator animation) {
581                if (show) {
582                    enablePageLayers();
583                }
584            }
585            @Override
586            public void onAnimationEnd(Animator animation) {
587                if (!show) {
588                    disablePageLayers();
589                }
590            }
591        });
592        mChildrenOutlineFadeAnimation.start();
593    }
594
595    public void setChildrenOutlineAlpha(float alpha) {
596        mChildrenOutlineAlpha = alpha;
597        for (int i = 0; i < getChildCount(); i++) {
598            getWidgetPageAt(i).setBackgroundAlpha(alpha);
599        }
600    }
601
602    public void setSidePagesAlpha(float alpha) {
603        // This gives the current page, or the destination page if in transit.
604        int curPage = getNextPage();
605        mSidePagesAlpha = alpha;
606        for (int i = 0; i < getChildCount(); i++) {
607            if (curPage != i) {
608                getWidgetPageAt(i).setContentAlpha(alpha);
609            } else {
610                // We lock the current page alpha to 1.
611                getWidgetPageAt(i).setContentAlpha(1.0f);
612            }
613        }
614    }
615
616    public void setChildrenOutlineMultiplier(float alpha) {
617        mChildrenOutlineAlpha = alpha;
618        for (int i = 0; i < getChildCount(); i++) {
619            getWidgetPageAt(i).setBackgroundAlphaMultiplier(alpha);
620        }
621    }
622
623    public float getSidePagesAlpha() {
624        return mSidePagesAlpha;
625    }
626
627    public float getChildrenOutlineAlpha() {
628        return mChildrenOutlineAlpha;
629    }
630
631    @Override
632    public boolean onLongClick(View v) {
633        // Disallow long pressing to reorder if the challenge is showing
634        boolean isChallengeOverlapping = mViewStateManager.isChallengeShowing() &&
635                mViewStateManager.isChallengeOverlapping();
636        if (!isChallengeOverlapping && startReordering()) {
637            return true;
638        }
639        return false;
640    }
641}
642