Merge "Add ACTION_SWITCH_DISPLAY_UNFOLD to LatencyTracker"

This commit is contained in:
TreeHugger Robot
2021-12-15 14:34:40 +00:00
committed by Android (Google) Code Review
7 changed files with 262 additions and 14 deletions

View File

@@ -122,6 +122,11 @@ public class LatencyTracker {
*/
public static final int ACTION_USER_SWITCH = 12;
/**
* Time it takes to turn on the inner screen for a foldable device.
*/
public static final int ACTION_SWITCH_DISPLAY_UNFOLD = 13;
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -135,7 +140,8 @@ public class LatencyTracker {
ACTION_ROTATE_SCREEN_SENSOR,
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
ACTION_LOCKSCREEN_UNLOCK,
ACTION_USER_SWITCH
ACTION_USER_SWITCH,
ACTION_SWITCH_DISPLAY_UNFOLD
};
/** @hide */
@@ -152,7 +158,8 @@ public class LatencyTracker {
ACTION_ROTATE_SCREEN_SENSOR,
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
ACTION_LOCKSCREEN_UNLOCK,
ACTION_USER_SWITCH
ACTION_USER_SWITCH,
ACTION_SWITCH_DISPLAY_UNFOLD
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -171,7 +178,8 @@ public class LatencyTracker {
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD
};
private static LatencyTracker sLatencyTracker;
@@ -257,6 +265,8 @@ public class LatencyTracker {
return "ACTION_LOCKSCREEN_UNLOCK";
case 13:
return "ACTION_USER_SWITCH";
case 14:
return "ACTION_SWITCH_DISPLAY_UNFOLD";
default:
throw new IllegalArgumentException("Invalid action");
}

View File

@@ -33,6 +33,12 @@ import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
import java.lang.IllegalStateException
import java.util.concurrent.Executor
/**
* Factory for [UnfoldTransitionProgressProvider].
*
* This is needed as Launcher has to create the object manually.
* Sysui create it using dagger (see [UnfoldTransitionModule]).
*/
fun createUnfoldTransitionProgressProvider(
context: Context,
config: UnfoldTransitionConfig,

View File

@@ -28,6 +28,7 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldLatencyTracker;
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.TaskViewFactory;
@@ -135,6 +136,7 @@ public interface SysUIComponent {
// No init method needed, just needs to be gotten so that it's created.
getMediaTttChipController();
getMediaTttCommandLineHelper();
getUnfoldLatencyTracker().init();
}
/**
@@ -155,6 +157,12 @@ public interface SysUIComponent {
@SysUISingleton
ContextComponentHelper getContextComponentHelper();
/**
* Creates a UnfoldLatencyTracker.
*/
@SysUISingleton
UnfoldLatencyTracker getUnfoldLatencyTracker();
/**
* Main dependency providing module.
*/

View File

@@ -0,0 +1,83 @@
/*
* 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
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.keyguard.ScreenLifecycle
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Logs performance metrics regarding time to turn the inner screen on.
*
* This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn].
* This should be used from only one process.
* For now, the focus is on the time the inner display is visible, but in the future, it is easily
* possible to monitor the time to go from the inner screen to the outer.
*/
@SysUISingleton
class UnfoldLatencyTracker @Inject constructor(
private val latencyTracker: LatencyTracker,
private val deviceStateManager: DeviceStateManager,
@UiBackground private val uiBgExecutor: Executor,
private val context: Context,
private val screenLifecycle: ScreenLifecycle
) : ScreenLifecycle.Observer {
private var folded: Boolean? = null
private val foldStateListener = FoldStateListener(context)
private val isFoldable: Boolean
get() = context.resources.getIntArray(
com.android.internal.R.array.config_foldedDeviceStates).isNotEmpty()
/** Registers for relevant events only if the device is foldable. */
fun init() {
if (!isFoldable) {
return
}
deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
screenLifecycle.addObserver(this)
}
/**
* To be called when the screen becomes visible.
*
* This is safe to call also when unsure whether the device is not a foldable, as it emits the
* end action event only if we previously received a fold state.
*/
override fun onScreenTurnedOn() {
if (folded == false) {
latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
}
}
private fun onFoldEvent(folded: Boolean) {
if (this.folded != folded) {
this.folded = folded
if (!folded) { // unfolding started
latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
}
}
}
private inner class FoldStateListener(context: Context) :
DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) })
}

