1 /* 2 * Copyright (C) 2020 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 17 package com.android.keyguard; 18 19 import static androidx.constraintlayout.widget.ConstraintSet.END; 20 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; 21 22 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; 23 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; 24 25 import android.animation.Animator; 26 import android.animation.ValueAnimator; 27 import android.annotation.Nullable; 28 import android.content.res.Configuration; 29 import android.graphics.Rect; 30 import android.transition.ChangeBounds; 31 import android.transition.Transition; 32 import android.transition.TransitionListenerAdapter; 33 import android.transition.TransitionManager; 34 import android.transition.TransitionSet; 35 import android.transition.TransitionValues; 36 import android.util.Slog; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.FrameLayout; 40 41 import androidx.annotation.NonNull; 42 import androidx.annotation.VisibleForTesting; 43 import androidx.constraintlayout.widget.ConstraintLayout; 44 import androidx.constraintlayout.widget.ConstraintSet; 45 import androidx.viewpager.widget.ViewPager; 46 47 import com.android.app.animation.Interpolators; 48 import com.android.internal.jank.InteractionJankMonitor; 49 import com.android.keyguard.KeyguardClockSwitch.ClockSize; 50 import com.android.keyguard.logging.KeyguardLogger; 51 import com.android.systemui.Dumpable; 52 import com.android.systemui.R; 53 import com.android.systemui.dump.DumpManager; 54 import com.android.systemui.flags.FeatureFlags; 55 import com.android.systemui.flags.Flags; 56 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; 57 import com.android.systemui.keyguard.shared.model.ScreenModel; 58 import com.android.systemui.keyguard.shared.model.ScreenState; 59 import com.android.systemui.plugins.ClockController; 60 import com.android.systemui.statusbar.notification.AnimatableProperty; 61 import com.android.systemui.statusbar.notification.PropertyAnimator; 62 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 63 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 64 import com.android.systemui.statusbar.phone.DozeParameters; 65 import com.android.systemui.statusbar.phone.ScreenOffAnimationController; 66 import com.android.systemui.statusbar.policy.ConfigurationController; 67 import com.android.systemui.statusbar.policy.KeyguardStateController; 68 import com.android.systemui.util.ViewController; 69 70 import kotlin.coroutines.CoroutineContext; 71 import kotlin.coroutines.EmptyCoroutineContext; 72 73 import java.io.PrintWriter; 74 75 import javax.inject.Inject; 76 77 /** 78 * Injectable controller for {@link KeyguardStatusView}. 79 */ 80 public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> implements 81 Dumpable { 82 private static final boolean DEBUG = KeyguardConstants.DEBUG; 83 @VisibleForTesting static final String TAG = "KeyguardStatusViewController"; 84 85 /** 86 * Duration to use for the animator when the keyguard status view alignment changes, and a 87 * custom clock animation is in use. 88 */ 89 private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000; 90 91 public static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = 92 new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 93 94 private final KeyguardSliceViewController mKeyguardSliceViewController; 95 private final KeyguardClockSwitchController mKeyguardClockSwitchController; 96 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 97 private final ConfigurationController mConfigurationController; 98 private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; 99 private final FeatureFlags mFeatureFlags; 100 private final InteractionJankMonitor mInteractionJankMonitor; 101 private final Rect mClipBounds = new Rect(); 102 private final KeyguardInteractor mKeyguardInteractor; 103 104 private Boolean mStatusViewCentered = true; 105 106 private DumpManager mDumpManager; 107 108 private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = 109 new TransitionListenerAdapter() { 110 @Override 111 public void onTransitionCancel(Transition transition) { 112 mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); 113 } 114 115 @Override 116 public void onTransitionEnd(Transition transition) { 117 mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); 118 } 119 }; 120 121 @Inject KeyguardStatusViewController( KeyguardStatusView keyguardStatusView, KeyguardSliceViewController keyguardSliceViewController, KeyguardClockSwitchController keyguardClockSwitchController, KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, KeyguardLogger logger, FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, DumpManager dumpManager)122 public KeyguardStatusViewController( 123 KeyguardStatusView keyguardStatusView, 124 KeyguardSliceViewController keyguardSliceViewController, 125 KeyguardClockSwitchController keyguardClockSwitchController, 126 KeyguardStateController keyguardStateController, 127 KeyguardUpdateMonitor keyguardUpdateMonitor, 128 ConfigurationController configurationController, 129 DozeParameters dozeParameters, 130 ScreenOffAnimationController screenOffAnimationController, 131 KeyguardLogger logger, 132 FeatureFlags featureFlags, 133 InteractionJankMonitor interactionJankMonitor, 134 KeyguardInteractor keyguardInteractor, 135 DumpManager dumpManager) { 136 super(keyguardStatusView); 137 mKeyguardSliceViewController = keyguardSliceViewController; 138 mKeyguardClockSwitchController = keyguardClockSwitchController; 139 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 140 mConfigurationController = configurationController; 141 mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, 142 dozeParameters, screenOffAnimationController, /* animateYPos= */ true, 143 logger.getBuffer()); 144 mInteractionJankMonitor = interactionJankMonitor; 145 mFeatureFlags = featureFlags; 146 mDumpManager = dumpManager; 147 mKeyguardInteractor = keyguardInteractor; 148 } 149 150 @Override onInit()151 public void onInit() { 152 mKeyguardClockSwitchController.init(); 153 154 mDumpManager.registerDumpable(getInstanceName(), this); 155 if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { 156 startCoroutines(EmptyCoroutineContext.INSTANCE); 157 } 158 } 159 startCoroutines(CoroutineContext context)160 void startCoroutines(CoroutineContext context) { 161 collectFlow(mView, mKeyguardInteractor.getDozeTimeTick(), 162 (Long millis) -> { 163 dozeTimeTick(); 164 }, context); 165 166 collectFlow(mView, mKeyguardInteractor.getScreenModel(), 167 (ScreenModel model) -> { 168 if (model.getState() == ScreenState.SCREEN_TURNING_ON) { 169 dozeTimeTick(); 170 } 171 }, context); 172 } 173 getView()174 public KeyguardStatusView getView() { 175 return mView; 176 } 177 178 @Override onViewAttached()179 protected void onViewAttached() { 180 mKeyguardUpdateMonitor.registerCallback(mInfoCallback); 181 mConfigurationController.addCallback(mConfigurationListener); 182 } 183 184 @Override onViewDetached()185 protected void onViewDetached() { 186 mKeyguardUpdateMonitor.removeCallback(mInfoCallback); 187 mConfigurationController.removeCallback(mConfigurationListener); 188 } 189 190 /** Sets the StatusView as shown on an external display. */ setDisplayedOnSecondaryDisplay()191 public void setDisplayedOnSecondaryDisplay() { 192 mKeyguardClockSwitchController.setShownOnSecondaryDisplay(true); 193 } 194 195 /** 196 * Called in notificationPanelViewController to avoid leak 197 */ onDestroy()198 public void onDestroy() { 199 mDumpManager.unregisterDumpable(getInstanceName()); 200 } 201 202 /** 203 * Updates views on doze time tick. 204 */ dozeTimeTick()205 public void dozeTimeTick() { 206 refreshTime(); 207 mKeyguardSliceViewController.refresh(); 208 } 209 210 /** 211 * Set which clock should be displayed on the keyguard. The other one will be automatically 212 * hidden. 213 */ displayClock(@lockSize int clockSize, boolean animate)214 public void displayClock(@ClockSize int clockSize, boolean animate) { 215 mKeyguardClockSwitchController.displayClock(clockSize, animate); 216 } 217 218 /** 219 * Performs fold to aod animation of the clocks (changes font weight from bold to thin). 220 * This animation is played when AOD is enabled and foldable device is fully folded, it is 221 * displayed on the outer screen 222 * @param foldFraction current fraction of fold animation complete 223 */ animateFoldToAod(float foldFraction)224 public void animateFoldToAod(float foldFraction) { 225 mKeyguardClockSwitchController.animateFoldToAod(foldFraction); 226 } 227 228 /** 229 * Sets a translationY on the views on the keyguard, except on the media view. 230 */ setTranslationY(float translationY, boolean excludeMedia)231 public void setTranslationY(float translationY, boolean excludeMedia) { 232 mView.setChildrenTranslationY(translationY, excludeMedia); 233 } 234 235 /** 236 * Set keyguard status view alpha. 237 */ setAlpha(float alpha)238 public void setAlpha(float alpha) { 239 if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { 240 mView.setAlpha(alpha); 241 } 242 } 243 244 /** 245 * Update the pivot position based on the parent view 246 */ updatePivot(float parentWidth, float parentHeight)247 public void updatePivot(float parentWidth, float parentHeight) { 248 mView.setPivotX(parentWidth / 2f); 249 mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f); 250 } 251 252 /** 253 * Get the height of the keyguard status view without the notification icon area, as that's 254 * only visible on AOD. 255 */ getLockscreenHeight()256 public int getLockscreenHeight() { 257 return mView.getHeight() - mKeyguardClockSwitchController.getNotificationIconAreaHeight(); 258 } 259 260 /** 261 * Get y-bottom position of the currently visible clock. 262 */ getClockBottom(int statusBarHeaderHeight)263 public int getClockBottom(int statusBarHeaderHeight) { 264 return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight); 265 } 266 267 /** 268 * @return true if the currently displayed clock is top aligned (as opposed to center aligned) 269 */ isClockTopAligned()270 public boolean isClockTopAligned() { 271 return mKeyguardClockSwitchController.isClockTopAligned(); 272 } 273 274 /** 275 * Pass top margin from ClockPositionAlgorithm in NotificationPanelViewController 276 * Use for clock view in LS to compensate for top margin to align to the screen 277 * Regardless of translation from AOD and unlock gestures 278 */ setLockscreenClockY(int clockY)279 public void setLockscreenClockY(int clockY) { 280 mKeyguardClockSwitchController.setLockscreenClockY(clockY); 281 } 282 283 /** 284 * Set whether the view accessibility importance mode. 285 */ setStatusAccessibilityImportance(int mode)286 public void setStatusAccessibilityImportance(int mode) { 287 mView.setImportantForAccessibility(mode); 288 } 289 290 @VisibleForTesting setProperty(AnimatableProperty property, float value, boolean animate)291 void setProperty(AnimatableProperty property, float value, boolean animate) { 292 PropertyAnimator.setProperty(mView, property, value, CLOCK_ANIMATION_PROPERTIES, animate); 293 } 294 295 /** 296 * Update position of the view with an optional animation 297 */ updatePosition(int x, int y, float scale, boolean animate)298 public void updatePosition(int x, int y, float scale, boolean animate) { 299 setProperty(AnimatableProperty.Y, y, animate); 300 301 ClockController clock = mKeyguardClockSwitchController.getClock(); 302 if (clock != null && clock.getConfig().getUseAlternateSmartspaceAODTransition()) { 303 // If requested, scale the entire view instead of just the clock view 304 mKeyguardClockSwitchController.updatePosition(x, 1f /* scale */, 305 CLOCK_ANIMATION_PROPERTIES, animate); 306 setProperty(AnimatableProperty.SCALE_X, scale, animate); 307 setProperty(AnimatableProperty.SCALE_Y, scale, animate); 308 } else { 309 mKeyguardClockSwitchController.updatePosition(x, scale, 310 CLOCK_ANIMATION_PROPERTIES, animate); 311 setProperty(AnimatableProperty.SCALE_X, 1f, animate); 312 setProperty(AnimatableProperty.SCALE_Y, 1f, animate); 313 } 314 } 315 316 /** 317 * Set the visibility of the keyguard status view based on some new state. 318 */ setKeyguardStatusViewVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)319 public void setKeyguardStatusViewVisibility( 320 int statusBarState, 321 boolean keyguardFadingAway, 322 boolean goingToFullShade, 323 int oldStatusBarState) { 324 mKeyguardVisibilityHelper.setViewVisibility( 325 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); 326 } 327 refreshTime()328 private void refreshTime() { 329 mKeyguardClockSwitchController.refresh(); 330 } 331 332 private final ConfigurationController.ConfigurationListener mConfigurationListener = 333 new ConfigurationController.ConfigurationListener() { 334 @Override 335 public void onLocaleListChanged() { 336 refreshTime(); 337 mKeyguardClockSwitchController.onLocaleListChanged(); 338 } 339 340 @Override 341 public void onConfigChanged(Configuration newConfig) { 342 mKeyguardClockSwitchController.onConfigChanged(); 343 } 344 }; 345 346 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 347 @Override 348 public void onTimeChanged() { 349 Slog.v(TAG, "onTimeChanged"); 350 refreshTime(); 351 } 352 353 @Override 354 public void onKeyguardVisibilityChanged(boolean visible) { 355 if (visible) { 356 if (DEBUG) Slog.v(TAG, "refresh statusview visible:true"); 357 refreshTime(); 358 } 359 } 360 }; 361 362 /** 363 * Rect that specifies how KSV should be clipped, on its parent's coordinates. 364 */ setClipBounds(Rect clipBounds)365 public void setClipBounds(Rect clipBounds) { 366 if (clipBounds != null) { 367 mClipBounds.set(clipBounds.left, (int) (clipBounds.top - mView.getY()), 368 clipBounds.right, (int) (clipBounds.bottom - mView.getY())); 369 mView.setClipBounds(mClipBounds); 370 } else { 371 mView.setClipBounds(null); 372 } 373 } 374 375 /** 376 * Returns true if the large clock will block the notification shelf in AOD 377 */ isLargeClockBlockingNotificationShelf()378 public boolean isLargeClockBlockingNotificationShelf() { 379 ClockController clock = mKeyguardClockSwitchController.getClock(); 380 return clock != null && clock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay(); 381 } 382 383 /** 384 * Set if the split shade is enabled 385 */ setSplitShadeEnabled(boolean enabled)386 public void setSplitShadeEnabled(boolean enabled) { 387 mKeyguardClockSwitchController.setSplitShadeEnabled(enabled); 388 } 389 390 /** 391 * Updates the alignment of the KeyguardStatusView and animates the transition if requested. 392 */ updateAlignment( ConstraintLayout layout, boolean splitShadeEnabled, boolean shouldBeCentered, boolean animate)393 public void updateAlignment( 394 ConstraintLayout layout, 395 boolean splitShadeEnabled, 396 boolean shouldBeCentered, 397 boolean animate) { 398 mKeyguardClockSwitchController.setSplitShadeCentered(splitShadeEnabled && shouldBeCentered); 399 if (mStatusViewCentered == shouldBeCentered) { 400 return; 401 } 402 403 mStatusViewCentered = shouldBeCentered; 404 if (layout == null) { 405 return; 406 } 407 408 ConstraintSet constraintSet = new ConstraintSet(); 409 constraintSet.clone(layout); 410 int guideline; 411 if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { 412 guideline = R.id.split_shade_guideline; 413 } else { 414 guideline = R.id.qs_edge_guideline; 415 } 416 417 int statusConstraint = shouldBeCentered ? PARENT_ID : guideline; 418 constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END); 419 if (!animate) { 420 constraintSet.applyTo(layout); 421 return; 422 } 423 424 mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); 425 /* This transition blocks any layout changes while running. For that reason 426 * special logic with setting visibility was added to {@link BcSmartspaceView#setDozing} 427 * for split shade to avoid jump of the media object. */ 428 ChangeBounds transition = new ChangeBounds(); 429 if (splitShadeEnabled) { 430 // Excluding media from the transition on split-shade, as it doesn't transition 431 // horizontally properly. 432 transition.excludeTarget(R.id.status_view_media_container, true); 433 434 // Exclude smartspace viewpager and its children from the transition. 435 // - Each step of the transition causes the ViewPager to invoke resize, 436 // which invokes scrolling to the recalculated position. The scrolling 437 // actions are congested, resulting in kinky translation, and 438 // delay in settling to the final position. (http://b/281620564#comment1) 439 // - Also, the scrolling is unnecessary in the transition. We just want 440 // the viewpager to stay on the same page. 441 // - Exclude by Class type instead of resource id, since the resource id 442 // isn't available for all devices, and probably better to exclude all 443 // ViewPagers any way. 444 transition.excludeTarget(ViewPager.class, true); 445 transition.excludeChildren(ViewPager.class, true); 446 } 447 448 transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 449 transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 450 451 ClockController clock = mKeyguardClockSwitchController.getClock(); 452 boolean customClockAnimation = clock != null 453 && clock.getLargeClock().getConfig().getHasCustomPositionUpdatedAnimation(); 454 455 if (customClockAnimation) { 456 // Find the clock, so we can exclude it from this transition. 457 FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large); 458 459 // The clock container can sometimes be null. If it is, just fall back to the 460 // old animation rather than setting up the custom animations. 461 if (clockContainerView == null || clockContainerView.getChildCount() == 0) { 462 transition.addListener(mKeyguardStatusAlignmentTransitionListener); 463 TransitionManager.beginDelayedTransition(layout, transition); 464 } else { 465 View clockView = clockContainerView.getChildAt(0); 466 467 TransitionSet set = new TransitionSet(); 468 set.addTransition(transition); 469 470 SplitShadeTransitionAdapter adapter = 471 new SplitShadeTransitionAdapter(mKeyguardClockSwitchController); 472 473 // Use linear here, so the actual clock can pick its own interpolator. 474 adapter.setInterpolator(Interpolators.LINEAR); 475 adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION); 476 adapter.addTarget(clockView); 477 set.addTransition(adapter); 478 479 if (splitShadeEnabled) { 480 // Exclude smartspace viewpager and its children from the transition set. 481 // - This is necessary in addition to excluding them from the 482 // ChangeBounds child transition. 483 // - Without this, the viewpager is scrolled to the new position 484 // (corresponding to its end size) before the size change is realized. 485 // Note that the size change is realized at the end of the ChangeBounds 486 // transition. With the "prescrolling", the viewpager ends up in a weird 487 // position, then recovers smoothly during the transition, and ends at 488 // the position for the current page. 489 // - Exclude by Class type instead of resource id, since the resource id 490 // isn't available for all devices, and probably better to exclude all 491 // ViewPagers any way. 492 set.excludeTarget(ViewPager.class, true); 493 set.excludeChildren(ViewPager.class, true); 494 } 495 496 set.addListener(mKeyguardStatusAlignmentTransitionListener); 497 TransitionManager.beginDelayedTransition(layout, set); 498 } 499 } else { 500 transition.addListener(mKeyguardStatusAlignmentTransitionListener); 501 TransitionManager.beginDelayedTransition(layout, transition); 502 } 503 504 constraintSet.applyTo(layout); 505 } 506 507 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)508 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 509 mView.dump(pw, args); 510 } 511 getInstanceName()512 String getInstanceName() { 513 return TAG + "#" + hashCode(); 514 } 515 516 @VisibleForTesting 517 static class SplitShadeTransitionAdapter extends Transition { 518 private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"; 519 private static final String PROP_BOUNDS_RIGHT = "splitShadeTransitionAdapter:boundsRight"; 520 private static final String PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"; 521 private static final String[] TRANSITION_PROPERTIES = { 522 PROP_BOUNDS_LEFT, PROP_BOUNDS_RIGHT, PROP_X_IN_WINDOW}; 523 524 private final KeyguardClockSwitchController mController; 525 526 @VisibleForTesting SplitShadeTransitionAdapter(KeyguardClockSwitchController controller)527 SplitShadeTransitionAdapter(KeyguardClockSwitchController controller) { 528 mController = controller; 529 } 530 captureValues(TransitionValues transitionValues)531 private void captureValues(TransitionValues transitionValues) { 532 transitionValues.values.put(PROP_BOUNDS_LEFT, transitionValues.view.getLeft()); 533 transitionValues.values.put(PROP_BOUNDS_RIGHT, transitionValues.view.getRight()); 534 int[] locationInWindowTmp = new int[2]; 535 transitionValues.view.getLocationInWindow(locationInWindowTmp); 536 transitionValues.values.put(PROP_X_IN_WINDOW, locationInWindowTmp[0]); 537 } 538 539 @Override captureEndValues(TransitionValues transitionValues)540 public void captureEndValues(TransitionValues transitionValues) { 541 captureValues(transitionValues); 542 } 543 544 @Override captureStartValues(TransitionValues transitionValues)545 public void captureStartValues(TransitionValues transitionValues) { 546 captureValues(transitionValues); 547 } 548 549 @Nullable 550 @Override createAnimator(@onNull ViewGroup sceneRoot, @Nullable TransitionValues startValues, @Nullable TransitionValues endValues)551 public Animator createAnimator(@NonNull ViewGroup sceneRoot, 552 @Nullable TransitionValues startValues, 553 @Nullable TransitionValues endValues) { 554 if (startValues == null || endValues == null) { 555 return null; 556 } 557 ValueAnimator anim = ValueAnimator.ofFloat(0, 1); 558 559 int fromLeft = (int) startValues.values.get(PROP_BOUNDS_LEFT); 560 int fromWindowX = (int) startValues.values.get(PROP_X_IN_WINDOW); 561 int toWindowX = (int) endValues.values.get(PROP_X_IN_WINDOW); 562 // Using windowX, to determine direction, instead of left, as in RTL the difference of 563 // toLeft - fromLeft is always positive, even when moving left. 564 int direction = toWindowX - fromWindowX > 0 ? 1 : -1; 565 566 anim.addUpdateListener(animation -> { 567 ClockController clock = mController.getClock(); 568 if (clock == null) { 569 return; 570 } 571 572 clock.getLargeClock().getAnimations() 573 .onPositionUpdated(fromLeft, direction, animation.getAnimatedFraction()); 574 }); 575 576 return anim; 577 } 578 579 @Override getTransitionProperties()580 public String[] getTransitionProperties() { 581 return TRANSITION_PROPERTIES; 582 } 583 } 584 } 585