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