TaskView.java revision fe03b40da4a513af759851dd399e39dfa90c3363
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.ObjectAnimator; 21import android.animation.ValueAnimator; 22import android.content.Context; 23import android.graphics.*; 24import android.util.AttributeSet; 25import android.view.View; 26import android.view.ViewOutlineProvider; 27import android.view.ViewPropertyAnimator; 28import android.view.animation.AccelerateInterpolator; 29import android.widget.FrameLayout; 30import com.android.systemui.R; 31import com.android.systemui.recents.Constants; 32import com.android.systemui.recents.RecentsConfiguration; 33import com.android.systemui.recents.misc.Utilities; 34import com.android.systemui.recents.model.Task; 35import com.android.systemui.statusbar.phone.PhoneStatusBar; 36 37/* A task view */ 38public class TaskView extends FrameLayout implements Task.TaskCallbacks, 39 View.OnClickListener, View.OnLongClickListener { 40 41 /** The TaskView callbacks */ 42 interface TaskViewCallbacks { 43 public void onTaskViewAppIconClicked(TaskView tv); 44 public void onTaskViewAppInfoClicked(TaskView tv); 45 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); 46 public void onTaskViewDismissed(TaskView tv); 47 public void onTaskViewClipStateChanged(TaskView tv); 48 public void onTaskViewFocusChanged(TaskView tv, boolean focused); 49 } 50 51 RecentsConfiguration mConfig; 52 53 float mTaskProgress; 54 ObjectAnimator mTaskProgressAnimator; 55 float mMaxDimScale; 56 int mDimAlpha; 57 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f); 58 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); 59 Paint mDimLayerPaint = new Paint(); 60 61 Task mTask; 62 boolean mTaskDataLoaded; 63 boolean mIsFocused; 64 boolean mFocusAnimationsEnabled; 65 boolean mClipViewInStack; 66 AnimateableViewBounds mViewBounds; 67 68 View mContent; 69 TaskViewThumbnail mThumbnailView; 70 TaskViewHeader mHeaderView; 71 View mActionButtonView; 72 TaskViewCallbacks mCb; 73 74 // Optimizations 75 ValueAnimator.AnimatorUpdateListener mUpdateDimListener = 76 new ValueAnimator.AnimatorUpdateListener() { 77 @Override 78 public void onAnimationUpdate(ValueAnimator animation) { 79 setTaskProgress((Float) animation.getAnimatedValue()); 80 } 81 }; 82 83 84 public TaskView(Context context) { 85 this(context, null); 86 } 87 88 public TaskView(Context context, AttributeSet attrs) { 89 this(context, attrs, 0); 90 } 91 92 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 93 this(context, attrs, defStyleAttr, 0); 94 } 95 96 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 97 super(context, attrs, defStyleAttr, defStyleRes); 98 mConfig = RecentsConfiguration.getInstance(); 99 mMaxDimScale = mConfig.taskStackMaxDim / 255f; 100 mClipViewInStack = true; 101 mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx); 102 setTaskProgress(getTaskProgress()); 103 setDim(getDim()); 104 if (mConfig.fakeShadows) { 105 setBackground(new FakeShadowDrawable(context.getResources(), mConfig)); 106 } 107 setOutlineProvider(mViewBounds); 108 } 109 110 /** Set callback */ 111 void setCallbacks(TaskViewCallbacks cb) { 112 mCb = cb; 113 } 114 115 /** Resets this TaskView for reuse. */ 116 void reset() { 117 resetViewProperties(); 118 resetNoUserInteractionState(); 119 setClipViewInStack(false); 120 setCallbacks(null); 121 } 122 123 /** Gets the task */ 124 Task getTask() { 125 return mTask; 126 } 127 128 /** Returns the view bounds. */ 129 AnimateableViewBounds getViewBounds() { 130 return mViewBounds; 131 } 132 133 @Override 134 protected void onFinishInflate() { 135 // Bind the views 136 mContent = findViewById(R.id.task_view_content); 137 mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar); 138 mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail); 139 mThumbnailView.updateClipToTaskBar(mHeaderView); 140 mActionButtonView = findViewById(R.id.lock_to_app_fab); 141 mActionButtonView.setOutlineProvider(new ViewOutlineProvider() { 142 @Override 143 public void getOutline(View view, Outline outline) { 144 // Set the outline to match the FAB background 145 outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight()); 146 } 147 }); 148 } 149 150 @Override 151 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 152 int width = MeasureSpec.getSize(widthMeasureSpec); 153 int height = MeasureSpec.getSize(heightMeasureSpec); 154 155 int widthWithoutPadding = width - mPaddingLeft - mPaddingRight; 156 int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; 157 158 // Measure the content 159 mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 160 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY)); 161 162 // Measure the bar view, and action button 163 mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 164 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY)); 165 mActionButtonView.measure( 166 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST), 167 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST)); 168 // Measure the thumbnail to be square 169 mThumbnailView.measure( 170 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), 171 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY)); 172 setMeasuredDimension(width, height); 173 invalidateOutline(); 174 } 175 176 /** Synchronizes this view's properties with the task's transform */ 177 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) { 178 updateViewPropertiesToTaskTransform(toTransform, duration, null); 179 } 180 181 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration, 182 ValueAnimator.AnimatorUpdateListener updateCallback) { 183 // Apply the transform 184 toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false, 185 !mConfig.fakeShadows, updateCallback); 186 187 // Update the task progress 188 Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator); 189 if (duration <= 0) { 190 setTaskProgress(toTransform.p); 191 } else { 192 mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p); 193 mTaskProgressAnimator.setDuration(duration); 194 mTaskProgressAnimator.addUpdateListener(mUpdateDimListener); 195 mTaskProgressAnimator.start(); 196 } 197 } 198 199 /** Resets this view's properties */ 200 void resetViewProperties() { 201 setDim(0); 202 setLayerType(View.LAYER_TYPE_NONE, null); 203 TaskViewTransform.reset(this); 204 } 205 206 /** 207 * When we are un/filtering, this method will set up the transform that we are animating to, 208 * in order to hide the task. 209 */ 210 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 211 // Fade the view out and slide it away 212 toTransform.alpha = 0f; 213 toTransform.translationY += 200; 214 toTransform.translationZ = 0; 215 } 216 217 /** 218 * When we are un/filtering, this method will setup the transform that we are animating from, 219 * in order to show the task. 220 */ 221 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 222 // Fade the view in 223 fromTransform.alpha = 0f; 224 } 225 226 /** Prepares this task view for the enter-recents animations. This is called earlier in the 227 * first layout because the actual animation into recents may take a long time. */ 228 void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, 229 boolean occludesLaunchTarget, int offscreenY) { 230 int initialDim = getDim(); 231 if (mConfig.launchedFromAppWithThumbnail) { 232 if (isTaskViewLaunchTargetTask) { 233 // Set the dim to 0 so we can animate it in 234 initialDim = 0; 235 // Hide the action button 236 mActionButtonView.setAlpha(0f); 237 } else if (occludesLaunchTarget) { 238 // Move the task view off screen (below) so we can animate it in 239 setTranslationY(offscreenY); 240 } 241 242 } else if (mConfig.launchedFromHome) { 243 // Move the task view off screen (below) so we can animate it in 244 setTranslationY(offscreenY); 245 setTranslationZ(0); 246 setScaleX(1f); 247 setScaleY(1f); 248 } 249 // Apply the current dim 250 setDim(initialDim); 251 // Prepare the thumbnail view alpha 252 mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask); 253 } 254 255 /** Animates this task view as it enters recents */ 256 void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { 257 final TaskViewTransform transform = ctx.currentTaskTransform; 258 int startDelay = 0; 259 260 if (mConfig.launchedFromAppWithThumbnail) { 261 if (mTask.isLaunchTarget) { 262 // Animate the dim/overlay 263 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) { 264 // Animate the thumbnail alpha before the dim animation (to prevent updating the 265 // hardware layer) 266 mThumbnailView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, 267 new Runnable() { 268 @Override 269 public void run() { 270 animateDimToProgress(0, mConfig.taskBarEnterAnimDuration, 271 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 272 } 273 }); 274 } else { 275 // Immediately start the dim animation 276 animateDimToProgress(mConfig.taskBarEnterAnimDelay, 277 mConfig.taskBarEnterAnimDuration, 278 ctx.postAnimationTrigger.decrementOnAnimationEnd()); 279 } 280 ctx.postAnimationTrigger.increment(); 281 282 // Animate the action button in 283 fadeInActionButton(true); 284 } else { 285 // Animate the task up if it was occluding the launch target 286 if (ctx.currentTaskOccludesLaunchTarget) { 287 setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx); 288 setAlpha(0f); 289 animate().alpha(1f) 290 .translationY(transform.translationY) 291 .setStartDelay(mConfig.taskBarEnterAnimDelay) 292 .setUpdateListener(null) 293 .setInterpolator(mConfig.fastOutSlowInInterpolator) 294 .setDuration(mConfig.taskViewEnterFromHomeDuration) 295 .withEndAction(new Runnable() { 296 @Override 297 public void run() { 298 // Decrement the post animation trigger 299 ctx.postAnimationTrigger.decrement(); 300 } 301 }) 302 .start(); 303 ctx.postAnimationTrigger.increment(); 304 } 305 } 306 startDelay = mConfig.taskBarEnterAnimDelay; 307 308 } else if (mConfig.launchedFromHome) { 309 // Animate the tasks up 310 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); 311 int delay = mConfig.taskViewEnterFromHomeDelay + 312 frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay; 313 314 setScaleX(transform.scale); 315 setScaleY(transform.scale); 316 if (!mConfig.fakeShadows) { 317 animate().translationZ(transform.translationZ); 318 } 319 animate() 320 .translationY(transform.translationY) 321 .setStartDelay(delay) 322 .setUpdateListener(ctx.updateListener) 323 .setInterpolator(mConfig.quintOutInterpolator) 324 .setDuration(mConfig.taskViewEnterFromHomeDuration + 325 frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay) 326 .withEndAction(new Runnable() { 327 @Override 328 public void run() { 329 // Decrement the post animation trigger 330 ctx.postAnimationTrigger.decrement(); 331 } 332 }) 333 .start(); 334 ctx.postAnimationTrigger.increment(); 335 startDelay = delay; 336 } 337 338 // Enable the focus animations from this point onwards so that they aren't affected by the 339 // window transitions 340 postDelayed(new Runnable() { 341 @Override 342 public void run() { 343 enableFocusAnimations(); 344 } 345 }, (startDelay / 2)); 346 } 347 348 public void fadeInActionButton(boolean withDelay) { 349 // Hide the action button 350 mActionButtonView.setAlpha(0f); 351 352 // Animate the action button in 353 ViewPropertyAnimator animator = mActionButtonView.animate().alpha(1f) 354 .setDuration(mConfig.taskBarEnterAnimDuration) 355 .setInterpolator(PhoneStatusBar.ALPHA_IN) 356 .withLayer(); 357 if (withDelay) { 358 animator.setStartDelay(mConfig.taskBarEnterAnimDelay); 359 } 360 animator.start(); 361 } 362 363 /** Animates this task view as it leaves recents by pressing home. */ 364 void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 365 animate() 366 .translationY(ctx.offscreenTranslationY) 367 .setStartDelay(0) 368 .setUpdateListener(null) 369 .setInterpolator(mConfig.fastOutLinearInInterpolator) 370 .setDuration(mConfig.taskViewExitToHomeDuration) 371 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) 372 .start(); 373 ctx.postAnimationTrigger.increment(); 374 } 375 376 /** Animates this task view as it exits recents */ 377 void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, 378 boolean occludesLaunchTarget, boolean lockToTask) { 379 if (isLaunchingTask) { 380 // Animate the thumbnail alpha back into full opacity for the window animation out 381 mThumbnailView.startLaunchTaskAnimation(postAnimRunnable); 382 383 // Animate the dim 384 if (mDimAlpha > 0) { 385 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); 386 anim.setDuration(mConfig.taskBarExitAnimDuration); 387 anim.setInterpolator(mConfig.fastOutLinearInInterpolator); 388 anim.start(); 389 } 390 391 // Animate the action button away 392 if (!lockToTask) { 393 float toScale = 0.9f; 394 mActionButtonView.animate() 395 .scaleX(toScale) 396 .scaleY(toScale); 397 } 398 mActionButtonView.animate() 399 .alpha(0f) 400 .setStartDelay(0) 401 .setDuration(mConfig.taskBarExitAnimDuration) 402 .setInterpolator(mConfig.fastOutLinearInInterpolator) 403 .withLayer() 404 .start(); 405 } else { 406 // Hide the dismiss button 407 mHeaderView.startLaunchTaskDismissAnimation(); 408 // If this is another view in the task grouping and is in front of the launch task, 409 // animate it away first 410 if (occludesLaunchTarget) { 411 animate().alpha(0f) 412 .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx) 413 .setStartDelay(0) 414 .setUpdateListener(null) 415 .setInterpolator(mConfig.fastOutLinearInInterpolator) 416 .setDuration(mConfig.taskBarExitAnimDuration) 417 .start(); 418 } 419 } 420 } 421 422 /** Animates the deletion of this task view */ 423 void startDeleteTaskAnimation(final Runnable r) { 424 // Disabling clipping with the stack while the view is animating away 425 setClipViewInStack(false); 426 427 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) 428 .alpha(0f) 429 .setStartDelay(0) 430 .setUpdateListener(null) 431 .setInterpolator(mConfig.fastOutSlowInInterpolator) 432 .setDuration(mConfig.taskViewRemoveAnimDuration) 433 .withEndAction(new Runnable() { 434 @Override 435 public void run() { 436 // We just throw this into a runnable because starting a view property 437 // animation using layers can cause inconsisten results if we try and 438 // update the layers while the animation is running. In some cases, 439 // the runnabled passed in may start an animation which also uses layers 440 // so we defer all this by posting this. 441 r.run(); 442 443 // Re-enable clipping with the stack (we will reuse this view) 444 setClipViewInStack(true); 445 } 446 }) 447 .start(); 448 } 449 450 /** Animates this task view if the user does not interact with the stack after a certain time. */ 451 void startNoUserInteractionAnimation() { 452 mHeaderView.startNoUserInteractionAnimation(); 453 } 454 455 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 456 void setNoUserInteractionState() { 457 mHeaderView.setNoUserInteractionState(); 458 } 459 460 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 461 void resetNoUserInteractionState() { 462 mHeaderView.resetNoUserInteractionState(); 463 } 464 465 /** Dismisses this task. */ 466 void dismissTask() { 467 // Animate out the view and call the callback 468 final TaskView tv = this; 469 startDeleteTaskAnimation(new Runnable() { 470 @Override 471 public void run() { 472 if (mCb != null) { 473 mCb.onTaskViewDismissed(tv); 474 } 475 } 476 }); 477 } 478 479 /** 480 * Returns whether this view should be clipped, or any views below should clip against this 481 * view. 482 */ 483 boolean shouldClipViewInStack() { 484 return mClipViewInStack && (getVisibility() == View.VISIBLE); 485 } 486 487 /** Sets whether this view should be clipped, or clipped against. */ 488 void setClipViewInStack(boolean clip) { 489 if (clip != mClipViewInStack) { 490 mClipViewInStack = clip; 491 if (mCb != null) { 492 mCb.onTaskViewClipStateChanged(this); 493 } 494 } 495 } 496 497 /** Sets the current task progress. */ 498 public void setTaskProgress(float p) { 499 mTaskProgress = p; 500 mViewBounds.setAlpha(p); 501 updateDimFromTaskProgress(); 502 } 503 504 /** Returns the current task progress. */ 505 public float getTaskProgress() { 506 return mTaskProgress; 507 } 508 509 /** Returns the current dim. */ 510 public void setDim(int dim) { 511 mDimAlpha = dim; 512 if (mConfig.useHardwareLayers) { 513 // Defer setting hardware layers if we have not yet measured, or there is no dim to draw 514 if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { 515 mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0)); 516 mDimLayerPaint.setColorFilter(mDimColorFilter); 517 mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 518 } 519 } else { 520 float dimAlpha = mDimAlpha / 255.0f; 521 if (mThumbnailView != null) { 522 mThumbnailView.setDimAlpha(dimAlpha); 523 } 524 if (mHeaderView != null) { 525 mHeaderView.setDimAlpha(dim); 526 } 527 } 528 } 529 530 /** Returns the current dim. */ 531 public int getDim() { 532 return mDimAlpha; 533 } 534 535 /** Animates the dim to the task progress. */ 536 void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) { 537 // Animate the dim into view as well 538 int toDim = getDimFromTaskProgress(); 539 if (toDim != getDim()) { 540 ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); 541 anim.setStartDelay(delay); 542 anim.setDuration(duration); 543 if (postAnimRunnable != null) { 544 anim.addListener(postAnimRunnable); 545 } 546 anim.start(); 547 } 548 } 549 550 /** Compute the dim as a function of the scale of this view. */ 551 int getDimFromTaskProgress() { 552 float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress); 553 return (int) (dim * 255); 554 } 555 556 /** Update the dim as a function of the scale of this view. */ 557 void updateDimFromTaskProgress() { 558 setDim(getDimFromTaskProgress()); 559 } 560 561 /**** View focus state ****/ 562 563 /** 564 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 565 * if the view is not currently visible, or we are in touch state (where we still want to keep 566 * track of focus). 567 */ 568 public void setFocusedTask(boolean animateFocusedState) { 569 mIsFocused = true; 570 if (mFocusAnimationsEnabled) { 571 // Focus the header bar 572 mHeaderView.onTaskViewFocusChanged(true, animateFocusedState); 573 } 574 // Update the thumbnail alpha with the focus 575 mThumbnailView.onFocusChanged(true); 576 // Call the callback 577 if (mCb != null) { 578 mCb.onTaskViewFocusChanged(this, true); 579 } 580 // Workaround, we don't always want it focusable in touch mode, but we want the first task 581 // to be focused after the enter-recents animation, which can be triggered from either touch 582 // or keyboard 583 setFocusableInTouchMode(true); 584 requestFocus(); 585 setFocusableInTouchMode(false); 586 invalidate(); 587 } 588 589 /** 590 * Unsets the focused task explicitly. 591 */ 592 void unsetFocusedTask() { 593 mIsFocused = false; 594 if (mFocusAnimationsEnabled) { 595 // Un-focus the header bar 596 mHeaderView.onTaskViewFocusChanged(false, true); 597 } 598 599 // Update the thumbnail alpha with the focus 600 mThumbnailView.onFocusChanged(false); 601 // Call the callback 602 if (mCb != null) { 603 mCb.onTaskViewFocusChanged(this, false); 604 } 605 invalidate(); 606 } 607 608 /** 609 * Updates the explicitly focused state when the view focus changes. 610 */ 611 @Override 612 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 613 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 614 if (!gainFocus) { 615 unsetFocusedTask(); 616 } 617 } 618 619 /** 620 * Returns whether we have explicitly been focused. 621 */ 622 public boolean isFocusedTask() { 623 return mIsFocused || isFocused(); 624 } 625 626 /** Enables all focus animations. */ 627 void enableFocusAnimations() { 628 boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; 629 mFocusAnimationsEnabled = true; 630 if (mIsFocused && !wasFocusAnimationsEnabled) { 631 // Re-notify the header if we were focused and animations were not previously enabled 632 mHeaderView.onTaskViewFocusChanged(true, true); 633 } 634 } 635 636 /**** TaskCallbacks Implementation ****/ 637 638 /** Binds this task view to the task */ 639 public void onTaskBound(Task t) { 640 mTask = t; 641 mTask.setCallbacks(this); 642 643 // Hide the action button if lock to app is disabled for this view 644 int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE; 645 if (mActionButtonView.getVisibility() != lockButtonVisibility) { 646 mActionButtonView.setVisibility(lockButtonVisibility); 647 requestLayout(); 648 } 649 } 650 651 @Override 652 public void onTaskDataLoaded() { 653 if (mThumbnailView != null && mHeaderView != null) { 654 // Bind each of the views to the new task data 655 mThumbnailView.rebindToTask(mTask); 656 mHeaderView.rebindToTask(mTask); 657 // Rebind any listeners 658 mHeaderView.mApplicationIcon.setOnClickListener(this); 659 mHeaderView.mDismissButton.setOnClickListener(this); 660 mActionButtonView.setOnClickListener(this); 661 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 662 if (mConfig.developerOptionsEnabled) { 663 mHeaderView.mApplicationIcon.setOnLongClickListener(this); 664 } 665 } 666 } 667 mTaskDataLoaded = true; 668 } 669 670 @Override 671 public void onTaskDataUnloaded() { 672 if (mThumbnailView != null && mHeaderView != null) { 673 // Unbind each of the views from the task data and remove the task callback 674 mTask.setCallbacks(null); 675 mThumbnailView.unbindFromTask(); 676 mHeaderView.unbindFromTask(); 677 // Unbind any listeners 678 mHeaderView.mApplicationIcon.setOnClickListener(null); 679 mHeaderView.mDismissButton.setOnClickListener(null); 680 mActionButtonView.setOnClickListener(null); 681 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { 682 mHeaderView.mApplicationIcon.setOnLongClickListener(null); 683 } 684 } 685 mTaskDataLoaded = false; 686 } 687 688 /** Enables/disables handling touch on this task view. */ 689 void setTouchEnabled(boolean enabled) { 690 setOnClickListener(enabled ? this : null); 691 } 692 693 /**** View.OnClickListener Implementation ****/ 694 695 @Override 696 public void onClick(final View v) { 697 final TaskView tv = this; 698 final boolean delayViewClick = (v != this) && (v != mActionButtonView); 699 if (delayViewClick) { 700 // We purposely post the handler delayed to allow for the touch feedback to draw 701 postDelayed(new Runnable() { 702 @Override 703 public void run() { 704 if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) { 705 if (mCb != null) { 706 mCb.onTaskViewAppIconClicked(tv); 707 } 708 } else if (v == mHeaderView.mDismissButton) { 709 dismissTask(); 710 } 711 } 712 }, 125); 713 } else { 714 if (v == mActionButtonView) { 715 // Reset the translation of the action button before we animate it out 716 mActionButtonView.setTranslationZ(0f); 717 } 718 if (mCb != null) { 719 mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView)); 720 } 721 } 722 } 723 724 /**** View.OnLongClickListener Implementation ****/ 725 726 @Override 727 public boolean onLongClick(View v) { 728 if (v == mHeaderView.mApplicationIcon) { 729 if (mCb != null) { 730 mCb.onTaskViewAppInfoClicked(this); 731 return true; 732 } 733 } 734 return false; 735 } 736} 737