Merge "CommunalSource.Connection Introduction"

This commit is contained in:
Bryce Lee
2021-12-09 23:28:53 +00:00
committed by Android (Google) Code Review
10 changed files with 217 additions and 181 deletions

View File

@@ -35,7 +35,23 @@ public interface CommunalSource {
* {@link Connector} defines an interface for {@link CommunalSource} instances to be generated.
*/
interface Connector {
ListenableFuture<Optional<CommunalSource>> connect();
Connection connect(Connection.Callback callback);
}
/**
* {@link Connection} defines an interface for an entity which holds the necessary components
* for establishing and maintaining a connection to the communal source.
*/
interface Connection {
/**
* {@link Callback} defines an interface for clients to be notified when a source is ready
*/
interface Callback {
void onSourceEstablished(Optional<CommunalSource> source);
void onDisconnected();
}
void disconnect();
}
/**
@@ -86,29 +102,4 @@ public interface CommunalSource {
* value will be {@code null} in case of a failure.
*/
ListenableFuture<CommunalViewResult> requestCommunalView(Context context);
/**
* Adds a {@link Callback} to receive future status updates regarding this
* {@link CommunalSource}.
*
* @param callback The {@link Callback} to be added.
*/
void addCallback(Callback callback);
/**
* Removes a {@link Callback} from receiving future updates.
*
* @param callback The {@link Callback} to be removed.
*/
void removeCallback(Callback callback);
/**
* An interface for receiving updates on the state of the {@link CommunalSource}.
*/
interface Callback {
/**
* Invoked when the {@link CommunalSource} is no longer available for use.
*/
void onDisconnected();
}
}

View File

