• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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