From edbea0fc971dfa0c44733fa28aaefb204f4cbd3f Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Tue, 24 Aug 2021 13:47:39 +0100 Subject: [PATCH] Add statusbar unfold animation Adds move from center animation to statusbar elements such as battery icon, notification icons, etc. when unfolding the device Bug: 193794564 Test: manual Change-Id: Ibd4548ee4c33ed3f2dfe7909916f6f64026bb71f --- .../animation/UnfoldMoveFromCenterAnimator.kt | 86 +++++++++++------ .../phone/PhoneStatusBarViewController.java | 39 -------- .../phone/PhoneStatusBarViewController.kt | 93 +++++++++++++++++++ .../systemui/statusbar/phone/StatusBar.java | 10 +- ...tusBarMoveFromCenterAnimationController.kt | 62 +++++++++++++ .../phone/dagger/StatusBarPhoneModule.java | 3 + .../UnfoldMoveFromCenterAnimatorTest.kt | 40 +++++++- .../phone/PhoneStatusBarViewControllerTest.kt | 25 ++++- .../statusbar/phone/StatusBarTest.java | 2 + 9 files changed, 287 insertions(+), 73 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt index e5933e6a9aea8..9010d51541568 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt @@ -36,6 +36,13 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( * [View.setTranslationY] */ private val translationApplier: TranslationApplier = object : TranslationApplier {}, + /** + * Allows to set custom implementation for getting + * view location. Could be useful if logical view bounds + * are different than actual bounds (e.g. view container may + * have larger width than width of the items in the container) + */ + private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {} ) : UnfoldTransitionProgressProvider.TransitionProgressListener { private val screenSize = Point() @@ -43,6 +50,8 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( private val animatedViews: MutableList = arrayListOf() + private var lastAnimationProgress: Float = 0f + /** * Updates display properties in order to calculate the initial position for the views * Must be called before [registerViewForAnimation] @@ -57,6 +66,19 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( windowManager.defaultDisplay.rotation == Surface.ROTATION_180 } + /** + * If target view positions have changed (e.g. because of layout changes) call this method + * to re-query view positions and update the translations + */ + fun updateViewPositions() { + animatedViews.forEach { animatedView -> + animatedView.view.get()?.let { + animatedView.updateAnimatedView(it) + } + } + onTransitionProgress(lastAnimationProgress) + } + /** * Registers a view to be animated, the view should be measured and layouted * After finishing the animation it is necessary to clear @@ -85,45 +107,30 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( ) } } + lastAnimationProgress = progress } - private fun createAnimatedView(view: View): AnimatedView { - val viewCenter = getViewCenter(view) + private fun createAnimatedView(view: View): AnimatedView = + AnimatedView(view = WeakReference(view)).updateAnimatedView(view) + + private fun AnimatedView.updateAnimatedView(view: View): AnimatedView { + val viewCenter = Point() + viewCenterProvider.getViewCenter(view, viewCenter) + val viewCenterX = viewCenter.x val viewCenterY = viewCenter.y - val translationX: Float - val translationY: Float - if (isVerticalFold) { val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX - translationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE - translationY = 0f + startTranslationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE + startTranslationY = 0f } else { val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY - translationX = 0f - translationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE + startTranslationX = 0f + startTranslationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE } - return AnimatedView( - view = WeakReference(view), - startTranslationX = translationX, - startTranslationY = translationY - ) - } - - private fun getViewCenter(view: View): Point { - val viewLocation = IntArray(2) - view.getLocationOnScreen(viewLocation) - - val viewX = viewLocation[0] - val viewY = viewLocation[1] - - val outPoint = Point() - outPoint.x = viewX + view.width / 2 - outPoint.y = viewY + view.height / 2 - - return outPoint + return this } /** @@ -139,10 +146,29 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( } } + /** + * Interface that allows to use custom logic to get the center of the view + */ + interface ViewCenterProvider { + /** + * Called when we need to get the center of the view + */ + fun getViewCenter(view: View, outPoint: Point) { + val viewLocation = IntArray(2) + view.getLocationOnScreen(viewLocation) + + val viewX = viewLocation[0] + val viewY = viewLocation[1] + + outPoint.x = viewX + view.width / 2 + outPoint.y = viewY + view.height / 2 + } + } + private class AnimatedView( val view: WeakReference, - val startTranslationX: Float, - val startTranslationY: Float + var startTranslationX: Float = 0f, + var startTranslationY: Float = 0f ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java deleted file mode 100644 index b36b67dc02c01..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.statusbar.phone; - -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.util.ViewController; - -/** Controller for {@link PhoneStatusBarView}. */ -public class PhoneStatusBarViewController extends ViewController { - - protected PhoneStatusBarViewController( - PhoneStatusBarView view, - CommandQueue commandQueue) { - super(view); - mView.setPanelEnabledProvider(commandQueue::panelsEnabled); - } - - @Override - protected void onViewAttached() { - } - - @Override - protected void onViewDetached() { - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt new file mode 100644 index 0000000000000..9799533a568d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -0,0 +1,93 @@ +/* + * 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.statusbar.phone + +import android.graphics.Point +import android.view.View +import android.view.ViewGroup +import com.android.systemui.R +import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.ViewController + +/** Controller for [PhoneStatusBarView]. */ +class PhoneStatusBarViewController( + view: PhoneStatusBarView, + commandQueue: CommandQueue, + statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController? +) : ViewController(view) { + + override fun onViewAttached() {} + override fun onViewDetached() {} + + init { + mView.setPanelEnabledProvider { + commandQueue.panelsEnabled() + } + + statusBarMoveFromCenterAnimationController?.let { animationController -> + val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side) + val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area) + + val viewCenterProvider = StatusBarViewsCenterProvider() + val viewsToAnimate = arrayOf( + statusBarLeftSide, + systemIconArea + ) + + animationController.init(viewsToAnimate, viewCenterProvider) + + mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ -> + val widthChanged = right - left != oldRight - oldLeft + if (widthChanged) { + statusBarMoveFromCenterAnimationController.onStatusBarWidthChanged() + } + } + } + } + + private class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider { + override fun getViewCenter(view: View, outPoint: Point) = + when (view.id) { + R.id.status_bar_left_side -> { + // items aligned to the start, return start center point + getViewEdgeCenter(view, outPoint, isStart = true) + } + R.id.system_icon_area -> { + // items aligned to the end, return end center point + getViewEdgeCenter(view, outPoint, isStart = false) + } + else -> super.getViewCenter(view, outPoint) + } + + /** + * Returns start or end (based on [isStart]) center point of the view + */ + private fun getViewEdgeCenter(view: View, outPoint: Point, isStart: Boolean) { + val isRtl = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL + val isLeftEdge = isRtl xor isStart + + val viewLocation = IntArray(2) + view.getLocationOnScreen(viewLocation) + + val viewX = viewLocation[0] + val viewY = viewLocation[1] + + outPoint.x = viewX + if (isLeftEdge) view.height / 2 else view.width - view.height / 2 + outPoint.y = viewY + view.height / 2 + } + } +} 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 685b0625b9a2b..32c4a0d4673bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -536,6 +536,7 @@ public class StatusBar extends SystemUI implements private final FeatureFlags mFeatureFlags; private final UnfoldTransitionConfig mUnfoldTransitionConfig; private final Lazy mUnfoldLightRevealOverlayAnimation; + private final Lazy mMoveFromCenterAnimation; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final MessageRouter mMessageRouter; private final WallpaperManager mWallpaperManager; @@ -768,6 +769,7 @@ public class StatusBar extends SystemUI implements BrightnessSlider.Factory brightnessSliderFactory, UnfoldTransitionConfig unfoldTransitionConfig, Lazy unfoldLightRevealOverlayAnimation, + Lazy statusBarUnfoldAnimationController, OngoingCallController ongoingCallController, SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, @@ -860,6 +862,7 @@ public class StatusBar extends SystemUI implements mBrightnessSliderFactory = brightnessSliderFactory; mUnfoldTransitionConfig = unfoldTransitionConfig; mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation; + mMoveFromCenterAnimation = statusBarUnfoldAnimationController; mOngoingCallController = ongoingCallController; mAnimationScheduler = animationScheduler; mStatusBarLocationPublisher = locationPublisher; @@ -1141,8 +1144,13 @@ public class StatusBar extends SystemUI implements sendInitialExpansionAmount(listener); } + StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null; + if (mUnfoldTransitionConfig.isEnabled()) { + moveFromCenterAnimation = mMoveFromCenterAnimation.get(); + } mPhoneStatusBarViewController = - new PhoneStatusBarViewController(mStatusBarView, mCommandQueue); + new PhoneStatusBarViewController(mStatusBarView, mCommandQueue, + moveFromCenterAnimation); 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 new file mode 100644 index 0000000000000..8af03aa2a3bea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt @@ -0,0 +1,62 @@ +/* + * 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.statusbar.phone + +import android.view.View +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.UnfoldTransitionProgressProvider.TransitionProgressListener +import javax.inject.Inject + +@SysUISingleton +class StatusBarMoveFromCenterAnimationController @Inject constructor( + private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + private val windowManager: WindowManager +) { + + private lateinit var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator + + fun init(viewsToAnimate: Array, viewCenterProvider: ViewCenterProvider) { + moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager, + viewCenterProvider = viewCenterProvider) + + unfoldTransitionProgressProvider.addCallback(object : TransitionProgressListener { + override fun onTransitionStarted() { + moveFromCenterAnimator.updateDisplayProperties() + + viewsToAnimate.forEach { + moveFromCenterAnimator.registerViewForAnimation(it) + } + } + + override fun onTransitionFinished() { + moveFromCenterAnimator.onTransitionFinished() + moveFromCenterAnimator.clearRegisteredViews() + } + + override fun onTransitionProgress(progress: Float) { + moveFromCenterAnimator.onTransitionProgress(progress) + } + }) + } + + fun onStatusBarWidthChanged() { + moveFromCenterAnimator.updateViewPositions() + } +} 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 63ee701425ed2..befea41a9919f 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 @@ -90,6 +90,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; +import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.StatusBarWindowView; @@ -213,6 +214,7 @@ public interface StatusBarPhoneModule { BrightnessSlider.Factory brightnessSliderFactory, UnfoldTransitionConfig unfoldTransitionConfig, Lazy unfoldLightRevealOverlayAnimation, + Lazy statusBarMoveFromCenterAnimation, OngoingCallController ongoingCallController, SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, @@ -307,6 +309,7 @@ public interface StatusBarPhoneModule { brightnessSliderFactory, unfoldTransitionConfig, unfoldLightRevealOverlayAnimation, + statusBarMoveFromCenterAnimation, ongoingCallController, animationScheduler, locationPublisher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt index ebc6f2aa6e9a8..6a68b71f639b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt @@ -157,6 +157,21 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { assertThat(view.translationY).isWithin(0.01f).of(3.75f) } + @Test + fun testUpdateViewPositions_viewOnTheLeftAndMovedToTheRight_viewTranslatedToTheLeft() { + givenScreen(width = 100, height = 100, rotation = ROTATION_0) + val view = createView(x = 20) + animator.registerViewForAnimation(view) + animator.onTransitionStarted() + animator.onTransitionProgress(0.5f) + view.updateMock(x = 80) // view moved from the left side to the right + + animator.updateViewPositions() + + // Negative translationX -> translated to the left + assertThat(view.translationX).isWithin(0.1f).of(-5.25f) + } + private fun createView( x: Int = 0, y: Int = 0, @@ -176,7 +191,30 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { whenever(view.width).thenReturn(width) whenever(view.height).thenReturn(height) - return view.apply { + view.updateMock(x, y, width, height, translationX, translationY) + + return view + } + + private fun View.updateMock( + x: Int = 0, + y: Int = 0, + width: Int = 10, + height: Int = 10, + translationX: Float = 0f, + translationY: Float = 0f + ) { + doAnswer { + val location = (it.arguments[0] as IntArray) + location[0] = x + location[1] = y + Unit + }.`when`(this).getLocationOnScreen(any()) + + whenever(this.width).thenReturn(width) + whenever(this.height).thenReturn(height) + + this.apply { setTranslationX(translationX) setTranslationY(translationY) } 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 d63730d596d03..c7d4794bd962e 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 @@ -16,7 +16,10 @@ package com.android.systemui.statusbar.phone +import android.view.LayoutInflater +import android.widget.FrameLayout import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue import com.google.common.truth.Truth.assertThat @@ -24,7 +27,10 @@ import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import com.android.systemui.R +import com.android.systemui.util.mockito.any @SmallTest class PhoneStatusBarViewControllerTest : SysuiTestCase() { @@ -32,14 +38,22 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var commandQueue: CommandQueue + @Mock + private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController + private lateinit var view: PhoneStatusBarView private lateinit var controller: PhoneStatusBarViewController @Before fun setUp() { MockitoAnnotations.initMocks(this) - view = PhoneStatusBarView(mContext, null) - controller = PhoneStatusBarViewController(view, commandQueue) + // create the view on main thread as it requires main looper + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val parent = FrameLayout(mContext) // add parent to keep layout params + view = LayoutInflater.from(mContext) + .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView + } + controller = PhoneStatusBarViewController(view, commandQueue, null) } @Test @@ -56,4 +70,11 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { assertThat(providerUsed).isTrue() } + + @Test + fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() { + controller = PhoneStatusBarViewController(view, commandQueue, moveFromCenterAnimation) + + verify(moveFromCenterAnimation).init(any(), any()) + } } 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 b23414bacf10a..3c0382b6efeca 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 @@ -254,6 +254,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 OngoingCallController mOngoingCallController; @Mock private SystemStatusAnimationScheduler mAnimationScheduler; @Mock private StatusBarLocationPublisher mLocationPublisher; @@ -428,6 +429,7 @@ public class StatusBarTest extends SysuiTestCase { mBrightnessSliderFactory, mUnfoldTransitionConfig, mUnfoldLightRevealOverlayAnimationLazy, + mMoveFromCenterAnimationLazy, mOngoingCallController, mAnimationScheduler, mLocationPublisher,