| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.keyguard; |
| |
| import android.animation.Animator; |
| import android.animation.ObjectAnimator; |
| import android.animation.PropertyValuesHolder; |
| import android.appwidget.AppWidgetHostView; |
| import android.appwidget.AppWidgetManager; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.LinearGradient; |
| import android.graphics.Paint; |
| import android.graphics.PorterDuff; |
| import android.graphics.PorterDuffXfermode; |
| import android.graphics.Rect; |
| import android.graphics.Shader; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.util.AttributeSet; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.widget.FrameLayout; |
| |
| public class KeyguardWidgetFrame extends FrameLayout { |
| private final static PorterDuffXfermode sAddBlendMode = |
| new PorterDuffXfermode(PorterDuff.Mode.ADD); |
| |
| static final float OUTLINE_ALPHA_MULTIPLIER = 0.6f; |
| static final int HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR = 0x99FF0000; |
| |
| // Temporarily disable this for the time being until we know why the gfx is messing up |
| static final boolean ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY = true; |
| |
| private int mGradientColor; |
| private LinearGradient mForegroundGradient; |
| private LinearGradient mLeftToRightGradient; |
| private LinearGradient mRightToLeftGradient; |
| private Paint mGradientPaint = new Paint(); |
| boolean mLeftToRight = true; |
| |
| private float mOverScrollAmount = 0f; |
| private final Rect mForegroundRect = new Rect(); |
| private int mForegroundAlpha = 0; |
| private CheckLongPressHelper mLongPressHelper; |
| private Animator mFrameFade; |
| private boolean mIsSmall = false; |
| private Handler mWorkerHandler; |
| |
| private float mBackgroundAlpha; |
| private float mContentAlpha; |
| private float mBackgroundAlphaMultiplier = 1.0f; |
| private Drawable mBackgroundDrawable; |
| private Rect mBackgroundRect = new Rect(); |
| |
| // These variables are all needed in order to size things properly before we're actually |
| // measured. |
| private int mSmallWidgetHeight; |
| private int mSmallFrameHeight; |
| private boolean mWidgetLockedSmall = false; |
| private int mMaxChallengeTop = -1; |
| private int mFrameStrokeAdjustment; |
| private boolean mPerformAppWidgetSizeUpdateOnBootComplete; |
| |
| // This will hold the width value before we've actually been measured |
| private int mFrameHeight; |
| |
| private boolean mIsHoveringOverDeleteDropTarget; |
| |
| // Multiple callers may try and adjust the alpha of the frame. When a caller shows |
| // the outlines, we give that caller control, and nobody else can fade them out. |
| // This prevents animation conflicts. |
| private Object mBgAlphaController; |
| |
| public KeyguardWidgetFrame(Context context) { |
| this(context, null, 0); |
| } |
| |
| public KeyguardWidgetFrame(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public KeyguardWidgetFrame(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| mLongPressHelper = new CheckLongPressHelper(this); |
| |
| Resources res = context.getResources(); |
| // TODO: this padding should really correspond to the padding embedded in the background |
| // drawable (ie. outlines). |
| float density = res.getDisplayMetrics().density; |
| int padding = (int) (res.getDisplayMetrics().density * 8); |
| setPadding(padding, padding, padding, padding); |
| |
| mFrameStrokeAdjustment = 2 + (int) (2 * density); |
| |
| // This will be overriden on phones based on the current security mode, however on tablets |
| // we need to specify a height. |
| mSmallWidgetHeight = |
| res.getDimensionPixelSize(R.dimen.kg_small_widget_height); |
| mBackgroundDrawable = res.getDrawable(R.drawable.kg_widget_bg_padded); |
| mGradientColor = res.getColor(R.color.kg_widget_pager_gradient); |
| mGradientPaint.setXfermode(sAddBlendMode); |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| cancelLongPress(); |
| KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallbacks); |
| |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallbacks); |
| } |
| |
| private KeyguardUpdateMonitorCallback mUpdateMonitorCallbacks = |
| new KeyguardUpdateMonitorCallback() { |
| @Override |
| public void onBootCompleted() { |
| if (mPerformAppWidgetSizeUpdateOnBootComplete) { |
| performAppWidgetSizeCallbacksIfNecessary(); |
| mPerformAppWidgetSizeUpdateOnBootComplete = false; |
| } |
| } |
| }; |
| |
| void setIsHoveringOverDeleteDropTarget(boolean isHovering) { |
| if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) { |
| if (mIsHoveringOverDeleteDropTarget != isHovering) { |
| mIsHoveringOverDeleteDropTarget = isHovering; |
| invalidate(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| // Watch for longpress events at this level to make sure |
| // users can always pick up this widget |
| switch (ev.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| mLongPressHelper.postCheckForLongPress(ev); |
| break; |
| case MotionEvent.ACTION_MOVE: |
| mLongPressHelper.onMove(ev); |
| break; |
| case MotionEvent.ACTION_POINTER_DOWN: |
| case MotionEvent.ACTION_UP: |
| case MotionEvent.ACTION_CANCEL: |
| mLongPressHelper.cancelLongPress(); |
| break; |
| } |
| |
| // Otherwise continue letting touch events fall through to children |
| return false; |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| // Watch for longpress events at this level to make sure |
| // users can always pick up this widget |
| switch (ev.getAction()) { |
| case MotionEvent.ACTION_MOVE: |
| mLongPressHelper.onMove(ev); |
| break; |
| case MotionEvent.ACTION_POINTER_DOWN: |
| case MotionEvent.ACTION_UP: |
| case MotionEvent.ACTION_CANCEL: |
| mLongPressHelper.cancelLongPress(); |
| break; |
| } |
| |
| // We return true here to ensure that we will get cancel / up signal |
| // even if none of our children have requested touch. |
| return true; |
| } |
| |
| @Override |
| public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { |
| super.requestDisallowInterceptTouchEvent(disallowIntercept); |
| cancelLongPress(); |
| } |
| |
| @Override |
| public void cancelLongPress() { |
| super.cancelLongPress(); |
| mLongPressHelper.cancelLongPress(); |
| } |
| |
| |
| private void drawGradientOverlay(Canvas c) { |
| mGradientPaint.setShader(mForegroundGradient); |
| mGradientPaint.setAlpha(mForegroundAlpha); |
| c.drawRect(mForegroundRect, mGradientPaint); |
| } |
| |
| private void drawHoveringOverDeleteOverlay(Canvas c) { |
| if (mIsHoveringOverDeleteDropTarget) { |
| c.drawColor(HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR); |
| } |
| } |
| |
| protected void drawBg(Canvas canvas) { |
| if (mBackgroundAlpha > 0.0f) { |
| Drawable bg = mBackgroundDrawable; |
| |
| bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255)); |
| bg.setBounds(mBackgroundRect); |
| bg.draw(canvas); |
| } |
| } |
| |
| @Override |
| protected void dispatchDraw(Canvas canvas) { |
| if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) { |
| canvas.save(); |
| } |
| drawBg(canvas); |
| super.dispatchDraw(canvas); |
| drawGradientOverlay(canvas); |
| if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) { |
| drawHoveringOverDeleteOverlay(canvas); |
| canvas.restore(); |
| } |
| } |
| |
| /** |
| * Because this view has fading outlines, it is essential that we enable hardware |
| * layers on the content (child) so that updating the alpha of the outlines doesn't |
| * result in the content layer being recreated. |
| */ |
| public void enableHardwareLayersForContent() { |
| View widget = getContent(); |
| if (widget != null) { |
| widget.setLayerType(LAYER_TYPE_HARDWARE, null); |
| } |
| } |
| |
| /** |
| * Because this view has fading outlines, it is essential that we enable hardware |
| * layers on the content (child) so that updating the alpha of the outlines doesn't |
| * result in the content layer being recreated. |
| */ |
| public void disableHardwareLayersForContent() { |
| View widget = getContent(); |
| if (widget != null) { |
| widget.setLayerType(LAYER_TYPE_NONE, null); |
| } |
| } |
| |
| public void enableHardwareLayers() { |
| setLayerType(LAYER_TYPE_HARDWARE, null); |
| } |
| |
| public void disableHardwareLayers() { |
| setLayerType(LAYER_TYPE_NONE, null); |
| } |
| |
| public View getContent() { |
| return getChildAt(0); |
| } |
| |
| public int getContentAppWidgetId() { |
| View content = getContent(); |
| if (content instanceof AppWidgetHostView) { |
| return ((AppWidgetHostView) content).getAppWidgetId(); |
| } else if (content instanceof KeyguardStatusView) { |
| return ((KeyguardStatusView) content).getAppWidgetId(); |
| } else { |
| return AppWidgetManager.INVALID_APPWIDGET_ID; |
| } |
| } |
| |
| public float getBackgroundAlpha() { |
| return mBackgroundAlpha; |
| } |
| |
| public void setBackgroundAlphaMultiplier(float multiplier) { |
| if (Float.compare(mBackgroundAlphaMultiplier, multiplier) != 0) { |
| mBackgroundAlphaMultiplier = multiplier; |
| invalidate(); |
| } |
| } |
| |
| public float getBackgroundAlphaMultiplier() { |
| return mBackgroundAlphaMultiplier; |
| } |
| |
| public void setBackgroundAlpha(float alpha) { |
| if (Float.compare(mBackgroundAlpha, alpha) != 0) { |
| mBackgroundAlpha = alpha; |
| invalidate(); |
| } |
| } |
| |
| public float getContentAlpha() { |
| return mContentAlpha; |
| } |
| |
| public void setContentAlpha(float alpha) { |
| mContentAlpha = alpha; |
| View content = getContent(); |
| if (content != null) { |
| content.setAlpha(alpha); |
| } |
| } |
| |
| /** |
| * Depending on whether the security is up, the widget size needs to change |
| * |
| * @param height The height of the widget, -1 for full height |
| */ |
| private void setWidgetHeight(int height) { |
| boolean needLayout = false; |
| View widget = getContent(); |
| if (widget != null) { |
| LayoutParams lp = (LayoutParams) widget.getLayoutParams(); |
| if (lp.height != height) { |
| needLayout = true; |
| lp.height = height; |
| } |
| } |
| if (needLayout) { |
| requestLayout(); |
| } |
| } |
| |
| public void setMaxChallengeTop(int top) { |
| boolean dirty = mMaxChallengeTop != top; |
| mMaxChallengeTop = top; |
| mSmallWidgetHeight = top - getPaddingTop(); |
| mSmallFrameHeight = top + getPaddingBottom(); |
| if (dirty && mIsSmall) { |
| setWidgetHeight(mSmallWidgetHeight); |
| setFrameHeight(mSmallFrameHeight); |
| } else if (dirty && mWidgetLockedSmall) { |
| setWidgetHeight(mSmallWidgetHeight); |
| } |
| } |
| |
| public boolean isSmall() { |
| return mIsSmall; |
| } |
| |
| public void adjustFrame(int challengeTop) { |
| int frameHeight = challengeTop + getPaddingBottom(); |
| setFrameHeight(frameHeight); |
| } |
| |
| public void shrinkWidget(boolean alsoShrinkFrame) { |
| mIsSmall = true; |
| setWidgetHeight(mSmallWidgetHeight); |
| |
| if (alsoShrinkFrame) { |
| setFrameHeight(mSmallFrameHeight); |
| } |
| } |
| |
| public int getSmallFrameHeight() { |
| return mSmallFrameHeight; |
| } |
| |
| public void shrinkWidget() { |
| shrinkWidget(true); |
| } |
| |
| public void setWidgetLockedSmall(boolean locked) { |
| if (locked) { |
| setWidgetHeight(mSmallWidgetHeight); |
| } |
| mWidgetLockedSmall = locked; |
| } |
| |
| public void resetSize() { |
| mIsSmall = false; |
| if (!mWidgetLockedSmall) { |
| setWidgetHeight(LayoutParams.MATCH_PARENT); |
| } |
| setFrameHeight(getMeasuredHeight()); |
| } |
| |
| public void setFrameHeight(int height) { |
| mFrameHeight = height; |
| mBackgroundRect.set(0, 0, getMeasuredWidth(), Math.min(mFrameHeight, getMeasuredHeight())); |
| mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment,getMeasuredWidth() - |
| mFrameStrokeAdjustment, Math.min(getMeasuredHeight(), mFrameHeight) - |
| mFrameStrokeAdjustment); |
| updateGradient(); |
| invalidate(); |
| } |
| |
| public void hideFrame(Object caller) { |
| fadeFrame(caller, false, 0f, KeyguardWidgetPager.CHILDREN_OUTLINE_FADE_OUT_DURATION); |
| } |
| |
| public void showFrame(Object caller) { |
| fadeFrame(caller, true, OUTLINE_ALPHA_MULTIPLIER, |
| KeyguardWidgetPager.CHILDREN_OUTLINE_FADE_IN_DURATION); |
| } |
| |
| public void fadeFrame(Object caller, boolean takeControl, float alpha, int duration) { |
| if (takeControl) { |
| mBgAlphaController = caller; |
| } |
| |
| if (mBgAlphaController != caller && mBgAlphaController != null) { |
| return; |
| } |
| |
| if (mFrameFade != null) { |
| mFrameFade.cancel(); |
| mFrameFade = null; |
| } |
| PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", alpha); |
| mFrameFade = ObjectAnimator.ofPropertyValuesHolder(this, bgAlpha); |
| mFrameFade.setDuration(duration); |
| mFrameFade.start(); |
| } |
| |
| private void updateGradient() { |
| float x0 = mLeftToRight ? 0 : mForegroundRect.width(); |
| float x1 = mLeftToRight ? mForegroundRect.width(): 0; |
| mLeftToRightGradient = new LinearGradient(x0, 0f, x1, 0f, |
| mGradientColor, 0, Shader.TileMode.CLAMP); |
| mRightToLeftGradient = new LinearGradient(x1, 0f, x0, 0f, |
| mGradientColor, 0, Shader.TileMode.CLAMP); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| super.onSizeChanged(w, h, oldw, oldh); |
| |
| if (!mIsSmall) { |
| mFrameHeight = h; |
| } |
| |
| // mFrameStrokeAdjustment is a cludge to prevent the overlay from drawing outside the |
| // rounded rect background. |
| mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment, |
| w - mFrameStrokeAdjustment, Math.min(h, mFrameHeight) - mFrameStrokeAdjustment); |
| |
| mBackgroundRect.set(0, 0, getMeasuredWidth(), Math.min(h, mFrameHeight)); |
| updateGradient(); |
| invalidate(); |
| } |
| |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| performAppWidgetSizeCallbacksIfNecessary(); |
| } |
| |
| private void performAppWidgetSizeCallbacksIfNecessary() { |
| View content = getContent(); |
| if (!(content instanceof AppWidgetHostView)) return; |
| |
| if (!KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) { |
| mPerformAppWidgetSizeUpdateOnBootComplete = true; |
| return; |
| } |
| |
| // TODO: there's no reason to force the AppWidgetHostView to catch duplicate size calls. |
| // We can do that even more cheaply here. It's not an issue right now since we're in the |
| // system process and hence no binder calls. |
| AppWidgetHostView awhv = (AppWidgetHostView) content; |
| float density = getResources().getDisplayMetrics().density; |
| |
| int width = (int) (content.getMeasuredWidth() / density); |
| int height = (int) (content.getMeasuredHeight() / density); |
| awhv.updateAppWidgetSize(null, width, height, width, height, true); |
| } |
| |
| void setOverScrollAmount(float r, boolean left) { |
| if (Float.compare(mOverScrollAmount, r) != 0) { |
| mOverScrollAmount = r; |
| mForegroundGradient = left ? mLeftToRightGradient : mRightToLeftGradient; |
| mForegroundAlpha = (int) Math.round((0.5f * r * 255)); |
| |
| // We bump up the alpha of the outline to hide the fact that the overlay is drawing |
| // over the rounded part of the frame. |
| float bgAlpha = Math.min(OUTLINE_ALPHA_MULTIPLIER + r * (1 - OUTLINE_ALPHA_MULTIPLIER), |
| 1f); |
| setBackgroundAlpha(bgAlpha); |
| invalidate(); |
| } |
| } |
| |
| public void onActive(boolean isActive) { |
| // hook for subclasses |
| } |
| |
| public boolean onUserInteraction(MotionEvent event) { |
| // hook for subclasses |
| return false; |
| } |
| |
| public void onBouncerShowing(boolean showing) { |
| // hook for subclasses |
| } |
| |
| public void setWorkerHandler(Handler workerHandler) { |
| mWorkerHandler = workerHandler; |
| } |
| |
| public Handler getWorkerHandler() { |
| return mWorkerHandler; |
| } |
| |
| } |