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