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