Marshal access to DreamOverlayStateController.

This changelist marshals calls on DreamoverlayStateController
on a single executor to ensure consistency and ordered
execution.

Bug: 211497162
Test: atest DreamOverlayStateController
Change-Id: If6870b9f1212bae845f39500b4d08925531f8de2
This commit is contained in:
Bryce Lee
2021-12-20 13:46:07 -08:00
parent d92bda8544
commit 5a0f8772b6
2 changed files with 64 additions and 30 deletions

View File

@@ -17,15 +17,20 @@
package com.android.systemui.dreams;
import androidx.annotation.NonNull;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.policy.CallbackController;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -77,12 +82,14 @@ public class DreamOverlayStateController implements
}
}
private final Executor mExecutor;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final HashMap<ComplicationToken, ComplicationProvider> mComplications = new HashMap<>();
@VisibleForTesting
@Inject
public DreamOverlayStateController() {
public DreamOverlayStateController(@Main Executor executor) {
mExecutor = executor;
}
/**
@@ -90,11 +97,16 @@ public class DreamOverlayStateController implements
* @param provider The {@link ComplicationProvider} providing the dream.
* @return The {@link ComplicationToken} tied to the supplied {@link ComplicationProvider}.
*/
public ComplicationToken addComplication(ComplicationProvider provider) {
final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
mComplications.put(token, provider);
notifyCallbacks();
return token;
public ListenableFuture<ComplicationToken> addComplication(ComplicationProvider provider) {
return CallbackToFutureAdapter.getFuture(completer -> {
mExecutor.execute(() -> {
final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
mComplications.put(token, provider);
notifyCallbacks();
completer.set(token);
});
return "DreamOverlayStateController::addComplication";
});
}
/**
@@ -103,14 +115,19 @@ public class DreamOverlayStateController implements
* to be removed.
* @return The removed {@link ComplicationProvider}, {@code null} if not found.
*/
public ComplicationProvider removeComplication(ComplicationToken token) {
final ComplicationProvider removedComplication = mComplications.remove(token);
public ListenableFuture<ComplicationProvider> removeComplication(ComplicationToken token) {
return CallbackToFutureAdapter.getFuture(completer -> {
mExecutor.execute(() -> {
final ComplicationProvider removedComplication = mComplications.remove(token);
if (removedComplication != null) {
notifyCallbacks();
}
if (removedComplication != null) {
notifyCallbacks();
}
completer.set(removedComplication);
});
return removedComplication;
return "DreamOverlayStateController::removeComplication";
});
}
private void notifyCallbacks() {
@@ -121,24 +138,28 @@ public class DreamOverlayStateController implements
@Override
public void addCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
if (mCallbacks.contains(callback)) {
return;
}
mExecutor.execute(() -> {
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
if (mCallbacks.contains(callback)) {
return;
}
mCallbacks.add(callback);
mCallbacks.add(callback);
if (mComplications.isEmpty()) {
return;
}
if (mComplications.isEmpty()) {
return;
}
callback.onComplicationsChanged();
callback.onComplicationsChanged();
});
}
@Override
public void removeCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
mCallbacks.remove(callback);
mExecutor.execute(() -> {
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
mCallbacks.remove(callback);
});
}
/**

View File

@@ -27,6 +27,10 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Before;
import org.junit.Test;
@@ -45,20 +49,25 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
@Mock
ComplicationProvider mProvider;
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testCallback() {
final DreamOverlayStateController stateController = new DreamOverlayStateController();
public void testCallback() throws Exception {
final DreamOverlayStateController stateController = new DreamOverlayStateController(
mExecutor);
stateController.addCallback(mCallback);
// Add complication and verify callback is notified.
final DreamOverlayStateController.ComplicationToken token =
final ListenableFuture<DreamOverlayStateController.ComplicationToken> tokenFuture =
stateController.addComplication(mProvider);
mExecutor.runAllReady();
verify(mCallback, times(1)).onComplicationsChanged();
final Collection<ComplicationProvider> providers = stateController.getComplications();
@@ -68,19 +77,23 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
clearInvocations(mCallback);
// Remove complication and verify callback is notified.
stateController.removeComplication(token);
stateController.removeComplication(tokenFuture.get());
mExecutor.runAllReady();
verify(mCallback, times(1)).onComplicationsChanged();
assertTrue(providers.isEmpty());
}
@Test
public void testNotifyOnCallbackAdd() {
final DreamOverlayStateController stateController = new DreamOverlayStateController();
final DreamOverlayStateController.ComplicationToken token =
stateController.addComplication(mProvider);
final DreamOverlayStateController stateController =
new DreamOverlayStateController(mExecutor);
stateController.addComplication(mProvider);
mExecutor.runAllReady();
// Verify callback occurs on add when an overlay is already present.
stateController.addCallback(mCallback);
mExecutor.runAllReady();
verify(mCallback, times(1)).onComplicationsChanged();
}
}