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