blob: 9f42a9ccfbc93de6826e5f5f08ec3fc0c5622112 [file] [log] [blame]
package com.android.clockwork.bluetooth;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.MockBluetoothProxyHelper;
import android.content.Context;
import android.net.ConnectivityManager;
import android.os.ParcelFileDescriptor;
import com.android.clockwork.bluetooth.proxy.ProxyServiceHelper;
import com.android.internal.util.IndentingPrintWriter;
import java.lang.reflect.Field;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
/** Test for {@link CompanionProxyShard} */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowBluetoothAdapter.class)
public class CompanionProxyShardTest {
private static final int INSTANCE = -1;
private static final int FD = 2;
private static final int NETWORK_SCORE = 123;
private static final int NETWORK_SCORE2 = 456;
private static final int DISCONNECT_STATUS = 789;
private static final int WHAT_START_SYSPROXY = 1;
private static final int WHAT_JNI_ACTIVE_NETWORK_STATE = 2;
private static final int WHAT_JNI_DISCONNECTED = 3;
private static final int WHAT_RESET_CONNECTION = 4;
private static final boolean CONNECTED = true;
private static final boolean DISCONNECTED = !CONNECTED;
private static final boolean WITH_INTERNET = true;
private static final boolean NO_INTERNET = !WITH_INTERNET;
private static final int INVALID_NETWORK_TYPE = ConnectivityManager.TYPE_NONE;
private @Mock BluetoothAdapter mockBluetoothAdapter;
private @Mock BluetoothDevice mockBluetoothDevice;
private @Mock Context mockContext;
private @Mock IndentingPrintWriter mockIndentingPrintWriter;
private @Mock ParcelFileDescriptor mockParcelFileDescriptor;
private @Mock ProxyServiceHelper mockProxyServiceHelper;
private @Mock CompanionProxyShard.Listener mockCompanionProxyShardListener;
private CompanionProxyShardTestClass mCompanionProxyShard;
private MockBluetoothProxyHelper mBluetoothProxyHelper;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowLooper.pauseMainLooper();
when(mockParcelFileDescriptor.detachFd()).thenReturn(FD);
mBluetoothProxyHelper = new MockBluetoothProxyHelper(mockBluetoothAdapter);
mBluetoothProxyHelper.setMockParcelFileDescriptor(mockParcelFileDescriptor);
when(mockProxyServiceHelper.getNetworkScore()).thenReturn(NETWORK_SCORE);
ShadowBluetoothAdapter.setAdapter(mockBluetoothAdapter);
}
@Test
public void testStartNetworkWithWifiInternet_WasDisconnected() {
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
verify(mockParcelFileDescriptor).detachFd();
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK SYSPROXY DELIVER
assertEquals(1, mCompanionProxyShard.connectNativeCount);
// Simulate JNI callback
mCompanionProxyShard.simulateJniCallbackConnect(ConnectivityManager.TYPE_WIFI, false);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_ACTIVE_NETWORK_STATE
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, WITH_INTERNET);
verify(mockProxyServiceHelper).startNetworkSession(anyString(), anyObject());
ensureMessageQueueEmpty();
}
@Test
public void testStartNetworkNoInternet_WasDisconnected() {
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
verify(mockParcelFileDescriptor).detachFd();
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK SYSPROXY DELIVER
assertEquals(1, mCompanionProxyShard.connectNativeCount);
// Simulate JNI callback
mCompanionProxyShard.simulateJniCallbackConnect(INVALID_NETWORK_TYPE, false);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_ACTIVE_NETWORK_STATE
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, NO_INTERNET);
verify(mockProxyServiceHelper).stopNetworkSession(anyString());
ensureMessageQueueEmpty();
}
@Test
public void testStartNetwork_WasConnectedWithWifiInternet() {
connectNetworkWithWifiInternet();
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, WITH_INTERNET);
mCompanionProxyShard.startNetwork();
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK SYSPROXY DELIVER
verify(mockCompanionProxyShardListener, times(2)).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, WITH_INTERNET);
verify(mockProxyServiceHelper, times(2)).startNetworkSession(anyString(), anyObject());
ensureMessageQueueEmpty();
}
@Test
public void testStartNetwork_WasConnectedNoInternet() {
connectNetworkNoInternet();
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, NO_INTERNET);
mCompanionProxyShard.startNetwork();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
verify(mockCompanionProxyShardListener, times(2)).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, NO_INTERNET);
verify(mockProxyServiceHelper, times(2)).stopNetworkSession(anyString());
ensureMessageQueueEmpty();
}
@Test
public void testStartNetwork_Closed() {
connectNetworkWithWifiInternet();
mCompanionProxyShard.mIsClosed = true;
mCompanionProxyShard.startNetwork();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
verify(mockProxyServiceHelper).getNetworkScore();
assertEquals(1, mCompanionProxyShard.mStartAttempts);
ensureMessageQueueEmpty();
}
@Test
public void testStartNetwork_AdapterIsNull() {
// Force bluetooth adapter to return null
ShadowBluetoothAdapter.forceNull = true;
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
verify(mockParcelFileDescriptor, never()).detachFd();
// Restore bluetooth adapter to return a valid instance
ShadowBluetoothAdapter.forceNull = false;
}
@Test
public void testStartNetwork_NullParcelFileDescriptor() {
mBluetoothProxyHelper.setMockParcelFileDescriptor(null);
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
// Simulate JNI callback
assertEquals(0, mCompanionProxyShard.connectNativeCount);
assertTrue(mCompanionProxyShard.mHandler.hasMessages(WHAT_RESET_CONNECTION));
}
@Test
public void testStartNetwork_BluetoothServiceIsNull() {
mBluetoothProxyHelper.setBluetoothService(null);
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
verify(mockParcelFileDescriptor, never()).detachFd();
assertTrue(mCompanionProxyShard.mHandler.hasMessages(WHAT_RESET_CONNECTION));
}
@Test
public void testUpdateNetwork_ConnectedWithWifiInternet() {
connectNetworkWithWifiInternet();
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, WITH_INTERNET);
when(mockProxyServiceHelper.getNetworkScore()).thenReturn(NETWORK_SCORE2);
mCompanionProxyShard.updateNetwork(NETWORK_SCORE2);
verify(mockProxyServiceHelper).setNetworkScore(NETWORK_SCORE);
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE2, WITH_INTERNET);
}
@Test
public void testUpdateNetwork_ConnectedNoInternet() {
connectNetworkNoInternet();
when(mockProxyServiceHelper.getNetworkScore()).thenReturn(NETWORK_SCORE2);
mCompanionProxyShard.updateNetwork(NETWORK_SCORE2);
verify(mockProxyServiceHelper).setNetworkScore(NETWORK_SCORE2);
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE2, NO_INTERNET);
}
@Test
public void testUpdateNetwork_Disconnected() {
when(mockProxyServiceHelper.getNetworkScore()).thenReturn(NETWORK_SCORE2);
mCompanionProxyShard = createCompanionProxyShard();
mCompanionProxyShard.updateNetwork(NETWORK_SCORE2);
verify(mockProxyServiceHelper).setNetworkScore(NETWORK_SCORE2);
verify(mockCompanionProxyShardListener).onProxyConnectionChange(DISCONNECTED,
NETWORK_SCORE2, false);
}
@Test
public void testWifiToCell() {
connectNetworkWithWifiInternet();
mCompanionProxyShard.simulateJniCallbackConnect(ConnectivityManager.TYPE_MOBILE, true);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_ACTIVE_NETWORK_STATE
assertEquals(ConnectivityManager.TYPE_MOBILE, mCompanionProxyShard.mNetworkType);
verify(mockCompanionProxyShardListener, times(2)).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, WITH_INTERNET);
verify(mockProxyServiceHelper).setMetered(false);
verify(mockProxyServiceHelper).setMetered(true);
}
@Test
public void testCellToWifi() {
connectNetworkWithCellInternet();
mCompanionProxyShard.simulateJniCallbackConnect(ConnectivityManager.TYPE_WIFI, false);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_ACTIVE_NETWORK_STATE
assertEquals(ConnectivityManager.TYPE_WIFI, mCompanionProxyShard.mNetworkType);
verify(mockCompanionProxyShardListener, times(2)).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, WITH_INTERNET);
verify(mockProxyServiceHelper).setMetered(true);
verify(mockProxyServiceHelper).setMetered(false);
}
@Test
public void testJniActiveNetworkState_AlreadyClosed() {
connectNetworkWithWifiInternet();
mCompanionProxyShard.mIsClosed = true;
mCompanionProxyShard.simulateJniCallbackConnect(ConnectivityManager.TYPE_WIFI, true);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_ACTIVE_NETWORK_STATE
ensureMessageQueueEmpty();
}
@Test
public void testJniActiveNetworkState_ConnectedPhoneWithCell() {
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK SYSPROXY DELIVER
mCompanionProxyShard.simulateJniCallbackConnect(ConnectivityManager.TYPE_MOBILE, true);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_ACTIVE_NETWORK_STATE
verify(mockProxyServiceHelper).startNetworkSession(anyString(), anyObject());
verify(mockCompanionProxyShardListener).onProxyConnectionChange(true, 123, true);
verify(mockProxyServiceHelper).setMetered(true);
ensureMessageQueueEmpty();
}
@Test
public void testJniActiveNetworkState_ConnectedPhoneNoInternet() {
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK SYSPROXY DELIVER
mCompanionProxyShard.simulateJniCallbackConnect(INVALID_NETWORK_TYPE, true);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_ACTIVE_NETWORK_STATE
verify(mockProxyServiceHelper).stopNetworkSession(anyString());
verify(mockCompanionProxyShardListener).onProxyConnectionChange(CONNECTED,
NETWORK_SCORE, NO_INTERNET);
ensureMessageQueueEmpty();
}
@Test
public void testJniDisconnect_NotClosed() {
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK SYSPROXY DELIVER
mCompanionProxyShard.simulateJniCallbackDisconnect(-1);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_DISCONNECT
verify(mockCompanionProxyShardListener).onProxyConnectionChange(DISCONNECTED,
NETWORK_SCORE, false);
verify(mockProxyServiceHelper).stopNetworkSession(anyString());
assertTrue(mCompanionProxyShard.mHandler.hasMessages(WHAT_START_SYSPROXY));
}
@Test
public void testJniDisconnect_Closed() {
mCompanionProxyShard = createCompanionProxyShard();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK RFCOMM SOCKET
ShadowLooper.runMainLooperOneTask(); // ASYNC TASK SYSPROXY DELIVER
mCompanionProxyShard.mIsClosed = true;
mCompanionProxyShard.simulateJniCallbackDisconnect(-1);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_DISCONNECT
ensureMessageQueueEmpty();
}
@Test
public void testClose_WasConnectedWithWifiInternet() {
connectNetworkWithWifiInternet();
mCompanionProxyShard.close();
verify(mockProxyServiceHelper).stopNetworkSession(anyString());
verify(mockCompanionProxyShardListener).onProxyConnectionChange(DISCONNECTED,
NETWORK_SCORE, false);
}
@Test
public void testResetConnection_SysproxyConnected() {
connectNetworkWithWifiInternet();
setWaitingForAsyncDiconnectResponse(true);
try {
mCompanionProxyShard.startNetwork();
ShadowLooper.runMainLooperOneTask(); // WHAT_START_SYSPROXY
assertTrue(mCompanionProxyShard.mHandler.hasMessages(WHAT_RESET_CONNECTION));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(1, mCompanionProxyShard.connectNativeCount);
assertEquals(1, mCompanionProxyShard.disconnectNativeCount);
assertEquals(mCompanionProxyShard.disconnectReturnValue,
getWaitingForAsyncDiconnectResponse());
} finally {
// Restore static variable to default
setWaitingForAsyncDiconnectResponse(false);
}
}
@Test
public void testResetConnection_SysproxyDisconnected() {
connectNetworkWithWifiInternet();
mCompanionProxyShard.onDisconnect(DISCONNECT_STATUS);
ShadowLooper.runMainLooperOneTask(); // WHAT_JNI_DISCONNECTED
assertTrue(mCompanionProxyShard.mHandler.hasMessages(WHAT_START_SYSPROXY));
setWaitingForAsyncDiconnectResponse(true);
try {
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(1, mCompanionProxyShard.connectNativeCount);
assertEquals(0, mCompanionProxyShard.disconnectNativeCount);
assertTrue(mCompanionProxyShard.mHandler.hasMessages(WHAT_START_SYSPROXY));
} finally {
// Restore static variable to default
setWaitingForAsyncDiconnectResponse(false);
}
}
@Test
public void testDump() {
mCompanionProxyShard = createCompanionProxyShard();
mCompanionProxyShard.dump(mockIndentingPrintWriter);
verify(mockIndentingPrintWriter).increaseIndent();
verify(mockIndentingPrintWriter).decreaseIndent();
}
// Create the companion proxy shard to be used in the tests.
// The class abstracts away dependencies on difficult framework methods and fields.
private CompanionProxyShardTestClass createCompanionProxyShard() {
CompanionProxyShardTestClass companionProxyShard
= new CompanionProxyShardTestClass(mockContext, mockProxyServiceHelper,
mockBluetoothDevice, mockCompanionProxyShardListener, NETWORK_SCORE);
return companionProxyShard;
}
private void ensureMessageQueueEmpty() {
for (int i = WHAT_START_SYSPROXY; i <= WHAT_RESET_CONNECTION; i++) {
assertFalse(mCompanionProxyShard.mHandler.hasMessages(i));
}
}
private void connectNetworkWithWifiInternet() {
doStartNetwork(ConnectivityManager.TYPE_WIFI, false);
assertEquals(ConnectivityManager.TYPE_WIFI, mCompanionProxyShard.mNetworkType);
}
private void connectNetworkWithCellInternet() {
doStartNetwork(ConnectivityManager.TYPE_MOBILE, true);
assertEquals(ConnectivityManager.TYPE_MOBILE, mCompanionProxyShard.mNetworkType);
}
private void connectNetworkNoInternet() {
doStartNetwork(INVALID_NETWORK_TYPE, false);
}
private void doStartNetwork(int networkType, boolean metered) {
mCompanionProxyShard = createCompanionProxyShard();
assertTrue(mCompanionProxyShard.mHandler.hasMessages(WHAT_START_SYSPROXY));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Simulate JNI callback
mCompanionProxyShard.simulateJniCallbackConnect(networkType, metered);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(1, mCompanionProxyShard.connectNativeCount);
}
private class CompanionProxyShardTestClass extends CompanionProxyShard {
int connectNativeCount;
int disconnectNativeCount;
boolean connectReturnValue = true;
boolean disconnectReturnValue = true;
private CompanionProxyShardTestClass(
final Context context,
final ProxyServiceHelper proxyServiceHelper,
final BluetoothDevice device,
final Listener listener,
final int networkScore) {
super(context, proxyServiceHelper, device, listener, networkScore);
}
@Override
protected boolean connectNative(int fd) {
connectNativeCount += 1;
return connectReturnValue;
}
void simulateJniCallbackConnect(int networkType, boolean isMetered) {
super.onActiveNetworkState(networkType, isMetered);
}
@Override
protected boolean disconnectNative() {
disconnectNativeCount += 1;
return disconnectReturnValue;
}
void simulateJniCallbackDisconnect(int status) {
super.onDisconnect(status);
}
}
private void setWaitingForAsyncDiconnectResponse(final boolean isWaiting) {
try {
Field field
= CompanionProxyShard.class.getDeclaredField("sWaitingForAsyncDisconnectResponse");
field.setAccessible(true);
field.set(null, isWaiting);
} catch (IllegalAccessException | NoSuchFieldException e) {
fail();
}
}
private boolean getWaitingForAsyncDiconnectResponse() {
boolean isWaiting = false;
try {
Field field
= CompanionProxyShard.class.getDeclaredField("sWaitingForAsyncDisconnectResponse");
field.setAccessible(true);
isWaiting = field.getBoolean(null);
} catch (IllegalAccessException | NoSuchFieldException e) {
fail();
}
return isWaiting;
}
}