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