Merge "Make remote Unfold progress receiver resilient to jank" into tm-qpr-dev am: 3020d2529a
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22138523 Change-Id: I81c9856e4a134f89aea53a4616e335851fc85800 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 android.testing.AndroidTestingRunner
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -27,23 +28,27 @@ import org.junit.runner.RunWith
|
|||||||
@SmallTest
|
@SmallTest
|
||||||
class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
|
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()
|
private val listener = TestUnfoldProgressListener()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
progressProvider.addCallback(listener)
|
progressProvider.addCallback(listener)
|
||||||
|
progressProviderWithoutFilter.addCallback(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun onTransitionStarted_propagated() {
|
fun onTransitionStarted_withFilter_propagated() {
|
||||||
progressProvider.onTransitionStarted()
|
progressProvider.onTransitionStarted()
|
||||||
|
|
||||||
listener.assertStarted()
|
listener.assertStarted()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun onTransitionProgress_propagated() {
|
fun onTransitionProgress_withFilter_propagated() {
|
||||||
progressProvider.onTransitionStarted()
|
progressProvider.onTransitionStarted()
|
||||||
|
|
||||||
progressProvider.onTransitionProgress(0.5f)
|
progressProvider.onTransitionProgress(0.5f)
|
||||||
@@ -52,7 +57,7 @@ class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun onTransitionEnded_propagated() {
|
fun onTransitionEnded_withFilter_propagated() {
|
||||||
progressProvider.onTransitionStarted()
|
progressProvider.onTransitionStarted()
|
||||||
progressProvider.onTransitionProgress(0.5f)
|
progressProvider.onTransitionProgress(0.5f)
|
||||||
|
|
||||||
@@ -62,11 +67,52 @@ class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun onTransitionStarted_afterCallbackRemoved_notPropagated() {
|
fun onTransitionStarted_withFilter_afterCallbackRemoved_notPropagated() {
|
||||||
progressProvider.removeCallback(listener)
|
progressProvider.removeCallback(listener)
|
||||||
|
|
||||||
progressProvider.onTransitionStarted()
|
progressProvider.onTransitionStarted()
|
||||||
|
|
||||||
listener.assertNotStarted()
|
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) {
|
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
|
package com.android.systemui.unfold
|
||||||
|
|
||||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
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.progress.RemoteUnfoldTransitionReceiver
|
||||||
import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
|
import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@@ -42,4 +43,6 @@ class UnfoldRemoteModule {
|
|||||||
remoteReceiver.addCallback(traceListener)
|
remoteReceiver.addCallback(traceListener)
|
||||||
return Optional.of(remoteReceiver)
|
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
|
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
|
||||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||||
import com.android.systemui.unfold.dagger.UnfoldMain
|
import com.android.systemui.unfold.dagger.UnfoldMain
|
||||||
|
import com.android.systemui.unfold.dagger.UseReceivingFilter
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -30,21 +34,40 @@ import javax.inject.Inject
|
|||||||
*/
|
*/
|
||||||
class RemoteUnfoldTransitionReceiver
|
class RemoteUnfoldTransitionReceiver
|
||||||
@Inject
|
@Inject
|
||||||
constructor(@UnfoldMain private val executor: Executor) :
|
constructor(
|
||||||
UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
|
@UseReceivingFilter useReceivingFilter: Boolean,
|
||||||
|
@UnfoldMain private val executor: Executor
|
||||||
|
) : UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
|
||||||
|
|
||||||
private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
|
private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
|
||||||
|
private val outputProgressListener = ProcessedProgressListener()
|
||||||
|
private val filter: TransitionProgressListener? =
|
||||||
|
if (useReceivingFilter) {
|
||||||
|
UnfoldRemoteFilter(outputProgressListener)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
override fun onTransitionStarted() {
|
override fun onTransitionStarted() {
|
||||||
executor.execute { listeners.forEach { it.onTransitionStarted() } }
|
executor.execute {
|
||||||
|
filter?.onTransitionStarted() ?: outputProgressListener.onTransitionStarted()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
override fun onTransitionProgress(progress: Float) {
|
override fun onTransitionProgress(progress: Float) {
|
||||||
executor.execute { listeners.forEach { it.onTransitionProgress(progress) } }
|
executor.execute {
|
||||||
|
filter?.onTransitionProgress(progress)
|
||||||
|
?: outputProgressListener.onTransitionProgress(progress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
override fun onTransitionFinished() {
|
override fun onTransitionFinished() {
|
||||||
executor.execute { listeners.forEach { it.onTransitionFinished() } }
|
executor.execute {
|
||||||
|
filter?.onTransitionFinished() ?: outputProgressListener.onTransitionFinished()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addCallback(listener: TransitionProgressListener) {
|
override fun addCallback(listener: TransitionProgressListener) {
|
||||||
@@ -58,4 +81,30 @@ constructor(@UnfoldMain private val executor: Executor) :
|
|||||||
override fun destroy() {
|
override fun destroy() {
|
||||||
listeners.clear()
|
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