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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user