blob: 61a64e3f33eef47f35efb3a83fddeba9b72f4a5c [file] [log] [blame]
Winson Chungdc61c4d2015-04-20 18:26:57 -07001/*
2 * Copyright (C) 2015 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.launcher3;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.TimeInterpolator;
23import android.animation.ValueAnimator;
24import android.content.Context;
25import android.content.res.Resources;
26import android.view.View;
Sunny Goyal1d08f702015-05-04 15:50:25 -070027import android.view.ViewGroup;
Winson Chungdc61c4d2015-04-20 18:26:57 -070028import android.view.accessibility.AccessibilityManager;
Sunny Goyal1d08f702015-05-04 15:50:25 -070029import android.view.accessibility.AccessibilityNodeInfo;
Winson Chungdc61c4d2015-04-20 18:26:57 -070030import android.view.animation.DecelerateInterpolator;
Sunny Goyal1d08f702015-05-04 15:50:25 -070031
Winson Chungdc61c4d2015-04-20 18:26:57 -070032import com.android.launcher3.util.Thunk;
33
34import java.util.HashMap;
35
36/**
37 * A convenience class to update a view's visibility state after an alpha animation.
38 */
39class AlphaUpdateListener extends AnimatorListenerAdapter implements ValueAnimator.AnimatorUpdateListener {
40 private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
41
42 private View mView;
43 private boolean mAccessibilityEnabled;
44
45 public AlphaUpdateListener(View v, boolean accessibilityEnabled) {
46 mView = v;
47 mAccessibilityEnabled = accessibilityEnabled;
48 }
49
50 @Override
51 public void onAnimationUpdate(ValueAnimator arg0) {
52 updateVisibility(mView, mAccessibilityEnabled);
53 }
54
55 public static void updateVisibility(View view, boolean accessibilityEnabled) {
56 // We want to avoid the extra layout pass by setting the views to GONE unless
57 // accessibility is on, in which case not setting them to GONE causes a glitch.
58 int invisibleState = accessibilityEnabled ? View.GONE : View.INVISIBLE;
59 if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
60 view.setVisibility(invisibleState);
61 } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
62 && view.getVisibility() != View.VISIBLE) {
63 view.setVisibility(View.VISIBLE);
64 }
65 }
66
67 @Override
68 public void onAnimationEnd(Animator arg0) {
69 updateVisibility(mView, mAccessibilityEnabled);
70 }
71
72 @Override
73 public void onAnimationStart(Animator arg0) {
74 // We want the views to be visible for animation, so fade-in/out is visible
75 mView.setVisibility(View.VISIBLE);
76 }
77}
78
79/**
80 * This interpolator emulates the rate at which the perceived scale of an object changes
81 * as its distance from a camera increases. When this interpolator is applied to a scale
82 * animation on a view, it evokes the sense that the object is shrinking due to moving away
83 * from the camera.
84 */
85class ZInterpolator implements TimeInterpolator {
86 private float focalLength;
87
88 public ZInterpolator(float foc) {
89 focalLength = foc;
90 }
91
92 public float getInterpolation(float input) {
93 return (1.0f - focalLength / (focalLength + input)) /
94 (1.0f - focalLength / (focalLength + 1.0f));
95 }
96}
97
98/**
99 * The exact reverse of ZInterpolator.
100 */
101class InverseZInterpolator implements TimeInterpolator {
102 private ZInterpolator zInterpolator;
103 public InverseZInterpolator(float foc) {
104 zInterpolator = new ZInterpolator(foc);
105 }
106 public float getInterpolation(float input) {
107 return 1 - zInterpolator.getInterpolation(1 - input);
108 }
109}
110
111/**
112 * InverseZInterpolator compounded with an ease-out.
113 */
114class ZoomInInterpolator implements TimeInterpolator {
115 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
116 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
117
118 public float getInterpolation(float input) {
119 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
120 }
121}
122
123/**
124 * Manages the animations between each of the workspace states.
125 */
126public class WorkspaceStateTransitionAnimation {
127
128 public static final String TAG = "WorkspaceStateTransitionAnimation";
129
130 public static final int SCROLL_TO_CURRENT_PAGE = -1;
131 @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350;
132
133 final @Thunk Launcher mLauncher;
134 final @Thunk Workspace mWorkspace;
135
136 @Thunk AnimatorSet mStateAnimator;
137 @Thunk float[] mOldBackgroundAlphas;
138 @Thunk float[] mOldAlphas;
139 @Thunk float[] mNewBackgroundAlphas;
140 @Thunk float[] mNewAlphas;
141 @Thunk int mLastChildCount = -1;
142
143 @Thunk float mCurrentScale;
144 @Thunk float mNewScale;
145
146 @Thunk final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
147
148 // These properties refer to the background protection gradient used for AllApps and Customize
149 @Thunk ValueAnimator mBackgroundFadeInAnimation;
150 @Thunk ValueAnimator mBackgroundFadeOutAnimation;
151
152 @Thunk float mSpringLoadedShrinkFactor;
153 @Thunk float mOverviewModeShrinkFactor;
154 @Thunk float mWorkspaceScrimAlpha;
155 @Thunk int mAllAppsTransitionTime;
156 @Thunk int mOverviewTransitionTime;
157 @Thunk int mOverlayTransitionTime;
158 @Thunk boolean mWorkspaceFadeInAdjacentScreens;
159
160 public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
161 mLauncher = launcher;
162 mWorkspace = workspace;
163
164 LauncherAppState app = LauncherAppState.getInstance();
165 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
166 Resources res = launcher.getResources();
167 mAllAppsTransitionTime = res.getInteger(R.integer.config_workspaceUnshrinkTime);
168 mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime);
169 mOverlayTransitionTime = res.getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
170 mSpringLoadedShrinkFactor =
171 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f;
172 mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
173 mOverviewModeShrinkFactor = grid.getOverviewModeScale();
174 mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
175 }
176
177 public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
178 int toPage, boolean animated,
179 HashMap<View, Integer> layerViews) {
180 getAnimation(fromState, toState, toPage, animated, layerViews);
181 return mStateAnimator;
182 }
183
184 public float getFinalScale() {
185 return mNewScale;
186 }
187
188 /**
189 * Starts a transition animation for the workspace.
190 */
191 private void getAnimation(final Workspace.State fromState, final Workspace.State toState,
192 int toPage, final boolean animated,
193 final HashMap<View, Integer> layerViews) {
194 AccessibilityManager am = (AccessibilityManager)
195 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
Sunny Goyal1d08f702015-05-04 15:50:25 -0700196 final boolean accessibilityEnabled = am.isEnabled();
Winson Chungdc61c4d2015-04-20 18:26:57 -0700197
198 // Reinitialize animation arrays for the current workspace state
199 reinitializeAnimationArrays();
200
201 // Cancel existing workspace animations and create a new animator set if requested
202 cancelAnimation();
203 if (animated) {
204 mStateAnimator = LauncherAnimUtils.createAnimatorSet();
205 }
206
207 // Update the workspace state
208 final boolean oldStateIsNormal = (fromState == Workspace.State.NORMAL);
209 final boolean oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
210 final boolean oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
211 final boolean oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
212 final boolean oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
213
214 final boolean stateIsNormal = (toState == Workspace.State.NORMAL);
215 final boolean stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
216 final boolean stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
217 final boolean stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
218 final boolean stateIsOverview = (toState == Workspace.State.OVERVIEW);
219
220 final boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
221 final boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
222 final boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
223 final boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
224 final boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
225
226 float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
227 float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
228 float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
229 // We keep the search bar visible on the workspace and in AllApps now
230 boolean showSearchBar = stateIsNormal ||
231 (mLauncher.isAllAppsSearchOverridden() && stateIsNormalHidden);
232 float finalSearchBarAlpha = showSearchBar ? 1f : 0f;
233 float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
234 mWorkspace.getOverviewModeTranslationY() : 0;
235
236 final int childCount = mWorkspace.getChildCount();
237 final int customPageCount = mWorkspace.numCustomPages();
238
239 mNewScale = 1.0f;
240
241 if (oldStateIsOverview) {
242 mWorkspace.disableFreeScroll();
243 } else if (stateIsOverview) {
244 mWorkspace.enableFreeScroll();
245 }
246
247 if (!stateIsNormal) {
248 if (stateIsSpringLoaded) {
249 mNewScale = mSpringLoadedShrinkFactor;
250 } else if (stateIsOverview || stateIsOverviewHidden) {
251 mNewScale = mOverviewModeShrinkFactor;
252 }
253 }
254
255 final int duration;
256 if (workspaceToAllApps || overviewToAllApps) {
257 duration = mAllAppsTransitionTime;
258 } else if (workspaceToOverview || overviewToWorkspace) {
259 duration = mOverviewTransitionTime;
260 } else {
261 duration = mOverlayTransitionTime;
262 }
263
264 if (toPage == SCROLL_TO_CURRENT_PAGE) {
265 toPage = mWorkspace.getPageNearestToCenterOfScreen();
266 }
267 mWorkspace.snapToPage(toPage, duration, mZoomInInterpolator);
268
269 for (int i = 0; i < childCount; i++) {
270 final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
271 boolean isCurrentPage = (i == toPage);
272 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
273 float finalAlpha;
274 if (stateIsNormalHidden || stateIsOverviewHidden) {
275 finalAlpha = 0f;
276 } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
277 finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
278 } else {
279 finalAlpha = 1f;
280 }
281
282 // If we are animating to/from the small state, then hide the side pages and fade the
283 // current page in
284 if (!mWorkspace.isSwitchingState()) {
285 if (workspaceToAllApps || allAppsToWorkspace) {
286 if (allAppsToWorkspace && isCurrentPage) {
287 initialAlpha = 0f;
288 } else if (!isCurrentPage) {
289 initialAlpha = finalAlpha = 0f;
290 }
291 cl.setShortcutAndWidgetAlpha(initialAlpha);
292 }
293 }
294
295 mOldAlphas[i] = initialAlpha;
296 mNewAlphas[i] = finalAlpha;
297 if (animated) {
298 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
299 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
300 } else {
301 cl.setBackgroundAlpha(finalBackgroundAlpha);
302 cl.setShortcutAndWidgetAlpha(finalAlpha);
303 }
304 }
305
306 final View searchBar = mLauncher.getOrCreateQsbBar();
Sunny Goyal1d08f702015-05-04 15:50:25 -0700307 final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
Winson Chungdc61c4d2015-04-20 18:26:57 -0700308 final View hotseat = mLauncher.getHotseat();
309 final View pageIndicator = mWorkspace.getPageIndicator();
310 if (animated) {
311 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
312 scale.scaleX(mNewScale)
313 .scaleY(mNewScale)
314 .translationY(finalWorkspaceTranslationY)
315 .setDuration(duration)
316 .setInterpolator(mZoomInInterpolator);
317 mStateAnimator.play(scale);
318 for (int index = 0; index < childCount; index++) {
319 final int i = index;
320 final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
321 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
322 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
323 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
324 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
325 } else {
326 if (layerViews != null) {
327 layerViews.put(cl, LauncherStateTransitionAnimation.BUILD_LAYER);
328 }
329 if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
330 LauncherViewPropertyAnimator alphaAnim =
331 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
332 alphaAnim.alpha(mNewAlphas[i])
333 .setDuration(duration)
334 .setInterpolator(mZoomInInterpolator);
335 mStateAnimator.play(alphaAnim);
336 }
337 if (mOldBackgroundAlphas[i] != 0 ||
338 mNewBackgroundAlphas[i] != 0) {
339 ValueAnimator bgAnim =
340 LauncherAnimUtils.ofFloat(cl, 0f, 1f);
341 bgAnim.setInterpolator(mZoomInInterpolator);
342 bgAnim.setDuration(duration);
343 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
344 public void onAnimationUpdate(float a, float b) {
345 cl.setBackgroundAlpha(
346 a * mOldBackgroundAlphas[i] +
347 b * mNewBackgroundAlphas[i]);
348 }
349 });
350 mStateAnimator.play(bgAnim);
351 }
352 }
353 }
354 Animator pageIndicatorAlpha = null;
355 if (pageIndicator != null) {
356 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
357 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
358 pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator,
359 accessibilityEnabled));
360 } else {
361 // create a dummy animation so we don't need to do null checks later
362 pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
363 }
364
365 LauncherViewPropertyAnimator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
366 .alpha(finalHotseatAndPageIndicatorAlpha);
367 hotseatAlpha.addListener(new AlphaUpdateListener(hotseat, accessibilityEnabled));
368
369 LauncherViewPropertyAnimator overviewPanelAlpha =
370 new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOverviewPanelAlpha);
371 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
372 accessibilityEnabled));
373
374 // For animation optimations, we may need to provide the Launcher transition
375 // with a set of views on which to force build layers in certain scenarios.
376 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
377 overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
378 if (layerViews != null) {
379 // If layerViews is not null, we add these views, and indicate that
380 // the caller can manage layer state.
381 layerViews.put(hotseat, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
382 layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
383 } else {
384 // Otherwise let the animator handle layer management.
385 hotseatAlpha.withLayer();
386 overviewPanelAlpha.withLayer();
387 }
388
389 if (workspaceToOverview) {
390 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
391 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
392 overviewPanelAlpha.setInterpolator(null);
393 } else if (overviewToWorkspace) {
394 pageIndicatorAlpha.setInterpolator(null);
395 hotseatAlpha.setInterpolator(null);
396 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
397 }
398
399 overviewPanelAlpha.setDuration(duration);
400 pageIndicatorAlpha.setDuration(duration);
401 hotseatAlpha.setDuration(duration);
402
403 // TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
404 // bar has no idea that it is hidden, and this has no idea what state the bar is
405 // actually in.
406 if (searchBar != null) {
407 LauncherViewPropertyAnimator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
408 .alpha(finalSearchBarAlpha);
409 searchBarAlpha.addListener(new AlphaUpdateListener(searchBar, accessibilityEnabled));
410 searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
411 if (layerViews != null) {
412 // If layerViews is not null, we add these views, and indicate that
413 // the caller can manage layer state.
414 layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
415 } else {
416 // Otherwise let the animator handle layer management.
417 searchBarAlpha.withLayer();
418 }
419 searchBarAlpha.setDuration(duration);
420 mStateAnimator.play(searchBarAlpha);
421 }
422
423 mStateAnimator.play(overviewPanelAlpha);
424 mStateAnimator.play(hotseatAlpha);
425 mStateAnimator.play(pageIndicatorAlpha);
426 mStateAnimator.addListener(new AnimatorListenerAdapter() {
427 @Override
428 public void onAnimationEnd(Animator animation) {
429 mStateAnimator = null;
Sunny Goyal1d08f702015-05-04 15:50:25 -0700430
431 if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
432 overviewPanel.getChildAt(0).performAccessibilityAction(
433 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
434 }
Winson Chungdc61c4d2015-04-20 18:26:57 -0700435 }
436 });
437 } else {
438 overviewPanel.setAlpha(finalOverviewPanelAlpha);
439 AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
440 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
441 AlphaUpdateListener.updateVisibility(hotseat, accessibilityEnabled);
442 if (pageIndicator != null) {
443 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
444 AlphaUpdateListener.updateVisibility(pageIndicator, accessibilityEnabled);
445 }
446 if (searchBar != null) {
447 searchBar.setAlpha(finalSearchBarAlpha);
448 AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
449 }
450 mWorkspace.updateCustomContentVisibility();
451 mWorkspace.setScaleX(mNewScale);
452 mWorkspace.setScaleY(mNewScale);
453 mWorkspace.setTranslationY(finalWorkspaceTranslationY);
Sunny Goyal1d08f702015-05-04 15:50:25 -0700454
455 if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
456 overviewPanel.getChildAt(0).performAccessibilityAction(
457 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
458 }
Winson Chungdc61c4d2015-04-20 18:26:57 -0700459 }
460
461 if (stateIsNormal) {
462 animateBackgroundGradient(0f, animated);
463 } else {
464 animateBackgroundGradient(mWorkspaceScrimAlpha, animated);
465 }
466 }
467
468 /**
469 * Reinitializes the arrays that we need for the animations on each page.
470 */
471 private void reinitializeAnimationArrays() {
472 final int childCount = mWorkspace.getChildCount();
473 if (mLastChildCount == childCount) return;
474
475 mOldBackgroundAlphas = new float[childCount];
476 mOldAlphas = new float[childCount];
477 mNewBackgroundAlphas = new float[childCount];
478 mNewAlphas = new float[childCount];
479 }
480
481 /**
482 * Animates the background scrim.
483 * TODO(winsonc): Is there a better place for this?
484 *
485 * @param finalAlpha the final alpha for the background scrim
486 * @param animated whether or not to set the background alpha immediately
487 */
488 private void animateBackgroundGradient(float finalAlpha, boolean animated) {
489 // Cancel any running background animations
490 cancelAnimator(mBackgroundFadeInAnimation);
491 cancelAnimator(mBackgroundFadeOutAnimation);
492
493 final DragLayer dragLayer = mLauncher.getDragLayer();
494 final float startAlpha = dragLayer.getBackgroundAlpha();
495 if (finalAlpha != startAlpha) {
496 if (animated) {
497 mBackgroundFadeOutAnimation =
498 LauncherAnimUtils.ofFloat(mWorkspace, startAlpha, finalAlpha);
499 mBackgroundFadeOutAnimation.addUpdateListener(
500 new ValueAnimator.AnimatorUpdateListener() {
501 public void onAnimationUpdate(ValueAnimator animation) {
502 dragLayer.setBackgroundAlpha(
503 ((Float)animation.getAnimatedValue()).floatValue());
504 }
505 });
506 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
507 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
508 mBackgroundFadeOutAnimation.start();
509 } else {
510 dragLayer.setBackgroundAlpha(finalAlpha);
511 }
512 }
513 }
514
515 /**
516 * Cancels the current animation.
517 */
518 private void cancelAnimation() {
519 cancelAnimator(mStateAnimator);
520 mStateAnimator = null;
521 }
522
523 /**
524 * Cancels the specified animation.
525 */
526 private void cancelAnimator(Animator animator) {
527 if (animator != null) {
528 animator.setDuration(0);
529 animator.cancel();
530 }
531 }
532}