Drive Launcher unfold animation from System UI

Before this cl, both Launcher and System UI processes were registering for hinge angle and device state (e.g. folded/unfolded) change events to calculate the current unfold animation progress. In some cases, launcher ui thread was busy, delaying the progress calculation from the hinge angle value (that was received ~at the same time by both processes). This resulted in launcher and sysui unfold animation not being synchronized in some cases.

With this cl, System UI process uses OverviewProxyService to send the unfold events to Launcher. In this way, both process always have the exact same progress (+- 1 frame)

This is currently guarded by a launcher flag, by default with the new behaviour, to allow devs to compare the experience and easily debug potential regressions.

Bug: 268490854
Test: Analysed perfetto trace + RemoteUnfoldTransitionProgressProviderTest + manual
Change-Id: Icdf3932644a545f4dbe077c8284911bf99f3d81c
This commit is contained in:
Nicolo' Mazzucato
2023-02-06 18:38:50 +00:00
parent a3f6566acc
commit 5903ebdc0d
14 changed files with 556 additions and 110 deletions

View File

@@ -43,6 +43,7 @@ public class QuickStepContract {
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";

View File

@@ -35,6 +35,8 @@ import com.android.systemui.unfold.FoldStateLogger;
import com.android.systemui.unfold.FoldStateLoggingProvider;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldLatencyTracker;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
@@ -137,6 +139,10 @@ public interface SysUIComponent {
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
getUnfoldTransitionProgressForwarder().ifPresent((forwarder) ->
progressProvider.addCallback(forwarder)
));
}
/**
@@ -163,6 +169,18 @@ public interface SysUIComponent {
@SysUISingleton
UnfoldLatencyTracker getUnfoldLatencyTracker();
/**
* Creates a UnfoldTransitionProgressProvider.
*/
@SysUISingleton
Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider();
/**
* Creates a UnfoldTransitionProgressForwarder.
*/
@SysUISingleton
Optional<UnfoldTransitionProgressForwarder> getUnfoldTransitionProgressForwarder();
/**
* Creates a FoldStateLoggingProvider.
*/

View File

