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