1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.bluetooth.cts_root; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 21 import static android.Manifest.permission.BLUETOOTH_SCAN; 22 import static android.Manifest.permission.DUMP; 23 import static android.Manifest.permission.PACKAGE_USAGE_STATS; 24 25 import static com.google.common.truth.Truth.assertThat; 26 27 import android.bluetooth.BluetoothAdapter; 28 import android.bluetooth.BluetoothProfile; 29 import android.bluetooth.cts.BTAdapterUtils; 30 import android.bluetooth.cts.TestUtils; 31 import android.content.Context; 32 import android.content.pm.PackageManager; 33 import android.util.Log; 34 35 import androidx.test.ext.junit.runners.AndroidJUnit4; 36 import androidx.test.filters.SmallTest; 37 import androidx.test.platform.app.InstrumentationRegistry; 38 39 import com.android.compatibility.common.util.CddTest; 40 import com.android.helpers.StatsdHelper; 41 import com.android.os.nano.AtomsProto; 42 import com.android.os.nano.StatsLog; 43 44 import org.junit.After; 45 import org.junit.Assume; 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 import java.util.List; 51 import java.util.Set; 52 53 /** 54 * Test cases that can only run in rooted environments 55 */ 56 @RunWith(AndroidJUnit4.class) 57 @SmallTest 58 public class BluetoothCddRootTest { 59 private static final String TAG = BluetoothCddRootTest.class.getSimpleName(); 60 private static final int BLUETOOTH_CORE_SPECIFICATION_4_2 = 0x08; 61 private static final int BLUETOOTH_CORE_SPECIFICATION_5_0 = 0x09; 62 private static final int BLUETOOTH_LOCAL_VERSION_REPORTED_ATOM_ID = 530; 63 // Some devices need some extra time after entering STATE_OFF 64 private static final int BLUETOOTH_TOGGLE_DELAY_MS = 2000; 65 66 private Context mContext; 67 private boolean mHasBluetooth; 68 private BluetoothAdapter mAdapter; 69 70 @Before setUp()71 public void setUp() { 72 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 73 mHasBluetooth = TestUtils.hasBluetooth(); 74 Assume.assumeTrue(mHasBluetooth); 75 TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, 76 BLUETOOTH_PRIVILEGED, BLUETOOTH_SCAN, DUMP, PACKAGE_USAGE_STATS); 77 assertThat(TestUtils.getAdoptedShellPermissions()).containsAtLeast(BLUETOOTH_CONNECT, 78 BLUETOOTH_PRIVILEGED, BLUETOOTH_SCAN, DUMP, PACKAGE_USAGE_STATS); 79 mAdapter = TestUtils.getBluetoothAdapterOrDie(); 80 if (mAdapter.isEnabled()) { 81 assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue(); 82 try { 83 Thread.sleep(BLUETOOTH_TOGGLE_DELAY_MS); 84 } catch (InterruptedException ignored) { } 85 } 86 } 87 88 @After tearDown()89 public void tearDown() { 90 if (!mHasBluetooth) { 91 return; 92 } 93 if (mAdapter != null && mAdapter.getState() != BluetoothAdapter.STATE_OFF) { 94 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) { 95 assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue(); 96 } 97 try { 98 Thread.sleep(BLUETOOTH_TOGGLE_DELAY_MS); 99 } catch (InterruptedException ignored) { } 100 } 101 mAdapter = null; 102 TestUtils.dropPermissionAsShellUid(); 103 } 104 105 @CddTest(requirements = {"7.4.3/C-1-1"}) 106 @Test test_C_1_1_VrHighPerformance()107 public void test_C_1_1_VrHighPerformance() { 108 Assume.assumeTrue(mContext.getPackageManager().hasSystemFeature( 109 PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)); 110 assertThat(mHasBluetooth).isTrue(); 111 AtomsProto.BluetoothLocalVersionsReported version = getBluetoothVersion(); 112 assertThat(version.hciVersion).isAtLeast(BLUETOOTH_CORE_SPECIFICATION_4_2); 113 assertThat(TestUtils.isBleSupported(mContext)).isTrue(); 114 // TODO: Enforce LE data length extension 115 } 116 117 @CddTest(requirements = {"7.4.3/H-1-1"}) 118 @Test test_H_1_1_AshaRequirements()119 public void test_H_1_1_AshaRequirements() { 120 Assume.assumeTrue(mHasBluetooth); 121 Assume.assumeTrue("Skip 7.4.3/H-1-1 test for non-BLE devices", 122 TestUtils.isBleSupported(mContext)); 123 Assume.assumeFalse("Skip 7.4.3/H-1-1 test for automotive devices", 124 TestUtils.isAutomotive(mContext)); 125 Assume.assumeFalse("Skip 7.4.3/H-1-1 test for watch devices", 126 TestUtils.isWatch(mContext)); 127 Assume.assumeFalse("Skip 7.4.3/H-1-1 test for TV devices", 128 TestUtils.isTv(mContext)); 129 AtomsProto.BluetoothLocalVersionsReported version = getBluetoothVersion(); 130 Assume.assumeTrue(version.hciVersion >= BLUETOOTH_CORE_SPECIFICATION_5_0); 131 assertThat(BTAdapterUtils.enableAdapter(mAdapter, mContext)).isTrue(); 132 assertThat(mAdapter.getSupportedProfiles()).contains(BluetoothProfile.HEARING_AID); 133 TestUtils.BluetoothCtsServiceConnector connector = 134 new TestUtils.BluetoothCtsServiceConnector(TAG, 135 BluetoothProfile.HEARING_AID, mAdapter, mContext); 136 try { 137 assertThat(connector.openProfileProxyAsync()).isTrue(); 138 assertThat(connector.waitForProfileConnect()).isTrue(); 139 assertThat(connector.getProfileProxy()).isNotNull(); 140 } finally { 141 connector.closeProfileProxy(); 142 } 143 } 144 145 @CddTest(requirements = {"7.4.3/C-12-1"}) 146 @Test test_C_12_1_Bluetooth5Requirements()147 public void test_C_12_1_Bluetooth5Requirements() { 148 Assume.assumeTrue(mHasBluetooth); 149 AtomsProto.BluetoothLocalVersionsReported version = getBluetoothVersion(); 150 if (version.hciVersion >= BLUETOOTH_CORE_SPECIFICATION_5_0) { 151 // Assert LMP Version is larger than or equal to HCI version 152 assertThat(version.lmpVersion).isAtLeast(version.hciVersion); 153 assertThat(mAdapter.isLe2MPhySupported()).isTrue(); 154 assertThat(mAdapter.isLeCodedPhySupported()).isTrue(); 155 assertThat(mAdapter.isLeExtendedAdvertisingSupported()).isTrue(); 156 assertThat(mAdapter.isLePeriodicAdvertisingSupported()).isTrue(); 157 assertThat(mAdapter.isMultipleAdvertisementSupported()).isTrue(); 158 // TODO: Enforce number of advertisement supported 159 // TODO: Enforce number of concurrent LE-ACL connections supported 160 } 161 } 162 163 /** 164 * Get Bluetooth version information. Bluetooth is enabled after this method call. 165 * 166 * Requires ROOT access on the running Android device 167 * 168 * @return Bluetooth version proto 169 */ getBluetoothVersion()170 private AtomsProto.BluetoothLocalVersionsReported getBluetoothVersion() { 171 Set<String> permissionsAdopted = TestUtils.getAdoptedShellPermissions(); 172 String[] permissionArray = permissionsAdopted.toArray(String[]::new); 173 if (mAdapter.isEnabled()) { 174 assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue(); 175 try { 176 Thread.sleep(BLUETOOTH_TOGGLE_DELAY_MS); 177 } catch (InterruptedException ignored) { } 178 } 179 StatsdHelper statsdHelper = new StatsdHelper(); 180 // Requires root to enable metrics 181 assertThat(statsdHelper.addEventConfig( 182 List.of(BLUETOOTH_LOCAL_VERSION_REPORTED_ATOM_ID))).isTrue(); 183 assertThat(BTAdapterUtils.enableAdapter(mAdapter, mContext)).isTrue(); 184 List<StatsLog.EventMetricData> metrics = statsdHelper.getEventMetrics(); 185 AtomsProto.BluetoothLocalVersionsReported summaryAtom = 186 new AtomsProto.BluetoothLocalVersionsReported(); 187 // When multiple atoms are reported use the maximum value of HCI version 188 // They should really all be the same 189 int i = 0; 190 for (StatsLog.EventMetricData data : metrics) { 191 AtomsProto.BluetoothLocalVersionsReported atom = 192 data.atom.getBluetoothLocalVersionsReported(); 193 if (atom == null) { 194 continue; 195 } 196 Log.i("BluetoothCddRootTest", "[" + i + "] HCI version is " + atom.hciVersion 197 + ", LMP version is " + atom.lmpVersion); 198 assertThat(atom.lmpManufacturerName).isGreaterThan(0); 199 assertThat(atom.lmpVersion).isGreaterThan(0); 200 assertThat(atom.hciVersion).isGreaterThan(0); 201 if (atom.hciVersion > summaryAtom.hciVersion) { 202 summaryAtom.lmpManufacturerName = atom.lmpManufacturerName; 203 summaryAtom.lmpVersion = atom.lmpVersion; 204 summaryAtom.lmpSubversion = atom.lmpSubversion; 205 summaryAtom.hciVersion = atom.hciVersion; 206 summaryAtom.hciRevision = atom.hciRevision; 207 } 208 i++; 209 } 210 TestUtils.dropPermissionAsShellUid(); 211 TestUtils.adoptPermissionAsShellUid(permissionArray); 212 return summaryAtom; 213 } 214 } 215