Merge "Introduce PersistentConnectionManager."
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.util.service;
|
||||
|
||||
/**
|
||||
* The {@link Observer} interface specifies an entity which listeners
|
||||
* can be informed of changes to the source, which will require updating. Note that this deals
|
||||
* with changes to the source itself, not content which will be updated through the interface.
|
||||
*/
|
||||
public interface Observer {
|
||||
/**
|
||||
* Callback for receiving updates from the {@link Observer}.
|
||||
*/
|
||||
interface Callback {
|
||||
/**
|
||||
* Invoked when the source has changed.
|
||||
*/
|
||||
void onSourceChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to receive future updates from the {@link Observer}.
|
||||
*/
|
||||
void addCallback(Callback callback);
|
||||
|
||||
/**
|
||||
* Removes a callback from receiving further updates.
|
||||
* @param callback
|
||||
*/
|
||||
void removeCallback(Callback callback);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.util.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.PatternMatcher;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.communal.CommunalSource;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link PackageObserver} allows for monitoring the system for changes relating to a particular
|
||||
* package. This can be used by {@link CommunalSource} clients to detect when a related package
|
||||
* has changed and reloading is necessary.
|
||||
*/
|
||||
public class PackageObserver implements Observer {
|
||||
private static final String TAG = "PackageObserver";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "package added receiver - onReceive");
|
||||
}
|
||||
|
||||
final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator();
|
||||
while (iter.hasNext()) {
|
||||
final Callback callback = iter.next().get();
|
||||
if (callback != null) {
|
||||
callback.onSourceChanged();
|
||||
} else {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final String mPackageName;
|
||||
private final Context mContext;
|
||||
|
||||
@Inject
|
||||
public PackageObserver(Context context, ComponentName component) {
|
||||
mContext = context;
|
||||
mPackageName = component.getPackageName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallback(Callback callback) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addCallback:" + callback);
|
||||
}
|
||||
mCallbacks.add(new WeakReference<>(callback));
|
||||
|
||||
// Only register for listening to package additions on first callback.
|
||||
if (mCallbacks.size() > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addDataScheme("package");
|
||||
filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL);
|
||||
// Note that we directly register the receiver here as data schemes are not supported by
|
||||
// BroadcastDispatcher.
|
||||
mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallback(Callback callback) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "removeCallback:" + callback);
|
||||
}
|
||||
final boolean removed = mCallbacks.removeIf(el -> el.get() == callback);
|
||||
|
||||
if (removed && mCallbacks.isEmpty()) {
|
||||
mContext.unregisterReceiver(mReceiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.util.service;
|
||||
|
||||
import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
|
||||
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
|
||||
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
|
||||
import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
|
||||
import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
import com.android.systemui.util.time.SystemClock;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
|
||||
* {@link ObservableServiceConnection}.
|
||||
* @param <T> The transformed connection type handled by the service.
|
||||
*/
|
||||
public class PersistentConnectionManager<T> {
|
||||
private static final String TAG = "PersistentConnManager";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final SystemClock mSystemClock;
|
||||
private final DelayableExecutor mMainExecutor;
|
||||
private final int mBaseReconnectDelayMs;
|
||||
private final int mMaxReconnectAttempts;
|
||||
private final int mMinConnectionDuration;
|
||||
private final Observer mObserver;
|
||||
|
||||
private int mReconnectAttempts = 0;
|
||||
private Runnable mCurrentReconnectCancelable;
|
||||
|
||||
private final ObservableServiceConnection<T> mConnection;
|
||||
|
||||
private final Runnable mConnectRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCurrentReconnectCancelable = null;
|
||||
mConnection.bind();
|
||||
}
|
||||
};
|
||||
|
||||
private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
|
||||
|
||||
private final ObservableServiceConnection.Callback mConnectionCallback =
|
||||
new ObservableServiceConnection.Callback() {
|
||||
private long mStartTime;
|
||||
|
||||
@Override
|
||||
public void onConnected(ObservableServiceConnection connection, Object proxy) {
|
||||
mStartTime = mSystemClock.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(ObservableServiceConnection connection, int reason) {
|
||||
if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
|
||||
initiateConnectionAttempt();
|
||||
} else {
|
||||
scheduleConnectionAttempt();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Inject
|
||||
public PersistentConnectionManager(
|
||||
SystemClock clock,
|
||||
DelayableExecutor mainExecutor,
|
||||
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
|
||||
@Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
|
||||
@Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
|
||||
@Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
|
||||
@Named(OBSERVER) Observer observer) {
|
||||
mSystemClock = clock;
|
||||
mMainExecutor = mainExecutor;
|
||||
mConnection = serviceConnection;
|
||||
mObserver = observer;
|
||||
|
||||
mMaxReconnectAttempts = maxReconnectAttempts;
|
||||
mBaseReconnectDelayMs = baseReconnectDelayMs;
|
||||
mMinConnectionDuration = minConnectionDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins the {@link PersistentConnectionManager} by connecting to the associated service.
|
||||
*/
|
||||
public void start() {
|
||||
mConnection.addCallback(mConnectionCallback);
|
||||
mObserver.addCallback(mObserverCallback);
|
||||
initiateConnectionAttempt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
|
||||
*/
|
||||
public void stop() {
|
||||
mConnection.removeCallback(mConnectionCallback);
|
||||
mObserver.removeCallback(mObserverCallback);
|
||||
mConnection.unbind();
|
||||
}
|
||||
|
||||
private void initiateConnectionAttempt() {
|
||||
// Reset attempts
|
||||
mReconnectAttempts = 0;
|
||||
|
||||
// The first attempt is always a direct invocation rather than delayed.
|
||||
mConnection.bind();
|
||||
}
|
||||
|
||||
private void scheduleConnectionAttempt() {
|
||||
// always clear cancelable if present.
|
||||
if (mCurrentReconnectCancelable != null) {
|
||||
mCurrentReconnectCancelable.run();
|
||||
mCurrentReconnectCancelable = null;
|
||||
}
|
||||
|
||||
if (mReconnectAttempts >= mMaxReconnectAttempts) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "exceeded max connection attempts.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final long reconnectDelayMs =
|
||||
(long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG,
|
||||
"scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
|
||||
}
|
||||
|
||||
mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
|
||||
reconnectDelayMs);
|
||||
|
||||
mReconnectAttempts++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.util.service.dagger;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/**
|
||||
* Module containing components and parameters for
|
||||
* {@link com.android.systemui.util.service.ObservableServiceConnection}
|
||||
* and {@link com.android.systemui.util.service.PersistentConnectionManager}.
|
||||
*/
|
||||
@Module(subcomponents = {
|
||||
PackageObserverComponent.class,
|
||||
})
|
||||
public class ObservableServiceModule {
|
||||
public static final String MAX_RECONNECT_ATTEMPTS = "max_reconnect_attempts";
|
||||
public static final String BASE_RECONNECT_DELAY_MS = "base_reconnect_attempts";
|
||||
public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
|
||||
public static final String SERVICE_CONNECTION = "service_connection";
|
||||
public static final String OBSERVER = "observer";
|
||||
|
||||
@Provides
|
||||
@Named(MAX_RECONNECT_ATTEMPTS)
|
||||
static int providesMaxReconnectAttempts(@Main Resources resources) {
|
||||
return resources.getInteger(
|
||||
R.integer.config_communalSourceMaxReconnectAttempts);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named(BASE_RECONNECT_DELAY_MS)
|
||||
static int provideBaseReconnectDelayMs(@Main Resources resources) {
|
||||
return resources.getInteger(
|
||||
R.integer.config_communalSourceReconnectBaseDelay);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named(MIN_CONNECTION_DURATION_MS)
|
||||
static int providesMinConnectionDuration(@Main Resources resources) {
|
||||
return resources.getInteger(
|
||||
R.integer.config_connectionMinDuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.util.service.dagger;
|
||||
|
||||
import android.content.ComponentName;
|
||||
|
||||
import com.android.systemui.util.service.PackageObserver;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
|
||||
/**
|
||||
* Generates a scoped {@link PackageObserver}.
|
||||
*/
|
||||
@Subcomponent
|
||||
public interface PackageObserverComponent {
|
||||
/**
|
||||
* Generates a {@link PackageObserverComponent} instance.
|
||||
*/
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
PackageObserverComponent create(@BindsInstance ComponentName component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PackageObserver}.
|
||||
*/
|
||||
PackageObserver getPackageObserver();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.util.service;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
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;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class PackageObserverTest extends SysuiTestCase {
|
||||
@Mock
|
||||
Context mContext;
|
||||
|
||||
@Mock
|
||||
Observer.Callback mCallback;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChange() {
|
||||
final PackageObserver observer = new PackageObserver(mContext,
|
||||
ComponentName.unflattenFromString("com.foo.bar/baz"));
|
||||
final ArgumentCaptor<BroadcastReceiver> receiverCapture =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
|
||||
observer.addCallback(mCallback);
|
||||
|
||||
// Verify broadcast receiver registered.
|
||||
verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt());
|
||||
|
||||
// Simulate package change.
|
||||
receiverCapture.getValue().onReceive(mContext, new Intent());
|
||||
|
||||
// Check that callback was informed.
|
||||
verify(mCallback).onSourceChanged();
|
||||
|
||||
observer.removeCallback(mCallback);
|
||||
|
||||
// Make sure receiver is unregistered on last callback removal
|
||||
verify(mContext).unregisterReceiver(receiverCapture.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.util.service;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
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 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;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class PersistentConnectionManagerTest extends SysuiTestCase {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private static final int RETRY_DELAY_MS = 1000;
|
||||
private static final int CONNECTION_MIN_DURATION_MS = 5000;
|
||||
|
||||
private FakeSystemClock mFakeClock = new FakeSystemClock();
|
||||
private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
|
||||
|
||||
@Mock
|
||||
private ObservableServiceConnection<Proxy> mConnection;
|
||||
|
||||
@Mock
|
||||
private Observer mObserver;
|
||||
|
||||
private static class Proxy {
|
||||
}
|
||||
|
||||
private PersistentConnectionManager<Proxy> mConnectionManager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mConnectionManager = new PersistentConnectionManager<>(
|
||||
mFakeClock,
|
||||
mFakeExecutor,
|
||||
mConnection,
|
||||
MAX_RETRIES,
|
||||
RETRY_DELAY_MS,
|
||||
CONNECTION_MIN_DURATION_MS,
|
||||
mObserver);
|
||||
}
|
||||
|
||||
private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend(
|
||||
ObservableServiceConnection<Proxy> mConnection, Proxy proxy) {
|
||||
ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
|
||||
ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
|
||||
|
||||
verify(mConnection).addCallback(connectionCallbackCaptor.capture());
|
||||
verify(mConnection).bind();
|
||||
Mockito.clearInvocations(mConnection);
|
||||
|
||||
final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue();
|
||||
if (proxy != null) {
|
||||
callback.onConnected(mConnection, proxy);
|
||||
} else {
|
||||
callback.onDisconnected(mConnection, 0);
|
||||
}
|
||||
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates initial connection.
|
||||
*/
|
||||
@Test
|
||||
public void testConnect() {
|
||||
mConnectionManager.start();
|
||||
captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures reconnection on disconnect.
|
||||
*/
|
||||
@Test
|
||||
public void testRetryOnBindFailure() {
|
||||
mConnectionManager.start();
|
||||
ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
|
||||
ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
|
||||
|
||||
verify(mConnection).addCallback(connectionCallbackCaptor.capture());
|
||||
|
||||
// Verify attempts happen. Note that we account for the retries plus initial attempt, which
|
||||
// is not scheduled.
|
||||
for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
|
||||
verify(mConnection).bind();
|
||||
Mockito.clearInvocations(mConnection);
|
||||
connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0);
|
||||
mFakeExecutor.advanceClockToNext();
|
||||
mFakeExecutor.runAllReady();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures rebind on package change.
|
||||
*/
|
||||
@Test
|
||||
public void testAttemptOnPackageChange() {
|
||||
mConnectionManager.start();
|
||||
verify(mConnection).bind();
|
||||
ArgumentCaptor<Observer.Callback> callbackCaptor =
|
||||
ArgumentCaptor.forClass(Observer.Callback.class);
|
||||
captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
|
||||
|
||||
verify(mObserver).addCallback(callbackCaptor.capture());
|
||||
|
||||
callbackCaptor.getValue().onSourceChanged();
|
||||
verify(mConnection).bind();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user