From fa92428107ba7fccd591870234a80e5c87ea1200 Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Wed, 13 Oct 2021 17:04:18 +0100 Subject: [PATCH] Do not animate statusbar unfold when in portrait orientation Disables unfold animation for statusbar when it is perpendicular to the screen fold. Moves ScopedUnfoldTransitionProgressProvider from launcher to shared SysUI/Launcher code. Introduces NaturalRotationUnfoldProgressProvider which emits transition events only when in natural rotation. Added proper disposal of resources when status bar is detached as PhoneStatusBarView is re-created when folding/unfolding. Bug: 201518277 Test: manual Test: atest com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProviderTest Change-Id: If4da3afca2b41c3675ccf6838e9ad6dac1b6f82e --- .../NaturalRotationUnfoldProgressProvider.kt | 73 +++++++++ ...copedUnfoldTransitionProgressProvider.java | 142 ++++++++++++++++++ .../phone/PhoneStatusBarViewController.kt | 62 ++++++-- .../systemui/statusbar/phone/StatusBar.java | 24 ++- ...tusBarMoveFromCenterAnimationController.kt | 47 +++--- .../phone/dagger/StatusBarPhoneModule.java | 7 +- .../systemui/unfold/UnfoldTransitionModule.kt | 27 ++++ .../phone/PhoneStatusBarViewControllerTest.kt | 55 +++++-- .../statusbar/phone/StatusBarTest.java | 7 +- ...turalRotationUnfoldProgressProviderTest.kt | 139 +++++++++++++++++ 10 files changed, 523 insertions(+), 60 deletions(-) create mode 100644 packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt create mode 100644 packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt new file mode 100644 index 0000000000000..e072d41e4eeec --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt @@ -0,0 +1,73 @@ +package com.android.systemui.unfold.util + +import android.content.Context +import android.os.RemoteException +import android.util.Log +import android.view.IRotationWatcher +import android.view.IWindowManager +import android.view.Surface +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener + +/** + * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has + * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180). + * It could be helpful to run the animation only when the display's rotation is perpendicular + * to the fold. + */ +class NaturalRotationUnfoldProgressProvider( + private val context: Context, + private val windowManagerInterface: IWindowManager, + unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider +) : UnfoldTransitionProgressProvider { + + private val scopedUnfoldTransitionProgressProvider = + ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) + private val rotationWatcher = RotationWatcher() + + private var isNaturalRotation: Boolean = false + + fun init() { + try { + windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId) + } catch (e: RemoteException) { + throw e.rethrowFromSystemServer() + } + + onRotationChanged(context.display.rotation) + } + + private fun onRotationChanged(rotation: Int) { + val isNewRotationNatural = rotation == Surface.ROTATION_0 || + rotation == Surface.ROTATION_180 + + if (isNaturalRotation != isNewRotationNatural) { + isNaturalRotation = isNewRotationNatural + scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(isNewRotationNatural) + } + } + + override fun destroy() { + try { + windowManagerInterface.removeRotationWatcher(rotationWatcher) + } catch (e: RemoteException) { + e.rethrowFromSystemServer() + } + + scopedUnfoldTransitionProgressProvider.destroy() + } + + override fun addCallback(listener: TransitionProgressListener) { + scopedUnfoldTransitionProgressProvider.addCallback(listener) + } + + override fun removeCallback(listener: TransitionProgressListener) { + scopedUnfoldTransitionProgressProvider.removeCallback(listener) + } + + private inner class RotationWatcher : IRotationWatcher.Stub() { + override fun onRotationChanged(rotation: Int) { + this@NaturalRotationUnfoldProgressProvider.onRotationChanged(rotation) + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java new file mode 100644 index 0000000000000..543232da303e5 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.unfold.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.systemui.unfold.UnfoldTransitionProgressProvider; +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener; + +import java.util.ArrayList; +import java.util.List; + +/** + * Manages progress listeners that can have smaller lifespan than the unfold animation. + * Allows to limit getting transition updates to only when + * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called + * with readyToHandleTransition = true + * + * If the transition has already started by the moment when the clients are ready to play + * the transition then it will report transition started callback and current animation progress. + */ +public final class ScopedUnfoldTransitionProgressProvider implements + UnfoldTransitionProgressProvider, TransitionProgressListener { + + private static final float PROGRESS_UNSET = -1f; + + @Nullable + private UnfoldTransitionProgressProvider mSource; + + private final List mListeners = new ArrayList<>(); + + private boolean mIsReadyToHandleTransition; + private boolean mIsTransitionRunning; + private float mLastTransitionProgress = PROGRESS_UNSET; + + public ScopedUnfoldTransitionProgressProvider() { + this(null); + } + + public ScopedUnfoldTransitionProgressProvider( + @Nullable UnfoldTransitionProgressProvider source) { + setSourceProvider(source); + } + + /** + * Sets the source for the unfold transition progress updates, + * it replaces current provider if it is already set + * @param provider transition provider that emits transition progress updates + */ + public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) { + if (mSource != null) { + mSource.removeCallback(this); + } + + if (provider != null) { + mSource = provider; + mSource.addCallback(this); + } else { + mSource = null; + } + } + + /** + * Allows to notify this provide whether the listeners can play the transition or not. + * Call this method with readyToHandleTransition = true when all listeners + * are ready to consume the transition progress events. + * Call it with readyToHandleTransition = false when listeners can't process the events. + */ + public void setReadyToHandleTransition(boolean isReadyToHandleTransition) { + if (mIsTransitionRunning) { + if (isReadyToHandleTransition) { + mListeners.forEach(TransitionProgressListener::onTransitionStarted); + + if (mLastTransitionProgress != PROGRESS_UNSET) { + mListeners.forEach(listener -> + listener.onTransitionProgress(mLastTransitionProgress)); + } + } else { + mIsTransitionRunning = false; + mListeners.forEach(TransitionProgressListener::onTransitionFinished); + } + } + + mIsReadyToHandleTransition = isReadyToHandleTransition; + } + + @Override + public void addCallback(@NonNull TransitionProgressListener listener) { + mListeners.add(listener); + } + + @Override + public void removeCallback(@NonNull TransitionProgressListener listener) { + mListeners.remove(listener); + } + + @Override + public void destroy() { + mSource.removeCallback(this); + } + + @Override + public void onTransitionStarted() { + this.mIsTransitionRunning = true; + if (mIsReadyToHandleTransition) { + mListeners.forEach(TransitionProgressListener::onTransitionStarted); + } + } + + @Override + public void onTransitionProgress(float progress) { + if (mIsReadyToHandleTransition) { + mListeners.forEach(listener -> listener.onTransitionProgress(progress)); + } + + mLastTransitionProgress = progress; + } + + @Override + public void onTransitionFinished() { + if (mIsReadyToHandleTransition) { + mListeners.forEach(TransitionProgressListener::onTransitionFinished); + } + + mIsTransitionRunning = false; + mLastTransitionProgress = PROGRESS_UNSET; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index e27f4585e3206..de21e73f81009 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -18,25 +18,28 @@ package com.android.systemui.statusbar.phone import android.graphics.Point import android.view.View import android.view.ViewGroup +import android.view.ViewTreeObserver import com.android.systemui.R import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.unfold.UNFOLD_STATUS_BAR +import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.util.ViewController +import javax.inject.Inject +import javax.inject.Named +import dagger.Lazy /** Controller for [PhoneStatusBarView]. */ -class PhoneStatusBarViewController( +class PhoneStatusBarViewController private constructor( view: PhoneStatusBarView, - statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, + @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?, + private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, touchEventHandler: PhoneStatusBarView.TouchEventHandler, ) : ViewController(view) { - override fun onViewAttached() {} - override fun onViewDetached() {} - - init { - mView.setTouchEventHandler(touchEventHandler) - - statusBarMoveFromCenterAnimationController?.let { animationController -> + override fun onViewAttached() { + moveFromCenterAnimationController?.let { animationController -> val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side) val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area) @@ -46,15 +49,33 @@ class PhoneStatusBarViewController( systemIconArea ) - animationController.init(viewsToAnimate, viewCenterProvider) + mView.viewTreeObserver.addOnPreDrawListener(object : + ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + animationController.onViewsReady(viewsToAnimate, viewCenterProvider) + mView.viewTreeObserver.removeOnPreDrawListener(this) + return true + } + }) mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ -> val widthChanged = right - left != oldRight - oldLeft if (widthChanged) { - statusBarMoveFromCenterAnimationController.onStatusBarWidthChanged() + moveFromCenterAnimationController.onStatusBarWidthChanged() } } } + + progressProvider?.setReadyToHandleTransition(true) + } + + override fun onViewDetached() { + progressProvider?.setReadyToHandleTransition(false) + moveFromCenterAnimationController?.onViewDetached() + } + + init { + mView.setTouchEventHandler(touchEventHandler) } fun setImportantForAccessibility(mode: Int) { @@ -92,4 +113,23 @@ class PhoneStatusBarViewController( outPoint.y = viewY + view.height / 2 } } + + class Factory @Inject constructor( + @Named(UNFOLD_STATUS_BAR) + private val progressProvider: Lazy, + private val moveFromCenterController: Lazy, + private val unfoldConfig: UnfoldTransitionConfig, + ) { + fun create( + view: PhoneStatusBarView, + touchEventHandler: PhoneStatusBarView.TouchEventHandler + ): PhoneStatusBarViewController { + return PhoneStatusBarViewController( + view, + if (unfoldConfig.isEnabled) progressProvider.get() else null, + if (unfoldConfig.isEnabled) moveFromCenterController.get() else null, + touchEventHandler + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index caa0dbe1a2c9a..3835b50d84e6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -233,6 +233,7 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.unfold.UnfoldTransitionWallpaperController; import com.android.systemui.unfold.config.UnfoldTransitionConfig; +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.MessageRouter; @@ -525,6 +526,7 @@ public class StatusBar extends SystemUI implements private QSPanelController mQSPanelController; private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory; + private final PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory; KeyguardIndicationController mKeyguardIndicationController; private View mReportRejectedTouch; @@ -543,8 +545,8 @@ public class StatusBar extends SystemUI implements private final FeatureFlags mFeatureFlags; private final UnfoldTransitionConfig mUnfoldTransitionConfig; private final Lazy mUnfoldLightRevealOverlayAnimation; + private final Lazy mNaturalUnfoldProgressProvider; private final Lazy mUnfoldWallpaperController; - private final Lazy mMoveFromCenterAnimation; private final WallpaperController mWallpaperController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final MessageRouter mMessageRouter; @@ -772,6 +774,7 @@ public class StatusBar extends SystemUI implements ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, OperatorNameViewController.Factory operatorNameViewControllerFactory, + PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -782,7 +785,7 @@ public class StatusBar extends SystemUI implements UnfoldTransitionConfig unfoldTransitionConfig, Lazy unfoldLightRevealOverlayAnimation, Lazy unfoldTransitionWallpaperController, - Lazy statusBarUnfoldAnimationController, + Lazy naturalRotationUnfoldProgressProvider, WallpaperController wallpaperController, OngoingCallController ongoingCallController, SystemStatusAnimationScheduler animationScheduler, @@ -812,6 +815,7 @@ public class StatusBar extends SystemUI implements mKeyguardStateController = keyguardStateController; mHeadsUpManager = headsUpManagerPhone; mOperatorNameViewControllerFactory = operatorNameViewControllerFactory; + mPhoneStatusBarViewControllerFactory = phoneStatusBarViewControllerFactory; mKeyguardIndicationController = keyguardIndicationController; mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; mDynamicPrivacyController = dynamicPrivacyController; @@ -879,9 +883,9 @@ public class StatusBar extends SystemUI implements mBrightnessSliderFactory = brightnessSliderFactory; mUnfoldTransitionConfig = unfoldTransitionConfig; mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation; + mNaturalUnfoldProgressProvider = naturalRotationUnfoldProgressProvider; mUnfoldWallpaperController = unfoldTransitionWallpaperController; mWallpaperController = wallpaperController; - mMoveFromCenterAnimation = statusBarUnfoldAnimationController; mOngoingCallController = ongoingCallController; mAnimationScheduler = animationScheduler; mStatusBarLocationPublisher = locationPublisher; @@ -1077,6 +1081,7 @@ public class StatusBar extends SystemUI implements if (mUnfoldTransitionConfig.isEnabled()) { mUnfoldLightRevealOverlayAnimation.get().init(); mUnfoldWallpaperController.get().init(); + mNaturalUnfoldProgressProvider.get().init(); } mPluginManager.addPluginListener( @@ -1178,16 +1183,9 @@ public class StatusBar extends SystemUI implements mNotificationPanelViewController.setBar(mStatusBarView); - StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null; - if (mUnfoldTransitionConfig.isEnabled()) { - moveFromCenterAnimation = mMoveFromCenterAnimation.get(); - } - mPhoneStatusBarViewController = - new PhoneStatusBarViewController( - mStatusBarView, - moveFromCenterAnimation, - mNotificationPanelViewController.getStatusBarTouchEventHandler() - ); + mPhoneStatusBarViewController = mPhoneStatusBarViewControllerFactory + .create(mStatusBarView, mNotificationPanelViewController + .getStatusBarTouchEventHandler()); mPhoneStatusBarViewController.init(); mBatteryMeterViewController = new BatteryMeterViewController( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt index 8af03aa2a3bea..2707fa3c235ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt @@ -20,43 +20,48 @@ import android.view.WindowManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider -import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UNFOLD_STATUS_BAR import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import javax.inject.Inject +import javax.inject.Named @SysUISingleton class StatusBarMoveFromCenterAnimationController @Inject constructor( - private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, - private val windowManager: WindowManager + @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider, + private val windowManager: WindowManager, ) { - private lateinit var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator + private val transitionListener = TransitionListener() + private var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator? = null - fun init(viewsToAnimate: Array, viewCenterProvider: ViewCenterProvider) { + fun onViewsReady(viewsToAnimate: Array, viewCenterProvider: ViewCenterProvider) { moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager, viewCenterProvider = viewCenterProvider) - unfoldTransitionProgressProvider.addCallback(object : TransitionProgressListener { - override fun onTransitionStarted() { - moveFromCenterAnimator.updateDisplayProperties() + moveFromCenterAnimator?.updateDisplayProperties() - viewsToAnimate.forEach { - moveFromCenterAnimator.registerViewForAnimation(it) - } - } + viewsToAnimate.forEach { + moveFromCenterAnimator?.registerViewForAnimation(it) + } - override fun onTransitionFinished() { - moveFromCenterAnimator.onTransitionFinished() - moveFromCenterAnimator.clearRegisteredViews() - } + progressProvider.addCallback(transitionListener) + } - override fun onTransitionProgress(progress: Float) { - moveFromCenterAnimator.onTransitionProgress(progress) - } - }) + fun onViewDetached() { + progressProvider.removeCallback(transitionListener) + moveFromCenterAnimator?.clearRegisteredViews() + moveFromCenterAnimator = null } fun onStatusBarWidthChanged() { - moveFromCenterAnimator.updateViewPositions() + moveFromCenterAnimator?.updateDisplayProperties() + moveFromCenterAnimator?.updateViewPositions() + } + + private inner class TransitionListener : TransitionProgressListener { + override fun onTransitionProgress(progress: Float) { + moveFromCenterAnimator?.onTransitionProgress(progress) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 568970790a180..c452a486cbe15 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -88,6 +88,7 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -111,6 +112,7 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.unfold.UnfoldTransitionWallpaperController; import com.android.systemui.unfold.config.UnfoldTransitionConfig; +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.MessageRouter; @@ -211,6 +213,7 @@ public interface StatusBarPhoneModule { ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, OperatorNameViewController.Factory operatorNameViewControllerFactory, + PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -220,6 +223,7 @@ public interface StatusBarPhoneModule { BrightnessSlider.Factory brightnessSliderFactory, UnfoldTransitionConfig unfoldTransitionConfig, Lazy unfoldLightRevealOverlayAnimation, + Lazy naturalRotationUnfoldProgressProvider, Lazy unfoldTransitionWallpaperController, Lazy statusBarMoveFromCenterAnimation, WallpaperController wallpaperController, @@ -311,6 +315,7 @@ public interface StatusBarPhoneModule { extensionController, userInfoControllerImpl, operatorNameViewControllerFactory, + phoneStatusBarViewControllerFactory, phoneStatusBarPolicy, keyguardIndicationController, demoModeController, @@ -321,7 +326,7 @@ public interface StatusBarPhoneModule { unfoldTransitionConfig, unfoldLightRevealOverlayAnimation, unfoldTransitionWallpaperController, - statusBarMoveFromCenterAnimation, + naturalRotationUnfoldProgressProvider, wallpaperController, ongoingCallController, animationScheduler, diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index b23aa20dcaa49..7e4ec67e6e182 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -20,15 +20,19 @@ import android.content.Context import android.hardware.SensorManager import android.hardware.devicestate.DeviceStateManager import android.os.Handler +import android.view.IWindowManager import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider +import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import dagger.Lazy import dagger.Module import dagger.Provides import java.util.Optional import java.util.concurrent.Executor +import javax.inject.Named import javax.inject.Singleton @Module @@ -60,6 +64,27 @@ class UnfoldTransitionModule { fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig = createConfig(context) + @Provides + @Singleton + fun provideNaturalRotationProgressProvider( + context: Context, + windowManager: IWindowManager, + unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider + ): NaturalRotationUnfoldProgressProvider = + NaturalRotationUnfoldProgressProvider( + context, + windowManager, + unfoldTransitionProgressProvider + ) + + @Provides + @Named(UNFOLD_STATUS_BAR) + @Singleton + fun provideStatusBarScopedTransitionProvider( + source: NaturalRotationUnfoldProgressProvider + ): ScopedUnfoldTransitionProgressProvider = + ScopedUnfoldTransitionProgressProvider(source) + @Provides @Singleton fun provideShellProgressProvider( @@ -72,3 +97,5 @@ class UnfoldTransitionModule { Optional.empty() } } + +const val UNFOLD_STATUS_BAR = "unfold_status_bar" \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 033f689945c86..310a8baadb786 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -19,19 +19,25 @@ package com.android.systemui.statusbar.phone import android.view.LayoutInflater import android.view.MotionEvent import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.view.ViewTreeObserver.OnPreDrawListener import android.widget.FrameLayout import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.ArgumentCaptor import org.mockito.Mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.mockito.Mockito.verify -import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @SmallTest @@ -48,10 +54,14 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController + @Mock + private lateinit var progressProvider: ScopedUnfoldTransitionProgressProvider private lateinit var view: PhoneStatusBarView private lateinit var controller: PhoneStatusBarViewController + private val unfoldConfig = UnfoldConfig() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -66,11 +76,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { view.setBar(mock(StatusBar::class.java)) } - controller = PhoneStatusBarViewController( - view, - null, - touchEventHandler, - ) + controller = createController(view) } @Test @@ -83,12 +89,38 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } @Test - fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() { - controller = PhoneStatusBarViewController( - view, moveFromCenterAnimation, touchEventHandler - ) + fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() { + val view = createViewMock() + val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) + unfoldConfig.isEnabled = true + controller = createController(view) + controller.init() - verify(moveFromCenterAnimation).init(any(), any()) + verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture()) + argumentCaptor.value.onPreDraw() + + verify(moveFromCenterAnimation).onViewsReady(any(), any()) + } + + private fun createViewMock(): PhoneStatusBarView { + val view = spy(view) + val viewTreeObserver = mock(ViewTreeObserver::class.java) + `when`(view.viewTreeObserver).thenReturn(viewTreeObserver) + `when`(view.isAttachedToWindow).thenReturn(true) + return view + } + + private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController { + return PhoneStatusBarViewController.Factory( + { progressProvider }, + { moveFromCenterAnimation }, + unfoldConfig + ).create(view, touchEventHandler) + } + + private class UnfoldConfig : UnfoldTransitionConfig { + override var isEnabled: Boolean = false + override var isHingeAngleEnabled: Boolean = false } private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler { @@ -97,6 +129,5 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { lastEvent = event return false } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 741d95f283e36..52b409674d8a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -144,6 +144,7 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.unfold.UnfoldTransitionWallpaperController; import com.android.systemui.unfold.config.UnfoldTransitionConfig; +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.MessageRouterImpl; @@ -259,7 +260,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private BrightnessSlider.Factory mBrightnessSliderFactory; @Mock private UnfoldTransitionConfig mUnfoldTransitionConfig; @Mock private Lazy mUnfoldLightRevealOverlayAnimationLazy; - @Mock private Lazy mMoveFromCenterAnimationLazy; + @Mock private Lazy mNaturalRotationProgressProvider; @Mock private Lazy mUnfoldWallpaperController; @Mock private WallpaperController mWallpaperController; @Mock private OngoingCallController mOngoingCallController; @@ -276,6 +277,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StartingSurface mStartingSurface; @Mock private OperatorNameViewController mOperatorNameViewController; @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; + @Mock private PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; private ShadeController mShadeController; @@ -430,6 +432,7 @@ public class StatusBarTest extends SysuiTestCase { mExtensionController, mUserInfoControllerImpl, mOperatorNameViewControllerFactory, + mPhoneStatusBarViewControllerFactory, mPhoneStatusBarPolicy, mKeyguardIndicationController, mDemoModeController, @@ -440,7 +443,7 @@ public class StatusBarTest extends SysuiTestCase { mUnfoldTransitionConfig, mUnfoldLightRevealOverlayAnimationLazy, mUnfoldWallpaperController, - mMoveFromCenterAnimationLazy, + mNaturalRotationProgressProvider, mWallpaperController, mOngoingCallController, mAnimationScheduler, diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt new file mode 100644 index 0000000000000..a3f17aa5ba554 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.unfold.util + +import android.testing.AndroidTestingRunner +import android.view.IRotationWatcher +import android.view.IWindowManager +import android.view.Surface +import androidx.test.filters.SmallTest +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.util.mockito.any +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() { + + @Mock + lateinit var windowManager: IWindowManager + + @Mock + lateinit var sourceProvider: UnfoldTransitionProgressProvider + + @Mock + lateinit var transitionListener: TransitionProgressListener + + lateinit var progressProvider: NaturalRotationUnfoldProgressProvider + + private val sourceProviderListenerCaptor = + ArgumentCaptor.forClass(TransitionProgressListener::class.java) + private val rotationWatcherCaptor = + ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + progressProvider = NaturalRotationUnfoldProgressProvider( + context, + windowManager, + sourceProvider + ) + + progressProvider.init() + + verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture()) + verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any()) + + progressProvider.addCallback(transitionListener) + } + + @Test + fun testNaturalRotation0_sendTransitionStartedEvent_eventReceived() { + onRotationChanged(Surface.ROTATION_0) + + source.onTransitionStarted() + + verify(transitionListener).onTransitionStarted() + } + + @Test + fun testNaturalRotation0_sendTransitionProgressEvent_eventReceived() { + onRotationChanged(Surface.ROTATION_0) + + source.onTransitionProgress(0.5f) + + verify(transitionListener).onTransitionProgress(0.5f) + } + + @Test + fun testNotNaturalRotation90_sendTransitionStartedEvent_eventNotReceived() { + onRotationChanged(Surface.ROTATION_90) + + source.onTransitionStarted() + + verify(transitionListener, never()).onTransitionStarted() + } + + @Test + fun testNaturalRotation90_sendTransitionProgressEvent_eventNotReceived() { + onRotationChanged(Surface.ROTATION_90) + + source.onTransitionProgress(0.5f) + + verify(transitionListener, never()).onTransitionProgress(0.5f) + } + + @Test + fun testRotationBecameUnnaturalDuringTransition_sendsTransitionFinishedEvent() { + onRotationChanged(Surface.ROTATION_0) + source.onTransitionStarted() + clearInvocations(transitionListener) + + onRotationChanged(Surface.ROTATION_90) + + verify(transitionListener).onTransitionFinished() + } + + @Test + fun testRotationBecameNaturalDuringTransition_sendsTransitionStartedEvent() { + onRotationChanged(Surface.ROTATION_90) + source.onTransitionStarted() + clearInvocations(transitionListener) + + onRotationChanged(Surface.ROTATION_0) + + verify(transitionListener).onTransitionStarted() + } + + private fun onRotationChanged(rotation: Int) { + rotationWatcherCaptor.value.onRotationChanged(rotation) + } + + private val source: TransitionProgressListener + get() = sourceProviderListenerCaptor.value +}