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.R;
|
||||||
import com.android.internal.util.DumpUtils;
|
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.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
@@ -77,8 +75,6 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,7 +230,6 @@ public class DreamService extends Service implements Window.Callback {
|
|||||||
private boolean mCanDoze;
|
private boolean mCanDoze;
|
||||||
private boolean mDozing;
|
private boolean mDozing;
|
||||||
private boolean mWindowless;
|
private boolean mWindowless;
|
||||||
private boolean mOverlayFinishing;
|
|
||||||
private int mDozeScreenState = Display.STATE_UNKNOWN;
|
private int mDozeScreenState = Display.STATE_UNKNOWN;
|
||||||
private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
|
private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
|
||||||
|
|
||||||
@@ -246,88 +241,7 @@ public class DreamService extends Service implements Window.Callback {
|
|||||||
private DreamServiceWrapper mDreamServiceWrapper;
|
private DreamServiceWrapper mDreamServiceWrapper;
|
||||||
private Runnable mDispatchAfterOnAttachedToWindow;
|
private Runnable mDispatchAfterOnAttachedToWindow;
|
||||||
|
|
||||||
private OverlayConnection mOverlayConnection;
|
private DreamOverlayConnectionHandler 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 final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
|
private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1030,18 +944,18 @@ public class DreamService extends Service implements Window.Callback {
|
|||||||
final Resources resources = getResources();
|
final Resources resources = getResources();
|
||||||
final Intent overlayIntent = new Intent().setComponent(overlayComponent);
|
final Intent overlayIntent = new Intent().setComponent(overlayComponent);
|
||||||
|
|
||||||
mOverlayConnection = new OverlayConnection(
|
mOverlayConnection = new DreamOverlayConnectionHandler(
|
||||||
/* context= */ this,
|
/* context= */ this,
|
||||||
getMainExecutor(),
|
Looper.getMainLooper(),
|
||||||
mHandler,
|
|
||||||
IDreamOverlay.Stub::asInterface,
|
|
||||||
overlayIntent,
|
overlayIntent,
|
||||||
/* flags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
|
|
||||||
resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
|
resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
|
||||||
resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
|
resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
|
||||||
resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
|
resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
|
||||||
|
|
||||||
mOverlayConnection.bind();
|
if (!mOverlayConnection.bind()) {
|
||||||
|
// Binding failed.
|
||||||
|
mOverlayConnection = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mDreamServiceWrapper;
|
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
|
// 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
|
// continuing. Note that the overlay cannot rely on the unbound state, since another dream
|
||||||
// might have bound to it in the meantime.
|
// might have bound to it in the meantime.
|
||||||
if (mOverlayConnection != null && !mOverlayFinishing) {
|
if (mOverlayConnection != null) {
|
||||||
// Set mOverlayFinish to true to only allow this consumer to be added once.
|
|
||||||
mOverlayFinishing = true;
|
|
||||||
mOverlayConnection.addConsumer(overlay -> {
|
mOverlayConnection.addConsumer(overlay -> {
|
||||||
try {
|
try {
|
||||||
overlay.endDream();
|
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);
|
Log.e(mTag, "could not inform overlay of dream end:" + e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mOverlayConnection.clearConsumers();
|
|
||||||
return;
|
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