KeyguardWidgetFrame.java revision 44dc1413768b5798168fb29ea277affb9a739033
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 */
16
17package com.android.internal.policy.impl.keyguard;
18
19import android.animation.Animator;
20import android.animation.ObjectAnimator;
21import android.animation.PropertyValuesHolder;
22import android.appwidget.AppWidgetHostView;
23import android.appwidget.AppWidgetManager;
24import android.content.Context;
25import android.content.res.Resources;
26import android.graphics.Canvas;
27import android.graphics.LinearGradient;
28import android.graphics.Paint;
29import android.graphics.PorterDuff;
30import android.graphics.PorterDuffXfermode;
31import android.graphics.Rect;
32import android.graphics.Shader;
33import android.graphics.drawable.Drawable;
34import android.os.Handler;
35import android.util.AttributeSet;
36import android.view.MotionEvent;
37import android.view.View;
38import android.widget.FrameLayout;
39
40import com.android.internal.R;
41
42public class KeyguardWidgetFrame extends FrameLayout {
43    private final static PorterDuffXfermode sAddBlendMode =
44            new PorterDuffXfermode(PorterDuff.Mode.ADD);
45
46    static final float OUTLINE_ALPHA_MULTIPLIER = 0.6f;
47    static final int HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR = 0x99FF0000;
48
49    // Temporarily disable this for the time being until we know why the gfx is messing up
50    static final boolean ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY = true;
51
52    private int mGradientColor;
53    private LinearGradient mForegroundGradient;
54    private LinearGradient mLeftToRightGradient;
55    private LinearGradient mRightToLeftGradient;
56    private Paint mGradientPaint = new Paint();
57    boolean mLeftToRight = true;
58
59    private float mOverScrollAmount = 0f;
60    private final Rect mForegroundRect = new Rect();
61    private int mForegroundAlpha = 0;
62    private CheckLongPressHelper mLongPressHelper;
63    private Animator mFrameFade;
64    private boolean mIsSmall = false;
65    private Handler mWorkerHandler;
66
67    private float mBackgroundAlpha;
68    private float mContentAlpha;
69    private float mBackgroundAlphaMultiplier = 1.0f;
70    private Drawable mBackgroundDrawable;
71    private Rect mBackgroundRect = new Rect();
72    private int mLastMeasuredWidth = -1;
73    private int mLastMeasuredHeight = 1;
74
75    // These variables are all needed in order to size things properly before we're actually
76    // measured.
77    private int mSmallWidgetHeight;
78    private int mSmallFrameHeight;
79    private boolean mWidgetLockedSmall = false;
80    private int mMaxChallengeTop = -1;
81    private int mFrameStrokeAdjustment;
82
83    // This will hold the width value before we've actually been measured
84    private int mFrameHeight;
85
86    private boolean mIsHoveringOverDeleteDropTarget;
87
88    // Multiple callers may try and adjust the alpha of the frame. When a caller shows
89    // the outlines, we give that caller control, and nobody else can fade them out.
90    // This prevents animation conflicts.
91    private Object mBgAlphaController;
92
93    public KeyguardWidgetFrame(Context context) {
94        this(context, null, 0);
95    }
96
97    public KeyguardWidgetFrame(Context context, AttributeSet attrs) {
98        this(context, attrs, 0);
99    }
100
101    public KeyguardWidgetFrame(Context context, AttributeSet attrs, int defStyle) {
102        super(context, attrs, defStyle);
103
104        mLongPressHelper = new CheckLongPressHelper(this);
105
106        Resources res = context.getResources();
107        // TODO: this padding should really correspond to the padding embedded in the background
108        // drawable (ie. outlines).
109        float density = res.getDisplayMetrics().density;
110        int padding = (int) (res.getDisplayMetrics().density * 8);
111        setPadding(padding, padding, padding, padding);
112
113        mFrameStrokeAdjustment = 2 + (int) (2 * density);
114
115        // This will be overriden on phones based on the current security mode, however on tablets
116        // we need to specify a height.
117        mSmallWidgetHeight =
118                res.getDimensionPixelSize(com.android.internal.R.dimen.kg_small_widget_height);
119        mBackgroundDrawable = res.getDrawable(R.drawable.kg_widget_bg_padded);
120        mGradientColor = res.getColor(com.android.internal.R.color.kg_widget_pager_gradient);
121        mGradientPaint.setXfermode(sAddBlendMode);
122    }
123
124    @Override
125    protected void onDetachedFromWindow() {
126        cancelLongPress();
127    }
128
129    void setIsHoveringOverDeleteDropTarget(boolean isHovering) {
130        if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) {
131            if (mIsHoveringOverDeleteDropTarget != isHovering) {
132                mIsHoveringOverDeleteDropTarget = isHovering;
133                invalidate();
134            }
135        }
136    }
137
138    @Override
139    public boolean onInterceptTouchEvent(MotionEvent ev) {
140        // Watch for longpress events at this level to make sure
141        // users can always pick up this widget
142        switch (ev.getAction()) {
143            case MotionEvent.ACTION_DOWN:
144                mLongPressHelper.postCheckForLongPress(ev);
145                break;
146            case MotionEvent.ACTION_MOVE:
147                mLongPressHelper.onMove(ev);
148                break;
149            case MotionEvent.ACTION_POINTER_DOWN:
150            case MotionEvent.ACTION_UP:
151            case MotionEvent.ACTION_CANCEL:
152                mLongPressHelper.cancelLongPress();
153                break;
154        }
155
156        // Otherwise continue letting touch events fall through to children
157        return false;
158    }
159
160    @Override
161    public boolean onTouchEvent(MotionEvent ev) {
162        // Watch for longpress events at this level to make sure
163        // users can always pick up this widget
164        switch (ev.getAction()) {
165            case MotionEvent.ACTION_MOVE:
166                mLongPressHelper.onMove(ev);
167                break;
168            case MotionEvent.ACTION_POINTER_DOWN:
169            case MotionEvent.ACTION_UP:
170            case MotionEvent.ACTION_CANCEL:
171                mLongPressHelper.cancelLongPress();
172                break;
173        }
174
175        // We return true here to ensure that we will get cancel / up signal
176        // even if none of our children have requested touch.
177        return true;
178    }
179
180    @Override
181    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
182        super.requestDisallowInterceptTouchEvent(disallowIntercept);
183        cancelLongPress();
184    }
185
186    @Override
187    public void cancelLongPress() {
188        super.cancelLongPress();
189        mLongPressHelper.cancelLongPress();
190    }
191
192
193    private void drawGradientOverlay(Canvas c) {
194        mGradientPaint.setShader(mForegroundGradient);
195        mGradientPaint.setAlpha(mForegroundAlpha);
196        c.drawRect(mForegroundRect, mGradientPaint);
197    }
198
199    private void drawHoveringOverDeleteOverlay(Canvas c) {
200        if (mIsHoveringOverDeleteDropTarget) {
201            c.drawColor(HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR);
202        }
203    }
204
205    protected void drawBg(Canvas canvas) {
206        if (mBackgroundAlpha > 0.0f) {
207            Drawable bg = mBackgroundDrawable;
208
209            bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
210            bg.setBounds(mBackgroundRect);
211            bg.draw(canvas);
212        }
213    }
214
215    @Override
216    protected void dispatchDraw(Canvas canvas) {
217        if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) {
218            canvas.save();
219        }
220        drawBg(canvas);
221        super.dispatchDraw(canvas);
222        drawGradientOverlay(canvas);
223        if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) {
224            drawHoveringOverDeleteOverlay(canvas);
225            canvas.restore();
226        }
227    }
228
229    /**
230     * Because this view has fading outlines, it is essential that we enable hardware
231     * layers on the content (child) so that updating the alpha of the outlines doesn't
232     * result in the content layer being recreated.
233     */
234    public void enableHardwareLayersForContent() {
235        View widget = getContent();
236        if (widget != null) {
237            widget.setLayerType(LAYER_TYPE_HARDWARE, null);
238        }
239    }
240
241    /**
242     * Because this view has fading outlines, it is essential that we enable hardware
243     * layers on the content (child) so that updating the alpha of the outlines doesn't
244     * result in the content layer being recreated.
245     */
246    public void disableHardwareLayersForContent() {
247        View widget = getContent();
248        if (widget != null) {
249            widget.setLayerType(LAYER_TYPE_NONE, null);
250        }
251    }
252
253    public void enableHardwareLayers() {
254        setLayerType(LAYER_TYPE_HARDWARE, null);
255    }
256
257    public void disableHardwareLayers() {
258        setLayerType(LAYER_TYPE_NONE, null);
259    }
260
261    public View getContent() {
262        return getChildAt(0);
263    }
264
265    public int getContentAppWidgetId() {
266        View content = getContent();
267        if (content instanceof AppWidgetHostView) {
268            return ((AppWidgetHostView) content).getAppWidgetId();
269        } else if (content instanceof KeyguardStatusView) {
270            return ((KeyguardStatusView) content).getAppWidgetId();
271        } else {
272            return AppWidgetManager.INVALID_APPWIDGET_ID;
273        }
274    }
275
276    public float getBackgroundAlpha() {
277        return mBackgroundAlpha;
278    }
279
280    public void setBackgroundAlphaMultiplier(float multiplier) {
281        if (Float.compare(mBackgroundAlphaMultiplier, multiplier) != 0) {
282            mBackgroundAlphaMultiplier = multiplier;
283            invalidate();
284        }
285    }
286
287    public float getBackgroundAlphaMultiplier() {
288        return mBackgroundAlphaMultiplier;
289    }
290
291    public void setBackgroundAlpha(float alpha) {
292        if (Float.compare(mBackgroundAlpha, alpha) != 0) {
293            mBackgroundAlpha = alpha;
294            invalidate();
295        }
296    }
297
298    public float getContentAlpha() {
299        return mContentAlpha;
300    }
301
302    public void setContentAlpha(float alpha) {
303        mContentAlpha = alpha;
304        View content = getContent();
305        if (content != null) {
306            content.setAlpha(alpha);
307        }
308    }
309
310    /**
311     * Depending on whether the security is up, the widget size needs to change
312     *
313     * @param height The height of the widget, -1 for full height
314     */
315    private void setWidgetHeight(int height) {
316        boolean needLayout = false;
317        View widget = getContent();
318        if (widget != null) {
319            LayoutParams lp = (LayoutParams) widget.getLayoutParams();
320            if (lp.height != height) {
321                needLayout = true;
322                lp.height = height;
323            }
324        }
325        if (needLayout) {
326            requestLayout();
327        }
328    }
329
330    public void setMaxChallengeTop(int top) {
331        boolean dirty = mMaxChallengeTop != top;
332        mMaxChallengeTop = top;
333        mSmallWidgetHeight = top - getPaddingTop();
334        mSmallFrameHeight = top + getPaddingBottom();
335        if (dirty && mIsSmall) {
336            setWidgetHeight(mSmallWidgetHeight);
337            setFrameHeight(mSmallFrameHeight);
338        } else if (dirty && mWidgetLockedSmall) {
339            setWidgetHeight(mSmallWidgetHeight);
340        }
341    }
342
343    public boolean isSmall() {
344        return mIsSmall;
345    }
346
347    public void adjustFrame(int challengeTop) {
348        int frameHeight = challengeTop + getPaddingBottom();
349        setFrameHeight(frameHeight);
350    }
351
352    public void shrinkWidget(boolean alsoShrinkFrame) {
353        mIsSmall = true;
354        setWidgetHeight(mSmallWidgetHeight);
355
356        if (alsoShrinkFrame) {
357            setFrameHeight(mSmallFrameHeight);
358        }
359    }
360
361    public int getSmallFrameHeight() {
362        return mSmallFrameHeight;
363    }
364
365    public void shrinkWidget() {
366        shrinkWidget(true);
367    }
368
369    public void setWidgetLockedSmall(boolean locked) {
370        if (locked) {
371            setWidgetHeight(mSmallWidgetHeight);
372        }
373        mWidgetLockedSmall = locked;
374    }
375
376    public void resetSize() {
377        mIsSmall = false;
378        if (!mWidgetLockedSmall) {
379            setWidgetHeight(LayoutParams.MATCH_PARENT);
380        }
381        setFrameHeight(getMeasuredHeight());
382    }
383
384    public void setFrameHeight(int height) {
385        mFrameHeight = height;
386        mBackgroundRect.set(0, 0, getMeasuredWidth(), Math.min(mFrameHeight, getMeasuredHeight()));
387        mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment,getMeasuredWidth() -
388                mFrameStrokeAdjustment, Math.min(getMeasuredHeight(), mFrameHeight) -
389                mFrameStrokeAdjustment);
390        updateGradient();
391        invalidate();
392    }
393
394    public void hideFrame(Object caller) {
395        fadeFrame(caller, false, 0f, KeyguardWidgetPager.CHILDREN_OUTLINE_FADE_OUT_DURATION);
396    }
397
398    public void showFrame(Object caller) {
399        fadeFrame(caller, true, OUTLINE_ALPHA_MULTIPLIER,
400                KeyguardWidgetPager.CHILDREN_OUTLINE_FADE_IN_DURATION);
401    }
402
403    public void fadeFrame(Object caller, boolean takeControl, float alpha, int duration) {
404        if (takeControl) {
405            mBgAlphaController = caller;
406        }
407
408        if (mBgAlphaController != caller) return;
409
410        if (mFrameFade != null) {
411            mFrameFade.cancel();
412            mFrameFade = null;
413        }
414        PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", alpha);
415        mFrameFade = ObjectAnimator.ofPropertyValuesHolder(this, bgAlpha);
416        mFrameFade.setDuration(duration);
417        mFrameFade.start();
418    }
419
420    private void updateGradient() {
421        float x0 = mLeftToRight ? 0 : mForegroundRect.width();
422        float x1 = mLeftToRight ? mForegroundRect.width(): 0;
423        mLeftToRightGradient = new LinearGradient(x0, 0f, x1, 0f,
424                mGradientColor, 0, Shader.TileMode.CLAMP);
425        mRightToLeftGradient = new LinearGradient(x1, 0f, x0, 0f,
426                mGradientColor, 0, Shader.TileMode.CLAMP);
427    }
428
429    @Override
430    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
431        super.onSizeChanged(w, h, oldw, oldh);
432
433        if (!mIsSmall) {
434            mFrameHeight = h;
435        }
436
437        // mFrameStrokeAdjustment is a cludge to prevent the overlay from drawing outside the
438        // rounded rect background.
439        mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment,
440                w - mFrameStrokeAdjustment, Math.min(h, mFrameHeight) - mFrameStrokeAdjustment);
441
442        mBackgroundRect.set(0, 0, getMeasuredWidth(), Math.min(h, mFrameHeight));
443        updateGradient();
444        invalidate();
445    }
446
447    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
448        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
449        performAppWidgetSizeCallbacksIfNecessary();
450    }
451
452    private void performAppWidgetSizeCallbacksIfNecessary() {
453        View content = getContent();
454        if (!(content instanceof AppWidgetHostView)) return;
455
456        boolean sizeDirty = content.getMeasuredWidth() != mLastMeasuredWidth ||
457                content.getMeasuredHeight() != mLastMeasuredHeight;
458        if (sizeDirty) {
459
460        }
461
462        AppWidgetHostView awhv = (AppWidgetHostView) content;
463        float density = getResources().getDisplayMetrics().density;
464
465        int width = (int) (content.getMeasuredWidth() / density);
466        int height = (int) (content.getMeasuredHeight() / density);
467        awhv.updateAppWidgetSize(null, width, height, width, height, true);
468    }
469
470    void setOverScrollAmount(float r, boolean left) {
471        if (Float.compare(mOverScrollAmount, r) != 0) {
472            mOverScrollAmount = r;
473            mForegroundGradient = left ? mLeftToRightGradient : mRightToLeftGradient;
474            mForegroundAlpha = (int) Math.round((0.5f * r * 255));
475
476            // We bump up the alpha of the outline to hide the fact that the overlay is drawing
477            // over the rounded part of the frame.
478            float bgAlpha = Math.min(OUTLINE_ALPHA_MULTIPLIER + r * (1 - OUTLINE_ALPHA_MULTIPLIER),
479                    1f);
480            setBackgroundAlpha(bgAlpha);
481            invalidate();
482        }
483    }
484
485    public void onActive(boolean isActive) {
486        // hook for subclasses
487    }
488
489    public boolean onUserInteraction(MotionEvent event) {
490        // hook for subclasses
491        return false;
492    }
493
494    public void setWorkerHandler(Handler workerHandler) {
495        mWorkerHandler = workerHandler;
496    }
497
498    public Handler getWorkerHandler() {
499        return mWorkerHandler;
500    }
501}
502