KeyguardWidgetPager.java revision 1254f2f42f7173ef51d0034975ab5cb7d44f8209
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.ObjectAnimator;
19import android.animation.PropertyValuesHolder;
20import android.animation.TimeInterpolator;
21import android.appwidget.AppWidgetHostView;
22import android.content.Context;
23import android.util.AttributeSet;
24import android.view.Gravity;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.View.OnLongClickListener;
28import android.view.ViewGroup;
29import android.view.animation.AccelerateInterpolator;
30import android.view.animation.DecelerateInterpolator;
31import android.widget.FrameLayout;
32
33import com.android.internal.widget.LockPatternUtils;
34
35public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener,
36        OnLongClickListener {
37
38    ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
39    private static float CAMERA_DISTANCE = 10000;
40    private static float TRANSITION_SCALE_FACTOR = 0.74f;
41    private static float TRANSITION_PIVOT = 0.65f;
42    private static float TRANSITION_MAX_ROTATION = 30;
43    private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
44    private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
45    private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
46    private KeyguardViewStateManager mViewStateManager;
47    private LockPatternUtils mLockPatternUtils;
48
49    // Related to the fading in / out background outlines
50    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
51    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
52    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
53    private ObjectAnimator mChildrenOutlineFadeInAnimation;
54    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
55    private float mChildrenOutlineAlpha = 0;
56    private float mSidePagesAlpha = 1f;
57
58    private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000;
59    private static final boolean CAFETERIA_TRAY = false;
60
61    private int mPage = 0;
62    private Callbacks mCallbacks;
63
64    public KeyguardWidgetPager(Context context, AttributeSet attrs) {
65        this(context, attrs, 0);
66    }
67
68    public KeyguardWidgetPager(Context context) {
69        this(null, null, 0);
70    }
71
72    public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) {
73        super(context, attrs, defStyle);
74        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
75            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
76        }
77
78        setPageSwitchListener(this);
79    }
80
81    public void setViewStateManager(KeyguardViewStateManager viewStateManager) {
82        mViewStateManager = viewStateManager;
83    }
84
85    public void setLockPatternUtils(LockPatternUtils l) {
86        mLockPatternUtils = l;
87    }
88
89    @Override
90    public void onPageSwitch(View newPage, int newPageIndex) {
91        boolean showingStatusWidget = false;
92        if (newPage instanceof ViewGroup) {
93            ViewGroup vg = (ViewGroup) newPage;
94            if (vg.getChildAt(0) instanceof KeyguardStatusView) {
95                showingStatusWidget = true;
96            }
97        }
98
99        // Disable the status bar clock if we're showing the default status widget
100        if (showingStatusWidget) {
101            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK);
102        } else {
103            setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK);
104        }
105
106        // Extend the display timeout if the user switches pages
107        if (mPage != newPageIndex) {
108            int oldPageIndex = mPage;
109            mPage = newPageIndex;
110            if (mCallbacks != null) {
111                mCallbacks.onUserActivityTimeoutChanged();
112                mCallbacks.userActivity();
113            }
114            KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex);
115            if (oldWidgetPage != null) {
116                oldWidgetPage.onActive(false);
117            }
118            KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex);
119            if (newWidgetPage != null) {
120                newWidgetPage.onActive(true);
121            }
122        }
123        if (mViewStateManager != null) {
124            mViewStateManager.onPageSwitch(newPage, newPageIndex);
125        }
126    }
127
128    public void showPagingFeedback() {
129        // Nothing yet.
130    }
131
132    public long getUserActivityTimeout() {
133        View page = getPageAt(mPage);
134        if (page instanceof ViewGroup) {
135            ViewGroup vg = (ViewGroup) page;
136            View view = vg.getChildAt(0);
137            if (!(view instanceof KeyguardStatusView)
138                    && !(view instanceof KeyguardMultiUserSelectorView)) {
139                return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT;
140            }
141        }
142        return -1;
143    }
144
145    public void setCallbacks(Callbacks callbacks) {
146        mCallbacks = callbacks;
147    }
148
149    public interface Callbacks {
150        public void userActivity();
151        public void onUserActivityTimeoutChanged();
152    }
153
154    public void addWidget(View widget) {
155        addWidget(widget, -1);
156    }
157
158
159    public void onRemoveView(View v) {
160        int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
161        mLockPatternUtils.removeAppWidget(appWidgetId);
162    }
163
164    public void onAddView(View v, int index) {
165        int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
166        getVisiblePages(mTempVisiblePagesRange);
167        boundByReorderablePages(true, mTempVisiblePagesRange);
168        // Subtract from the index to take into account pages before the reorderable
169        // pages (e.g. the "add widget" page)
170        mLockPatternUtils.addAppWidget(appWidgetId, index - mTempVisiblePagesRange[0]);
171    }
172
173    /*
174     * We wrap widgets in a special frame which handles drawing the over scroll foreground.
175     */
176    public void addWidget(View widget, int pageIndex) {
177        KeyguardWidgetFrame frame;
178        // All views contained herein should be wrapped in a KeyguardWidgetFrame
179        if (!(widget instanceof KeyguardWidgetFrame)) {
180            frame = new KeyguardWidgetFrame(getContext());
181            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
182                    LayoutParams.MATCH_PARENT);
183            lp.gravity = Gravity.TOP;
184            // The framework adds a default padding to AppWidgetHostView. We don't need this padding
185            // for the Keyguard, so we override it to be 0.
186            widget.setPadding(0,  0, 0, 0);
187            if (widget instanceof AppWidgetHostView) {
188                AppWidgetHostView awhv = (AppWidgetHostView) widget;
189                widget.setContentDescription(awhv.getAppWidgetInfo().label);
190            }
191            frame.addView(widget, lp);
192        } else {
193            frame = (KeyguardWidgetFrame) widget;
194        }
195
196        ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams(
197                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
198        frame.setOnLongClickListener(this);
199
200        if (pageIndex == -1) {
201            addView(frame, pageLp);
202        } else {
203            addView(frame, pageIndex, pageLp);
204        }
205    }
206
207    // We enforce that all children are KeyguardWidgetFrames
208    @Override
209    public void addView(View child, int index) {
210        enforceKeyguardWidgetFrame(child);
211        super.addView(child, index);
212    }
213
214    @Override
215    public void addView(View child, int width, int height) {
216        enforceKeyguardWidgetFrame(child);
217        super.addView(child, width, height);
218    }
219
220    @Override
221    public void addView(View child, LayoutParams params) {
222        enforceKeyguardWidgetFrame(child);
223        super.addView(child, params);
224    }
225
226    @Override
227    public void addView(View child, int index, LayoutParams params) {
228        enforceKeyguardWidgetFrame(child);
229        super.addView(child, index, params);
230    }
231
232    private void enforceKeyguardWidgetFrame(View child) {
233        if (!(child instanceof KeyguardWidgetFrame)) {
234            throw new IllegalArgumentException(
235                    "KeyguardWidgetPager children must be KeyguardWidgetFrames");
236        }
237    }
238
239    public KeyguardWidgetFrame getWidgetPageAt(int index) {
240        // This is always a valid cast as we've guarded the ability to
241        return (KeyguardWidgetFrame) getChildAt(index);
242    }
243
244    protected void onUnhandledTap(MotionEvent ev) {
245        showPagingFeedback();
246    }
247
248    @Override
249    protected void onPageBeginMoving() {
250        // Enable hardware layers while pages are moving
251        // TODO: We should only do this for the two views that are actually moving
252        int children = getChildCount();
253        for (int i = 0; i < children; i++) {
254            getWidgetPageAt(i).enableHardwareLayersForContent();
255        }
256
257        if (mViewStateManager != null) {
258            mViewStateManager.onPageBeginMoving();
259        }
260        showOutlinesAndSidePages();
261    }
262
263    @Override
264    protected void onPageEndMoving() {
265        // Disable hardware layers while pages are moving
266        int children = getChildCount();
267        for (int i = 0; i < children; i++) {
268            getWidgetPageAt(i).disableHardwareLayersForContent();
269        }
270
271        if (mViewStateManager != null) {
272            mViewStateManager.onPageEndMoving();
273        }
274        hideOutlinesAndSidePages();
275    }
276
277    /*
278     * This interpolator emulates the rate at which the perceived scale of an object changes
279     * as its distance from a camera increases. When this interpolator is applied to a scale
280     * animation on a view, it evokes the sense that the object is shrinking due to moving away
281     * from the camera.
282     */
283    static class ZInterpolator implements TimeInterpolator {
284        private float focalLength;
285
286        public ZInterpolator(float foc) {
287            focalLength = foc;
288        }
289
290        public float getInterpolation(float input) {
291            return (1.0f - focalLength / (focalLength + input)) /
292                (1.0f - focalLength / (focalLength + 1.0f));
293        }
294    }
295
296    @Override
297    public String getCurrentPageDescription() {
298        final int nextPageIndex = getNextPage();
299        if (nextPageIndex >= 0 && nextPageIndex < getChildCount()) {
300            KeyguardWidgetFrame frame = getWidgetPageAt(nextPageIndex);
301            CharSequence title = frame.getChildAt(0).getContentDescription();
302            if (title == null) {
303                title = "";
304            }
305            return mContext.getString(
306                    com.android.internal.R.string.keyguard_accessibility_widget_changed,
307                    title, nextPageIndex + 1, getChildCount());
308        }
309        return super.getCurrentPageDescription();
310    }
311
312    @Override
313    protected void overScroll(float amount) {
314        acceleratedOverScroll(amount);
315    }
316
317    float backgroundAlphaInterpolator(float r) {
318        return r;
319    }
320
321    private void updatePageAlphaValues(int screenCenter) {
322        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
323        if (!isInOverscroll) {
324            for (int i = 0; i < getChildCount(); i++) {
325                KeyguardWidgetFrame child = getWidgetPageAt(i);
326                if (child != null) {
327                    float scrollProgress = getScrollProgress(screenCenter, child, i);
328                    // TODO: Set content alpha
329                    if (!isReordering(false)) {
330                        child.setBackgroundAlphaMultiplier(
331                                backgroundAlphaInterpolator(Math.abs(scrollProgress)));
332                    } else {
333                        child.setBackgroundAlphaMultiplier(1f);
334                    }
335                }
336            }
337        }
338    }
339
340    // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
341    @Override
342    protected void screenScrolled(int screenCenter) {
343        super.screenScrolled(screenCenter);
344        updatePageAlphaValues(screenCenter);
345        for (int i = 0; i < getChildCount(); i++) {
346            KeyguardWidgetFrame v = getWidgetPageAt(i);
347            if (v == mDragView) continue;
348            if (v != null) {
349                float scrollProgress = getScrollProgress(screenCenter, v, i);
350                float interpolatedProgress =
351                        mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
352
353                float scale = 1.0f;
354                float translationX = 0;
355                float alpha = 1.0f;
356
357                if (CAFETERIA_TRAY) {
358                    scale = (1 - interpolatedProgress) +
359                            interpolatedProgress * TRANSITION_SCALE_FACTOR;
360                    translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
361
362                    if (scrollProgress < 0) {
363                        alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
364                            1 - Math.abs(scrollProgress)) : 1.0f;
365                    } else {
366                        // On large screens we need to fade the page as it nears its leftmost position
367                        alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
368                    }
369                }
370
371                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
372                int pageWidth = v.getMeasuredWidth();
373                int pageHeight = v.getMeasuredHeight();
374
375                if (PERFORM_OVERSCROLL_ROTATION) {
376                    if (i == 0 && scrollProgress < 0) {
377                        // Overscroll to the left
378                        v.setPivotX(TRANSITION_PIVOT * pageWidth);
379                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
380                        v.setOverScrollAmount(Math.abs(scrollProgress), true);
381                        scale = 1.0f;
382                        alpha = 1.0f;
383                        // On the first page, we don't want the page to have any lateral motion
384                        translationX = 0;
385                    } else if (i == getChildCount() - 1 && scrollProgress > 0) {
386                        // Overscroll to the right
387                        v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
388                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
389                        scale = 1.0f;
390                        alpha = 1.0f;
391                        v.setOverScrollAmount(Math.abs(scrollProgress), false);
392                        // On the last page, we don't want the page to have any lateral motion.
393                        translationX = 0;
394                    } else {
395                        v.setPivotY(pageHeight / 2.0f);
396                        v.setPivotX(pageWidth / 2.0f);
397                        v.setRotationY(0f);
398                        v.setOverScrollAmount(0, false);
399                    }
400                }
401
402                if (CAFETERIA_TRAY) {
403                    v.setTranslationX(translationX);
404                    v.setScaleX(scale);
405                    v.setScaleY(scale);
406                }
407                v.setAlpha(alpha);
408
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    protected void onStartReordering() {
422        super.onStartReordering();
423        setChildrenOutlineMultiplier(1.0f);
424        showOutlinesAndSidePages();
425    }
426
427    @Override
428    protected void onEndReordering() {
429        super.onEndReordering();
430        hideOutlinesAndSidePages();
431    }
432
433    void showOutlinesAndSidePages() {
434        if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
435        if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
436
437        PropertyValuesHolder outlinesAlpha =
438                PropertyValuesHolder.ofFloat("childrenOutlineAlpha", 1.0f);
439        PropertyValuesHolder sidePagesAlpha = PropertyValuesHolder.ofFloat("sidePagesAlpha", 1.0f);
440        mChildrenOutlineFadeInAnimation =
441                ObjectAnimator.ofPropertyValuesHolder(this, outlinesAlpha, sidePagesAlpha);
442
443        mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
444        mChildrenOutlineFadeInAnimation.start();
445    }
446
447    public void showInitialPageHints() {
448        // We start with everything showing
449        setChildrenOutlineAlpha(1.0f);
450        setSidePagesAlpha(1.0f);
451        setChildrenOutlineMultiplier(1.0f);
452
453        int currPage = getCurrentPage();
454        KeyguardWidgetFrame frame = getWidgetPageAt(currPage);
455        frame.setBackgroundAlphaMultiplier(0f);
456    }
457
458    void hideOutlinesAndSidePages() {
459        if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
460        if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
461
462        PropertyValuesHolder outlinesAlpha =
463                PropertyValuesHolder.ofFloat("childrenOutlineAlpha", 0f);
464        PropertyValuesHolder sidePagesAlpha = PropertyValuesHolder.ofFloat("sidePagesAlpha", 0f);
465        mChildrenOutlineFadeOutAnimation =
466                ObjectAnimator.ofPropertyValuesHolder(this, outlinesAlpha, sidePagesAlpha);
467
468        mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
469        mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
470        mChildrenOutlineFadeOutAnimation.start();
471    }
472
473    public void setChildrenOutlineAlpha(float alpha) {
474        mChildrenOutlineAlpha = alpha;
475        for (int i = 0; i < getChildCount(); i++) {
476            getWidgetPageAt(i).setBackgroundAlpha(alpha);
477        }
478    }
479
480    public void setSidePagesAlpha(float alpha) {
481        // This gives the current page, or the destination page if in transit.
482        int curPage = getNextPage();
483        mSidePagesAlpha = alpha;
484        for (int i = 0; i < getChildCount(); i++) {
485            if (curPage != i) {
486                getWidgetPageAt(i).setContentAlpha(alpha);
487            } else {
488                // We lock the current page alpha to 1.
489                getWidgetPageAt(i).setContentAlpha(1.0f);
490            }
491        }
492    }
493
494    public void setChildrenOutlineMultiplier(float alpha) {
495        mChildrenOutlineAlpha = alpha;
496        for (int i = 0; i < getChildCount(); i++) {
497            getWidgetPageAt(i).setBackgroundAlphaMultiplier(alpha);
498        }
499    }
500
501    public float getSidePagesAlpha() {
502        return mSidePagesAlpha;
503    }
504
505    public float getChildrenOutlineAlpha() {
506        return mChildrenOutlineAlpha;
507    }
508
509    @Override
510    public boolean onLongClick(View v) {
511        if (startReordering()) {
512            return true;
513        }
514        return false;
515    }
516}
517