@@ -25,6 +25,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
@@ -99,6 +100,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
@@ -144,6 +146,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final CommandQueue mCommandQueue;
private final UserTracker mUserTracker;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
private final Optional<UnfoldTransitionProgressForwarder> mUnfoldTransitionProgressForwarder;
private final UiEventLogger mUiEventLogger;
private final DisplayTracker mDisplayTracker;
@@ -415,6 +418,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
mUnfoldTransitionProgressForwarder.ifPresent(
unfoldProgressForwarder -> params.putBinder(
KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER,
unfoldProgressForwarder.asBinder()));
// Add all the interfaces exposed by the shell
mShellInterface.createExternalInterfaces(params);
@@ -512,7 +519,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
DisplayTracker displayTracker,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
AssistUtils assistUtils,
DumpManager dumpManager) {
DumpManager dumpManager,
Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder
) {
// b/241601880: This component shouldn't be running for a non-primary user
if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
@@ -538,6 +547,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mSysUiState.addCallback(this::notifySystemUiStateFlags);
mUiEventLogger = uiEventLogger;
mDisplayTracker = displayTracker;
mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
dumpManager.registerDumpable(getClass().getSimpleName(), this);

View File

@@ -20,17 +20,13 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.util.TestFoldStateProvider
import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,9 +41,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
@Before
fun setUp() {
progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(
foldStateProvider
)
progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
progressProvider.addCallback(listener)
}
@@ -79,9 +73,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
)
with(listener.ensureTransitionFinished()) {
assertHasSingleFinishingEvent()
}
with(listener.ensureTransitionFinished()) { assertHasSingleFinishingEvent() }
}
@Test
@@ -150,106 +142,12 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
)
with(listener.ensureTransitionFinished()) {
assertHasFoldAnimationAtTheEnd()
}
}
private class TestUnfoldProgressListener : TransitionProgressListener {
private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
private var currentRecording: UnfoldTransitionRecording? = null
override fun onTransitionStarted() {
assertWithMessage("Trying to start a transition when it is already in progress")
.that(currentRecording).isNull()
currentRecording = UnfoldTransitionRecording()
}
override fun onTransitionProgress(progress: Float) {
assertWithMessage("Received transition progress event when it's not started")
.that(currentRecording).isNotNull()
currentRecording!!.addProgress(progress)
}
override fun onTransitionFinishing() {
assertWithMessage("Received transition finishing event when it's not started")
.that(currentRecording).isNotNull()
currentRecording!!.onFinishing()
}
override fun onTransitionFinished() {
assertWithMessage("Received transition finish event when it's not started")
.that(currentRecording).isNotNull()
recordings += currentRecording!!
currentRecording = null
}
fun ensureTransitionFinished(): UnfoldTransitionRecording {
waitForCondition { recordings.size == 1 }
return recordings.first()
}
class UnfoldTransitionRecording {
private val progressHistory: MutableList<Float> = arrayListOf()
private var finishingInvocations: Int = 0
fun addProgress(progress: Float) {
assertThat(progress).isAtMost(1.0f)
assertThat(progress).isAtLeast(0.0f)
progressHistory += progress
}
fun onFinishing() {
finishingInvocations++
}
fun assertIncreasingProgress() {
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory).isInOrder()
}
fun assertDecreasingProgress() {
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory).isInOrder(Comparator.reverseOrder<Float>())
}
fun assertFinishedWithUnfold() {
assertThat(progressHistory).isNotEmpty()
assertThat(progressHistory.last()).isEqualTo(1.0f)
}
fun assertFinishedWithFold() {
assertThat(progressHistory).isNotEmpty()
assertThat(progressHistory.last()).isEqualTo(0.0f)
}
fun assertHasFoldAnimationAtTheEnd() {
// Check that there are at least a few decreasing events at the end
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory.takeLast(MIN_ANIMATION_EVENTS))
.isInOrder(Comparator.reverseOrder<Float>())
assertThat(progressHistory.last()).isEqualTo(0.0f)
}
fun assertHasSingleFinishingEvent() {
assertWithMessage("onTransitionFinishing callback should be invoked exactly " +
"one time").that(finishingInvocations).isEqualTo(1)
}
}
private companion object {
private const val MIN_ANIMATION_EVENTS = 5
}
with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() }
}
private fun runOnMainThreadWithInterval(vararg blocks: () -> Unit, intervalMillis: Long = 60) {
blocks.forEach {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
it()
}
InstrumentationRegistry.getInstrumentation().runOnMainSync { it() }
Thread.sleep(intervalMillis)
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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 com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
private val progressProvider = RemoteUnfoldTransitionReceiver { it.run() }
private val listener = TestUnfoldProgressListener()
@Before
fun setUp() {
progressProvider.addCallback(listener)
}
@Test
fun onTransitionStarted_propagated() {
progressProvider.onTransitionStarted()
listener.assertStarted()
}
@Test
fun onTransitionProgress_propagated() {
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0.5f)
listener.assertLastProgress(0.5f)
}
@Test
fun onTransitionEnded_propagated() {
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0.5f)
progressProvider.onTransitionFinished()
listener.ensureTransitionFinished()
}
@Test
fun onTransitionStarted_afterCallbackRemoved_notPropagated() {
progressProvider.removeCallback(listener)
progressProvider.onTransitionStarted()
listener.assertNotStarted()
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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 com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
/** Listener usable by tests with some handy assertions. */
class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionProgressListener {
private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
private var currentRecording: UnfoldTransitionRecording? = null
override fun onTransitionStarted() {
assertWithMessage("Trying to start a transition when it is already in progress")
.that(currentRecording)
.isNull()
currentRecording = UnfoldTransitionRecording()
}
override fun onTransitionProgress(progress: Float) {
assertWithMessage("Received transition progress event when it's not started")
.that(currentRecording)
.isNotNull()
currentRecording!!.addProgress(progress)
}
override fun onTransitionFinishing() {
assertWithMessage("Received transition finishing event when it's not started")
.that(currentRecording)
.isNotNull()
currentRecording!!.onFinishing()
}
override fun onTransitionFinished() {
assertWithMessage("Received transition finish event when it's not started")
.that(currentRecording)
.isNotNull()
recordings += currentRecording!!
currentRecording = null
}
fun ensureTransitionFinished(): UnfoldTransitionRecording {
waitForCondition { recordings.size == 1 }
return recordings.first()
}
fun assertStarted() {
assertWithMessage("Transition didn't start").that(currentRecording).isNotNull()
}
fun assertNotStarted() {
assertWithMessage("Transition started").that(currentRecording).isNull()
}
fun assertLastProgress(progress: Float) {
currentRecording?.assertLastProgress(progress) ?: error("unfold not in progress.")
}
class UnfoldTransitionRecording {
private val progressHistory: MutableList<Float> = arrayListOf()
private var finishingInvocations: Int = 0
fun addProgress(progress: Float) {
assertThat(progress).isAtMost(1.0f)
assertThat(progress).isAtLeast(0.0f)
progressHistory += progress
}
fun onFinishing() {
finishingInvocations++
}
fun assertIncreasingProgress() {
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory).isInOrder()
}
fun assertDecreasingProgress() {
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory).isInOrder(Comparator.reverseOrder<Float>())
}
fun assertFinishedWithUnfold() {
assertThat(progressHistory).isNotEmpty()
assertThat(progressHistory.last()).isEqualTo(1.0f)
}
fun assertFinishedWithFold() {
assertThat(progressHistory).isNotEmpty()
assertThat(progressHistory.last()).isEqualTo(0.0f)
}
fun assertHasFoldAnimationAtTheEnd() {
// Check that there are at least a few decreasing events at the end
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory.takeLast(MIN_ANIMATION_EVENTS))
.isInOrder(Comparator.reverseOrder<Float>())
assertThat(progressHistory.last()).isEqualTo(0.0f)
}
fun assertHasSingleFinishingEvent() {
assertWithMessage(
"onTransitionFinishing callback should be invoked exactly " + "one time"
)
.that(finishingInvocations)
.isEqualTo(1)
}
fun assertLastProgress(progress: Float) {
assertThat(progressHistory.last()).isEqualTo(progress)
}
}
private companion object {
private const val MIN_ANIMATION_EVENTS = 5
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Provider
import javax.inject.Singleton
/** Binds classes needed to provide unfold transition progresses to another process. */
@Module
class UnfoldRemoteModule {
@Provides
@Singleton
fun provideTransitionProvider(
config: UnfoldTransitionConfig,
traceListener: ATraceLoggerTransitionProgressListener,
remoteReceiverProvider: Provider<RemoteUnfoldTransitionReceiver>,
): Optional<RemoteUnfoldTransitionReceiver> {
if (!config.isEnabled) {
return Optional.empty()
}
val remoteReceiver = remoteReceiverProvider.get()
remoteReceiver.addCallback(traceListener)
return Optional.of(remoteReceiver)
}
}

View File

@@ -24,6 +24,7 @@ import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -68,3 +69,38 @@ interface UnfoldSharedComponent {
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
val rotationChangeProvider: RotationChangeProvider
}
/**
* Generates a [RemoteTransitionProgress] usable to receive unfold transition progress from another
* process.
*/
@Singleton
@Component(modules = [UnfoldRemoteModule::class])
interface RemoteUnfoldSharedComponent {
@Component.Factory
interface Factory {
fun create(
@BindsInstance context: Context,
@BindsInstance config: UnfoldTransitionConfig,
@BindsInstance @UnfoldMain executor: Executor,
@BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
@BindsInstance windowManager: IWindowManager,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
): RemoteUnfoldSharedComponent
}
val remoteTransitionProgress: Optional<RemoteUnfoldTransitionReceiver>
val rotationChangeProvider: RotationChangeProvider
}
/**
* Usable to receive and propagate unfold transition progresses
*
* All unfold events received by [remoteReceiver] will be propagated to [localProvider].
* [remoteReceiver] is meant to receive events from a remote process (E.g. from a binder service).
*/
data class RemoteTransitionProgress(
val localProvider: UnfoldTransitionProgressProvider,
val remoteReceiver: UnfoldTransitionProgressProvider.TransitionProgressListener
)

View File

@@ -19,6 +19,7 @@ package com.android.systemui.unfold
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
@@ -102,4 +103,16 @@ internal class UnfoldSharedInternalModule {
EmptyHingeAngleProvider
}
}
@Provides
@Singleton
fun provideProgressForwarder(
config: UnfoldTransitionConfig,
progressForwarder: Provider<UnfoldTransitionProgressForwarder>
): Optional<UnfoldTransitionProgressForwarder> {
if (!config.isEnabled) {
return Optional.empty()
}
return Optional.of(progressForwarder.get())
}
}

