TaskView.java revision 480dd72daf927283997bdb4060091299add66832
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.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.content.Context; 24import android.graphics.Color; 25import android.graphics.Paint; 26import android.graphics.PorterDuff; 27import android.graphics.PorterDuffColorFilter; 28import android.graphics.Rect; 29import android.util.AttributeSet; 30import android.view.View; 31import android.view.animation.AccelerateInterpolator; 32import android.widget.FrameLayout; 33import com.android.systemui.R; 34import com.android.systemui.recents.AlternateRecentsComponent; 35import com.android.systemui.recents.Constants; 36import com.android.systemui.recents.RecentsConfiguration; 37import com.android.systemui.recents.model.Task; 38 39/* A task view */ 40public class TaskView extends FrameLayout implements Task.TaskCallbacks, 41 TaskFooterView.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener { 42 /** The TaskView callbacks */ 43 interface TaskViewCallbacks { 44 public void onTaskViewAppIconClicked(TaskView tv); 45 public void onTaskViewAppInfoClicked(TaskView tv); 46 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); 47 public void onTaskViewDismissed(TaskView tv); 48 public void onTaskViewClipStateChanged(TaskView tv); 49 } 50 51 RecentsConfiguration mConfig; 52 53 int mDim; 54 int mMaxDim; 55 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(); 56 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY); 57 58 Task mTask; 59 boolean mTaskDataLoaded; 60 boolean mIsFocused; 61 boolean mIsFullScreenView; 62 boolean mClipViewInStack; 63 AnimateableViewBounds mViewBounds; 64 Paint mLayerPaint = new Paint(); 65 66 TaskThumbnailView mThumbnailView; 67 TaskBarView mBarView; 68 TaskFooterView mFooterView; 69 TaskViewCallbacks mCb; 70 71 // Optimizations 72 ValueAnimator.AnimatorUpdateListener mUpdateDimListener = 73 new ValueAnimator.AnimatorUpdateListener() { 74 @Override 75 public void onAnimationUpdate(ValueAnimator animation) { 76 updateDimOverlayFromScale(); 77 } 78 }; 79 80 81 public TaskView(Context context) { 82 this(context, null); 83 } 84 85 public TaskView(Context context, AttributeSet attrs) { 86 this(context, attrs, 0); 87 } 88 89 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 90 this(context, attrs, defStyleAttr, 0); 91 } 92 93 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 94 super(context, attrs, defStyleAttr, defStyleRes); 95 mConfig = RecentsConfiguration.getInstance(); 96 mMaxDim = mConfig.taskStackMaxDim; 97 mClipViewInStack = true; 98 mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx); 99 setOutlineProvider(mViewBounds); 100 setDim(getDim()); 101 } 102 103 /** Set callback */ 104 void setCallbacks(TaskViewCallbacks cb) { 105 mCb = cb; 106 } 107 108 /** Gets the task */ 109 Task getTask() { 110 return mTask; 111 } 112 113 /** Returns the view bounds. */ 114 AnimateableViewBounds getViewBounds() { 115 return mViewBounds; 116 } 117 118 @Override 119 protected void onFinishInflate() { 120 // Bind the views 121 mBarView = (TaskBarView) findViewById(R.id.task_view_bar); 122 mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail); 123 mFooterView = (TaskFooterView) findViewById(R.id.lock_to_app); 124 if (mConfig.lockToAppEnabled) { 125 mFooterView.setCallbacks(this); 126 } else { 127 mFooterView.setVisibility(View.GONE); 128 } 129 } 130 131 @Override 132 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 133 int width = MeasureSpec.getSize(widthMeasureSpec); 134 int height = MeasureSpec.getSize(heightMeasureSpec); 135 136 // Measure the bar view, thumbnail, and footer 137 mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 138 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY)); 139 mFooterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 140 MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight, 141 MeasureSpec.EXACTLY)); 142 if (mIsFullScreenView) { 143 // Measure the thumbnail height to be the full dimensions 144 mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 145 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 146 } else { 147 // Measure the thumbnail to be square 148 mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 149 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)); 150 } 151 setMeasuredDimension(width, height); 152 invalidateOutline(); 153 } 154 155 /** Synchronizes this view's properties with the task's transform */ 156 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) { 157 // Update the bar view 158 mBarView.updateViewPropertiesToTaskTransform(toTransform, duration); 159 160 // If we are a full screen view, then only update the Z to keep it in order 161 // XXX: Also update/animate the dim as well 162 if (mIsFullScreenView) { 163 if (Constants.DebugFlags.App.EnableShadows && 164 toTransform.hasTranslationZChangedFrom(getTranslationZ())) { 165 setTranslationZ(toTransform.translationZ); 166 } 167 return; 168 } 169 170 // Apply the transform 171 toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false, 172 mUpdateDimListener); 173 } 174 175 /** Resets this view's properties */ 176 void resetViewProperties() { 177 setDim(0); 178 TaskViewTransform.reset(this); 179 } 180 181 /** 182 * When we are un/filtering, this method will set up the transform that we are animating to, 183 * in order to hide the task. 184 */ 185 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 186 // Fade the view out and slide it away 187 toTransform.alpha = 0f; 188 toTransform.translationY += 200; 189 toTransform.translationZ = 0; 190 } 191 192 /** 193 * When we are un/filtering, this method will setup the transform that we are animating from, 194 * in order to show the task. 195 */ 196 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 197 // Fade the view in 198 fromTransform.alpha = 0f; 199 } 200 201 /** Prepares this task view for the enter-recents animations. This is called earlier in the 202 * first layout because the actual animation into recents may take a long time. */ 203 public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offscreenY) { 204 if (mConfig.launchedFromAppWithScreenshot) { 205 if (isTaskViewLaunchTargetTask) { 206 // Also hide the front most task bar view so we can animate it in 207 // mBarView.prepareEnterRecentsAnimation(); 208 } else { 209 // Don't do anything for the side views when animating in 210 } 211 212 } else if (mConfig.launchedFromAppWithThumbnail) { 213 if (isTaskViewLaunchTargetTask) { 214 // Hide the front most task bar view so we can animate it in 215 mBarView.prepareEnterRecentsAnimation(); 216 // Set the dim to 0 so we can animate it in 217 setDim(0); 218 } 219 220 } else if (mConfig.launchedFromHome) { 221 // Move the task view off screen (below) so we can animate it in 222 setTranslationY(offscreenY); 223 if (Constants.DebugFlags.App.EnableShadows) { 224 setTranslationZ(0); 225 } 226 setScaleX(1f); 227 setScaleY(1f); 228 } 229 } 230 231 /** Animates this task view as it enters recents */ 232 public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 233 TaskViewTransform transform = ctx.currentTaskTransform; 234 Rect taskRect = ctx.currentTaskRect; 235 236 if (mConfig.launchedFromAppWithScreenshot) { 237 if (mTask.isLaunchTarget) { 238 int duration = mConfig.taskViewEnterFromHomeDuration * 5; 239 int windowInsetTop = mConfig.systemInsets.top; // XXX: Should be for the window 240 float taskScale = ((float) taskRect.width() / getMeasuredWidth()) * transform.scale; 241 float taskTranslationY = taskRect.top + transform.translationY - windowInsetTop; 242 243 // Animate the top clip 244 mViewBounds.animateClipTop(windowInsetTop, duration, 245 new ValueAnimator.AnimatorUpdateListener() { 246 @Override 247 public void onAnimationUpdate(ValueAnimator animation) { 248 int y = (Integer) animation.getAnimatedValue(); 249 mBarView.setTranslationY(y); 250 } 251 }); 252 // Animate the bottom or right clip 253 int size = Math.round((taskRect.width() / taskScale)); 254 if (mConfig.hasHorizontalLayout()) { 255 mViewBounds.animateClipRight(getMeasuredWidth() - size, duration); 256 } else { 257 mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration); 258 } 259 animate() 260 .scaleX(taskScale) 261 .scaleY(taskScale) 262 .translationY(taskTranslationY) 263 .setDuration(duration) 264 .withEndAction(new Runnable() { 265 @Override 266 public void run() { 267 // Animate the task bar of the first task view 268 mBarView.startEnterRecentsAnimation(0, mThumbnailView.enableTaskBarClipAsRunnable(mBarView)); 269 // Animate the footer into view (if it is the front most task) 270 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration); 271 // Decrement the post animation trigger 272 ctx.postAnimationTrigger.decrement(); 273 274 // XXX Request layout and only start hte next animation after the next 275 // layout 276 277 setIsFullScreen(false); 278 mThumbnailView.unbindFromScreenshot(); 279 280 // Recycle the full screen screenshot 281 AlternateRecentsComponent.consumeLastScreenshot(); 282 } 283 }) 284 .start(); 285 } else { 286 // Otherwise, just enable the thumbnail clip 287 mThumbnailView.enableTaskBarClip(mBarView); 288 289 // Animate the footer into view 290 animateFooterVisibility(true, 0); 291 } 292 ctx.postAnimationTrigger.increment(); 293 294 } else if (mConfig.launchedFromAppWithThumbnail) { 295 if (mTask.isLaunchTarget) { 296 // Animate the task bar of the first task view 297 mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, 298 mThumbnailView.enableTaskBarClipAsRunnable(mBarView)); 299 300 // Animate the dim into view as well 301 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale()); 302 anim.setStartDelay(mConfig.taskBarEnterAnimDelay); 303 anim.setDuration(mConfig.taskBarEnterAnimDuration); 304 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 305 anim.addListener(new AnimatorListenerAdapter() { 306 @Override 307 public void onAnimationEnd(Animator animation) { 308 // Decrement the post animation trigger 309 ctx.postAnimationTrigger.decrement(); 310 } 311 }); 312 anim.start(); 313 ctx.postAnimationTrigger.increment(); 314 315 // Animate the footer into view 316 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration); 317 } else { 318 mThumbnailView.enableTaskBarClip(mBarView); 319 } 320 321 } else if (mConfig.launchedFromHome) { 322 // Animate the tasks up 323 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 324 int delay = mConfig.taskBarEnterAnimDelay + 325 frontIndex * mConfig.taskViewEnterFromHomeDelay; 326 if (Constants.DebugFlags.App.EnableShadows) { 327 animate().translationZ(transform.translationZ); 328 } 329 animate() 330 .scaleX(transform.scale) 331 .scaleY(transform.scale) 332 .translationY(transform.translationY) 333 .setStartDelay(delay) 334 .setUpdateListener(null) 335 .setInterpolator(mConfig.quintOutInterpolator) 336 .setDuration(mConfig.taskViewEnterFromHomeDuration) 337 .withEndAction(new Runnable() { 338 @Override 339 public void run() { 340 mThumbnailView.enableTaskBarClip(mBarView); 341 // Decrement the post animation trigger 342 ctx.postAnimationTrigger.decrement(); 343 } 344 }) 345 .start(); 346 ctx.postAnimationTrigger.increment(); 347 348 // Animate the footer into view 349 animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration); 350 351 } else { 352 // Otherwise, just enable the thumbnail clip 353 mThumbnailView.enableTaskBarClip(mBarView); 354 355 // Animate the footer into view 356 animateFooterVisibility(true, 0); 357 } 358 } 359 360 /** Animates this task view as it leaves recents by pressing home. */ 361 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 362 animate() 363 .translationY(ctx.offscreenTranslationY) 364 .setStartDelay(0) 365 .setUpdateListener(null) 366 .setInterpolator(mConfig.fastOutLinearInInterpolator) 367 .setDuration(mConfig.taskViewExitToHomeDuration) 368 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 369 .start(); 370 ctx.postAnimationTrigger.increment(); 371 } 372 373 /** Animates this task view as it exits recents */ 374 public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) { 375 if (isLaunchingTask) { 376 // Disable the thumbnail clip and animate the bar out 377 mBarView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r); 378 379 // Animate the dim 380 if (mDim > 0) { 381 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 382 anim.setDuration(mConfig.taskBarExitAnimDuration); 383 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 384 anim.start(); 385 } 386 } else { 387 // Hide the dismiss button 388 mBarView.startLaunchTaskDismissAnimation(); 389 } 390 } 391 392 /** Animates the deletion of this task view */ 393 public void startDeleteTaskAnimation(final Runnable r) { 394 // Disabling clipping with the stack while the view is animating away 395 setClipViewInStack(false); 396 397 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) 398 .alpha(0f) 399 .setStartDelay(0) 400 .setUpdateListener(null) 401 .setInterpolator(mConfig.fastOutSlowInInterpolator) 402 .setDuration(mConfig.taskViewRemoveAnimDuration) 403 .withEndAction(new Runnable() { 404 @Override 405 public void run() { 406 // We just throw this into a runnable because starting a view property 407 // animation using layers can cause inconsisten results if we try and 408 // update the layers while the animation is running. In some cases, 409 // the runnabled passed in may start an animation which also uses layers 410 // so we defer all this by posting this. 411 r.run(); 412 413 // Re-enable clipping with the stack (we will reuse this view) 414 setClipViewInStack(true); 415 } 416 }) 417 .start(); 418 } 419 420 /** Animates this task view if the user does not interact with the stack after a certain time. */ 421 public void startNoUserInteractionAnimation() { 422 mBarView.startNoUserInteractionAnimation(); 423 } 424 425 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 426 public void setNoUserInteractionState() { 427 mBarView.setNoUserInteractionState(); 428 } 429 430 /** Sets whether this task view is full screen or not. */ 431 void setIsFullScreen(boolean isFullscreen) { 432 mIsFullScreenView = isFullscreen; 433 mBarView.setIsFullscreen(isFullscreen); 434 if (isFullscreen) { 435 // If we are full screen, then disable the bottom outline clip for the footer 436 mViewBounds.setOutlineClipBottom(0); 437 } 438 } 439 440 /** Returns whether this task view should currently be drawn as a full screen view. */ 441 boolean isFullScreenView() { 442 return mIsFullScreenView; 443 } 444 445 /** 446 * Returns whether this view should be clipped, or any views below should clip against this 447 * view. 448 */ 449 boolean shouldClipViewInStack() { 450 return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE); 451 } 452 453 /** Sets whether this view should be clipped, or clipped against. */ 454 void setClipViewInStack(boolean clip) { 455 if (clip != mClipViewInStack) { 456 mClipViewInStack = clip; 457 mCb.onTaskViewClipStateChanged(this); 458 } 459 } 460 461 /** Gets the max footer height. */ 462 public int getMaxFooterHeight() { 463 if (mConfig.lockToAppEnabled) { 464 return mFooterView.mMaxFooterHeight; 465 } else { 466 return 0; 467 } 468 } 469 470 /** Animates the footer into and out of view. */ 471 void animateFooterVisibility(boolean visible, int duration) { 472 // Hide the footer if we are a full screen view 473 if (mIsFullScreenView) return; 474 // Hide the footer if the current task can not be locked to 475 if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return; 476 // Otherwise, animate the visibility 477 mFooterView.animateFooterVisibility(visible, duration); 478 } 479 480 /** Returns the current dim. */ 481 public void setDim(int dim) { 482 mDim = dim; 483 int inverse = 255 - mDim; 484 mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse)); 485 mLayerPaint.setColorFilter(mDimColorFilter); 486 setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint); 487 } 488 489 /** Returns the current dim. */ 490 public int getDim() { 491 return mDim; 492 } 493 494 /** Compute the dim as a function of the scale of this view. */ 495 int getDimOverlayFromScale() { 496 float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale; 497 float scaleRange = 1f - minScale; 498 float dim = (1f - getScaleX()) / scaleRange; 499 dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f)); 500 return Math.max(0, Math.min(mMaxDim, (int) (dim * 255))); 501 } 502 503 /** Update the dim as a function of the scale of this view. */ 504 void updateDimOverlayFromScale() { 505 setDim(getDimOverlayFromScale()); 506 } 507 508 /**** View focus state ****/ 509 510 /** 511 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 512 * if the view is not currently visible, or we are in touch state (where we still want to keep 513 * track of focus). 514 */ 515 public void setFocusedTask() { 516 mIsFocused = true; 517 // Workaround, we don't always want it focusable in touch mode, but we want the first task 518 // to be focused after the enter-recents animation, which can be triggered from either touch 519 // or keyboard 520 setFocusableInTouchMode(true); 521 requestFocus(); 522 setFocusableInTouchMode(false); 523 invalidate(); 524 } 525 526 /** 527 * Updates the explicitly focused state when the view focus changes. 528 */ 529 @Override 530 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 531 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 532 if (!gainFocus) { 533 mIsFocused = false; 534 invalidate(); 535 } 536 } 537 538 /** 539 * Returns whether we have explicitly been focused. 540 */ 541 public boolean isFocusedTask() { 542 return mIsFocused || isFocused(); 543 } 544 545 /**** TaskCallbacks Implementation ****/ 546 547 /** Binds this task view to the task */ 548 public void onTaskBound(Task t) { 549 mTask = t; 550 mTask.setCallbacks(this); 551 if (getMeasuredWidth() == 0) { 552 // If we haven't yet measured, we should just set the footer height with any animation 553 animateFooterVisibility(t.lockToThisTask, 0); 554 } else { 555 animateFooterVisibility(t.lockToThisTask, mConfig.taskViewLockToAppLongAnimDuration); 556 } 557 } 558 559 @Override 560 public void onTaskDataLoaded() { 561 if (mThumbnailView != null && mBarView != null) { 562 // Bind each of the views to the new task data 563 if (mIsFullScreenView) { 564 mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot()); 565 } else { 566 mThumbnailView.rebindToTask(mTask); 567 } 568 mBarView.rebindToTask(mTask); 569 // Rebind any listeners 570 if (Constants.DebugFlags.App.EnableTaskFiltering) { 571 mBarView.mApplicationIcon.setOnClickListener(this); 572 } 573 mBarView.mDismissButton.setOnClickListener(this); 574 mFooterView.setOnClickListener(this); 575 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 576 if (mConfig.developerOptionsEnabled) { 577 mBarView.mApplicationIcon.setOnLongClickListener(this); 578 } 579 } 580 } 581 mTaskDataLoaded = true; 582 } 583 584 @Override 585 public void onTaskDataUnloaded() { 586 if (mThumbnailView != null && mBarView != null) { 587 // Unbind each of the views from the task data and remove the task callback 588 mTask.setCallbacks(null); 589 mThumbnailView.unbindFromTask(); 590 mBarView.unbindFromTask(); 591 // Unbind any listeners 592 if (Constants.DebugFlags.App.EnableTaskFiltering) { 593 mBarView.mApplicationIcon.setOnClickListener(null); 594 } 595 mBarView.mDismissButton.setOnClickListener(null); 596 mFooterView.setOnClickListener(null); 597 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 598 mBarView.mApplicationIcon.setOnLongClickListener(null); 599 } 600 } 601 mTaskDataLoaded = false; 602 } 603 604 /** Enables/disables handling touch on this task view. */ 605 void setTouchEnabled(boolean enabled) { 606 setOnClickListener(enabled ? this : null); 607 } 608 609 /**** TaskFooterView.TaskFooterViewCallbacks ****/ 610 611 @Override 612 public void onTaskFooterHeightChanged(int height, int maxHeight) { 613 if (mIsFullScreenView) { 614 // Disable the bottom outline clip when fullscreen 615 mViewBounds.setOutlineClipBottom(0); 616 } else { 617 // Update the bottom clip in our outline provider 618 mViewBounds.setOutlineClipBottom(maxHeight - height); 619 } 620 } 621 622 /**** View.OnClickListener Implementation ****/ 623 624 @Override 625 public void onClick(final View v) { 626 // We purposely post the handler delayed to allow for the touch feedback to draw 627 final TaskView tv = this; 628 postDelayed(new Runnable() { 629 @Override 630 public void run() { 631 if (v == mBarView.mApplicationIcon) { 632 mCb.onTaskViewAppIconClicked(tv); 633 } else if (v == mBarView.mDismissButton) { 634 // Animate out the view and call the callback 635 startDeleteTaskAnimation(new Runnable() { 636 @Override 637 public void run() { 638 mCb.onTaskViewDismissed(tv); 639 } 640 }); 641 // Hide the footer 642 tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration); 643 } else if (v == tv || v == mFooterView) { 644 mCb.onTaskViewClicked(tv, tv.getTask(), (v == mFooterView)); 645 } 646 } 647 }, 125); 648 } 649 650 /**** View.OnLongClickListener Implementation ****/ 651 652 @Override 653 public boolean onLongClick(View v) { 654 if (v == mBarView.mApplicationIcon) { 655 mCb.onTaskViewAppInfoClicked(this); 656 return true; 657 } 658 return false; 659 } 660} 661