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