Merge "Ensure ordering of overlay callbacks" into tm-qpr-dev am: ced571cb63

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

Change-Id: Ic53541c3cffe6c137d4d2fdd2a0d1576bd17fc08
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Lucas Silva
2023-03-13 20:03:39 +00:00
committed by Automerger Merge Worker
4 changed files with 199 additions and 55 deletions

View File

@@ -27,6 +27,8 @@ import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import android.view.WindowManager; import android.view.WindowManager;
import java.util.concurrent.Executor;
/** /**
* Basic implementation of for {@link IDreamOverlay} for testing. * Basic implementation of for {@link IDreamOverlay} for testing.
@@ -40,6 +42,12 @@ public abstract class DreamOverlayService extends Service {
// The last client that started dreaming and hasn't ended // The last client that started dreaming and hasn't ended
private OverlayClient mCurrentClient; private OverlayClient mCurrentClient;
/**
* Executor used to run callbacks that subclasses will implement. Any calls coming over Binder
* from {@link OverlayClient} should perform the work they need to do on this executor.
*/
private Executor mExecutor;
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService} // requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub { private static class OverlayClient extends IDreamOverlayClient.Stub {
@@ -61,8 +69,6 @@ public abstract class DreamOverlayService extends Service {
mService.startDream(this, params); mService.startDream(this, params);
} }
@Override @Override
public void wakeUp() { public void wakeUp() {
mService.wakeUp(this, () -> { mService.wakeUp(this, () -> {
@@ -97,12 +103,20 @@ public abstract class DreamOverlayService extends Service {
} }
private void startDream(OverlayClient client, WindowManager.LayoutParams params) { private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
endDream(mCurrentClient); // Run on executor as this is a binder call from OverlayClient.
mExecutor.execute(() -> {
endDreamInternal(mCurrentClient);
mCurrentClient = client; mCurrentClient = client;
onStartDream(params); onStartDream(params);
});
} }
private void endDream(OverlayClient client) { private void endDream(OverlayClient client) {
// Run on executor as this is a binder call from OverlayClient.
mExecutor.execute(() -> endDreamInternal(client));
}
private void endDreamInternal(OverlayClient client) {
if (client == null || client != mCurrentClient) { if (client == null || client != mCurrentClient) {
return; return;
} }
@@ -112,11 +126,14 @@ public abstract class DreamOverlayService extends Service {
} }
private void wakeUp(OverlayClient client, Runnable callback) { private void wakeUp(OverlayClient client, Runnable callback) {
// Run on executor as this is a binder call from OverlayClient.
mExecutor.execute(() -> {
if (mCurrentClient != client) { if (mCurrentClient != client) {
return; return;
} }
onWakeUp(callback); onWakeUp(callback);
});
} }
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() { private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@@ -134,6 +151,25 @@ public abstract class DreamOverlayService extends Service {
public DreamOverlayService() { public DreamOverlayService() {
} }
/**
* This constructor allows providing an executor to run callbacks on.
*
* @hide
*/
public DreamOverlayService(@NonNull Executor executor) {
mExecutor = executor;
}
@Override
public void onCreate() {
super.onCreate();
if (mExecutor == null) {
// If no executor was provided, use the main executor. onCreate is the earliest time
// getMainExecutor is available.
mExecutor = getMainExecutor();
}
}
@Nullable @Nullable
@Override @Override
public final IBinder onBind(@NonNull Intent intent) { public final IBinder onBind(@NonNull Intent intent) {
@@ -143,6 +179,10 @@ public abstract class DreamOverlayService extends Service {
/** /**
* This method is overridden by implementations to handle when the dream has started and the * This method is overridden by implementations to handle when the dream has started and the
* window is ready to be interacted with. * window is ready to be interacted with.
*
* This callback will be run on the {@link Executor} provided in the constructor if provided, or
* on the main executor if none was provided.
*
* @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the * @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the
* dream window. * dream window.
*/ */
@@ -153,6 +193,9 @@ public abstract class DreamOverlayService extends Service {
* to wakeup. This allows any overlay animations to run. By default, the method will invoke * to wakeup. This allows any overlay animations to run. By default, the method will invoke
* the callback immediately. * the callback immediately.
* *
* This callback will be run on the {@link Executor} provided in the constructor if provided, or
* on the main executor if none was provided.
*
* @param onCompleteCallback The callback to trigger to notify the dream service that the * @param onCompleteCallback The callback to trigger to notify the dream service that the
* overlay has completed waking up. * overlay has completed waking up.
* @hide * @hide
@@ -164,6 +207,9 @@ public abstract class DreamOverlayService extends Service {
/** /**
* This method is overridden by implementations to handle when the dream has ended. There may * This method is overridden by implementations to handle when the dream has ended. There may
* be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}. * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
*
* This callback will be run on the {@link Executor} provided in the constructor if provided, or
* on the main executor if none was provided.
*/ */
public void onEndDream() { public void onEndDream() {
} }

View File

@@ -139,6 +139,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
ComponentName lowLightDreamComponent, ComponentName lowLightDreamComponent,
DreamOverlayCallbackController dreamOverlayCallbackController, DreamOverlayCallbackController dreamOverlayCallbackController,
@Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) { @Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) {
super(executor);
mContext = context; mContext = context;
mExecutor = executor; mExecutor = executor;
mWindowManager = windowManager; mWindowManager = windowManager;
@@ -176,7 +177,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
@Override @Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
mExecutor.execute(() -> {
setCurrentStateLocked(Lifecycle.State.STARTED); setCurrentStateLocked(Lifecycle.State.STARTED);
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -207,7 +207,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
return; return;
} }
setCurrentStateLocked(Lifecycle.State.RESUMED); setCurrentStateLocked(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true); mStateController.setOverlayActive(true);
final ComponentName dreamComponent = getDreamComponent(); final ComponentName dreamComponent = getDreamComponent();
@@ -217,14 +216,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mDreamOverlayCallbackController.onStartDream(); mDreamOverlayCallbackController.onStartDream();
mStarted = true; mStarted = true;
});
} }
@Override @Override
public void onEndDream() { public void onEndDream() {
mExecutor.execute(() -> {
resetCurrentDreamOverlayLocked(); resetCurrentDreamOverlayLocked();
});
} }
private Lifecycle.State getCurrentStateLocked() { private Lifecycle.State getCurrentStateLocked() {
@@ -237,12 +233,10 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
@Override @Override
public void onWakeUp(@NonNull Runnable onCompletedCallback) { public void onWakeUp(@NonNull Runnable onCompletedCallback) {
mExecutor.execute(() -> {
if (mDreamOverlayContainerViewController != null) { if (mDreamOverlayContainerViewController != null) {
mDreamOverlayCallbackController.onWakeUp(); mDreamOverlayCallbackController.onWakeUp();
mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor); mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
} }
});
} }
/** /**

View File

@@ -20,7 +20,9 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -60,6 +62,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@@ -254,6 +257,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
// Inform the overlay service of dream starting. // Inform the overlay service of dream starting.
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/); true /*shouldShowComplication*/);
mMainExecutor.runAllReady();
assertThat(mService.shouldShowComplications()).isTrue(); assertThat(mService.shouldShowComplications()).isTrue();
} }
@@ -296,6 +300,48 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
verify(mStateController).setEntryAnimationsFinished(false); verify(mStateController).setEntryAnimationsFinished(false);
} }
@Test
public void testImmediateEndDream() throws Exception {
final IDreamOverlayClient client = getClient();
// Start the dream, but don't execute any Runnables put on the executor yet. We delay
// executing Runnables as the timing isn't guaranteed and we want to verify that the overlay
// starts and finishes in the proper order even if Runnables are delayed.
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
// Immediately end the dream.
client.endDream();
// Run any scheduled Runnables.
mMainExecutor.runAllReady();
// The overlay starts then finishes.
InOrder inOrder = inOrder(mWindowManager);
inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
}
@Test
public void testEndDreamDuringStartDream() throws Exception {
final IDreamOverlayClient client = getClient();
// Schedule the endDream call in the middle of the startDream implementation, as any
// ordering is possible.
doAnswer(invocation -> {
client.endDream();
return null;
}).when(mStateController).setOverlayActive(true);
// Start the dream.
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
// The overlay starts then finishes.
InOrder inOrder = inOrder(mWindowManager);
inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
}
@Test @Test
public void testDestroy() throws RemoteException { public void testDestroy() throws RemoteException {
final IDreamOverlayClient client = getClient(); final IDreamOverlayClient client = getClient();

View File

@@ -18,6 +18,8 @@ package com.android.server.dreams;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -39,10 +41,13 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.concurrent.Executor;
/** /**
* A collection of tests to exercise {@link DreamOverlayService}. * A collection of tests to exercise {@link DreamOverlayService}.
*/ */
@@ -60,6 +65,9 @@ public class DreamOverlayServiceTest {
@Mock @Mock
IDreamOverlayCallback mOverlayCallback; IDreamOverlayCallback mOverlayCallback;
@Mock
Executor mExecutor;
/** /**
* {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for * {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
* tracking interactions across {@link IDreamOverlay} binder interface. The service reports * tracking interactions across {@link IDreamOverlay} binder interface. The service reports
@@ -78,8 +86,8 @@ public class DreamOverlayServiceTest {
private final Monitor mMonitor; private final Monitor mMonitor;
TestDreamOverlayService(Monitor monitor) { TestDreamOverlayService(Monitor monitor, Executor executor) {
super(); super(executor);
mMonitor = monitor; mMonitor = monitor;
} }
@@ -117,14 +125,64 @@ public class DreamOverlayServiceTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
} }
/**
* Verifies that callbacks for subclasses are run on the provided executor.
*/
@Test
public void testCallbacksRunOnExecutor() throws RemoteException {
final TestDreamOverlayService.Monitor monitor = Mockito.mock(
TestDreamOverlayService.Monitor.class);
final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
final IBinder binder = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
final IDreamOverlayClient client = getClient(overlay);
// Start the dream.
client.startDream(mLayoutParams, mOverlayCallback,
FIRST_DREAM_COMPONENT.flattenToString(), false);
// The callback should not have run yet.
verify(monitor, never()).onStartDream();
// Run the Runnable sent to the executor.
ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mExecutor).execute(mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
// Callback is run.
verify(monitor).onStartDream();
// Verify onWakeUp is run on the executor.
client.wakeUp();
verify(monitor, never()).onWakeUp();
mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mExecutor).execute(mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
verify(monitor).onWakeUp();
// Verify onEndDream is run on the executor.
client.endDream();
verify(monitor, never()).onEndDream();
mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mExecutor).execute(mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
verify(monitor).onEndDream();
}
/** /**
* Verifies that only the currently started dream is able to affect the overlay. * Verifies that only the currently started dream is able to affect the overlay.
*/ */
@Test @Test
public void testOverlayClientInteraction() throws RemoteException { public void testOverlayClientInteraction() throws RemoteException {
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mExecutor).execute(any());
final TestDreamOverlayService.Monitor monitor = Mockito.mock( final TestDreamOverlayService.Monitor monitor = Mockito.mock(
TestDreamOverlayService.Monitor.class); TestDreamOverlayService.Monitor.class);
final TestDreamOverlayService service = new TestDreamOverlayService(monitor); final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
final IBinder binder = service.onBind(new Intent()); final IBinder binder = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder); final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);