View File

@@ -63,3 +63,26 @@ fun createUnfoldSharedComponent(
tracingTagPrefix,
windowManager,
)
/**
* Factory for [RemoteUnfoldSharedComponent].
*
* Wraps [DaggerRemoteUnfoldSharedComponent] (that is autogenerated), for better discoverability.
*/
fun createRemoteUnfoldSharedComponent(
context: Context,
config: UnfoldTransitionConfig,
mainExecutor: Executor,
singleThreadBgExecutor: Executor,
tracingTagPrefix: String,
windowManager: IWindowManager,
): RemoteUnfoldSharedComponent =
DaggerRemoteUnfoldSharedComponent.factory()
.create(
context,
config,
mainExecutor,
singleThreadBgExecutor,
windowManager,
tracingTagPrefix,
)

View File

@@ -0,0 +1,34 @@
/*
* 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 com.android.systemui.unfold.progress.IUnfoldTransitionListener;
/**
* Interface exposed by System UI to allow remote process to register for unfold animation events.
*/
oneway interface IUnfoldAnimation {
/**
* Sets a listener for the animation.
*
* Only one listener is supported. If there are multiple, the earlier one will be overridden.
*/
void setListener(in IUnfoldTransitionListener listener);
}

View File

@@ -0,0 +1,38 @@
/*
* 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;
/**
* Implemented by remote processes to receive unfold animation events from System UI.
*/
oneway interface IUnfoldTransitionListener {
/**
* Sent when unfold animation started.
*/
void onTransitionStarted() = 1;
/**
* Sent when unfold animation progress changes.
*/
void onTransitionProgress(float progress) = 2;
/**
* Sent when unfold animation finished.
*/
void onTransitionFinished() = 3;
}

