Merge "Add statusbar unfold animation" into sc-v2-dev am: a58a7d6a43

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15675156

Change-Id: I776e1875e2acfb18242dbd302f7256c13933f153
This commit is contained in:
Nick Chameyev
2021-09-14 10:18:16 +00:00
committed by Automerger Merge Worker
9 changed files with 287 additions and 73 deletions

View File

@@ -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<AnimatedView> = 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<View>,
val startTranslationX: Float,
val startTranslationY: Float
var startTranslationX: Float = 0f,
var startTranslationY: Float = 0f
)
}

View File

@@ -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<PhoneStatusBarView> {
protected PhoneStatusBarViewController(
PhoneStatusBarView view,
CommandQueue commandQueue) {
super(view);
mView.setPanelEnabledProvider(commandQueue::panelsEnabled);
}
@Override
protected void onViewAttached() {
}
@Override
protected void onViewDetached() {
}
}

View File

@@ -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<PhoneStatusBarView>(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
}
}
}

View File

@@ -536,6 +536,7 @@ public class StatusBar extends SystemUI implements
private final FeatureFlags mFeatureFlags;
private final UnfoldTransitionConfig mUnfoldTransitionConfig;
private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation;
private final Lazy<StatusBarMoveFromCenterAnimationController> 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> unfoldLightRevealOverlayAnimation,
Lazy<StatusBarMoveFromCenterAnimationController> 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(

View File

@@ -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<View>, 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()
}
}

View File

@@ -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> unfoldLightRevealOverlayAnimation,
Lazy<StatusBarMoveFromCenterAnimationController> statusBarMoveFromCenterAnimation,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
@@ -307,6 +309,7 @@ public interface StatusBarPhoneModule {
brightnessSliderFactory,
unfoldTransitionConfig,
unfoldLightRevealOverlayAnimation,
statusBarMoveFromCenterAnimation,
ongoingCallController,
animationScheduler,
locationPublisher,

View File

@@ -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)
}

View File

@@ -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())
}
}

View File

@@ -254,6 +254,7 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
@Mock private UnfoldTransitionConfig mUnfoldTransitionConfig;
@Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy;
@Mock private Lazy<StatusBarMoveFromCenterAnimationController> 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,