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