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