@@ -21,12 +21,14 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.google.android.collect.Lists;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -41,6 +43,7 @@ public class CommunalSourceMonitor {
// A list of {@link Callback} that have registered to receive updates.
private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
private final CommunalConditionsMonitor mConditionsMonitor;
private final Executor mExecutor;
private CommunalSource mCurrentSource;
@@ -50,14 +53,6 @@ public class CommunalSourceMonitor {
// Whether the class is currently listening for condition changes.
private boolean mListeningForConditions = false;
private CommunalSource.Callback mSourceCallback = new CommunalSource.Callback() {
@Override
public void onDisconnected() {
// Clear source reference.
setSource(null /* source */);
}
};
private final CommunalConditionsMonitor.Callback mConditionsCallback =
allConditionsMet -> {
if (mAllCommunalConditionsMet != allConditionsMet) {
@@ -70,7 +65,9 @@ public class CommunalSourceMonitor {
@VisibleForTesting
@Inject
public CommunalSourceMonitor(CommunalConditionsMonitor communalConditionsMonitor) {
public CommunalSourceMonitor(@Main Executor executor,
CommunalConditionsMonitor communalConditionsMonitor) {
mExecutor = executor;
mConditionsMonitor = communalConditionsMonitor;
}
@@ -81,35 +78,28 @@ public class CommunalSourceMonitor {
* @param source The new {@link CommunalSource}.
*/
public void setSource(CommunalSource source) {
if (mCurrentSource != null) {
mCurrentSource.removeCallback(mSourceCallback);
}
mCurrentSource = source;
if (mAllCommunalConditionsMet) {
executeOnSourceAvailableCallbacks();
}
// Add callback to be informed when the source disconnects.
if (mCurrentSource != null) {
mCurrentSource.addCallback(mSourceCallback);
}
}
private void executeOnSourceAvailableCallbacks() {
// If the new source is valid, inform registered Callbacks of its presence.
Iterator<WeakReference<Callback>> itr = mCallbacks.iterator();
while (itr.hasNext()) {
Callback cb = itr.next().get();
if (cb == null) {
itr.remove();
} else {
cb.onSourceAvailable(
(mAllCommunalConditionsMet && mCurrentSource != null) ? new WeakReference<>(
mCurrentSource) : null);
mExecutor.execute(() -> {
// If the new source is valid, inform registered Callbacks of its presence.
Iterator<WeakReference<Callback>> itr = mCallbacks.iterator();
while (itr.hasNext()) {
Callback cb = itr.next().get();
if (cb == null) {
itr.remove();
} else {
cb.onSourceAvailable(
(mAllCommunalConditionsMet && mCurrentSource != null)
? new WeakReference<>(mCurrentSource) : null);
}
}
}
});
}
/**
@@ -118,17 +108,19 @@ public class CommunalSourceMonitor {
* @param callback The {@link Callback} to add.
*/
public void addCallback(Callback callback) {
mCallbacks.add(new WeakReference<>(callback));
mExecutor.execute(() -> {
mCallbacks.add(new WeakReference<>(callback));
// Inform the callback of any already present CommunalSource.
if (mAllCommunalConditionsMet && mCurrentSource != null) {
callback.onSourceAvailable(new WeakReference<>(mCurrentSource));
}
// Inform the callback of any already present CommunalSource.
if (mAllCommunalConditionsMet && mCurrentSource != null) {
callback.onSourceAvailable(new WeakReference<>(mCurrentSource));
}
if (!mListeningForConditions) {
mConditionsMonitor.addCallback(mConditionsCallback);
mListeningForConditions = true;
}
if (!mListeningForConditions) {
mConditionsMonitor.addCallback(mConditionsCallback);
mListeningForConditions = true;
}
});
}
/**
@@ -137,12 +129,14 @@ public class CommunalSourceMonitor {
* @param callback The {@link Callback} to add.
*/
public void removeCallback(Callback callback) {
mCallbacks.removeIf(el -> el.get() == callback);
mExecutor.execute(() -> {
mCallbacks.removeIf(el -> el.get() == callback);
if (mCallbacks.isEmpty() && mListeningForConditions) {
mConditionsMonitor.removeCallback(mConditionsCallback);
mListeningForConditions = false;
}
if (mCallbacks.isEmpty() && mListeningForConditions) {
mConditionsMonitor.removeCallback(mConditionsCallback);
mListeningForConditions = false;
}
});
}
/**

View File

@@ -27,8 +27,6 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Optional;
import javax.inject.Inject;
@@ -53,10 +51,11 @@ public class CommunalSourcePrimer extends CoreStartable {
private int mReconnectAttempts = 0;
private Runnable mCurrentReconnectCancelable;
private ListenableFuture<Optional<CommunalSource>> mGetSourceFuture;
private final Optional<CommunalSource.Connector> mConnector;
private final Optional<CommunalSource.Observer> mObserver;
private final Optional<CommunalSource.Connector> mConnector;
private CommunalSource.Connection mCurrentConnection;
private final Runnable mConnectRunnable = new Runnable() {
@Override
@@ -66,6 +65,10 @@ public class CommunalSourcePrimer extends CoreStartable {
}
};
private final CommunalSource.Observer.Callback mObserverCallback = () -> {
initiateConnectionAttempt();
};
@Inject
public CommunalSourcePrimer(Context context, @Main Resources resources,
SystemClock clock,
@@ -132,7 +135,7 @@ public class CommunalSourcePrimer extends CoreStartable {
@Override
protected void onBootCompleted() {
if (mObserver.isPresent()) {
mObserver.get().addCallback(() -> initiateConnectionAttempt());
mObserver.get().addCallback(mObserverCallback);
}
initiateConnectionAttempt();
}
@@ -142,34 +145,36 @@ public class CommunalSourcePrimer extends CoreStartable {
Log.d(TAG, "attempting to communal to communal source");
}
if (mGetSourceFuture != null) {
if (mCurrentConnection != null) {
if (DEBUG) {
Log.d(TAG, "canceling in-flight connection");
}
mGetSourceFuture.cancel(true);
mCurrentConnection.disconnect();
}
mGetSourceFuture = mConnector.get().connect();
mGetSourceFuture.addListener(() -> {
try {
final long startTime = mSystemClock.currentTimeMillis();
Optional<CommunalSource> result = mGetSourceFuture.get();
if (result.isPresent()) {
final CommunalSource source = result.get();
source.addCallback(() -> {
if (mSystemClock.currentTimeMillis() - startTime > mMinConnectionDuration) {
initiateConnectionAttempt();
} else {
scheduleConnectionAttempt();
}
});
mCurrentConnection = mConnector.get().connect(new CommunalSource.Connection.Callback() {
private long mStartTime;
@Override
public void onSourceEstablished(Optional<CommunalSource> optionalSource) {
mStartTime = mSystemClock.currentTimeMillis();
if (optionalSource.isPresent()) {
final CommunalSource source = optionalSource.get();
mMonitor.setSource(source);
} else {
scheduleConnectionAttempt();
}
} catch (Exception e) {
e.printStackTrace();
}
}, mMainExecutor);
@Override
public void onDisconnected() {
if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
initiateConnectionAttempt();
} else {
scheduleConnectionAttempt();
}
}
});
}
}

View File

@@ -519,8 +519,6 @@ public class NotificationPanelViewController extends PanelViewController {
private WeakReference<CommunalSource> mCommunalSource;
private final CommunalSource.Callback mCommunalSourceCallback;
private final CommandQueue mCommandQueue;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final UserManager mUserManager;
@@ -906,9 +904,6 @@ public class NotificationPanelViewController extends PanelViewController {
mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
mCommunalSourceCallback = () -> {
mUiExecutor.execute(() -> setCommunalSource(null /*source*/));
};
mCommunalSourceMonitorCallback = (source) -> {
mUiExecutor.execute(() -> setCommunalSource(source));
@@ -4713,7 +4708,6 @@ public class NotificationPanelViewController extends PanelViewController {
CommunalSource existingSource = mCommunalSource != null ? mCommunalSource.get() : null;
if (existingSource != null) {
existingSource.removeCallback(mCommunalSourceCallback);
mCommunalViewController.show(null /*source*/);
}
@@ -4722,7 +4716,6 @@ public class NotificationPanelViewController extends PanelViewController {
CommunalSource currentSource = mCommunalSource != null ? mCommunalSource.get() : null;
// Set source and register callback
if (currentSource != null && mCommunalViewController != null) {
currentSource.addCallback(mCommunalSourceCallback);
mCommunalViewController.show(source);
}

View File

@@ -16,9 +16,7 @@
package com.android.systemui.util.service;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,6 +24,8 @@ import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import com.android.systemui.dagger.qualifiers.Main;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -35,6 +35,8 @@ import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
/**
* {@link ObservableServiceConnection} is a concrete implementation of {@link ServiceConnection}
* that enables monitoring the status of a binder connection. It also aides in automatically
@@ -119,17 +121,17 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
* Default constructor for {@link ObservableServiceConnection}.
* @param context The context from which the service will be bound with.
* @param serviceIntent The intent to bind service with.
* @param flags The flags to use during the binding
* @param executor The executor for connection callbacks to be delivered on
* @param transformer A {@link ServiceTransformer} for transforming the resulting service
* into a desired type.
*/
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
@Context.BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
@Main Executor executor,
ServiceTransformer<T> transformer) {
mContext = context;
mServiceIntent = serviceIntent;
mFlags = flags;
mFlags = Context.BIND_AUTO_CREATE;
mExecutor = executor;
mTransformer = transformer;
mCallbacks = new ArrayList<>();

View File

@@ -29,6 +29,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -45,6 +47,8 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase {
@Mock
private CommunalConditionsMonitor mCommunalConditionsMonitor;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -56,7 +60,7 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase {
return null;
}).when(mCommunalConditionsMonitor).addCallback(any());
mMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
mMonitor = new CommunalSourceMonitor(mExecutor, mCommunalConditionsMonitor);
final CommunalManagerUpdater updater = new CommunalManagerUpdater(mContext, mMonitor);
updater.start();
clearInvocations(mCommunalManager);
@@ -65,6 +69,7 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase {
@Test
public void testUpdateSystemService_false() {
mMonitor.setSource(null);
mExecutor.runAllReady();
verify(mCommunalManager).setCommunalViewShowing(false);
}
@@ -72,6 +77,7 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase {
public void testUpdateSystemService_true() {
final CommunalSource source = mock(CommunalSource.class);
mMonitor.setSource(source);
mExecutor.runAllReady();
verify(mCommunalManager).setCommunalViewShowing(true);
}
}

View File

@@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -52,11 +54,12 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
@Captor private ArgumentCaptor<CommunalConditionsMonitor.Callback> mConditionsCallbackCaptor;
private CommunalSourceMonitor mCommunalSourceMonitor;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mCommunalSourceMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
mCommunalSourceMonitor = new CommunalSourceMonitor(mExecutor, mCommunalConditionsMonitor);
}
@Test
@@ -64,7 +67,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class);
final CommunalSource source = mock(CommunalSource.class);
mCommunalSourceMonitor.setSource(source);
setSource(source);
mCommunalSourceMonitor.addCallback(callback);
setConditionsMet(true);
@@ -78,7 +81,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
mCommunalSourceMonitor.addCallback(callback);
mCommunalSourceMonitor.removeCallback(callback);
mCommunalSourceMonitor.setSource(source);
setSource(source);
verify(callback, never()).onSourceAvailable(any());
}
@@ -91,7 +94,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
mCommunalSourceMonitor.addCallback(callback);
setConditionsMet(true);
clearInvocations(callback);
mCommunalSourceMonitor.setSource(source);
setSource(source);
verifyOnSourceAvailableCalledWith(callback, source);
}
@@ -103,7 +106,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
mCommunalSourceMonitor.addCallback(callback);
setConditionsMet(false);
mCommunalSourceMonitor.setSource(source);
setSource(source);
verify(callback, never()).onSourceAvailable(any());
}
@@ -114,7 +117,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
final CommunalSource source = mock(CommunalSource.class);
mCommunalSourceMonitor.addCallback(callback);
mCommunalSourceMonitor.setSource(source);
setSource(source);
// The callback should not have executed since communal is disabled.
verify(callback, never()).onSourceAvailable(any());
@@ -130,7 +133,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
final CommunalSource source = mock(CommunalSource.class);
mCommunalSourceMonitor.addCallback(callback);
mCommunalSourceMonitor.setSource(source);
setSource(source);
setConditionsMet(true);
verifyOnSourceAvailableCalledWith(callback, source);
@@ -151,9 +154,16 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
// Pushes an update on whether the communal conditions are met, assuming that a callback has
// been registered with the communal conditions monitor.
private void setConditionsMet(boolean value) {
mExecutor.runAllReady();
verify(mCommunalConditionsMonitor).addCallback(mConditionsCallbackCaptor.capture());
final CommunalConditionsMonitor.Callback conditionsCallback =
mConditionsCallbackCaptor.getValue();
conditionsCallback.onConditionsChanged(value);
mExecutor.runAllReady();
}
private void setSource(CommunalSource source) {
mCommunalSourceMonitor.setSource(source);
mExecutor.runAllReady();
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.communal;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -26,14 +27,16 @@ import android.content.Context;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeExecutor;;
import com.android.systemui.util.ref.GcWeakReference;
import com.android.systemui.util.time.FakeSystemClock;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,6 +55,35 @@ public class CommunalSourcePrimerTest extends SysuiTestCase {
private static final int RETRY_DELAY_MS = 1000;
private static final int CONNECTION_MIN_DURATION_MS = 5000;
// A simple implementation of {@link CommunalSource.Observer} to capture a callback value.
// Used to ensure the references to a {@link CommunalSource.Observer.Callback} can be fully
// removed.
private static class FakeObserver implements CommunalSource.Observer {
public GcWeakReference<Callback> mLastCallback;
@Override
public void addCallback(Callback callback) {
mLastCallback = new GcWeakReference<>(callback);
}
@Override
public void removeCallback(Callback callback) {
if (mLastCallback.get() == callback) {
mLastCallback = null;
}
}
}
// A simple implementation of {@link CommunalSource} to capture callback values. This
// implementation better emulates the {@link WeakReference} wrapping behavior of
// {@link CommunalSource} implementations than a mock.
private static class FakeSource implements CommunalSource {
@Override
public ListenableFuture<CommunalViewResult> requestCommunalView(Context context) {
return null;
}
}
@Mock
private Context mContext;
@@ -61,8 +93,7 @@ public class CommunalSourcePrimerTest extends SysuiTestCase {
private FakeSystemClock mFakeClock = new FakeSystemClock();
private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
@Mock
private CommunalSource mSource;
private FakeSource mSource = new FakeSource();
@Mock
private CommunalSourceMonitor mCommunalSourceMonitor;
@@ -71,7 +102,9 @@ public class CommunalSourcePrimerTest extends SysuiTestCase {
private CommunalSource.Connector mConnector;
@Mock
private CommunalSource.Observer mObserver;
private CommunalSource.Connection mConnection;
private FakeObserver mObserver = new FakeObserver();
private CommunalSourcePrimer mPrimer;
@@ -93,35 +126,35 @@ public class CommunalSourcePrimerTest extends SysuiTestCase {
mCommunalSourceMonitor, Optional.of(mConnector), Optional.of(mObserver));
}
private CommunalSource.Connection.Callback captureCallbackAndSend(
CommunalSource.Connector connector, Optional<CommunalSource> source) {
ArgumentCaptor<CommunalSource.Connection.Callback> connectionCallback =
ArgumentCaptor.forClass(CommunalSource.Connection.Callback.class);
verify(connector).connect(connectionCallback.capture());
Mockito.clearInvocations(connector);
final CommunalSource.Connection.Callback callback = connectionCallback.getValue();
callback.onSourceEstablished(source);
return callback;
}
@Test
public void testConnect() {
when(mConnector.connect()).thenReturn(
CallbackToFutureAdapter.getFuture(completer -> {
completer.set(Optional.of(mSource));
return "test";
}));
mPrimer.onBootCompleted();
mFakeExecutor.runAllReady();
captureCallbackAndSend(mConnector, Optional.of(mSource));
verify(mCommunalSourceMonitor).setSource(mSource);
}
@Test
public void testRetryOnBindFailure() throws Exception {
when(mConnector.connect()).thenReturn(
CallbackToFutureAdapter.getFuture(completer -> {
completer.set(Optional.empty());
return "test";
}));
mPrimer.onBootCompleted();
mFakeExecutor.runAllReady();
// 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(mConnector, times(1)).connect();
clearInvocations(mConnector);
captureCallbackAndSend(mConnector, Optional.empty());
mFakeExecutor.advanceClockToNext();
mFakeExecutor.runAllReady();
}
@@ -131,76 +164,42 @@ public class CommunalSourcePrimerTest extends SysuiTestCase {
@Test
public void testRetryOnDisconnectFailure() throws Exception {
when(mConnector.connect()).thenReturn(
CallbackToFutureAdapter.getFuture(completer -> {
completer.set(Optional.of(mSource));
return "test";
}));
mPrimer.onBootCompleted();
mFakeExecutor.runAllReady();
// 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(mConnector, times(1)).connect();
clearInvocations(mConnector);
ArgumentCaptor<CommunalSource.Callback> callbackCaptor =
ArgumentCaptor.forClass(CommunalSource.Callback.class);
verify(mSource).addCallback(callbackCaptor.capture());
clearInvocations(mSource);
final CommunalSource.Connection.Callback callback =
captureCallbackAndSend(mConnector, Optional.of(mSource));
verify(mCommunalSourceMonitor).setSource(Mockito.notNull());
clearInvocations(mCommunalSourceMonitor);
callbackCaptor.getValue().onDisconnected();
callback.onDisconnected();
mFakeExecutor.advanceClockToNext();
mFakeExecutor.runAllReady();
}
verify(mConnector, never()).connect();
verify(mConnector, never()).connect(any());
}
@Test
public void testAttemptOnPackageChange() {
when(mConnector.connect()).thenReturn(
CallbackToFutureAdapter.getFuture(completer -> {
completer.set(Optional.empty());
return "test";
}));
mPrimer.onBootCompleted();
mFakeExecutor.runAllReady();
captureCallbackAndSend(mConnector, Optional.empty());
final ArgumentCaptor<CommunalSource.Observer.Callback> callbackCaptor =
ArgumentCaptor.forClass(CommunalSource.Observer.Callback.class);
verify(mObserver).addCallback(callbackCaptor.capture());
mObserver.mLastCallback.get().onSourceChanged();
clearInvocations(mConnector);
callbackCaptor.getValue().onSourceChanged();
verify(mConnector, times(1)).connect();
verify(mConnector, times(1)).connect(any());
}
@Test
public void testDisconnect() {
final ArgumentCaptor<CommunalSource.Callback> callbackCaptor =
ArgumentCaptor.forClass(CommunalSource.Callback.class);
when(mConnector.connect()).thenReturn(
CallbackToFutureAdapter.getFuture(completer -> {
completer.set(Optional.of(mSource));
return "test";
}));
mPrimer.onBootCompleted();
mFakeExecutor.runAllReady();
final CommunalSource.Connection.Callback callback =
captureCallbackAndSend(mConnector, Optional.of(mSource));
verify(mCommunalSourceMonitor).setSource(mSource);
verify(mSource).addCallback(callbackCaptor.capture());
clearInvocations(mConnector);
mFakeClock.advanceTime(CONNECTION_MIN_DURATION_MS + 1);
callbackCaptor.getValue().onDisconnected();
mFakeExecutor.runAllReady();
callback.onDisconnected();
verify(mConnector).connect();
verify(mConnector).connect(any());
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.ref;
import com.android.internal.util.GcUtils;
import java.lang.ref.WeakReference;
/**
* A WeakReference subclass that forces gc/finalizing on access.
*/
public class GcWeakReference<T> extends WeakReference<T> {
public GcWeakReference(T referent) {
super(referent);
}
@Override
public T get() throws RuntimeException {
GcUtils.runGcAndFinalizersSync();
return super.get();
}
}

View File

@@ -99,7 +99,7 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
@Test
public void testConnect() {
ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
mIntent, 0, mExecutor, mTransformer);
mIntent, mExecutor, mTransformer);
// Register twice to ensure only one callback occurs.
connection.addCallback(mCallback);
connection.addCallback(mCallback);
@@ -119,7 +119,7 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
@Test
public void testDisconnect() {
ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
mIntent, 0, mExecutor, mTransformer);
mIntent, mExecutor, mTransformer);
connection.addCallback(mCallback);
connection.onServiceDisconnected(mComponentName);