StackStateAnimator.java revision 75c95044a8c5c073d30dcc9bd21157939f161043
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.statusbar.stack; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.PropertyValuesHolder; 23import android.animation.ValueAnimator; 24import android.view.View; 25import android.view.animation.AnimationUtils; 26import android.view.animation.Interpolator; 27 28import com.android.systemui.R; 29import com.android.systemui.statusbar.ExpandableView; 30 31import java.util.ArrayList; 32import java.util.HashSet; 33import java.util.Set; 34import java.util.Stack; 35 36/** 37 * An stack state animator which handles animations to new StackScrollStates 38 */ 39public class StackStateAnimator { 40 41 public static final int ANIMATION_DURATION_STANDARD = 360; 42 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; 43 44 private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; 45 private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; 46 private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag; 47 private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; 48 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag; 49 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag; 50 private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; 51 private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; 52 private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag; 53 private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; 54 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag; 55 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag; 56 private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag; 57 private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; 58 private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag; 59 private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; 60 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag; 61 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag; 62 63 private final Interpolator mFastOutSlowInInterpolator; 64 public NotificationStackScrollLayout mHostLayout; 65 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mHandledEvents = 66 new ArrayList<>(); 67 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = 68 new ArrayList<>(); 69 private Set<Animator> mAnimatorSet = new HashSet<Animator>(); 70 private Stack<AnimatorListenerAdapter> mAnimationListenerPool 71 = new Stack<AnimatorListenerAdapter>(); 72 private AnimationFilter mAnimationFilter = new AnimationFilter(); 73 private long mCurrentLength; 74 75 private ValueAnimator mTopOverScrollAnimator; 76 private ValueAnimator mBottomOverScrollAnimator; 77 78 public StackStateAnimator(NotificationStackScrollLayout hostLayout) { 79 mHostLayout = hostLayout; 80 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(), 81 android.R.interpolator.fast_out_slow_in); 82 } 83 84 public boolean isRunning() { 85 return !mAnimatorSet.isEmpty(); 86 } 87 88 public void startAnimationForEvents( 89 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, 90 StackScrollState finalState) { 91 92 processAnimationEvents(mAnimationEvents, finalState); 93 94 int childCount = mHostLayout.getChildCount(); 95 mAnimationFilter.applyCombination(mNewEvents); 96 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents); 97 for (int i = 0; i < childCount; i++) { 98 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); 99 StackScrollState.ViewState viewState = finalState.getViewStateForView(child); 100 if (viewState == null) { 101 continue; 102 } 103 104 startAnimations(child, viewState); 105 106 child.setClipBounds(null); 107 } 108 if (!isRunning()) { 109 // no child has preformed any animation, lets finish 110 onAnimationFinished(); 111 } 112 } 113 114 /** 115 * Start an animation to the given viewState 116 */ 117 private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState) { 118 int childVisibility = child.getVisibility(); 119 boolean wasVisible = childVisibility == View.VISIBLE; 120 final float alpha = viewState.alpha; 121 if (!wasVisible && alpha != 0 && !viewState.gone) { 122 child.setVisibility(View.VISIBLE); 123 } 124 // start translationY animation 125 if (child.getTranslationY() != viewState.yTranslation) { 126 startYTranslationAnimation(child, viewState); 127 } 128 // start translationZ animation 129 if (child.getTranslationZ() != viewState.zTranslation) { 130 startZTranslationAnimation(child, viewState); 131 } 132 // start scale animation 133 if (child.getScaleX() != viewState.scale) { 134 startScaleAnimation(child, viewState); 135 } 136 // start alpha animation 137 if (alpha != child.getAlpha()) { 138 startAlphaAnimation(child, viewState); 139 } 140 // start height animation 141 if (viewState.height != child.getActualHeight()) { 142 startHeightAnimation(child, viewState); 143 } 144 // start dimmed animation 145 child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); 146 } 147 148 private void startHeightAnimation(final ExpandableView child, 149 StackScrollState.ViewState viewState) { 150 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); 151 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); 152 int newEndValue = viewState.height; 153 if (previousEndValue != null && previousEndValue == newEndValue) { 154 return; 155 } 156 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); 157 if (!mAnimationFilter.animateHeight) { 158 // just a local update was performed 159 if (previousAnimator != null) { 160 // we need to increase all animation keyframes of the previous animator by the 161 // relative change to the end value 162 PropertyValuesHolder[] values = previousAnimator.getValues(); 163 int relativeDiff = newEndValue - previousEndValue; 164 int newStartValue = previousStartValue + relativeDiff; 165 values[0].setIntValues(newStartValue, newEndValue); 166 child.setTag(TAG_START_HEIGHT, newStartValue); 167 child.setTag(TAG_END_HEIGHT, newEndValue); 168 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 169 return; 170 } else { 171 // no new animation needed, let's just apply the value 172 child.setActualHeight(newEndValue, false); 173 return; 174 } 175 } 176 177 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue); 178 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 179 @Override 180 public void onAnimationUpdate(ValueAnimator animation) { 181 child.setActualHeight((int) animation.getAnimatedValue(), 182 false /* notifyListeners */); 183 } 184 }); 185 animator.setInterpolator(mFastOutSlowInInterpolator); 186 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 187 animator.setDuration(newDuration); 188 animator.addListener(getGlobalAnimationFinishedListener()); 189 // remove the tag when the animation is finished 190 animator.addListener(new AnimatorListenerAdapter() { 191 @Override 192 public void onAnimationEnd(Animator animation) { 193 child.setTag(TAG_ANIMATOR_HEIGHT, null); 194 child.setTag(TAG_START_HEIGHT, null); 195 child.setTag(TAG_END_HEIGHT, null); 196 } 197 }); 198 startInstantly(animator); 199 child.setTag(TAG_ANIMATOR_HEIGHT, animator); 200 child.setTag(TAG_START_HEIGHT, child.getActualHeight()); 201 child.setTag(TAG_END_HEIGHT, newEndValue); 202 } 203 204 private void startAlphaAnimation(final ExpandableView child, 205 final StackScrollState.ViewState viewState) { 206 Float previousStartValue = getChildTag(child,TAG_START_ALPHA); 207 Float previousEndValue = getChildTag(child,TAG_END_ALPHA); 208 final float newEndValue = viewState.alpha; 209 if (previousEndValue != null && previousEndValue == newEndValue) { 210 return; 211 } 212 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); 213 if (!mAnimationFilter.animateAlpha) { 214 // just a local update was performed 215 if (previousAnimator != null) { 216 // we need to increase all animation keyframes of the previous animator by the 217 // relative change to the end value 218 PropertyValuesHolder[] values = previousAnimator.getValues(); 219 float relativeDiff = newEndValue - previousEndValue; 220 float newStartValue = previousStartValue + relativeDiff; 221 values[0].setFloatValues(newStartValue, newEndValue); 222 child.setTag(TAG_START_ALPHA, newStartValue); 223 child.setTag(TAG_END_ALPHA, newEndValue); 224 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 225 return; 226 } else { 227 // no new animation needed, let's just apply the value 228 child.setAlpha(newEndValue); 229 if (newEndValue == 0) { 230 child.setVisibility(View.INVISIBLE); 231 } 232 } 233 } 234 235 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, 236 child.getAlpha(), newEndValue); 237 animator.setInterpolator(mFastOutSlowInInterpolator); 238 // Handle layer type 239 final int currentLayerType = child.getLayerType(); 240 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 241 animator.addListener(new AnimatorListenerAdapter() { 242 public boolean mWasCancelled; 243 244 @Override 245 public void onAnimationEnd(Animator animation) { 246 child.setLayerType(currentLayerType, null); 247 if (newEndValue == 0 && !mWasCancelled) { 248 child.setVisibility(View.INVISIBLE); 249 } 250 child.setTag(TAG_ANIMATOR_ALPHA, null); 251 child.setTag(TAG_START_ALPHA, null); 252 child.setTag(TAG_END_ALPHA, null); 253 } 254 255 @Override 256 public void onAnimationCancel(Animator animation) { 257 mWasCancelled = true; 258 } 259 260 @Override 261 public void onAnimationStart(Animator animation) { 262 mWasCancelled = false; 263 } 264 }); 265 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 266 animator.setDuration(newDuration); 267 animator.addListener(getGlobalAnimationFinishedListener()); 268 // remove the tag when the animation is finished 269 animator.addListener(new AnimatorListenerAdapter() { 270 @Override 271 public void onAnimationEnd(Animator animation) { 272 273 } 274 }); 275 startInstantly(animator); 276 child.setTag(TAG_ANIMATOR_ALPHA, animator); 277 child.setTag(TAG_START_ALPHA, child.getAlpha()); 278 child.setTag(TAG_END_ALPHA, newEndValue); 279 } 280 281 private void startZTranslationAnimation(final ExpandableView child, 282 final StackScrollState.ViewState viewState) { 283 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); 284 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); 285 float newEndValue = viewState.zTranslation; 286 if (previousEndValue != null && previousEndValue == newEndValue) { 287 return; 288 } 289 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); 290 if (!mAnimationFilter.animateZ) { 291 // just a local update was performed 292 if (previousAnimator != null) { 293 // we need to increase all animation keyframes of the previous animator by the 294 // relative change to the end value 295 PropertyValuesHolder[] values = previousAnimator.getValues(); 296 float relativeDiff = newEndValue - previousEndValue; 297 float newStartValue = previousStartValue + relativeDiff; 298 values[0].setFloatValues(newStartValue, newEndValue); 299 child.setTag(TAG_START_TRANSLATION_Z, newStartValue); 300 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 301 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 302 return; 303 } else { 304 // no new animation needed, let's just apply the value 305 child.setTranslationZ(newEndValue); 306 } 307 } 308 309 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, 310 child.getTranslationZ(), newEndValue); 311 animator.setInterpolator(mFastOutSlowInInterpolator); 312 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 313 animator.setDuration(newDuration); 314 animator.addListener(getGlobalAnimationFinishedListener()); 315 // remove the tag when the animation is finished 316 animator.addListener(new AnimatorListenerAdapter() { 317 @Override 318 public void onAnimationEnd(Animator animation) { 319 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); 320 child.setTag(TAG_START_TRANSLATION_Z, null); 321 child.setTag(TAG_END_TRANSLATION_Z, null); 322 } 323 }); 324 startInstantly(animator); 325 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); 326 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); 327 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 328 } 329 330 private void startYTranslationAnimation(final ExpandableView child, 331 StackScrollState.ViewState viewState) { 332 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); 333 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); 334 float newEndValue = viewState.yTranslation; 335 if (previousEndValue != null && previousEndValue == newEndValue) { 336 return; 337 } 338 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); 339 if (!mAnimationFilter.animateY) { 340 // just a local update was performed 341 if (previousAnimator != null) { 342 // we need to increase all animation keyframes of the previous animator by the 343 // relative change to the end value 344 PropertyValuesHolder[] values = previousAnimator.getValues(); 345 float relativeDiff = newEndValue - previousEndValue; 346 float newStartValue = previousStartValue + relativeDiff; 347 values[0].setFloatValues(newStartValue, newEndValue); 348 child.setTag(TAG_START_TRANSLATION_Y, newStartValue); 349 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 350 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 351 return; 352 } else { 353 // no new animation needed, let's just apply the value 354 child.setTranslationY(newEndValue); 355 return; 356 } 357 } 358 359 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, 360 child.getTranslationY(), newEndValue); 361 animator.setInterpolator(mFastOutSlowInInterpolator); 362 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 363 animator.setDuration(newDuration); 364 animator.addListener(getGlobalAnimationFinishedListener()); 365 // remove the tag when the animation is finished 366 animator.addListener(new AnimatorListenerAdapter() { 367 @Override 368 public void onAnimationEnd(Animator animation) { 369 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); 370 child.setTag(TAG_START_TRANSLATION_Y, null); 371 child.setTag(TAG_END_TRANSLATION_Y, null); 372 } 373 }); 374 startInstantly(animator); 375 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); 376 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); 377 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 378 } 379 380 private void startScaleAnimation(final ExpandableView child, 381 StackScrollState.ViewState viewState) { 382 Float previousStartValue = getChildTag(child, TAG_START_SCALE); 383 Float previousEndValue = getChildTag(child, TAG_END_SCALE); 384 float newEndValue = viewState.scale; 385 if (previousEndValue != null && previousEndValue == newEndValue) { 386 return; 387 } 388 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE); 389 if (!mAnimationFilter.animateScale) { 390 // just a local update was performed 391 if (previousAnimator != null) { 392 // we need to increase all animation keyframes of the previous animator by the 393 // relative change to the end value 394 PropertyValuesHolder[] values = previousAnimator.getValues(); 395 float relativeDiff = newEndValue - previousEndValue; 396 float newStartValue = previousStartValue + relativeDiff; 397 values[0].setFloatValues(newStartValue, newEndValue); 398 values[1].setFloatValues(newStartValue, newEndValue); 399 child.setTag(TAG_START_SCALE, newStartValue); 400 child.setTag(TAG_END_SCALE, newEndValue); 401 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 402 return; 403 } else { 404 // no new animation needed, let's just apply the value 405 child.setScaleX(newEndValue); 406 child.setScaleY(newEndValue); 407 } 408 } 409 410 PropertyValuesHolder holderX = 411 PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue); 412 PropertyValuesHolder holderY = 413 PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue); 414 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY); 415 animator.setInterpolator(mFastOutSlowInInterpolator); 416 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 417 animator.setDuration(newDuration); 418 animator.addListener(getGlobalAnimationFinishedListener()); 419 // remove the tag when the animation is finished 420 animator.addListener(new AnimatorListenerAdapter() { 421 @Override 422 public void onAnimationEnd(Animator animation) { 423 child.setTag(TAG_ANIMATOR_SCALE, null); 424 child.setTag(TAG_START_SCALE, null); 425 child.setTag(TAG_END_SCALE, null); 426 } 427 }); 428 startInstantly(animator); 429 child.setTag(TAG_ANIMATOR_SCALE, animator); 430 child.setTag(TAG_START_SCALE, child.getScaleX()); 431 child.setTag(TAG_END_SCALE, newEndValue); 432 } 433 434 /** 435 * Start an animator instantly instead of waiting on the next synchronization frame 436 */ 437 public static void startInstantly(ValueAnimator animator) { 438 animator.start(); 439 animator.setCurrentPlayTime(0); 440 } 441 442 /** 443 * @return an adapter which ensures that onAnimationFinished is called once no animation is 444 * running anymore 445 */ 446 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() { 447 if (!mAnimationListenerPool.empty()) { 448 return mAnimationListenerPool.pop(); 449 } 450 451 // We need to create a new one, no reusable ones found 452 return new AnimatorListenerAdapter() { 453 private boolean mWasCancelled; 454 455 @Override 456 public void onAnimationEnd(Animator animation) { 457 mAnimatorSet.remove(animation); 458 if (mAnimatorSet.isEmpty() && !mWasCancelled) { 459 onAnimationFinished(); 460 } 461 mAnimationListenerPool.push(this); 462 } 463 464 @Override 465 public void onAnimationCancel(Animator animation) { 466 mWasCancelled = true; 467 } 468 469 @Override 470 public void onAnimationStart(Animator animation) { 471 mAnimatorSet.add(animation); 472 mWasCancelled = false; 473 } 474 }; 475 } 476 477 private <T> T getChildTag(View child, int tag) { 478 return (T) child.getTag(tag); 479 } 480 481 /** 482 * Cancel the previous animator and get the duration of the new animation. 483 * 484 * @param previousAnimator the animator which was running before 485 * @return the new duration 486 */ 487 private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) { 488 long newDuration = mCurrentLength; 489 if (previousAnimator != null) { 490 // We take either the desired length of the new animation or the remaining time of 491 // the previous animator, whichever is longer. 492 newDuration = Math.max(previousAnimator.getDuration() 493 - previousAnimator.getCurrentPlayTime(), newDuration); 494 previousAnimator.cancel(); 495 } 496 return newDuration; 497 } 498 499 private void onAnimationFinished() { 500 mHandledEvents.clear(); 501 mNewEvents.clear(); 502 mHostLayout.onChildAnimationFinished(); 503 } 504 505 /** 506 * Process the animationEvents for a new animation 507 * 508 * @param animationEvents the animation events for the animation to perform 509 * @param finalState the final state to animate to 510 */ 511 private void processAnimationEvents( 512 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, 513 StackScrollState finalState) { 514 mNewEvents.clear(); 515 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { 516 View changingView = event.changingView; 517 if (!mHandledEvents.contains(event)) { 518 if (event.animationType == NotificationStackScrollLayout.AnimationEvent 519 .ANIMATION_TYPE_ADD) { 520 521 // This item is added, initialize it's properties. 522 StackScrollState.ViewState viewState = finalState 523 .getViewStateForView(changingView); 524 if (viewState == null) { 525 // The position for this child was never generated, let's continue. 526 continue; 527 } 528 changingView.setAlpha(0); 529 changingView.setTranslationY(viewState.yTranslation); 530 changingView.setTranslationZ(viewState.zTranslation); 531 } 532 mHandledEvents.add(event); 533 mNewEvents.add(event); 534 } 535 } 536 } 537 538 public void animateOverScrollToAmount(float targetAmount, final boolean onTop) { 539 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); 540 cancelOverScrollAnimators(onTop); 541 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount, 542 targetAmount); 543 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD); 544 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 545 @Override 546 public void onAnimationUpdate(ValueAnimator animation) { 547 float currentOverScroll = (float) animation.getAnimatedValue(); 548 mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */, 549 false /* cancelAnimators */); 550 } 551 }); 552 overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator); 553 overScrollAnimator.start(); 554 if (onTop) { 555 mTopOverScrollAnimator = overScrollAnimator; 556 } else { 557 mBottomOverScrollAnimator = overScrollAnimator; 558 } 559 } 560 561 public void cancelOverScrollAnimators(boolean onTop) { 562 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator; 563 if (currentAnimator != null) { 564 currentAnimator.cancel(); 565 } 566 } 567} 568