Replace RotationUtils#Rotation with Surface#Rotation for wired charging animation.

When layout the ripple animation, we only care about the device
rotation, and not whether it's in portrait or landscape. (Consider a
case where the device has a larger width by default.)
Thus we should use Surface#Rotation that always give us the angle
based on the counter clock-wise (i.e. natural) rotation.

Note that R.bool.config_reverseDefaultRotation does not affect the
rotation order. Also tested on both devices with different config value.

Bug: 264992323
Test: Manual, WiredChargingRippleControllerTest
Change-Id: I25daae0ed4969ebb6e9387ea936b85b2c33bcdcf
This commit is contained in:
Yein Jo
2023-01-13 03:01:49 +00:00
parent c9425a388f
commit b9f622bcf3
3 changed files with 111 additions and 24 deletions

View File

@@ -20,7 +20,7 @@ import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.os.SystemProperties
import android.util.DisplayMetrics
import android.view.Surface
import android.view.View
import android.view.WindowManager
import com.android.internal.annotations.VisibleForTesting
@@ -36,7 +36,6 @@ import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.leak.RotationUtils
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import javax.inject.Inject
@@ -172,30 +171,28 @@ class WiredChargingRippleController @Inject constructor(
}
private fun layoutRipple() {
val displayMetrics = DisplayMetrics()
context.display.getRealMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val bounds = windowManager.currentWindowMetrics.bounds
val width = bounds.width()
val height = bounds.height()
val maxDiameter = Integer.max(width, height) * 2f
rippleView.setMaxSize(maxDiameter, maxDiameter)
when (RotationUtils.getExactRotation(context)) {
RotationUtils.ROTATION_LANDSCAPE -> {
when (context.display.rotation) {
Surface.ROTATION_0 -> {
rippleView.setCenter(
width * normalizedPortPosX, height * normalizedPortPosY)
}
Surface.ROTATION_90 -> {
rippleView.setCenter(
width * normalizedPortPosY, height * (1 - normalizedPortPosX))
}
RotationUtils.ROTATION_UPSIDE_DOWN -> {
Surface.ROTATION_180 -> {
rippleView.setCenter(
width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
}
RotationUtils.ROTATION_SEASCAPE -> {
Surface.ROTATION_270 -> {
rippleView.setCenter(
width * (1 - normalizedPortPosY), height * normalizedPortPosX)
}
else -> {
// ROTATION_NONE
rippleView.setCenter(
width * normalizedPortPosX, height * normalizedPortPosY)
}
}
}

View File

@@ -1,15 +1,17 @@
/*
* Copyright (C) 2017 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
* 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.
* 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.util.leak;
@@ -26,7 +28,27 @@ import android.view.Surface;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class RotationUtils {
/**
* Utility class that provides device orientation.
*
* <p>Consider using {@link Surface.Rotation} or add a function that respects device aspect ratio
* and {@code android.internal.R.bool.config_reverseDefaultRotation}.
*
* <p>If you only care about the rotation, use {@link Surface.Rotation}, as it always gives the
* counter clock-wise rotation. (e.g. If you have a device that has a charging port at the bottom,
* rotating three times in counter clock direction will give you {@link Surface#ROTATION_270} while
* having the charging port on the left side of the device.)
*
* <p>If you need whether the device is in portrait or landscape (or their opposites), please add a
* function here that respects the device aspect ratio and
* {@code android.internal.R.bool.config_reverseDefaultRotation} together.
*
* <p>Note that {@code android.internal.R.bool.config_reverseDefaultRotation} does not change the
* winding order. In other words, the rotation order (counter clock-wise) will remain the same. It
* only flips the device orientation, such that portrait becomes upside down, landscape becomes
* seascape.
*/
public final class RotationUtils {
public static final int ROTATION_NONE = 0;
public static final int ROTATION_LANDSCAPE = 1;

View File

@@ -16,18 +16,23 @@
package com.android.systemui.charging
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.Surface
import android.view.View
import android.view.WindowManager
import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
@@ -35,12 +40,12 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -54,6 +59,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var rippleView: RippleView
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var windowMetrics: WindowMetrics
private val systemClock = FakeSystemClock()
@Before
@@ -66,6 +72,9 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
rippleView.setupShader()
controller.rippleView = rippleView // Replace the real ripple view with a mock instance
controller.registerCallbacks()
`when`(windowMetrics.bounds).thenReturn(Rect(0, 0, 100, 100))
`when`(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
}
@Test
@@ -164,4 +173,63 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
}
@Test
fun testRipple_layoutsCorrectly() {
// Sets the correct ripple size.
val width = 100
val height = 200
whenever(windowMetrics.bounds).thenReturn(Rect(0, 0, width, height))
// Trigger ripple.
val captor = ArgumentCaptor
.forClass(BatteryController.BatteryStateChangeCallback::class.java)
verify(batteryController).addCallback(captor.capture())
captor.value.onBatteryLevelChanged(
/* unusedBatteryLevel= */ 0,
/* plugged in= */ true,
/* charging= */ false)
val attachListenerCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())
val runnableCaptor =
ArgumentCaptor.forClass(Runnable::class.java)
attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
verify(rippleView).startRipple(runnableCaptor.capture())
// Verify size and center position.
val maxSize = 400f // Double the max value between width and height.
verify(rippleView).setMaxSize(maxWidth = maxSize, maxHeight = maxSize)
val normalizedPortPosX =
context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_x)
val normalizedPortPosY =
context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
val expectedCenterX: Float
val expectedCenterY: Float
when (context.display.rotation) {
Surface.ROTATION_90 -> {
expectedCenterX = width * normalizedPortPosY
expectedCenterY = height * (1 - normalizedPortPosX)
}
Surface.ROTATION_180 -> {
expectedCenterX = width * (1 - normalizedPortPosX)
expectedCenterY = height * (1 - normalizedPortPosY)
}
Surface.ROTATION_270 -> {
expectedCenterX = width * (1 - normalizedPortPosY)
expectedCenterY = height * normalizedPortPosX
}
else -> { // Surface.ROTATION_0
expectedCenterX = width * normalizedPortPosX
expectedCenterY = height * normalizedPortPosY
}
}
verify(rippleView).setCenter(expectedCenterX, expectedCenterY)
}
}