Merge "Fix DreamService -> DreamOverlayService synchronization issues." into tm-qpr-dev
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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 android.service.dreams;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ObservableServiceConnection;
|
||||
import com.android.internal.util.PersistentServiceConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Handles the service connection to {@link IDreamOverlay}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public final class DreamOverlayConnectionHandler {
|
||||
private static final String TAG = "DreamOverlayConnection";
|
||||
|
||||
private static final int MSG_ADD_CONSUMER = 1;
|
||||
private static final int MSG_REMOVE_CONSUMER = 2;
|
||||
private static final int MSG_OVERLAY_CLIENT_READY = 3;
|
||||
|
||||
private final Handler mHandler;
|
||||
private final PersistentServiceConnection<IDreamOverlay> mConnection;
|
||||
// Retrieved Client
|
||||
private IDreamOverlayClient mClient;
|
||||
// A list of pending requests to execute on the overlay.
|
||||
private final List<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
|
||||
private final OverlayConnectionCallback mCallback;
|
||||
|
||||
DreamOverlayConnectionHandler(
|
||||
Context context,
|
||||
Looper looper,
|
||||
Intent serviceIntent,
|
||||
int minConnectionDurationMs,
|
||||
int maxReconnectAttempts,
|
||||
int baseReconnectDelayMs) {
|
||||
this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts,
|
||||
baseReconnectDelayMs, new Injector());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public DreamOverlayConnectionHandler(
|
||||
Context context,
|
||||
Looper looper,
|
||||
Intent serviceIntent,
|
||||
int minConnectionDurationMs,
|
||||
int maxReconnectAttempts,
|
||||
int baseReconnectDelayMs,
|
||||
Injector injector) {
|
||||
mCallback = new OverlayConnectionCallback();
|
||||
mHandler = new Handler(looper, new OverlayHandlerCallback());
|
||||
mConnection = injector.buildConnection(
|
||||
context,
|
||||
mHandler,
|
||||
serviceIntent,
|
||||
minConnectionDurationMs,
|
||||
maxReconnectAttempts,
|
||||
baseReconnectDelayMs
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to the overlay service. If binding fails, we automatically call unbind to clean
|
||||
* up resources.
|
||||
*
|
||||
* @return true if binding was successful, false otherwise.
|
||||
*/
|
||||
public boolean bind() {
|
||||
mConnection.addCallback(mCallback);
|
||||
final boolean success = mConnection.bind();
|
||||
if (!success) {
|
||||
unbind();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind from the overlay service, clearing any pending callbacks.
|
||||
*/
|
||||
public void unbind() {
|
||||
mConnection.removeCallback(mCallback);
|
||||
// Remove any pending messages.
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
mClient = null;
|
||||
mConsumers.clear();
|
||||
mConnection.unbind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a consumer to run once the overlay service has connected. If the overlay service
|
||||
* disconnects (eg binding dies) and then reconnects, this consumer will be re-run unless
|
||||
* removed.
|
||||
*
|
||||
* @param consumer The consumer to run. This consumer is always executed asynchronously.
|
||||
*/
|
||||
public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
|
||||
final Message msg = mHandler.obtainMessage(MSG_ADD_CONSUMER, consumer);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the consumer, preventing this consumer from being called again.
|
||||
*
|
||||
* @param consumer The consumer to remove.
|
||||
*/
|
||||
public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
|
||||
final Message msg = mHandler.obtainMessage(MSG_REMOVE_CONSUMER, consumer);
|
||||
mHandler.sendMessage(msg);
|
||||
// Clear any pending messages to add this consumer
|
||||
mHandler.removeMessages(MSG_ADD_CONSUMER, consumer);
|
||||
}
|
||||
|
||||
private final class OverlayHandlerCallback implements Handler.Callback {
|
||||
@Override
|
||||
public boolean handleMessage(@NonNull Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_OVERLAY_CLIENT_READY:
|
||||
onOverlayClientReady((IDreamOverlayClient) msg.obj);
|
||||
break;
|
||||
case MSG_ADD_CONSUMER:
|
||||
onAddConsumer((Consumer<IDreamOverlayClient>) msg.obj);
|
||||
break;
|
||||
case MSG_REMOVE_CONSUMER:
|
||||
onRemoveConsumer((Consumer<IDreamOverlayClient>) msg.obj);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void onOverlayClientReady(IDreamOverlayClient client) {
|
||||
mClient = client;
|
||||
for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
|
||||
consumer.accept(mClient);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAddConsumer(Consumer<IDreamOverlayClient> consumer) {
|
||||
if (mClient != null) {
|
||||
consumer.accept(mClient);
|
||||
}
|
||||
mConsumers.add(consumer);
|
||||
}
|
||||
|
||||
private void onRemoveConsumer(Consumer<IDreamOverlayClient> consumer) {
|
||||
mConsumers.remove(consumer);
|
||||
}
|
||||
|
||||
private final class OverlayConnectionCallback implements
|
||||
ObservableServiceConnection.Callback<IDreamOverlay> {
|
||||
|
||||
private final IDreamOverlayClientCallback mClientCallback =
|
||||
new IDreamOverlayClientCallback.Stub() {
|
||||
@Override
|
||||
public void onDreamOverlayClient(IDreamOverlayClient client) {
|
||||
final Message msg =
|
||||
mHandler.obtainMessage(MSG_OVERLAY_CLIENT_READY, client);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onConnected(
|
||||
ObservableServiceConnection<IDreamOverlay> connection,
|
||||
IDreamOverlay service) {
|
||||
try {
|
||||
service.getClient(mClientCallback);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "could not get DreamOverlayClient", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
|
||||
int reason) {
|
||||
mClient = null;
|
||||
// Cancel any pending messages about the overlay being ready, since it is no
|
||||
// longer ready.
|
||||
mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injector for testing
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class Injector {
|
||||
/**
|
||||
* Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden
|
||||
* in tests with a fake clock.
|
||||
*/
|
||||
public PersistentServiceConnection<IDreamOverlay> buildConnection(
|
||||
Context context,
|
||||
Handler handler,
|
||||
Intent serviceIntent,
|
||||
int minConnectionDurationMs,
|
||||
int maxReconnectAttempts,
|
||||
int baseReconnectDelayMs) {
|
||||
final Executor executor = handler::post;
|
||||
final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
|
||||
return new PersistentServiceConnection<>(
|
||||
context,
|
||||
executor,
|
||||
handler,
|
||||
IDreamOverlay.Stub::asInterface,
|
||||
serviceIntent,
|
||||
flags,
|
||||
minConnectionDurationMs,
|
||||
maxReconnectAttempts,
|
||||
baseReconnectDelayMs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,8 +68,6 @@ import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.ObservableServiceConnection;
|
||||
import com.android.internal.util.PersistentServiceConnection;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
@@ -77,8 +75,6 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -234,7 +230,6 @@ public class DreamService extends Service implements Window.Callback {
|
||||
private boolean mCanDoze;
|
||||
private boolean mDozing;
|
||||
private boolean mWindowless;
|
||||
private boolean mOverlayFinishing;
|
||||
private int mDozeScreenState = Display.STATE_UNKNOWN;
|
||||
private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
|
||||
|
||||
@@ -246,88 +241,7 @@ public class DreamService extends Service implements Window.Callback {
|
||||
private DreamServiceWrapper mDreamServiceWrapper;
|
||||
private Runnable mDispatchAfterOnAttachedToWindow;
|
||||
|
||||
private OverlayConnection mOverlayConnection;
|
||||
|
||||
private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
|
||||
// Retrieved Client
|
||||
private IDreamOverlayClient mClient;
|
||||
|
||||
// A list of pending requests to execute on the overlay.
|
||||
private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
|
||||
|
||||
private final IDreamOverlayClientCallback mClientCallback =
|
||||
new IDreamOverlayClientCallback.Stub() {
|
||||
@Override
|
||||
public void onDreamOverlayClient(IDreamOverlayClient client) {
|
||||
mClient = client;
|
||||
|
||||
for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
|
||||
consumer.accept(mClient);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
|
||||
@Override
|
||||
public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
|
||||
IDreamOverlay service) {
|
||||
try {
|
||||
service.getClient(mClientCallback);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "could not get DreamOverlayClient", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
|
||||
int reason) {
|
||||
mClient = null;
|
||||
}
|
||||
};
|
||||
|
||||
OverlayConnection(Context context,
|
||||
Executor executor,
|
||||
Handler handler,
|
||||
ServiceTransformer<IDreamOverlay> transformer,
|
||||
Intent serviceIntent,
|
||||
int flags,
|
||||
int minConnectionDurationMs,
|
||||
int maxReconnectAttempts,
|
||||
int baseReconnectDelayMs) {
|
||||
super(context, executor, handler, transformer, serviceIntent, flags,
|
||||
minConnectionDurationMs,
|
||||
maxReconnectAttempts, baseReconnectDelayMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bind() {
|
||||
addCallback(mCallback);
|
||||
return super.bind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
removeCallback(mCallback);
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
|
||||
execute(() -> {
|
||||
mConsumers.add(consumer);
|
||||
if (mClient != null) {
|
||||
consumer.accept(mClient);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
|
||||
execute(() -> mConsumers.remove(consumer));
|
||||
}
|
||||
|
||||
public void clearConsumers() {
|
||||
execute(() -> mConsumers.clear());
|
||||
}
|
||||
}
|
||||
private DreamOverlayConnectionHandler mOverlayConnection;
|
||||
|
||||
private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
|
||||
@Override
|
||||
@@ -1030,18 +944,18 @@ public class DreamService extends Service implements Window.Callback {
|
||||
final Resources resources = getResources();
|
||||
final Intent overlayIntent = new Intent().setComponent(overlayComponent);
|
||||
|
||||
mOverlayConnection = new OverlayConnection(
|
||||
mOverlayConnection = new DreamOverlayConnectionHandler(
|
||||
/* context= */ this,
|
||||
getMainExecutor(),
|
||||
mHandler,
|
||||
IDreamOverlay.Stub::asInterface,
|
||||
Looper.getMainLooper(),
|
||||
overlayIntent,
|
||||
/* flags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
|
||||
resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
|
||||
resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
|
||||
resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
|
||||
|
||||
mOverlayConnection.bind();
|
||||
if (!mOverlayConnection.bind()) {
|
||||
// Binding failed.
|
||||
mOverlayConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
return mDreamServiceWrapper;
|
||||
@@ -1069,9 +983,7 @@ public class DreamService extends Service implements Window.Callback {
|
||||
// If there is an active overlay connection, signal that the dream is ending before
|
||||
// continuing. Note that the overlay cannot rely on the unbound state, since another dream
|
||||
// might have bound to it in the meantime.
|
||||
if (mOverlayConnection != null && !mOverlayFinishing) {
|
||||
// Set mOverlayFinish to true to only allow this consumer to be added once.
|
||||
mOverlayFinishing = true;
|
||||
if (mOverlayConnection != null) {
|
||||
mOverlayConnection.addConsumer(overlay -> {
|
||||
try {
|
||||
overlay.endDream();
|
||||
@@ -1082,7 +994,6 @@ public class DreamService extends Service implements Window.Callback {
|
||||
Log.e(mTag, "could not inform overlay of dream end:" + e);
|
||||
}
|
||||
});
|
||||
mOverlayConnection.clearConsumers();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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 android.service.dreams;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.util.ObservableServiceConnection;
|
||||
import com.android.internal.util.PersistentServiceConnection;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DreamOverlayConnectionHandlerTest {
|
||||
private static final int MIN_CONNECTION_DURATION_MS = 100;
|
||||
private static final int MAX_RECONNECT_ATTEMPTS = 3;
|
||||
private static final int BASE_RECONNECT_DELAY_MS = 50;
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private PersistentServiceConnection<IDreamOverlay> mConnection;
|
||||
@Mock
|
||||
private Intent mServiceIntent;
|
||||
@Mock
|
||||
private IDreamOverlay mOverlayService;
|
||||
@Mock
|
||||
private IDreamOverlayClient mOverlayClient;
|
||||
|
||||
private TestLooper mTestLooper;
|
||||
private DreamOverlayConnectionHandler mDreamOverlayConnectionHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mTestLooper = new TestLooper();
|
||||
mDreamOverlayConnectionHandler = new DreamOverlayConnectionHandler(
|
||||
mContext,
|
||||
mTestLooper.getLooper(),
|
||||
mServiceIntent,
|
||||
MIN_CONNECTION_DURATION_MS,
|
||||
MAX_RECONNECT_ATTEMPTS,
|
||||
BASE_RECONNECT_DELAY_MS,
|
||||
new TestInjector(mConnection));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumerShouldRunImmediatelyWhenClientAvailable() throws RemoteException {
|
||||
mDreamOverlayConnectionHandler.bind();
|
||||
connectService();
|
||||
provideClient();
|
||||
|
||||
final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
|
||||
mDreamOverlayConnectionHandler.addConsumer(consumer);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(consumer).accept(mOverlayClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumerShouldRunAfterClientAvailable() throws RemoteException {
|
||||
mDreamOverlayConnectionHandler.bind();
|
||||
connectService();
|
||||
|
||||
final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
|
||||
mDreamOverlayConnectionHandler.addConsumer(consumer);
|
||||
mTestLooper.dispatchAll();
|
||||
// No client yet, so we shouldn't have executed
|
||||
verify(consumer, never()).accept(mOverlayClient);
|
||||
|
||||
provideClient();
|
||||
mTestLooper.dispatchAll();
|
||||
verify(consumer).accept(mOverlayClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumerShouldNeverRunIfClientConnectsAndDisconnects() throws RemoteException {
|
||||
mDreamOverlayConnectionHandler.bind();
|
||||
connectService();
|
||||
|
||||
final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
|
||||
mDreamOverlayConnectionHandler.addConsumer(consumer);
|
||||
mTestLooper.dispatchAll();
|
||||
// No client yet, so we shouldn't have executed
|
||||
verify(consumer, never()).accept(mOverlayClient);
|
||||
|
||||
provideClient();
|
||||
// Service disconnected before looper could handle the message.
|
||||
disconnectService();
|
||||
mTestLooper.dispatchAll();
|
||||
verify(consumer, never()).accept(mOverlayClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumerShouldNeverRunIfUnbindCalled() throws RemoteException {
|
||||
mDreamOverlayConnectionHandler.bind();
|
||||
connectService();
|
||||
provideClient();
|
||||
|
||||
final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
|
||||
mDreamOverlayConnectionHandler.addConsumer(consumer);
|
||||
mDreamOverlayConnectionHandler.unbind();
|
||||
mTestLooper.dispatchAll();
|
||||
// We unbinded immediately after adding consumer, so should never have run.
|
||||
verify(consumer, never()).accept(mOverlayClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumersOnlyRunOnceIfUnbound() throws RemoteException {
|
||||
mDreamOverlayConnectionHandler.bind();
|
||||
connectService();
|
||||
provideClient();
|
||||
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
// Add 10 consumers in a row which call unbind within the consumer.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
mDreamOverlayConnectionHandler.addConsumer(client -> {
|
||||
counter.getAndIncrement();
|
||||
mDreamOverlayConnectionHandler.unbind();
|
||||
});
|
||||
}
|
||||
mTestLooper.dispatchAll();
|
||||
// Only the first consumer should have run, since we unbinded.
|
||||
assertThat(counter.get()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumerShouldRunAgainAfterReconnect() throws RemoteException {
|
||||
mDreamOverlayConnectionHandler.bind();
|
||||
connectService();
|
||||
provideClient();
|
||||
|
||||
final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
|
||||
mDreamOverlayConnectionHandler.addConsumer(consumer);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(consumer, times(1)).accept(mOverlayClient);
|
||||
|
||||
disconnectService();
|
||||
mTestLooper.dispatchAll();
|
||||
// No new calls should happen when service disconnected.
|
||||
verify(consumer, times(1)).accept(mOverlayClient);
|
||||
|
||||
connectService();
|
||||
provideClient();
|
||||
mTestLooper.dispatchAll();
|
||||
// We should trigger the consumer again once the server reconnects.
|
||||
verify(consumer, times(2)).accept(mOverlayClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumerShouldNeverRunIfRemovedImmediately() throws RemoteException {
|
||||
mDreamOverlayConnectionHandler.bind();
|
||||
connectService();
|
||||
provideClient();
|
||||
|
||||
final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
|
||||
mDreamOverlayConnectionHandler.addConsumer(consumer);
|
||||
mDreamOverlayConnectionHandler.removeConsumer(consumer);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(consumer, never()).accept(mOverlayClient);
|
||||
}
|
||||
|
||||
private void connectService() {
|
||||
final ObservableServiceConnection.Callback<IDreamOverlay> callback =
|
||||
captureConnectionCallback();
|
||||
callback.onConnected(mConnection, mOverlayService);
|
||||
}
|
||||
|
||||
private void disconnectService() {
|
||||
final ObservableServiceConnection.Callback<IDreamOverlay> callback =
|
||||
captureConnectionCallback();
|
||||
callback.onDisconnected(mConnection, /* reason= */ 0);
|
||||
}
|
||||
|
||||
private void provideClient() throws RemoteException {
|
||||
final IDreamOverlayClientCallback callback = captureClientCallback();
|
||||
callback.onDreamOverlayClient(mOverlayClient);
|
||||
}
|
||||
|
||||
private ObservableServiceConnection.Callback<IDreamOverlay> captureConnectionCallback() {
|
||||
ArgumentCaptor<ObservableServiceConnection.Callback<IDreamOverlay>>
|
||||
callbackCaptor =
|
||||
ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
|
||||
verify(mConnection).addCallback(callbackCaptor.capture());
|
||||
return callbackCaptor.getValue();
|
||||
}
|
||||
|
||||
private IDreamOverlayClientCallback captureClientCallback() throws RemoteException {
|
||||
ArgumentCaptor<IDreamOverlayClientCallback> callbackCaptor =
|
||||
ArgumentCaptor.forClass(IDreamOverlayClientCallback.class);
|
||||
verify(mOverlayService, atLeastOnce()).getClient(callbackCaptor.capture());
|
||||
return callbackCaptor.getValue();
|
||||
}
|
||||
|
||||
static class TestInjector extends DreamOverlayConnectionHandler.Injector {
|
||||
private final PersistentServiceConnection<IDreamOverlay> mConnection;
|
||||
|
||||
TestInjector(PersistentServiceConnection<IDreamOverlay> connection) {
|
||||
mConnection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentServiceConnection<IDreamOverlay> buildConnection(Context context,
|
||||
Handler handler, Intent serviceIntent, int minConnectionDurationMs,
|
||||
int maxReconnectAttempts, int baseReconnectDelayMs) {
|
||||
return mConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user