• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
Cling(Context context)89     public Cling(Context context) {
90         this(context, null, 0);
91     }
92 
Cling(Context context, AttributeSet attrs)93     public Cling(Context context, AttributeSet attrs) {
94         this(context, attrs, 0);
95     }
96 
Cling(Context context, AttributeSet attrs, int defStyle)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 
init(Launcher l, View scrim)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 
setFocusedHotseatApp(int drawableId, int appRank, ComponentName cn, String title, String description)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 
setOpenFolderRect(Rect r)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 
updateMigrationWorkspaceBubblePosition()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 
updateWorkspaceBubblePosition()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 
getWorkspaceCutOutBounds(DisplayMetrics metrics)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 
show(boolean animate, int duration)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 
hide(final int duration, final Runnable postCb)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 
cleanup()357     void cleanup() {
358         mBackground = null;
359         mIsInitialized = false;
360     }
361 
bringScrimToFront()362     void bringScrimToFront() {
363         if (mScrimView != null) {
364             mScrimView.bringToFront();
365         }
366     }
367 
368     @Override
setInsets(Rect insets)369     public void setInsets(Rect insets) {
370         mInsets.set(insets);
371         setPadding(insets.left, insets.top, insets.right, insets.bottom);
372     }
373 
getContent()374     View getContent() {
375         return findViewById(R.id.content);
376     }
377 
getDrawIdentifier()378     String getDrawIdentifier() {
379         return mDrawIdentifier;
380     }
381 
382     @Override
focusSearch(int direction)383     public View focusSearch(int direction) {
384         return this.focusSearch(this, direction);
385     }
386 
387     @Override
focusSearch(View focused, int direction)388     public View focusSearch(View focused, int direction) {
389         return FocusFinder.getInstance().findNextFocus(this, focused, direction);
390     }
391 
392     @Override
onHoverEvent(MotionEvent event)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
onTouchEvent(android.view.MotionEvent event)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
onTouch(View v, MotionEvent ev)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
onClick(View v)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
onLongClick(View v)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
dispatchDraw(Canvas canvas)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