TaskViewHeader.java revision 9b5c847a19bf7dbfe3fe00f11cbbdf581794fccc
1/* 2 * Copyright (C) 2014 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.systemui.recents.views; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.annotation.Nullable; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.pm.ActivityInfo; 25import android.content.res.Resources; 26import android.graphics.Canvas; 27import android.graphics.Color; 28import android.graphics.ColorFilter; 29import android.graphics.Paint; 30import android.graphics.PixelFormat; 31import android.graphics.PorterDuff; 32import android.graphics.Rect; 33import android.graphics.drawable.Drawable; 34import android.os.CountDownTimer; 35import android.support.v4.graphics.ColorUtils; 36import android.util.AttributeSet; 37import android.view.View; 38import android.view.ViewAnimationUtils; 39import android.view.ViewStub; 40import android.widget.FrameLayout; 41import android.widget.ImageView; 42import android.widget.ProgressBar; 43import android.widget.TextView; 44 45import com.android.internal.logging.MetricsLogger; 46import com.android.systemui.Interpolators; 47import com.android.systemui.R; 48import com.android.systemui.recents.Constants; 49import com.android.systemui.recents.Recents; 50import com.android.systemui.recents.events.EventBus; 51import com.android.systemui.recents.events.activity.LaunchTaskEvent; 52import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; 53import com.android.systemui.recents.misc.SystemServicesProxy; 54import com.android.systemui.recents.misc.Utilities; 55import com.android.systemui.recents.model.Task; 56 57import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 58import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 59import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 60 61/* The task bar view */ 62public class TaskViewHeader extends FrameLayout 63 implements View.OnClickListener, View.OnLongClickListener { 64 65 private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.125f; 66 private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f; 67 private static final int OVERLAY_REVEAL_DURATION = 250; 68 private static final long FOCUS_INDICATOR_INTERVAL_MS = 30; 69 70 /** 71 * A color drawable that draws a slight highlight at the top to help it stand out. 72 */ 73 private class HighlightColorDrawable extends Drawable { 74 75 private Paint mHighlightPaint = new Paint(); 76 private Paint mBackgroundPaint = new Paint(); 77 78 public HighlightColorDrawable() { 79 mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0)); 80 mBackgroundPaint.setAntiAlias(true); 81 mHighlightPaint.setColor(Color.argb(255, 255, 255, 255)); 82 mHighlightPaint.setAntiAlias(true); 83 } 84 85 public void setColorAndDim(int color, float dimAlpha) { 86 mBackgroundPaint.setColor(color); 87 88 ColorUtils.colorToHSL(color, mTmpHSL); 89 // TODO: Consider using the saturation of the color to adjust the lightness as well 90 mTmpHSL[2] = Math.min(1f, 91 mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); 92 mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL)); 93 94 invalidateSelf(); 95 } 96 97 @Override 98 public void setColorFilter(@Nullable ColorFilter colorFilter) { 99 // Do nothing 100 } 101 102 @Override 103 public void setAlpha(int alpha) { 104 // Do nothing 105 } 106 107 @Override 108 public void draw(Canvas canvas) { 109 // Draw the highlight at the top edge (but put the bottom edge just out of view) 110 canvas.drawRoundRect(0, 0, mTaskViewRect.width(), 111 2 * Math.max(mHighlightHeight, mCornerRadius), 112 mCornerRadius, mCornerRadius, mHighlightPaint); 113 114 // Draw the background with the rounded corners 115 canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(), 116 getHeight() + mCornerRadius, 117 mCornerRadius, mCornerRadius, mBackgroundPaint); 118 } 119 120 @Override 121 public int getOpacity() { 122 return PixelFormat.OPAQUE; 123 } 124 } 125 126 Task mTask; 127 128 // Header views 129 ImageView mIconView; 130 TextView mTitleView; 131 ImageView mMoveTaskButton; 132 ImageView mDismissButton; 133 ViewStub mAppOverlayViewStub; 134 FrameLayout mAppOverlayView; 135 ImageView mAppIconView; 136 ImageView mAppInfoView; 137 TextView mAppTitleView; 138 ViewStub mFocusTimerIndicatorStub; 139 ProgressBar mFocusTimerIndicator; 140 141 // Header drawables 142 Rect mTaskViewRect = new Rect(); 143 int mCornerRadius; 144 int mHighlightHeight; 145 float mDimAlpha; 146 Drawable mLightDismissDrawable; 147 Drawable mDarkDismissDrawable; 148 Drawable mLightFreeformIcon; 149 Drawable mDarkFreeformIcon; 150 Drawable mLightFullscreenIcon; 151 Drawable mDarkFullscreenIcon; 152 Drawable mLightInfoIcon; 153 Drawable mDarkInfoIcon; 154 int mTaskBarViewLightTextColor; 155 int mTaskBarViewDarkTextColor; 156 int mMoveTaskTargetStackId = INVALID_STACK_ID; 157 158 // Header background 159 private HighlightColorDrawable mBackground; 160 private HighlightColorDrawable mOverlayBackground; 161 private float[] mTmpHSL = new float[3]; 162 163 // Header dim, which is only used when task view hardware layers are not used 164 private Paint mDimLayerPaint = new Paint(); 165 166 private CountDownTimer mFocusTimerCountDown; 167 168 public TaskViewHeader(Context context) { 169 this(context, null); 170 } 171 172 public TaskViewHeader(Context context, AttributeSet attrs) { 173 this(context, attrs, 0); 174 } 175 176 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) { 177 this(context, attrs, defStyleAttr, 0); 178 } 179 180 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 181 super(context, attrs, defStyleAttr, defStyleRes); 182 setWillNotDraw(false); 183 184 // Load the dismiss resources 185 Resources res = context.getResources(); 186 mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light); 187 mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark); 188 mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); 189 mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight); 190 mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color); 191 mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color); 192 mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light); 193 mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark); 194 mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light); 195 mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark); 196 mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light); 197 mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark); 198 199 // Configure the background and dim 200 mBackground = new HighlightColorDrawable(); 201 mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f); 202 setBackground(mBackground); 203 mOverlayBackground = new HighlightColorDrawable(); 204 mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0)); 205 mDimLayerPaint.setAntiAlias(true); 206 } 207 208 /** 209 * Resets this header along with the TaskView. 210 */ 211 public void reset() { 212 hideAppOverlay(true /* immediate */); 213 } 214 215 @Override 216 protected void onFinishInflate() { 217 SystemServicesProxy ssp = Recents.getSystemServices(); 218 219 // Initialize the icon and description views 220 mIconView = (ImageView) findViewById(R.id.icon); 221 mIconView.setClickable(false); 222 mIconView.setOnLongClickListener(this); 223 mTitleView = (TextView) findViewById(R.id.title); 224 mDismissButton = (ImageView) findViewById(R.id.dismiss_task); 225 if (ssp.hasFreeformWorkspaceSupport()) { 226 mMoveTaskButton = (ImageView) findViewById(R.id.move_task); 227 mMoveTaskButton.setVisibility(View.VISIBLE); 228 } 229 mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub); 230 mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub); 231 } 232 233 /** 234 * Called when the task view frame changes, allowing us to move the contents of the header 235 * to match the frame changes. 236 */ 237 public void onTaskViewSizeChanged(int width, int height) { 238 // TODO: Optimize this path 239 mTaskViewRect.set(0, 0, width, height); 240 boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE; 241 boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask(); 242 int appIconWidth = mIconView.getMeasuredWidth(); 243 int activityDescWidth = (mTask != null) 244 ? (int) mTitleView.getPaint().measureText(mTask.title) 245 : mTitleView.getMeasuredWidth(); 246 int dismissIconWidth = mDismissButton.getMeasuredWidth(); 247 int moveTaskIconWidth = mMoveTaskButton.getVisibility() == View.VISIBLE 248 ? mMoveTaskButton.getMeasuredWidth() 249 : 0; 250 251 // Priority-wise, we show the activity icon first, the dismiss icon if there is room, the 252 // move-task icon if there is room, and then finally, the activity label if there is room 253 if (isFreeformTask && width < (appIconWidth + dismissIconWidth)) { 254 mTitleView.setVisibility(View.INVISIBLE); 255 if (updateMoveTaskButton) { 256 mMoveTaskButton.setVisibility(View.INVISIBLE); 257 } 258 mDismissButton.setVisibility(View.INVISIBLE); 259 } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + 260 moveTaskIconWidth)) { 261 mTitleView.setVisibility(View.INVISIBLE); 262 if (updateMoveTaskButton) { 263 mMoveTaskButton.setVisibility(View.INVISIBLE); 264 } 265 mDismissButton.setVisibility(View.VISIBLE); 266 } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + moveTaskIconWidth + 267 activityDescWidth)) { 268 mTitleView.setVisibility(View.INVISIBLE); 269 if (updateMoveTaskButton) { 270 mMoveTaskButton.setVisibility(View.VISIBLE); 271 } 272 mDismissButton.setVisibility(View.VISIBLE); 273 } else { 274 mTitleView.setVisibility(View.VISIBLE); 275 if (updateMoveTaskButton) { 276 mMoveTaskButton.setVisibility(View.VISIBLE); 277 } 278 mDismissButton.setVisibility(View.VISIBLE); 279 } 280 if (updateMoveTaskButton) { 281 mMoveTaskButton.setTranslationX(width - getMeasuredWidth()); 282 } 283 mDismissButton.setTranslationX(width - getMeasuredWidth()); 284 invalidate(); 285 } 286 287 @Override 288 protected void dispatchDraw(Canvas canvas) { 289 super.dispatchDraw(canvas); 290 291 // Draw the dim layer with the rounded corners 292 canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius, 293 mCornerRadius, mCornerRadius, mDimLayerPaint); 294 } 295 296 /** Starts the focus timer. */ 297 public void startFocusTimerIndicator(int duration) { 298 if (mFocusTimerIndicator == null) { 299 return; 300 } 301 302 mFocusTimerIndicator.setVisibility(View.VISIBLE); 303 mFocusTimerIndicator.setMax(duration); 304 mFocusTimerIndicator.setProgress(duration); 305 if (mFocusTimerCountDown != null) { 306 mFocusTimerCountDown.cancel(); 307 } 308 mFocusTimerCountDown = new CountDownTimer(duration, 309 FOCUS_INDICATOR_INTERVAL_MS) { 310 public void onTick(long millisUntilFinished) { 311 mFocusTimerIndicator.setProgress((int) millisUntilFinished); 312 } 313 314 public void onFinish() { 315 // Do nothing 316 } 317 }.start(); 318 } 319 320 /** Cancels the focus timer. */ 321 public void cancelFocusTimerIndicator() { 322 if (mFocusTimerIndicator == null) { 323 return; 324 } 325 326 if (mFocusTimerCountDown != null) { 327 mFocusTimerCountDown.cancel(); 328 mFocusTimerIndicator.setProgress(0); 329 mFocusTimerIndicator.setVisibility(View.INVISIBLE); 330 } 331 } 332 333 /** Returns the secondary color for a primary color. */ 334 int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) { 335 int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK; 336 return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f); 337 } 338 339 /** 340 * Sets the dim alpha, only used when we are not using hardware layers. 341 * (see RecentsConfiguration.useHardwareLayers) 342 */ 343 void setDimAlpha(float dimAlpha) { 344 mDimAlpha = dimAlpha; 345 updateBackgroundColor(dimAlpha); 346 } 347 348 /** 349 * Updates the background and highlight colors for this header. 350 */ 351 private void updateBackgroundColor(float dimAlpha) { 352 if (mTask != null) { 353 mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha); 354 // TODO: Consider using the saturation of the color to adjust the lightness as well 355 ColorUtils.colorToHSL(mTask.colorPrimary, mTmpHSL); 356 mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); 357 mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha); 358 mDimLayerPaint.setAlpha((int) (dimAlpha * 255)); 359 invalidate(); 360 } 361 } 362 363 /** Binds the bar view to the task */ 364 public void rebindToTask(Task t, boolean touchExplorationEnabled) { 365 SystemServicesProxy ssp = Recents.getSystemServices(); 366 mTask = t; 367 368 // If an activity icon is defined, then we use that as the primary icon to show in the bar, 369 // otherwise, we fall back to the application icon 370 updateBackgroundColor(mDimAlpha); 371 if (t.icon != null) { 372 mIconView.setImageDrawable(t.icon); 373 } 374 if (!mTitleView.getText().toString().equals(t.title)) { 375 mTitleView.setText(t.title); 376 } 377 mTitleView.setContentDescription(t.contentDescription); 378 mTitleView.setTextColor(t.useLightOnPrimaryColor ? 379 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); 380 mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? 381 mLightDismissDrawable : mDarkDismissDrawable); 382 mDismissButton.setContentDescription(t.dismissDescription); 383 384 // When freeform workspaces are enabled, then update the move-task button depending on the 385 // current task 386 if (ssp.hasFreeformWorkspaceSupport()) { 387 if (t.isFreeformTask()) { 388 mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID; 389 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor 390 ? mLightFullscreenIcon 391 : mDarkFullscreenIcon); 392 } else { 393 mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID; 394 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor 395 ? mLightFreeformIcon 396 : mDarkFreeformIcon); 397 } 398 } 399 400 if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) { 401 if (mFocusTimerIndicator == null) { 402 mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate(); 403 } 404 mFocusTimerIndicator.getProgressDrawable() 405 .setColorFilter( 406 getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor), 407 PorterDuff.Mode.SRC_IN); 408 } 409 410 // In accessibility, a single click on the focused app info button will show it 411 if (touchExplorationEnabled) { 412 mIconView.setOnClickListener(this); 413 } 414 } 415 416 /** Unbinds the bar view from the task */ 417 void unbindFromTask(boolean touchExplorationEnabled) { 418 mTask = null; 419 mIconView.setImageDrawable(null); 420 if (touchExplorationEnabled) { 421 mIconView.setOnClickListener(null); 422 } 423 } 424 425 /** Animates this task bar if the user does not interact with the stack after a certain time. */ 426 void startNoUserInteractionAnimation() { 427 int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration); 428 mDismissButton.setOnClickListener(this); 429 mDismissButton.setVisibility(View.VISIBLE); 430 mDismissButton.animate() 431 .alpha(1f) 432 .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) 433 .setDuration(duration) 434 .start(); 435 mMoveTaskButton.setOnClickListener(this); 436 mMoveTaskButton.setVisibility(View.VISIBLE); 437 mMoveTaskButton.animate() 438 .alpha(1f) 439 .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) 440 .setDuration(duration) 441 .start(); 442 } 443 444 /** 445 * Mark this task view that the user does has not interacted with the stack after a certain 446 * time. 447 */ 448 void setNoUserInteractionState() { 449 mDismissButton.setVisibility(View.VISIBLE); 450 mDismissButton.animate().cancel(); 451 mDismissButton.setAlpha(1f); 452 mDismissButton.setOnClickListener(this); 453 mMoveTaskButton.setVisibility(View.VISIBLE); 454 mMoveTaskButton.animate().cancel(); 455 mMoveTaskButton.setAlpha(1f); 456 mMoveTaskButton.setOnClickListener(this); 457 } 458 459 /** 460 * Resets the state tracking that the user has not interacted with the stack after a certain 461 * time. 462 */ 463 void resetNoUserInteractionState() { 464 mDismissButton.setVisibility(View.INVISIBLE); 465 mDismissButton.setAlpha(0f); 466 mDismissButton.setOnClickListener(null); 467 mMoveTaskButton.setVisibility(View.INVISIBLE); 468 mMoveTaskButton.setAlpha(0f); 469 mMoveTaskButton.setOnClickListener(null); 470 } 471 472 @Override 473 protected int[] onCreateDrawableState(int extraSpace) { 474 475 // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged. 476 // This is to prevent layer trashing when the view is pressed. 477 return new int[] {}; 478 } 479 480 @Override 481 public void onClick(View v) { 482 if (v == mIconView) { 483 // In accessibility, a single click on the focused app info button will show it 484 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 485 } else if (v == mDismissButton) { 486 TaskView tv = Utilities.findParent(this, TaskView.class); 487 tv.dismissTask(); 488 489 // Keep track of deletions by the dismiss button 490 MetricsLogger.histogram(getContext(), "overview_task_dismissed_source", 491 Constants.Metrics.DismissSourceHeaderButton); 492 } else if (v == mMoveTaskButton) { 493 TaskView tv = Utilities.findParent(this, TaskView.class); 494 Rect bounds = mMoveTaskTargetStackId == FREEFORM_WORKSPACE_STACK_ID 495 ? new Rect(mTaskViewRect) 496 : new Rect(); 497 EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds, 498 mMoveTaskTargetStackId, false)); 499 } else if (v == mAppInfoView) { 500 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 501 } else if (v == mAppIconView) { 502 hideAppOverlay(false /* immediate */); 503 } 504 } 505 506 @Override 507 public boolean onLongClick(View v) { 508 if (v == mIconView) { 509 showAppOverlay(); 510 return true; 511 } else if (v == mAppIconView) { 512 hideAppOverlay(false /* immediate */); 513 return true; 514 } 515 return false; 516 } 517 518 /** 519 * Shows the application overlay. 520 */ 521 private void showAppOverlay() { 522 // Skip early if the task is invalid 523 SystemServicesProxy ssp = Recents.getSystemServices(); 524 ComponentName cn = mTask.key.getComponent(); 525 int userId = mTask.key.userId; 526 ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId); 527 if (activityInfo == null) { 528 return; 529 } 530 531 // Inflate the overlay if necessary 532 if (mAppOverlayView == null) { 533 mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate(); 534 mAppOverlayView.setBackground(mOverlayBackground); 535 mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon); 536 mAppIconView.setOnClickListener(this); 537 mAppIconView.setOnLongClickListener(this); 538 mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info); 539 mAppInfoView.setOnClickListener(this); 540 mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title); 541 } 542 543 // Update the overlay contents for the current app 544 mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId)); 545 mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ? 546 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); 547 mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo, 548 userId)); 549 mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor 550 ? mLightInfoIcon 551 : mDarkInfoIcon); 552 mAppOverlayView.setVisibility(View.VISIBLE); 553 554 int x = mIconView.getLeft() + mIconView.getWidth() / 2; 555 int y = mIconView.getTop() + mIconView.getHeight() / 2; 556 Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0, 557 getWidth()); 558 revealAnim.setDuration(OVERLAY_REVEAL_DURATION); 559 revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 560 revealAnim.start(); 561 } 562 563 /** 564 * Hide the application overlay. 565 */ 566 private void hideAppOverlay(boolean immediate) { 567 // Skip if we haven't even loaded the overlay yet 568 if (mAppOverlayView == null) { 569 return; 570 } 571 572 if (immediate) { 573 mAppOverlayView.setVisibility(View.GONE); 574 } else { 575 int x = mIconView.getLeft() + mIconView.getWidth() / 2; 576 int y = mIconView.getTop() + mIconView.getHeight() / 2; 577 Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 578 getWidth(), 0); 579 revealAnim.setDuration(OVERLAY_REVEAL_DURATION); 580 revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 581 revealAnim.addListener(new AnimatorListenerAdapter() { 582 @Override 583 public void onAnimationEnd(Animator animation) { 584 mAppOverlayView.setVisibility(View.GONE); 585 } 586 }); 587 revealAnim.start(); 588 } 589 } 590} 591