KeyguardWidgetFrame.java revision 1962e264660b37b7e386eb2ac84fc915dd7c75dd
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.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 40public 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) { 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 void enableHardwareLayers() { 275 setLayerType(LAYER_TYPE_HARDWARE, null); 276 } 277 278 public void disableHardwareLayers() { 279 setLayerType(LAYER_TYPE_NONE, null); 280 } 281 282 public View getContent() { 283 return getChildAt(0); 284 } 285 286 public int getContentAppWidgetId() { 287 View content = getContent(); 288 if (content instanceof AppWidgetHostView) { 289 return ((AppWidgetHostView) content).getAppWidgetId(); 290 } else if (content instanceof KeyguardStatusView) { 291 return ((KeyguardStatusView) content).getAppWidgetId(); 292 } else { 293 return AppWidgetManager.INVALID_APPWIDGET_ID; 294 } 295 } 296 297 public float getBackgroundAlpha() { 298 return mBackgroundAlpha; 299 } 300 301 public void setBackgroundAlphaMultiplier(float multiplier) { 302 if (Float.compare(mBackgroundAlphaMultiplier, multiplier) != 0) { 303 mBackgroundAlphaMultiplier = multiplier; 304 invalidate(); 305 } 306 } 307 308 public float getBackgroundAlphaMultiplier() { 309 return mBackgroundAlphaMultiplier; 310 } 311 312 public void setBackgroundAlpha(float alpha) { 313 if (Float.compare(mBackgroundAlpha, alpha) != 0) { 314 mBackgroundAlpha = alpha; 315 invalidate(); 316 } 317 } 318 319 public float getContentAlpha() { 320 return mContentAlpha; 321 } 322 323 public void setContentAlpha(float alpha) { 324 mContentAlpha = alpha; 325 View content = getContent(); 326 if (content != null) { 327 content.setAlpha(alpha); 328 } 329 } 330 331 /** 332 * Depending on whether the security is up, the widget size needs to change 333 * 334 * @param height The height of the widget, -1 for full height 335 */ 336 private void setWidgetHeight(int height) { 337 boolean needLayout = false; 338 View widget = getContent(); 339 if (widget != null) { 340 LayoutParams lp = (LayoutParams) widget.getLayoutParams(); 341 if (lp.height != height) { 342 needLayout = true; 343 lp.height = height; 344 } 345 } 346 if (needLayout) { 347 requestLayout(); 348 } 349 } 350 351 public void setMaxChallengeTop(int top) { 352 boolean dirty = mMaxChallengeTop != top; 353 mMaxChallengeTop = top; 354 mSmallWidgetHeight = top - getPaddingTop(); 355 mSmallFrameHeight = top + getPaddingBottom(); 356 if (dirty && mIsSmall) { 357 setWidgetHeight(mSmallWidgetHeight); 358 setFrameHeight(mSmallFrameHeight); 359 } else if (dirty && mWidgetLockedSmall) { 360 setWidgetHeight(mSmallWidgetHeight); 361 } 362 } 363 364 public boolean isSmall() { 365 return mIsSmall; 366 } 367 368 public void adjustFrame(int challengeTop) { 369 int frameHeight = challengeTop + getPaddingBottom(); 370 setFrameHeight(frameHeight); 371 } 372 373 public void shrinkWidget(boolean alsoShrinkFrame) { 374 mIsSmall = true; 375 setWidgetHeight(mSmallWidgetHeight); 376 377 if (alsoShrinkFrame) { 378 setFrameHeight(mSmallFrameHeight); 379 } 380 } 381 382 public int getSmallFrameHeight() { 383 return mSmallFrameHeight; 384 } 385 386 public void shrinkWidget() { 387 shrinkWidget(true); 388 } 389 390 public void setWidgetLockedSmall(boolean locked) { 391 if (locked) { 392 setWidgetHeight(mSmallWidgetHeight); 393 } 394 mWidgetLockedSmall = locked; 395 } 396 397 public void resetSize() { 398 mIsSmall = false; 399 if (!mWidgetLockedSmall) { 400 setWidgetHeight(LayoutParams.MATCH_PARENT); 401 } 402 setFrameHeight(getMeasuredHeight()); 403 } 404 405 public void setFrameHeight(int height) { 406 mFrameHeight = height; 407 mBackgroundRect.set(0, 0, getMeasuredWidth(), Math.min(mFrameHeight, getMeasuredHeight())); 408 mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment,getMeasuredWidth() - 409 mFrameStrokeAdjustment, Math.min(getMeasuredHeight(), mFrameHeight) - 410 mFrameStrokeAdjustment); 411 updateGradient(); 412 invalidate(); 413 } 414 415 public void hideFrame(Object caller) { 416 fadeFrame(caller, false, 0f, KeyguardWidgetPager.CHILDREN_OUTLINE_FADE_OUT_DURATION); 417 } 418 419 public void showFrame(Object caller) { 420 fadeFrame(caller, true, OUTLINE_ALPHA_MULTIPLIER, 421 KeyguardWidgetPager.CHILDREN_OUTLINE_FADE_IN_DURATION); 422 } 423 424 public void fadeFrame(Object caller, boolean takeControl, float alpha, int duration) { 425 if (takeControl) { 426 mBgAlphaController = caller; 427 } 428 429 if (mBgAlphaController != caller && mBgAlphaController != null) { 430 return; 431 } 432 433 if (mFrameFade != null) { 434 mFrameFade.cancel(); 435 mFrameFade = null; 436 } 437 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", alpha); 438 mFrameFade = ObjectAnimator.ofPropertyValuesHolder(this, bgAlpha); 439 mFrameFade.setDuration(duration); 440 mFrameFade.start(); 441 } 442 443 private void updateGradient() { 444 float x0 = mLeftToRight ? 0 : mForegroundRect.width(); 445 float x1 = mLeftToRight ? mForegroundRect.width(): 0; 446 mLeftToRightGradient = new LinearGradient(x0, 0f, x1, 0f, 447 mGradientColor, 0, Shader.TileMode.CLAMP); 448 mRightToLeftGradient = new LinearGradient(x1, 0f, x0, 0f, 449 mGradientColor, 0, Shader.TileMode.CLAMP); 450 } 451 452 @Override 453 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 454 super.onSizeChanged(w, h, oldw, oldh); 455 456 if (!mIsSmall) { 457 mFrameHeight = h; 458 } 459 460 // mFrameStrokeAdjustment is a cludge to prevent the overlay from drawing outside the 461 // rounded rect background. 462 mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment, 463 w - mFrameStrokeAdjustment, Math.min(h, mFrameHeight) - mFrameStrokeAdjustment); 464 465 mBackgroundRect.set(0, 0, getMeasuredWidth(), Math.min(h, mFrameHeight)); 466 updateGradient(); 467 invalidate(); 468 } 469 470 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 471 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 472 performAppWidgetSizeCallbacksIfNecessary(); 473 } 474 475 private void performAppWidgetSizeCallbacksIfNecessary() { 476 View content = getContent(); 477 if (!(content instanceof AppWidgetHostView)) return; 478 479 if (!KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) { 480 mPerformAppWidgetSizeUpdateOnBootComplete = true; 481 return; 482 } 483 484 // TODO: there's no reason to force the AppWidgetHostView to catch duplicate size calls. 485 // We can do that even more cheaply here. It's not an issue right now since we're in the 486 // system process and hence no binder calls. 487 AppWidgetHostView awhv = (AppWidgetHostView) content; 488 float density = getResources().getDisplayMetrics().density; 489 490 int width = (int) (content.getMeasuredWidth() / density); 491 int height = (int) (content.getMeasuredHeight() / density); 492 awhv.updateAppWidgetSize(null, width, height, width, height, true); 493 } 494 495 void setOverScrollAmount(float r, boolean left) { 496 if (Float.compare(mOverScrollAmount, r) != 0) { 497 mOverScrollAmount = r; 498 mForegroundGradient = left ? mLeftToRightGradient : mRightToLeftGradient; 499 mForegroundAlpha = (int) Math.round((0.5f * r * 255)); 500 501 // We bump up the alpha of the outline to hide the fact that the overlay is drawing 502 // over the rounded part of the frame. 503 float bgAlpha = Math.min(OUTLINE_ALPHA_MULTIPLIER + r * (1 - OUTLINE_ALPHA_MULTIPLIER), 504 1f); 505 setBackgroundAlpha(bgAlpha); 506 invalidate(); 507 } 508 } 509 510 public void onActive(boolean isActive) { 511 // hook for subclasses 512 } 513 514 public boolean onUserInteraction(MotionEvent event) { 515 // hook for subclasses 516 return false; 517 } 518 519 public void onBouncerShowing(boolean showing) { 520 // hook for subclasses 521 } 522 523 public void setWorkerHandler(Handler workerHandler) { 524 mWorkerHandler = workerHandler; 525 } 526 527 public Handler getWorkerHandler() { 528 return mWorkerHandler; 529 } 530 531} 532