View File

@@ -0,0 +1,110 @@
/*
* 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
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.unfold.util.FoldableDeviceStates
import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UnfoldLatencyTrackerTest : SysuiTestCase() {
@Mock
lateinit var latencyTracker: LatencyTracker
@Mock
lateinit var deviceStateManager: DeviceStateManager
@Mock
lateinit var screenLifecycle: ScreenLifecycle
@Captor
private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
@Captor
private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
private lateinit var deviceStates: FoldableDeviceStates
private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
unfoldLatencyTracker = UnfoldLatencyTracker(
latencyTracker,
deviceStateManager,
context.mainExecutor,
context,
screenLifecycle
)
deviceStates = FoldableTestUtils.findDeviceStates(context)
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
verify(screenLifecycle).addObserver(screenLifecycleCaptor.capture())
}
@Test
fun unfold_eventPropagated() {
sendFoldEvent(folded = false)
sendScreenTurnedOnEvent()
verify(latencyTracker).onActionStart(any())
verify(latencyTracker).onActionEnd(any())
}
@Test
fun fold_eventNotPropagated() {
sendFoldEvent(folded = true)
sendScreenTurnedOnEvent() // outer display on.
verifyNoMoreInteractions(latencyTracker)
}
@Test
fun onScreenTurnedOn_stateNeverSet_eventNotPropagated() {
sendScreenTurnedOnEvent()
verifyNoMoreInteractions(latencyTracker)
}
private fun sendFoldEvent(folded: Boolean) {
val state = if (folded) deviceStates.folded else deviceStates.unfolded
foldStateListenerCaptor.value.onStateChanged(state)
}
private fun sendScreenTurnedOnEvent() {
screenLifecycleCaptor.value.onScreenTurnedOn()
}
}

View File

@@ -26,9 +26,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
import com.android.systemui.unfold.util.FoldableDeviceStates
import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,8 +71,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
private val foldUpdates: MutableList<Int> = arrayListOf()
private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
private var foldedDeviceState: Int = 0
private var unfoldedDeviceState: Int = 0
private lateinit var deviceStates: FoldableDeviceStates
private var scheduledRunnable: Runnable? = null
private var scheduledRunnableDelay: Long? = null
@@ -79,13 +79,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val foldedDeviceStates: IntArray = context.resources.getIntArray(
com.android.internal.R.array.config_foldedDeviceStates)
assumeTrue("Test should be launched on a foldable device",
foldedDeviceStates.isNotEmpty())
foldedDeviceState = foldedDeviceStates.maxOrNull()!!
unfoldedDeviceState = foldedDeviceState + 1
deviceStates = FoldableTestUtils.findDeviceStates(context)
foldStateProvider = DeviceFoldStateProvider(
context,
@@ -282,7 +276,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
private fun setFoldState(folded: Boolean) {
val state = if (folded) foldedDeviceState else unfoldedDeviceState
val state = if (folded) deviceStates.folded else deviceStates.unfolded
foldStateListenerCaptor.value.onStateChanged(state)
}

View File

@@ -0,0 +1,37 @@
/*
* 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.content.Context
import org.junit.Assume.assumeTrue
object FoldableTestUtils {
/** Finds device state for folded and unfolded. */
fun findDeviceStates(context: Context): FoldableDeviceStates {
val foldedDeviceStates: IntArray = context.resources.getIntArray(
com.android.internal.R.array.config_foldedDeviceStates)
assumeTrue("Test should be launched on a foldable device",
foldedDeviceStates.isNotEmpty())
val folded = foldedDeviceStates.maxOrNull()!!
val unfolded = folded + 1
return FoldableDeviceStates(folded = folded, unfolded = unfolded)
}
}
data class FoldableDeviceStates(val folded: Int, val unfolded: Int)