Merge "Make remote Unfold progress receiver resilient to jank" into tm-qpr-dev am: 3020d2529a am: a0617029b9

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

Change-Id: Ifb4891f512a505e012e56a0a04fbd952fb679916
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2023-03-27 01:36:37 +00:00
committed by Automerger Merge Worker
7 changed files with 285 additions and 11 deletions

View File

@@ -18,6 +18,7 @@ package com.android.systemui.unfold.progress
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
@@ -27,23 +28,27 @@ import org.junit.runner.RunWith
@SmallTest
class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
private val progressProvider = RemoteUnfoldTransitionReceiver { it.run() }
private val progressProvider =
RemoteUnfoldTransitionReceiver(useReceivingFilter = true) { runOnMainSync(it) }
private val progressProviderWithoutFilter =
RemoteUnfoldTransitionReceiver(useReceivingFilter = false) { it.run() }
private val listener = TestUnfoldProgressListener()
@Before
fun setUp() {
progressProvider.addCallback(listener)
progressProviderWithoutFilter.addCallback(listener)
}
@Test
fun onTransitionStarted_propagated() {
fun onTransitionStarted_withFilter_propagated() {
progressProvider.onTransitionStarted()
listener.assertStarted()
}
@Test
fun onTransitionProgress_propagated() {
fun onTransitionProgress_withFilter_propagated() {
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0.5f)
@@ -52,7 +57,7 @@ class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
}
@Test
fun onTransitionEnded_propagated() {
fun onTransitionEnded_withFilter_propagated() {
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0.5f)
@@ -62,11 +67,52 @@ class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
}
@Test
fun onTransitionStarted_afterCallbackRemoved_notPropagated() {
fun onTransitionStarted_withFilter_afterCallbackRemoved_notPropagated() {
progressProvider.removeCallback(listener)
progressProvider.onTransitionStarted()
listener.assertNotStarted()
}
@Test
fun onTransitionStarted_withoutFilter_propagated() {
progressProviderWithoutFilter.onTransitionStarted()
listener.assertStarted()
}
@Test
fun onTransitionProgress_withoutFilter_propagated() {
progressProviderWithoutFilter.onTransitionStarted()
progressProviderWithoutFilter.onTransitionProgress(0.5f)
listener.assertLastProgress(0.5f)
}
@Test
fun onTransitionEnded_withoutFilter_propagated() {
progressProviderWithoutFilter.onTransitionStarted()
progressProviderWithoutFilter.onTransitionProgress(0.5f)
progressProviderWithoutFilter.onTransitionFinished()
listener.ensureTransitionFinished()
}
@Test
fun onTransitionStarted_withoutFilter_afterCallbackRemoved_notPropagated() {
progressProviderWithoutFilter.removeCallback(listener)
progressProviderWithoutFilter.onTransitionStarted()
listener.assertNotStarted()
}
private fun runOnMainSync(f: Runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync { f.run() }
// Sleep as the animator used from the filter has a callback that happens at every frame.
Thread.sleep(60)
}
}

View File

@@ -126,7 +126,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
}
fun assertLastProgress(progress: Float) {
assertThat(progressHistory.last()).isEqualTo(progress)
assertThat(progressHistory.last()).isWithin(1.0E-4F).of(progress)
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 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.progress
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.SysuiTestCase
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UnfoldRemoteFilterTest : SysuiTestCase() {
private val listener = TestUnfoldProgressListener()
private val progressProvider = UnfoldRemoteFilter(listener)
@Test
fun onTransitionStarted_propagated() {
runOnMainThreadWithInterval({ progressProvider.onTransitionStarted() })
listener.assertStarted()
}
@Test
fun onTransitionProgress_withInterval_propagated() {
runOnMainThreadWithInterval(
{ progressProvider.onTransitionStarted() },
{ progressProvider.onTransitionProgress(0.5f) }
)
listener.assertLastProgress(0.5f)
}
@Test
fun onTransitionEnded_propagated() {
runOnMainThreadWithInterval(
{ progressProvider.onTransitionStarted() },
{ progressProvider.onTransitionProgress(0.5f) },
{ progressProvider.onTransitionFinished() },
)
listener.ensureTransitionFinished()
}
private fun runOnMainThreadWithInterval(
vararg blocks: () -> Unit,
interval: Duration = 60.milliseconds
) {
blocks.forEach {
InstrumentationRegistry.getInstrumentation().runOnMainSync { it() }
Thread.sleep(interval.inWholeMilliseconds)
}
}
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.unfold
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UseReceivingFilter
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
import dagger.Module
@@ -42,4 +43,6 @@ class UnfoldRemoteModule {
remoteReceiver.addCallback(traceListener)
return Optional.of(remoteReceiver)
}
@Provides @UseReceivingFilter fun useReceivingFilter(): Boolean = true
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2022 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.dagger
import javax.inject.Qualifier
/** Annotates whether to use a filter in [RemoteUnfoldTransitionReceiver]. */
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UseReceivingFilter

View File

@@ -16,9 +16,13 @@
package com.android.systemui.unfold.progress
import android.util.Log
import androidx.annotation.BinderThread
import androidx.annotation.FloatRange
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.dagger.UseReceivingFilter
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -30,21 +34,40 @@ import javax.inject.Inject
*/
class RemoteUnfoldTransitionReceiver
@Inject
constructor(@UnfoldMain private val executor: Executor) :
UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
constructor(
@UseReceivingFilter useReceivingFilter: Boolean,
@UnfoldMain private val executor: Executor
) : UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
private val outputProgressListener = ProcessedProgressListener()
private val filter: TransitionProgressListener? =
if (useReceivingFilter) {
UnfoldRemoteFilter(outputProgressListener)
} else {
null
}
@BinderThread
override fun onTransitionStarted() {
executor.execute { listeners.forEach { it.onTransitionStarted() } }
executor.execute {
filter?.onTransitionStarted() ?: outputProgressListener.onTransitionStarted()
}
}
@BinderThread
override fun onTransitionProgress(progress: Float) {
executor.execute { listeners.forEach { it.onTransitionProgress(progress) } }
executor.execute {
filter?.onTransitionProgress(progress)
?: outputProgressListener.onTransitionProgress(progress)
}
}
@BinderThread
override fun onTransitionFinished() {
executor.execute { listeners.forEach { it.onTransitionFinished() } }
executor.execute {
filter?.onTransitionFinished() ?: outputProgressListener.onTransitionFinished()
}
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -58,4 +81,30 @@ constructor(@UnfoldMain private val executor: Executor) :
override fun destroy() {
listeners.clear()
}
private inner class ProcessedProgressListener : TransitionProgressListener {
override fun onTransitionStarted() {
log { "onTransitionStarted" }
listeners.forEach { it.onTransitionStarted() }
}
override fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {
log { "onTransitionProgress" }
listeners.forEach { it.onTransitionProgress(progress) }
}
override fun onTransitionFinished() {
log { "onTransitionFinished" }
listeners.forEach { it.onTransitionFinished() }
}
}
private fun log(s: () -> String) {
if (DEBUG) {
Log.d(TAG, s())
}
}
}
private const val TAG = "RemoteUnfoldReceiver"
private val DEBUG = false

View File

@@ -0,0 +1,85 @@
package com.android.systemui.unfold.progress
import android.os.Trace
import android.util.Log
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
/**
* Makes progress received from other processes resilient to jank.
*
* Sender and receiver processes might have different frame-rates. If the sending process is
* dropping a frame due to jank (or generally because it's main thread is too busy), we don't want
* the receiving process to drop progress frames as well. For this reason, a spring animator pass
* (with very high stiffness) is applied to the incoming progress. This adds a small delay to the
* progress (~30ms), but guarantees an always smooth animation on the receiving end.
*/
class UnfoldRemoteFilter(
private val listener: UnfoldTransitionProgressProvider.TransitionProgressListener
) : UnfoldTransitionProgressProvider.TransitionProgressListener {
private val springAnimation =
SpringAnimation(this, AnimationProgressProperty).apply {
spring =
SpringForce().apply {
dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
stiffness = 100_000f
finalPosition = 1.0f
}
setMinValue(0f)
setMaxValue(1f)
minimumVisibleChange = 0.001f
}
private var inProgress = false
private var processedProgress: Float = 0.0f
set(newProgress) {
if (inProgress) {
logCounter({ "$TAG#filtered_progress" }, newProgress)
listener.onTransitionProgress(newProgress)
} else {
Log.e(TAG, "Filtered progress received received while animation not in progress.")
}
field = newProgress
}
override fun onTransitionStarted() {
listener.onTransitionStarted()
inProgress = true
}
override fun onTransitionProgress(progress: Float) {
logCounter({ "$TAG#plain_remote_progress" }, progress)
if (inProgress) {
springAnimation.animateToFinalPosition(progress)
} else {
Log.e(TAG, "Progress received while not in progress.")
}
}
override fun onTransitionFinished() {
inProgress = false
listener.onTransitionFinished()
}
private object AnimationProgressProperty :
FloatPropertyCompat<UnfoldRemoteFilter>("UnfoldRemoteFilter") {
override fun setValue(provider: UnfoldRemoteFilter, value: Float) {
provider.processedProgress = value
}
override fun getValue(provider: UnfoldRemoteFilter): Float = provider.processedProgress
}
private fun logCounter(name: () -> String, progress: Float) {
if (DEBUG) {
Trace.setCounter(name(), (progress * 100).toLong())
}
}
}
private val TAG = "UnfoldRemoteFilter"
private val DEBUG = false