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