Snap for 6955225 from a306f39c33430b2d4c5eb901de14b1c4c090698b to rvc-qpr2-release
Change-Id: I3218fee92f6bce9a9b119d2698a71b297d860999
diff --git a/src/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparer.java b/src/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparer.java
new file mode 100644
index 0000000..aa2836a
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparer.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 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.tradefed.targetprep.multi;
+
+import com.android.loganalysis.util.config.OptionClass;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.Sl4aBluetoothUtil;
+import com.android.tradefed.util.Sl4aBluetoothUtil.BluetoothAccessLevel;
+import com.android.tradefed.util.Sl4aBluetoothUtil.BluetoothPriorityLevel;
+import com.android.tradefed.util.Sl4aBluetoothUtil.BluetoothProfile;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** A multi-target preparer helps make Bluetooth pairing (and connection) between two devices. */
+@OptionClass(alias = "bluetooth-multi-target-pairing")
+public class PairingMultiTargetPreparer extends BaseMultiTargetPreparer {
+
+ @Option(
+ name = "bt-connection-primary-device",
+ description = "The target name of the primary device during BT pairing.",
+ mandatory = true)
+ private String mPrimaryDeviceName;
+
+ @Option(
+ name = "with-connection",
+ description =
+ "Connect the profiles once the devices are paired."
+ + " If true, given bluetooth profiles will be connected."
+ + " If false, the connection status is non-deterministic."
+ + " Devices will be paired but connection will not be done explicitly."
+ + " The connection status depends on the type of device,"
+ + " i.e. some devices will automatically connect, but some won't.")
+ private boolean mConnectDevices = true;
+
+ @Option(
+ name = "bt-profile",
+ description =
+ "A set of Bluetooth profiles that will be connected if connection is needed."
+ + " They should be specified as Bluetooth profile name defined in"
+ + " android.bluetooth.BluetoothProfile")
+ private Set<BluetoothProfile> mProfiles = new HashSet<>();
+
+ @VisibleForTesting
+ void setBluetoothUtil(Sl4aBluetoothUtil util) {
+ mUtil = util;
+ }
+
+ private ITestDevice mPrimaryDevice;
+ private ITestDevice mCompanionDevice;
+ private Sl4aBluetoothUtil mUtil = new Sl4aBluetoothUtil();
+
+ @Override
+ public void setUp(IInvocationContext context)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ setDeviceInfos(context.getDeviceBuildMap());
+ try {
+ if (!mUtil.enable(mPrimaryDevice)) {
+ throw new TargetSetupError(
+ "Failed to enable Bluetooth", mPrimaryDevice.getDeviceDescriptor());
+ }
+ if (!mUtil.enable(mCompanionDevice)) {
+ throw new TargetSetupError(
+ "Failed to enable Bluetooth", mCompanionDevice.getDeviceDescriptor());
+ }
+ if (!mUtil.pair(mPrimaryDevice, mCompanionDevice)) {
+ throw new TargetSetupError(
+ "Bluetooth pairing failed.", mPrimaryDevice.getDeviceDescriptor());
+ }
+ // Always enable PBAP between primary and companion devices in case it's not enabled
+ // For now, assume PBAP client profile is always on primary device, and enable PBAP on
+ // companion device.
+ if (!mUtil.changeProfileAccessPermission(
+ mCompanionDevice,
+ mPrimaryDevice,
+ BluetoothProfile.PBAP,
+ BluetoothAccessLevel.ACCESS_ALLOWED)) {
+ throw new TargetSetupError(
+ "Failed to allow PBAP access", mCompanionDevice.getDeviceDescriptor());
+ }
+ if (!mUtil.setProfilePriority(
+ mPrimaryDevice,
+ mCompanionDevice,
+ Collections.singleton(BluetoothProfile.PBAP_CLIENT),
+ BluetoothPriorityLevel.PRIORITY_ON)) {
+ throw new TargetSetupError(
+ "Failed to turn on PBAP client priority",
+ mPrimaryDevice.getDeviceDescriptor());
+ }
+ if (mConnectDevices && mProfiles.size() > 0) {
+ if (!mUtil.connect(mPrimaryDevice, mCompanionDevice, mProfiles)) {
+ throw new TargetSetupError(
+ "Failed to connect bluetooth profiles",
+ mPrimaryDevice.getDeviceDescriptor());
+ }
+ }
+ } finally {
+ mUtil.stopSl4a();
+ }
+ }
+
+ private void setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos) throws TargetSetupError {
+ List<ITestDevice> devices = new ArrayList<>(deviceInfos.keySet());
+ if (devices.size() != 2) {
+ throw new TargetSetupError(
+ "The preparer assumes 2 devices only", devices.get(0).getDeviceDescriptor());
+ }
+ try {
+ int primaryIdx = mPrimaryDeviceName.equals(devices.get(0).getProductType()) ? 0 : 1;
+ mPrimaryDevice = devices.get(primaryIdx);
+ mCompanionDevice = devices.get(1 - primaryIdx);
+ } catch (DeviceNotAvailableException e) {
+ throw new TargetSetupError(
+ "Device is not available", e, devices.get(0).getDeviceDescriptor());
+ }
+ }
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 832cc950..6df25d7 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -256,6 +256,7 @@
import com.android.tradefed.targetprep.app.NoApkTestSkipperTest;
import com.android.tradefed.targetprep.multi.MergeMultiBuildTargetPreparerTest;
import com.android.tradefed.targetprep.multi.MixImageZipPreparerTest;
+import com.android.tradefed.targetprep.multi.PairingMultiTargetPreparerTest;
import com.android.tradefed.targetprep.suite.SuiteApkInstallerTest;
import com.android.tradefed.testtype.AndroidJUnitTestTest;
import com.android.tradefed.testtype.ArtRunTestTest;
@@ -725,6 +726,7 @@
// targetprep.multi
MergeMultiBuildTargetPreparerTest.class,
MixImageZipPreparerTest.class,
+ PairingMultiTargetPreparerTest.class,
// targetprep.suite
SuiteApkInstallerTest.class,
diff --git a/tests/src/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparerTest.java
new file mode 100644
index 0000000..9878ffe
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 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.tradefed.targetprep.multi;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.util.Sl4aBluetoothUtil;
+import com.android.tradefed.util.Sl4aBluetoothUtil.BluetoothProfile;
+import com.android.tradefed.util.Sl4aBluetoothUtil.BluetoothAccessLevel;
+import com.android.tradefed.util.Sl4aBluetoothUtil.BluetoothPriorityLevel;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** Unit tests for {@link PairingMultiTargetPreparer}. */
+@RunWith(JUnit4.class)
+public class PairingMultiTargetPreparerTest {
+
+ private static final String PRIMARY_DEVICE_NAME = "primary";
+ private static final String SECONDARY_DEVICE_NAME = "secondary";
+
+ @Mock private IInvocationContext mContext;
+ @Mock private ITestDevice mPrimary;
+ @Mock private IBuildInfo mPrimaryBuild;
+ @Mock private ITestDevice mSecondary;
+ @Mock private IBuildInfo mSecondaryBuild;
+ @Mock private Sl4aBluetoothUtil mUtil;
+ private PairingMultiTargetPreparer mPreparer;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ when(mPrimary.getProductType()).thenReturn(PRIMARY_DEVICE_NAME);
+ when(mSecondary.getProductType()).thenReturn(SECONDARY_DEVICE_NAME);
+ Map<ITestDevice, IBuildInfo> deviceBuildMap = new HashMap<>();
+ deviceBuildMap.put(mPrimary, mPrimaryBuild);
+ deviceBuildMap.put(mSecondary, mSecondaryBuild);
+ when(mContext.getDeviceBuildMap()).thenReturn(deviceBuildMap);
+
+ when(mUtil.enable(any(ITestDevice.class))).thenReturn(true);
+ when(mUtil.pair(any(ITestDevice.class), any(ITestDevice.class))).thenReturn(true);
+ when(mUtil.changeProfileAccessPermission(
+ any(ITestDevice.class),
+ any(ITestDevice.class),
+ any(BluetoothProfile.class),
+ any(BluetoothAccessLevel.class)))
+ .thenReturn(true);
+ when(mUtil.setProfilePriority(
+ any(ITestDevice.class),
+ any(ITestDevice.class),
+ anySet(),
+ any(BluetoothPriorityLevel.class)))
+ .thenReturn(true);
+
+ mPreparer = new PairingMultiTargetPreparer();
+ mPreparer.setBluetoothUtil(mUtil);
+
+ OptionSetter setter = new OptionSetter(mPreparer);
+ setter.setOptionValue("bt-connection-primary-device", PRIMARY_DEVICE_NAME);
+ }
+
+ @Test
+ public void testPairWithConnection() throws Exception {
+ setBluetoothProfiles();
+ when(mUtil.connect(any(ITestDevice.class), any(ITestDevice.class), anySet()))
+ .thenReturn(true);
+ mPreparer.setUp(mContext);
+ verify(mUtil).enable(mPrimary);
+ verify(mUtil).enable(mSecondary);
+ verify(mUtil).pair(mPrimary, mSecondary);
+ verify(mUtil)
+ .connect(
+ eq(mPrimary),
+ eq(mSecondary),
+ argThat((Set<BluetoothProfile> profiles) -> profiles.size() == 3));
+ verify(mUtil).stopSl4a();
+ }
+
+ @Test
+ public void testPairWithoutConnection() throws Exception {
+ OptionSetter setter = new OptionSetter(mPreparer);
+ setter.setOptionValue("with-connection", "false");
+ setBluetoothProfiles();
+ mPreparer.setUp(mContext);
+ verify(mUtil).enable(mPrimary);
+ verify(mUtil).enable(mSecondary);
+ verify(mUtil).pair(mPrimary, mSecondary);
+ verify(mUtil, never()).connect(any(ITestDevice.class), any(ITestDevice.class), anySet());
+ verify(mUtil).stopSl4a();
+ }
+
+ @Test
+ public void testPairWithEmptyProfiles() throws Exception {
+ mPreparer.setUp(mContext);
+ verify(mUtil).enable(mPrimary);
+ verify(mUtil).enable(mSecondary);
+ verify(mUtil).pair(mPrimary, mSecondary);
+ verify(mUtil, never()).connect(any(ITestDevice.class), any(ITestDevice.class), anySet());
+ verify(mUtil).stopSl4a();
+ }
+
+ private void setBluetoothProfiles() throws ConfigurationException {
+ OptionSetter setter = new OptionSetter(mPreparer);
+ setter.setOptionValue("bt-connection-primary-device", PRIMARY_DEVICE_NAME);
+ setter.setOptionValue("bt-profile", BluetoothProfile.A2DP.name());
+ setter.setOptionValue("bt-profile", BluetoothProfile.HEADSET.name());
+ setter.setOptionValue("bt-profile", BluetoothProfile.MAP.name());
+ }
+}