Merge "Add BackupEncryptionService which provides an IntermediateEncryptingTransport"
This commit is contained in:
@@ -18,6 +18,7 @@ android_app {
|
||||
name: "BackupEncryption",
|
||||
srcs: ["src/**/*.java"],
|
||||
libs: ["backup-encryption-protos"],
|
||||
static_libs: ["backuplib"],
|
||||
optimize: { enabled: false },
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
|
||||
@@ -20,5 +20,14 @@
|
||||
package="com.android.server.backup.encryption"
|
||||
android:sharedUserId="android.uid.system" >
|
||||
|
||||
<application android:allowBackup="false" />
|
||||
<application android:allowBackup="false" >
|
||||
<!-- This service does not need to be exported because it shares uid with the system server
|
||||
which is the only client. -->
|
||||
<service android:name=".BackupEncryptionService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.encryption.BACKUP_ENCRYPTION" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.server.backup.encryption;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.encryption.transport.IntermediateEncryptingTransport;
|
||||
import com.android.server.backup.encryption.transport.IntermediateEncryptingTransportManager;
|
||||
|
||||
/**
|
||||
* This service provides encryption of backup data. For an intent used to bind to this service, it
|
||||
* provides an {@link IntermediateEncryptingTransport} which is an implementation of {@link
|
||||
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from the
|
||||
* real {@link IBackupTransport}.
|
||||
*/
|
||||
public class BackupEncryptionService extends Service {
|
||||
public static final String TAG = "BackupEncryption";
|
||||
private static IntermediateEncryptingTransportManager sTransportManager = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.i(TAG, "onCreate:" + this);
|
||||
if (sTransportManager == null) {
|
||||
Log.i(TAG, "Creating IntermediateEncryptingTransportManager");
|
||||
sTransportManager = new IntermediateEncryptingTransportManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.i(TAG, "onDestroy:" + this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// TODO (b141536117): Check connection with TransportClient.connect and return null on fail.
|
||||
return sTransportManager.get(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
sTransportManager.cleanup(intent);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.server.backup.encryption.transport;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.transport.DelegatingTransport;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
|
||||
/**
|
||||
* This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
|
||||
* sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
|
||||
* TransportClient.connect(String)}.
|
||||
*/
|
||||
public class IntermediateEncryptingTransport extends DelegatingTransport {
|
||||
private final TransportClient mTransportClient;
|
||||
private final Object mConnectLock = new Object();
|
||||
private volatile IBackupTransport mRealTransport;
|
||||
|
||||
@VisibleForTesting
|
||||
IntermediateEncryptingTransport(TransportClient transportClient) {
|
||||
mTransportClient = transportClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IBackupTransport getDelegate() throws RemoteException {
|
||||
if (mRealTransport == null) {
|
||||
connect();
|
||||
}
|
||||
return mRealTransport;
|
||||
}
|
||||
|
||||
private void connect() throws RemoteException {
|
||||
synchronized (mConnectLock) {
|
||||
if (mRealTransport == null) {
|
||||
mRealTransport = mTransportClient.connect("IntermediateEncryptingTransport");
|
||||
if (mRealTransport == null) {
|
||||
throw new RemoteException("Could not connect: " + mTransportClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TransportClient getClient() {
|
||||
return mTransportClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.server.backup.encryption.transport;
|
||||
|
||||
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.transport.TransportClientManager;
|
||||
import com.android.server.backup.transport.TransportStats;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances.
|
||||
*/
|
||||
public class IntermediateEncryptingTransportManager {
|
||||
private static final String CALLER = "IntermediateEncryptingTransportManager";
|
||||
private final TransportClientManager mTransportClientManager;
|
||||
private final Object mTransportsLock = new Object();
|
||||
private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
|
||||
|
||||
@VisibleForTesting
|
||||
IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
|
||||
mTransportClientManager = transportClientManager;
|
||||
}
|
||||
|
||||
public IntermediateEncryptingTransportManager(Context context) {
|
||||
this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the {@link ComponentName} corresponding to the real {@link IBackupTransport}, and
|
||||
* provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
|
||||
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
|
||||
* the real {@link IBackupTransport}.
|
||||
* @param intent {@link Intent} created with a call to {@link
|
||||
* TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
|
||||
* @return
|
||||
*/
|
||||
public IntermediateEncryptingTransport get(Intent intent) {
|
||||
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
|
||||
Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
|
||||
synchronized (mTransportsLock) {
|
||||
return mTransports.computeIfAbsent(transportIntent.getComponent(),
|
||||
c -> create(transportIntent));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link IntermediateEncryptingTransport}.
|
||||
*/
|
||||
private IntermediateEncryptingTransport create(Intent realTransportIntent) {
|
||||
return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
|
||||
realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to
|
||||
* {@link #get(Intent)} with this {@link Intent}.
|
||||
*/
|
||||
public void cleanup(Intent intent) {
|
||||
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
|
||||
Log.i(TAG, "cleanup: intent:" + intent + " transportIntent:" + transportIntent);
|
||||
|
||||
IntermediateEncryptingTransport transport;
|
||||
synchronized (mTransportsLock) {
|
||||
transport = mTransports.remove(transportIntent.getComponent());
|
||||
}
|
||||
if (transport != null) {
|
||||
mTransportClientManager.disposeOfTransportClient(transport.getClient(), CALLER);
|
||||
} else {
|
||||
Log.i(TAG, "Could not find IntermediateEncryptingTransport");
|
||||
}
|
||||
}
|
||||
}
|
||||
22
packages/BackupEncryption/test/unittest/Android.bp
Normal file
22
packages/BackupEncryption/test/unittest/Android.bp
Normal file
@@ -0,0 +1,22 @@
|
||||
android_test {
|
||||
name: "BackupEncryptionUnitTests",
|
||||
srcs: ["src/**/*.java"],
|
||||
static_libs: [
|
||||
"androidx.test.runner",
|
||||
"androidx.test.rules",
|
||||
"mockito-target-minus-junit4",
|
||||
"platform-test-annotations",
|
||||
"truth-prebuilt",
|
||||
"testables",
|
||||
"testng",
|
||||
],
|
||||
libs: [
|
||||
"android.test.mock",
|
||||
"android.test.base",
|
||||
"android.test.runner",
|
||||
"BackupEncryption",
|
||||
],
|
||||
test_suites: ["device-tests"],
|
||||
instrumentation_for: "BackupEncryption",
|
||||
certificate: "platform",
|
||||
}
|
||||
27
packages/BackupEncryption/test/unittest/AndroidManifest.xml
Normal file
27
packages/BackupEncryption/test/unittest/AndroidManifest.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.server.backup.encryption.unittests"
|
||||
android:sharedUserId="android.uid.system" >
|
||||
<application android:testOnly="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
<instrumentation
|
||||
android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.server.backup.encryption"
|
||||
android:label="Backup Encryption Unit Tests" />
|
||||
</manifest>
|
||||
29
packages/BackupEncryption/test/unittest/AndroidTest.xml
Normal file
29
packages/BackupEncryption/test/unittest/AndroidTest.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2019 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.
|
||||
-->
|
||||
<configuration description="Runs Backup Encryption Unit Tests.">
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="install-arg" value="-t" />
|
||||
<option name="test-file-name" value="BackupEncryptionUnitTests.apk" />
|
||||
</target_preparer>
|
||||
|
||||
<option name="test-tag" value="BackupEncryptionUnitTests" />
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="com.android.server.backup.encryption.unittests" />
|
||||
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
|
||||
</test>
|
||||
</configuration>
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.server.backup.encryption.transport;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotSame;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
import com.android.server.backup.transport.TransportClientManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IntermediateEncryptingTransportManagerTest {
|
||||
@Mock private TransportClient mTransportClient;
|
||||
@Mock private TransportClientManager mTransportClientManager;
|
||||
|
||||
private final ComponentName mTransportComponent = new ComponentName("pkg", "class");
|
||||
private final Bundle mExtras = new Bundle();
|
||||
private Intent mEncryptingTransportIntent;
|
||||
private IntermediateEncryptingTransportManager mIntermediateEncryptingTransportManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mExtras.putInt("test", 1);
|
||||
mEncryptingTransportIntent =
|
||||
TransportClientManager.getEncryptingTransportIntent(mTransportComponent)
|
||||
.putExtras(mExtras);
|
||||
mIntermediateEncryptingTransportManager =
|
||||
new IntermediateEncryptingTransportManager(mTransportClientManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_createsClientWithRealTransportComponentAndExtras() {
|
||||
when(mTransportClientManager.getTransportClient(any(), any(), any()))
|
||||
.thenReturn(mTransportClient);
|
||||
|
||||
IntermediateEncryptingTransport intermediateEncryptingTransport =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
|
||||
assertEquals(mTransportClient, intermediateEncryptingTransport.getClient());
|
||||
verify(mTransportClientManager, times(1))
|
||||
.getTransportClient(eq(mTransportComponent), argThat(mExtras::kindofEquals), any());
|
||||
verifyNoMoreInteractions(mTransportClientManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_callTwice_returnsSameTransport() {
|
||||
IntermediateEncryptingTransport transport1 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
IntermediateEncryptingTransport transport2 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
|
||||
assertEquals(transport1, transport2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanup_disposesTransportClient() {
|
||||
when(mTransportClientManager.getTransportClient(any(), any(), any()))
|
||||
.thenReturn(mTransportClient);
|
||||
|
||||
IntermediateEncryptingTransport transport =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
|
||||
|
||||
verify(mTransportClientManager, times(1)).getTransportClient(any(), any(), any());
|
||||
verify(mTransportClientManager, times(1))
|
||||
.disposeOfTransportClient(eq(mTransportClient), any());
|
||||
verifyNoMoreInteractions(mTransportClientManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanup_removesCachedTransport() {
|
||||
when(mTransportClientManager.getTransportClient(any(), any(), any()))
|
||||
.thenReturn(mTransportClient);
|
||||
|
||||
IntermediateEncryptingTransport transport1 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
|
||||
IntermediateEncryptingTransport transport2 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
|
||||
assertNotSame(transport1, transport2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.server.backup.encryption.transport;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IntermediateEncryptingTransportTest {
|
||||
@Mock private IBackupTransport mRealTransport;
|
||||
@Mock private TransportClient mTransportClient;
|
||||
|
||||
private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mIntermediateEncryptingTransport = new IntermediateEncryptingTransport(mTransportClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDelegate_callsConnect() throws Exception {
|
||||
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
|
||||
|
||||
IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
|
||||
|
||||
assertEquals(mRealTransport, ret);
|
||||
verify(mTransportClient, times(1)).connect(anyString());
|
||||
verifyNoMoreInteractions(mTransportClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDelegate_callTwice_callsConnectOnce() throws Exception {
|
||||
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
|
||||
|
||||
IBackupTransport ret1 = mIntermediateEncryptingTransport.getDelegate();
|
||||
IBackupTransport ret2 = mIntermediateEncryptingTransport.getDelegate();
|
||||
|
||||
assertEquals(mRealTransport, ret1);
|
||||
assertEquals(mRealTransport, ret2);
|
||||
verify(mTransportClient, times(1)).connect(anyString());
|
||||
verifyNoMoreInteractions(mTransportClient);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user