View File

@@ -0,0 +1,61 @@
/*
* 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 com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.dagger.UnfoldMain
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Receives unfold events from remote senders (System UI).
*
* A binder to an instance to this class (created with [RemoteUnfoldTransitionReceiver.asBinder])
* should be sent to the remote process providing events.
*/
class RemoteUnfoldTransitionReceiver
@Inject
constructor(@UnfoldMain private val executor: Executor) :
UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
override fun onTransitionStarted() {
executor.execute { listeners.forEach { it.onTransitionStarted() } }
}
override fun onTransitionProgress(progress: Float) {
executor.execute { listeners.forEach { it.onTransitionProgress(progress) } }
}
override fun onTransitionFinished() {
executor.execute { listeners.forEach { it.onTransitionFinished() } }
}
override fun addCallback(listener: TransitionProgressListener) {
listeners += listener
}
override fun removeCallback(listener: TransitionProgressListener) {
listeners -= listener
}
override fun destroy() {
listeners.clear()
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.os.RemoteException
import android.util.Log
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import javax.inject.Inject
/** Forwards received unfold events to [remoteListener], when present. */
class UnfoldTransitionProgressForwarder @Inject constructor() :
TransitionProgressListener, IUnfoldAnimation.Stub() {
private var remoteListener: IUnfoldTransitionListener? = null
override fun onTransitionStarted() {
try {
Log.d(TAG, "onTransitionStarted")
remoteListener?.onTransitionStarted()
} catch (e: RemoteException) {
Log.e(TAG, "Failed call onTransitionStarted", e)
}
}
override fun onTransitionFinished() {
try {
Log.d(TAG, "onTransitionFinished")
remoteListener?.onTransitionFinished()
} catch (e: RemoteException) {
Log.e(TAG, "Failed call onTransitionFinished", e)
}
}
override fun onTransitionProgress(progress: Float) {
try {
remoteListener?.onTransitionProgress(progress)
} catch (e: RemoteException) {
Log.e(TAG, "Failed call onTransitionProgress", e)
}
}
override fun setListener(listener: IUnfoldTransitionListener?) {
remoteListener = listener
}
companion object {
private val TAG = UnfoldTransitionProgressForwarder::class.java.simpleName
}
}