1 /* 2 * Copyright (C) 2022 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; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assert.assertThrows; 24 25 import android.app.UiAutomation; 26 import android.bluetooth.BluetoothAdapter; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.BluetoothGattCharacteristic; 29 import android.bluetooth.BluetoothGattServer; 30 import android.bluetooth.BluetoothGattServerCallback; 31 import android.bluetooth.BluetoothGattService; 32 import android.bluetooth.BluetoothManager; 33 import android.content.Context; 34 35 import androidx.test.ext.junit.runners.AndroidJUnit4; 36 import androidx.test.platform.app.InstrumentationRegistry; 37 38 import com.android.compatibility.common.util.CddTest; 39 40 import org.junit.After; 41 import org.junit.Assume; 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 46 import java.util.Arrays; 47 import java.util.UUID; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.TimeUnit; 50 51 @RunWith(AndroidJUnit4.class) 52 public class BluetoothGattServerTest { 53 54 private static final int LATCH_TIMEOUT_MS = 1000; 55 private final UUID TEST_UUID = UUID.fromString("0000110a-0000-1000-8000-00805f9b34fb"); 56 private Context mContext; 57 private BluetoothAdapter mBluetoothAdapter; 58 private BluetoothGattServer mBluetoothGattServer; 59 private BluetoothManager mBluetoothManager; 60 private UiAutomation mUIAutomation; 61 private CountDownLatch mLatch; 62 63 @Before setUp()64 public void setUp() throws Exception { 65 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 66 67 Assume.assumeTrue(TestUtils.isBleSupported(mContext)); 68 69 mUIAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 70 mUIAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT); 71 mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter(); 72 assertThat(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext)).isTrue(); 73 mBluetoothManager = mContext.getSystemService(BluetoothManager.class); 74 mLatch = new CountDownLatch(1); 75 mBluetoothGattServer = 76 mBluetoothManager.openGattServer( 77 mContext, 78 new BluetoothGattServerCallback() { 79 @Override 80 public void onServiceAdded(int status, BluetoothGattService service) { 81 mLatch.countDown(); 82 } 83 }); 84 } 85 86 @After tearDown()87 public void tearDown() throws Exception { 88 if (mUIAutomation != null) { 89 mUIAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT); 90 } 91 92 if (mBluetoothAdapter != null && mBluetoothGattServer != null) { 93 mBluetoothGattServer.close(); 94 mBluetoothGattServer = null; 95 } 96 97 mBluetoothAdapter = null; 98 mLatch = null; 99 100 if (mUIAutomation != null) { 101 mUIAutomation.dropShellPermissionIdentity(); 102 } 103 } 104 105 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 106 @Test getConnectedDevices()107 public void getConnectedDevices() { 108 assertThrows( 109 UnsupportedOperationException.class, 110 () -> mBluetoothGattServer.getConnectedDevices()); 111 } 112 113 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 114 @Test getConnectionState()115 public void getConnectionState() { 116 assertThrows( 117 UnsupportedOperationException.class, 118 () -> mBluetoothGattServer.getConnectionState(null)); 119 } 120 121 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 122 @Test getDevicesMatchingConnectionStates()123 public void getDevicesMatchingConnectionStates() { 124 assertThrows( 125 UnsupportedOperationException.class, 126 () -> mBluetoothGattServer.getDevicesMatchingConnectionStates(null)); 127 } 128 129 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 130 @Test getService()131 public void getService() throws InterruptedException { 132 // Service is null after initialization with public constructor 133 assertThat(mBluetoothGattServer.getService(TEST_UUID)).isNull(); 134 BluetoothGattCharacteristic characteristic = 135 new BluetoothGattCharacteristic(TEST_UUID, 0x0A, 0x11); 136 BluetoothGattService service = 137 new BluetoothGattService(TEST_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); 138 139 service.addCharacteristic(characteristic); 140 // If service is added successfully, latch.countDown() happens in the callback 141 mBluetoothGattServer.addService(service); 142 mLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS); 143 144 assertThat(mBluetoothGattServer.getService(TEST_UUID)).isEqualTo(service); 145 } 146 147 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 148 @Test getServices()149 public void getServices() { 150 assertThat(mBluetoothGattServer.getServices()).isEmpty(); 151 } 152 153 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 154 @Test readPhy()155 public void readPhy() { 156 BluetoothDevice testDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC"); 157 mUIAutomation.dropShellPermissionIdentity(); 158 assertThrows(SecurityException.class, () -> mBluetoothGattServer.readPhy(testDevice)); 159 } 160 161 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 162 @Test setPreferredPhy()163 public void setPreferredPhy() { 164 BluetoothDevice testDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC"); 165 mUIAutomation.dropShellPermissionIdentity(); 166 assertThrows( 167 SecurityException.class, 168 () -> 169 mBluetoothGattServer.setPreferredPhy( 170 testDevice, 171 BluetoothDevice.PHY_LE_1M_MASK, 172 BluetoothDevice.PHY_LE_1M_MASK, 173 BluetoothDevice.PHY_OPTION_NO_PREFERRED)); 174 } 175 176 @CddTest(requirements = {"7.4.3/C-2-1", "7.4.3/C-3-2"}) 177 @Test notifyCharacteristicChanged_withValueOverMaxLength()178 public void notifyCharacteristicChanged_withValueOverMaxLength() { 179 BluetoothDevice testDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC"); 180 BluetoothGattCharacteristic characteristic = 181 new BluetoothGattCharacteristic(TEST_UUID, 0x0A, 0x11); 182 BluetoothGattService service = 183 new BluetoothGattService(TEST_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); 184 service.addCharacteristic(characteristic); 185 186 // 512 is the max attribute length 187 byte[] notification = new byte[513]; 188 Arrays.fill(notification, (byte) 0x01); 189 190 assertThrows( 191 IllegalArgumentException.class, 192 () -> 193 mBluetoothGattServer.notifyCharacteristicChanged( 194 testDevice, characteristic, false, notification)); 195 } 196 } 197