1 /* 2 * Copyright (C) 2011 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.launcher3; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.res.Resources; 25 import android.content.res.TypedArray; 26 import android.graphics.*; 27 import android.graphics.drawable.Drawable; 28 import android.util.AttributeSet; 29 import android.util.DisplayMetrics; 30 import android.view.FocusFinder; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.animation.AccelerateInterpolator; 35 import android.widget.FrameLayout; 36 import android.widget.TextView; 37 38 public class Cling extends FrameLayout implements Insettable, View.OnClickListener, 39 View.OnLongClickListener, View.OnTouchListener { 40 41 private static String FIRST_RUN_PORTRAIT = "first_run_portrait"; 42 private static String FIRST_RUN_LANDSCAPE = "first_run_landscape"; 43 44 private static String WORKSPACE_PORTRAIT = "workspace_portrait"; 45 private static String WORKSPACE_LANDSCAPE = "workspace_landscape"; 46 private static String WORKSPACE_LARGE = "workspace_large"; 47 private static String WORKSPACE_CUSTOM = "workspace_custom"; 48 49 private static String MIGRATION_PORTRAIT = "migration_portrait"; 50 private static String MIGRATION_LANDSCAPE = "migration_landscape"; 51 52 private static String MIGRATION_WORKSPACE_PORTRAIT = "migration_workspace_portrait"; 53 private static String MIGRATION_WORKSPACE_LARGE_PORTRAIT = "migration_workspace_large_portrait"; 54 private static String MIGRATION_WORKSPACE_LANDSCAPE = "migration_workspace_landscape"; 55 56 private static String FOLDER_PORTRAIT = "folder_portrait"; 57 private static String FOLDER_LANDSCAPE = "folder_landscape"; 58 private static String FOLDER_LARGE = "folder_large"; 59 60 private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60; 61 private static float FIRST_RUN_MAX_CIRCLE_RADIUS_DPS = 180; 62 private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50; 63 private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60; 64 private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30; 65 private static float MIGRATION_WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 42; 66 private static float MIGRATION_WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 46; 67 68 private Launcher mLauncher; 69 private boolean mIsInitialized; 70 private String mDrawIdentifier; 71 private Drawable mBackground; 72 73 private int[] mTouchDownPt = new int[2]; 74 75 private Drawable mFocusedHotseatApp; 76 private ComponentName mFocusedHotseatAppComponent; 77 private Rect mFocusedHotseatAppBounds; 78 79 private Paint mErasePaint; 80 private Paint mBorderPaint; 81 private Paint mBubblePaint; 82 private Paint mDotPaint; 83 84 private View mScrimView; 85 private int mBackgroundColor; 86 87 private final Rect mInsets = new Rect(); 88 89 public Cling(Context context) { 90 this(context, null, 0); 91 } 92 93 public Cling(Context context, AttributeSet attrs) { 94 this(context, attrs, 0); 95 } 96 97 public Cling(Context context, AttributeSet attrs, int defStyle) { 98 super(context, attrs, defStyle); 99 100 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Cling, defStyle, 0); 101 mDrawIdentifier = a.getString(R.styleable.Cling_drawIdentifier); 102 a.recycle(); 103 104 setClickable(true); 105 106 } 107 108 void init(Launcher l, View scrim) { 109 if (!mIsInitialized) { 110 mLauncher = l; 111 mScrimView = scrim; 112 mBackgroundColor = 0xcc000000; 113 setOnLongClickListener(this); 114 setOnClickListener(this); 115 setOnTouchListener(this); 116 117 mErasePaint = new Paint(); 118 mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); 119 mErasePaint.setColor(0xFFFFFF); 120 mErasePaint.setAlpha(0); 121 mErasePaint.setAntiAlias(true); 122 123 mBorderPaint = new Paint(); 124 mBorderPaint.setColor(0xFFFFFFFF); 125 mBorderPaint.setAntiAlias(true); 126 127 int circleColor = getResources().getColor( 128 R.color.first_run_cling_circle_background_color); 129 mBubblePaint = new Paint(); 130 mBubblePaint.setColor(circleColor); 131 mBubblePaint.setAntiAlias(true); 132 133 mDotPaint = new Paint(); 134 mDotPaint.setColor(0x72BBED); 135 mDotPaint.setAntiAlias(true); 136 137 mIsInitialized = true; 138 } 139 } 140 141 void setFocusedHotseatApp(int drawableId, int appRank, ComponentName cn, String title, 142 String description) { 143 // Get the app to draw 144 Resources r = getResources(); 145 int appIconId = drawableId; 146 Hotseat hotseat = mLauncher.getHotseat(); 147 // Skip the focused app in the large layouts 148 if (!mDrawIdentifier.equals(WORKSPACE_LARGE) && 149 hotseat != null && appIconId > -1 && appRank > -1 && !title.isEmpty() && 150 !description.isEmpty()) { 151 // Set the app bounds 152 int x = hotseat.getCellXFromOrder(appRank); 153 int y = hotseat.getCellYFromOrder(appRank); 154 Rect pos = hotseat.getCellCoordinates(x, y); 155 LauncherAppState app = LauncherAppState.getInstance(); 156 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 157 mFocusedHotseatApp = getResources().getDrawable(appIconId); 158 mFocusedHotseatAppComponent = cn; 159 mFocusedHotseatAppBounds = new Rect(pos.left, pos.top, 160 pos.left + Utilities.sIconTextureWidth, 161 pos.top + Utilities.sIconTextureHeight); 162 Utilities.scaleRectAboutCenter(mFocusedHotseatAppBounds, 163 ((float) grid.hotseatIconSizePx / grid.iconSizePx)); 164 165 // Set the title 166 TextView v = (TextView) findViewById(R.id.focused_hotseat_app_title); 167 if (v != null) { 168 v.setText(title); 169 } 170 171 // Set the description 172 v = (TextView) findViewById(R.id.focused_hotseat_app_description); 173 if (v != null) { 174 v.setText(description); 175 } 176 177 // Show the bubble 178 View bubble = findViewById(R.id.focused_hotseat_app_bubble); 179 bubble.setVisibility(View.VISIBLE); 180 } 181 } 182 183 void setOpenFolderRect(Rect r) { 184 if (mDrawIdentifier.equals(FOLDER_LANDSCAPE) || 185 mDrawIdentifier.equals(FOLDER_LARGE)) { 186 ViewGroup vg = (ViewGroup) findViewById(R.id.folder_bubble); 187 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) vg.getLayoutParams(); 188 lp.topMargin = r.top - mInsets.bottom; 189 lp.leftMargin = r.right; 190 vg.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); 191 vg.requestLayout(); 192 } 193 } 194 195 void updateMigrationWorkspaceBubblePosition() { 196 DisplayMetrics metrics = new DisplayMetrics(); 197 mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics); 198 199 // Get the page indicator bounds 200 LauncherAppState app = LauncherAppState.getInstance(); 201 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 202 Rect pageIndicatorBounds = grid.getWorkspacePageIndicatorBounds(mInsets); 203 204 if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT)) { 205 View bubble = findViewById(R.id.migration_workspace_cling_bubble); 206 ViewGroup.MarginLayoutParams lp = 207 (ViewGroup.MarginLayoutParams) bubble.getLayoutParams(); 208 lp.bottomMargin = grid.heightPx - pageIndicatorBounds.top; 209 bubble.requestLayout(); 210 } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT)) { 211 View bubble = findViewById(R.id.content); 212 ViewGroup.MarginLayoutParams lp = 213 (ViewGroup.MarginLayoutParams) bubble.getLayoutParams(); 214 lp.bottomMargin = grid.heightPx - pageIndicatorBounds.top; 215 bubble.requestLayout(); 216 } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) { 217 View bubble = findViewById(R.id.content); 218 ViewGroup.MarginLayoutParams lp = 219 (ViewGroup.MarginLayoutParams) bubble.getLayoutParams(); 220 if (grid.isLayoutRtl) { 221 lp.leftMargin = pageIndicatorBounds.right; 222 } else { 223 lp.rightMargin = (grid.widthPx - pageIndicatorBounds.left); 224 } 225 bubble.requestLayout(); 226 } 227 } 228 229 void updateWorkspaceBubblePosition() { 230 DisplayMetrics metrics = new DisplayMetrics(); 231 mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics); 232 233 // Get the cut-out bounds 234 LauncherAppState app = LauncherAppState.getInstance(); 235 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 236 Rect cutOutBounds = getWorkspaceCutOutBounds(metrics); 237 238 if (mDrawIdentifier.equals(WORKSPACE_LARGE)) { 239 View bubble = findViewById(R.id.workspace_cling_bubble); 240 ViewGroup.MarginLayoutParams lp = 241 (ViewGroup.MarginLayoutParams) bubble.getLayoutParams(); 242 lp.bottomMargin = grid.heightPx - cutOutBounds.top - mInsets.bottom; 243 bubble.requestLayout(); 244 } 245 } 246 247 private Rect getWorkspaceCutOutBounds(DisplayMetrics metrics) { 248 int halfWidth = metrics.widthPixels / 2; 249 int halfHeight = metrics.heightPixels / 2; 250 int yOffset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics); 251 if (mDrawIdentifier.equals(WORKSPACE_LARGE)) { 252 yOffset = 0; 253 } 254 int radius = DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics); 255 return new Rect(halfWidth - radius, halfHeight - yOffset - radius, halfWidth + radius, 256 halfHeight - yOffset + radius); 257 } 258 259 void show(boolean animate, int duration) { 260 setVisibility(View.VISIBLE); 261 setLayerType(View.LAYER_TYPE_HARDWARE, null); 262 if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || 263 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || 264 mDrawIdentifier.equals(WORKSPACE_LARGE) || 265 mDrawIdentifier.equals(WORKSPACE_CUSTOM) || 266 mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) || 267 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) || 268 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) { 269 View content = getContent(); 270 content.setAlpha(0f); 271 content.animate() 272 .alpha(1f) 273 .setDuration(duration) 274 .setListener(null) 275 .start(); 276 setAlpha(1f); 277 } else { 278 if (animate) { 279 buildLayer(); 280 setAlpha(0f); 281 animate() 282 .alpha(1f) 283 .setInterpolator(new AccelerateInterpolator()) 284 .setDuration(duration) 285 .setListener(null) 286 .start(); 287 } else { 288 setAlpha(1f); 289 } 290 } 291 292 // Show the scrim if necessary 293 if (mScrimView != null) { 294 mScrimView.setVisibility(View.VISIBLE); 295 mScrimView.setAlpha(0f); 296 mScrimView.animate() 297 .alpha(1f) 298 .setDuration(duration) 299 .setListener(null) 300 .start(); 301 } 302 303 setFocusableInTouchMode(true); 304 post(new Runnable() { 305 public void run() { 306 setFocusable(true); 307 requestFocus(); 308 } 309 }); 310 } 311 312 void hide(final int duration, final Runnable postCb) { 313 if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) || 314 mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE) || 315 mDrawIdentifier.equals(MIGRATION_PORTRAIT) || 316 mDrawIdentifier.equals(MIGRATION_LANDSCAPE)) { 317 View content = getContent(); 318 content.animate() 319 .alpha(0f) 320 .setDuration(duration) 321 .setListener(new AnimatorListenerAdapter() { 322 public void onAnimationEnd(Animator animation) { 323 // We are about to trigger the workspace cling, so don't do anything else 324 setVisibility(View.GONE); 325 postCb.run(); 326 }; 327 }) 328 .start(); 329 } else { 330 animate() 331 .alpha(0f) 332 .setDuration(duration) 333 .setListener(new AnimatorListenerAdapter() { 334 public void onAnimationEnd(Animator animation) { 335 // We are about to trigger the workspace cling, so don't do anything else 336 setVisibility(View.GONE); 337 postCb.run(); 338 }; 339 }) 340 .start(); 341 } 342 343 // Show the scrim if necessary 344 if (mScrimView != null) { 345 mScrimView.animate() 346 .alpha(0f) 347 .setDuration(duration) 348 .setListener(new AnimatorListenerAdapter() { 349 public void onAnimationEnd(Animator animation) { 350 mScrimView.setVisibility(View.GONE); 351 }; 352 }) 353 .start(); 354 } 355 } 356 357 void cleanup() { 358 mBackground = null; 359 mIsInitialized = false; 360 } 361 362 void bringScrimToFront() { 363 if (mScrimView != null) { 364 mScrimView.bringToFront(); 365 } 366 } 367 368 @Override 369 public void setInsets(Rect insets) { 370 mInsets.set(insets); 371 setPadding(insets.left, insets.top, insets.right, insets.bottom); 372 } 373 374 View getContent() { 375 return findViewById(R.id.content); 376 } 377 378 String getDrawIdentifier() { 379 return mDrawIdentifier; 380 } 381 382 @Override 383 public View focusSearch(int direction) { 384 return this.focusSearch(this, direction); 385 } 386 387 @Override 388 public View focusSearch(View focused, int direction) { 389 return FocusFinder.getInstance().findNextFocus(this, focused, direction); 390 } 391 392 @Override 393 public boolean onHoverEvent(MotionEvent event) { 394 return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) 395 || mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) 396 || mDrawIdentifier.equals(WORKSPACE_LARGE) 397 || mDrawIdentifier.equals(WORKSPACE_CUSTOM)); 398 } 399 400 @Override 401 public boolean onTouchEvent(android.view.MotionEvent event) { 402 if (mDrawIdentifier.equals(FOLDER_PORTRAIT) || 403 mDrawIdentifier.equals(FOLDER_LANDSCAPE) || 404 mDrawIdentifier.equals(FOLDER_LARGE)) { 405 Folder f = mLauncher.getWorkspace().getOpenFolder(); 406 if (f != null) { 407 Rect r = new Rect(); 408 f.getHitRect(r); 409 if (r.contains((int) event.getX(), (int) event.getY())) { 410 return false; 411 } 412 } 413 } 414 return super.onTouchEvent(event); 415 }; 416 417 @Override 418 public boolean onTouch(View v, MotionEvent ev) { 419 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 420 mTouchDownPt[0] = (int) ev.getX(); 421 mTouchDownPt[1] = (int) ev.getY(); 422 } 423 return false; 424 } 425 426 @Override 427 public void onClick(View v) { 428 if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || 429 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || 430 mDrawIdentifier.equals(WORKSPACE_LARGE)) { 431 if (mFocusedHotseatAppBounds != null && 432 mFocusedHotseatAppBounds.contains(mTouchDownPt[0], mTouchDownPt[1])) { 433 // Launch the activity that is being highlighted 434 Intent intent = new Intent(Intent.ACTION_MAIN); 435 intent.setComponent(mFocusedHotseatAppComponent); 436 intent.addCategory(Intent.CATEGORY_LAUNCHER); 437 mLauncher.startActivity(intent, null); 438 mLauncher.getLauncherClings().dismissWorkspaceCling(this); 439 } 440 } 441 } 442 443 @Override 444 public boolean onLongClick(View v) { 445 if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || 446 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || 447 mDrawIdentifier.equals(WORKSPACE_LARGE)) { 448 mLauncher.getLauncherClings().dismissWorkspaceCling(null); 449 return true; 450 } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) || 451 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) || 452 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) { 453 mLauncher.getLauncherClings().dismissMigrationWorkspaceCling(null); 454 return true; 455 } 456 return false; 457 } 458 459 @Override 460 protected void dispatchDraw(Canvas canvas) { 461 if (mIsInitialized) { 462 canvas.save(); 463 464 // Get the page indicator bounds 465 LauncherAppState app = LauncherAppState.getInstance(); 466 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 467 Rect pageIndicatorBounds = grid.getWorkspacePageIndicatorBounds(mInsets); 468 469 // Get the background override if there is one 470 if (mBackground == null) { 471 if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) { 472 mBackground = getResources().getDrawable(R.drawable.bg_cling5); 473 } 474 } 475 // Draw the background 476 Bitmap eraseBg = null; 477 Canvas eraseCanvas = null; 478 if (mScrimView != null) { 479 // Skip drawing the background 480 mScrimView.setBackgroundColor(mBackgroundColor); 481 } else if (mBackground != null) { 482 mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); 483 mBackground.draw(canvas); 484 } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || 485 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || 486 mDrawIdentifier.equals(WORKSPACE_LARGE) || 487 mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) || 488 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) || 489 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) { 490 // Initialize the draw buffer (to allow punching through) 491 eraseBg = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), 492 Bitmap.Config.ARGB_8888); 493 eraseCanvas = new Canvas(eraseBg); 494 eraseCanvas.drawColor(mBackgroundColor); 495 } else { 496 canvas.drawColor(mBackgroundColor); 497 } 498 499 // Draw everything else 500 DisplayMetrics metrics = new DisplayMetrics(); 501 mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics); 502 float alpha = getAlpha(); 503 View content = getContent(); 504 if (content != null) { 505 alpha *= content.getAlpha(); 506 } 507 if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) || 508 mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) { 509 // Draw the circle 510 View bubbleContent = findViewById(R.id.bubble_content); 511 Rect bubbleRect = new Rect(); 512 bubbleContent.getGlobalVisibleRect(bubbleRect); 513 mBubblePaint.setAlpha((int) (255 * alpha)); 514 float buffer = DynamicGrid.pxFromDp(FIRST_RUN_CIRCLE_BUFFER_DPS, metrics); 515 float maxRadius = DynamicGrid.pxFromDp(FIRST_RUN_MAX_CIRCLE_RADIUS_DPS, metrics); 516 float radius = Math.min(maxRadius, (bubbleContent.getMeasuredWidth() + buffer) / 2); 517 canvas.drawCircle(metrics.widthPixels / 2, 518 bubbleRect.centerY(), radius, 519 mBubblePaint); 520 } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || 521 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || 522 mDrawIdentifier.equals(WORKSPACE_LARGE)) { 523 Rect cutOutBounds = getWorkspaceCutOutBounds(metrics); 524 // Draw the outer circle 525 mErasePaint.setAlpha(128); 526 eraseCanvas.drawCircle(cutOutBounds.centerX(), cutOutBounds.centerY(), 527 DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics), 528 mErasePaint); 529 // Draw the inner circle 530 mErasePaint.setAlpha(0); 531 eraseCanvas.drawCircle(cutOutBounds.centerX(), cutOutBounds.centerY(), 532 DynamicGrid.pxFromDp(WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics), 533 mErasePaint); 534 canvas.drawBitmap(eraseBg, 0, 0, null); 535 eraseCanvas.setBitmap(null); 536 eraseBg = null; 537 538 // Draw the focused hotseat app icon 539 if (mFocusedHotseatAppBounds != null && mFocusedHotseatApp != null) { 540 mFocusedHotseatApp.setBounds(mFocusedHotseatAppBounds.left, 541 mFocusedHotseatAppBounds.top, mFocusedHotseatAppBounds.right, 542 mFocusedHotseatAppBounds.bottom); 543 mFocusedHotseatApp.setAlpha((int) (255 * alpha)); 544 mFocusedHotseatApp.draw(canvas); 545 } 546 } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) || 547 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) || 548 mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) { 549 int offset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics); 550 // Draw the outer circle 551 eraseCanvas.drawCircle(pageIndicatorBounds.centerX(), 552 pageIndicatorBounds.centerY(), 553 DynamicGrid.pxFromDp(MIGRATION_WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics), 554 mBorderPaint); 555 // Draw the inner circle 556 mErasePaint.setAlpha(0); 557 eraseCanvas.drawCircle(pageIndicatorBounds.centerX(), 558 pageIndicatorBounds.centerY(), 559 DynamicGrid.pxFromDp(MIGRATION_WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics), 560 mErasePaint); 561 canvas.drawBitmap(eraseBg, 0, 0, null); 562 eraseCanvas.setBitmap(null); 563 eraseBg = null; 564 } 565 canvas.restore(); 566 } 567 568 // Draw the rest of the cling 569 super.dispatchDraw(canvas); 570 }; 571 } 572