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.nearby.cts;
18 
19 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
20 import static android.Manifest.permission.READ_DEVICE_CONFIG;
21 import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
22 import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
23 import static android.nearby.ScanCallback.ERROR_UNSUPPORTED;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.junit.Assert.assertThrows;
28 import static org.junit.Assume.assumeTrue;
29 
30 import android.app.UiAutomation;
31 import android.bluetooth.test_utils.EnableBluetoothRule;
32 import android.content.Context;
33 import android.location.LocationManager;
34 import android.nearby.BroadcastCallback;
35 import android.nearby.BroadcastRequest;
36 import android.nearby.NearbyDevice;
37 import android.nearby.NearbyManager;
38 import android.nearby.OffloadCapability;
39 import android.nearby.PresenceBroadcastRequest;
40 import android.nearby.PresenceDevice;
41 import android.nearby.PrivateCredential;
42 import android.nearby.ScanCallback;
43 import android.nearby.ScanRequest;
44 import android.os.Build;
45 import android.os.Process;
46 import android.os.UserHandle;
47 import android.provider.DeviceConfig;
48 
49 import androidx.annotation.NonNull;
50 import androidx.annotation.RequiresApi;
51 import androidx.test.InstrumentationRegistry;
52 import androidx.test.ext.junit.runners.AndroidJUnit4;
53 import androidx.test.filters.SdkSuppress;
54 
55 import com.android.compatibility.common.util.SystemUtil;
56 import com.android.modules.utils.build.SdkLevel;
57 
58 import org.junit.Before;
59 import org.junit.ClassRule;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 
63 import java.util.Collections;
64 import java.util.List;
65 import java.util.concurrent.CountDownLatch;
66 import java.util.concurrent.Executor;
67 import java.util.concurrent.Executors;
68 import java.util.concurrent.TimeUnit;
69 import java.util.function.Consumer;
70 
71 /**
72  * TODO(b/215435939) This class doesn't include any logic yet. Because SELinux denies access to
73  * NearbyManager.
74  */
75 @RunWith(AndroidJUnit4.class)
76 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
77 public class NearbyManagerTest {
78 
79     @ClassRule public static final EnableBluetoothRule sEnableBluetooth = new EnableBluetoothRule();
80 
81     private static final byte[] SALT = new byte[]{1, 2};
82     private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
83     private static final byte[] META_DATA_ENCRYPTION_KEY = new byte[14];
84     private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1};
85     private static final String DEVICE_NAME = "test_device";
86     private static final int BLE_MEDIUM = 1;
87 
88     private Context mContext;
89     private NearbyManager mNearbyManager;
90     private UiAutomation mUiAutomation =
91             InstrumentationRegistry.getInstrumentation().getUiAutomation();
92 
93     private ScanRequest mScanRequest = new ScanRequest.Builder()
94             .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
95             .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
96             .setBleEnabled(true)
97             .build();
98     private PresenceDevice.Builder mBuilder =
99             new PresenceDevice.Builder("deviceId", SALT, SECRET_ID, META_DATA_ENCRYPTION_KEY);
100 
101     private  ScanCallback mScanCallback = new ScanCallback() {
102         @Override
103         public void onDiscovered(@NonNull NearbyDevice device) {
104         }
105 
106         @Override
107         public void onUpdated(@NonNull NearbyDevice device) {
108         }
109 
110         @Override
111         public void onLost(@NonNull NearbyDevice device) {
112         }
113 
114         @Override
115         public void onError(int errorCode) {
116         }
117     };
118 
119     private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
120 
121     @Before
setUp()122     public void setUp() {
123         mUiAutomation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG,
124                 BLUETOOTH_PRIVILEGED);
125         String nameSpace = SdkLevel.isAtLeastU() ? DeviceConfig.NAMESPACE_NEARBY
126                 : DeviceConfig.NAMESPACE_TETHERING;
127         DeviceConfig.setProperty(nameSpace,
128                 "nearby_enable_presence_broadcast_legacy",
129                 "true", false);
130 
131         mContext = InstrumentationRegistry.getContext();
132         mNearbyManager = mContext.getSystemService(NearbyManager.class);
133     }
134 
135     @Test
136     @SdkSuppress(minSdkVersion = 32, codeName = "T")
test_startAndStopScan()137     public void test_startAndStopScan() {
138         mNearbyManager.startScan(mScanRequest, EXECUTOR, mScanCallback);
139         mNearbyManager.stopScan(mScanCallback);
140     }
141 
142     @Test
143     @SdkSuppress(minSdkVersion = 32, codeName = "T")
test_startScan_noPrivilegedPermission()144     public void test_startScan_noPrivilegedPermission() {
145         mUiAutomation.dropShellPermissionIdentity();
146         assertThrows(SecurityException.class, () -> mNearbyManager
147                 .startScan(mScanRequest, EXECUTOR, mScanCallback));
148     }
149 
150     @Test
151     @SdkSuppress(minSdkVersion = 32, codeName = "T")
test_stopScan_noPrivilegedPermission()152     public void test_stopScan_noPrivilegedPermission() {
153         mNearbyManager.startScan(mScanRequest, EXECUTOR, mScanCallback);
154         mUiAutomation.dropShellPermissionIdentity();
155         assertThrows(SecurityException.class, () -> mNearbyManager.stopScan(mScanCallback));
156     }
157 
158     @Test
159     @SdkSuppress(minSdkVersion = 32, codeName = "T")
testStartStopBroadcast()160     public void testStartStopBroadcast() throws InterruptedException {
161         PrivateCredential credential = new PrivateCredential.Builder(SECRET_ID, AUTHENTICITY_KEY,
162                 META_DATA_ENCRYPTION_KEY, DEVICE_NAME)
163                 .setIdentityType(IDENTITY_TYPE_PRIVATE)
164                 .build();
165         BroadcastRequest broadcastRequest =
166                 new PresenceBroadcastRequest.Builder(
167                         Collections.singletonList(BLE_MEDIUM), SALT, credential)
168                         .addAction(123)
169                         .build();
170 
171         CountDownLatch latch = new CountDownLatch(1);
172         BroadcastCallback callback = status -> {
173             latch.countDown();
174             assertThat(status).isEqualTo(BroadcastCallback.STATUS_OK);
175         };
176         mNearbyManager.startBroadcast(broadcastRequest, Executors.newSingleThreadExecutor(),
177                 callback);
178         latch.await(10, TimeUnit.SECONDS);
179         mNearbyManager.stopBroadcast(callback);
180     }
181 
182     @Test
183     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
queryOffloadScanSupport()184     public void queryOffloadScanSupport() {
185         OffloadCallback callback = new OffloadCallback();
186         mNearbyManager.queryOffloadCapability(EXECUTOR, callback);
187     }
188 
189     @Test
190     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
testAllCallbackMethodsExits()191     public void testAllCallbackMethodsExits() {
192         mScanCallback.onDiscovered(mBuilder.setRssi(-10).build());
193         mScanCallback.onUpdated(mBuilder.setRssi(-5).build());
194         mScanCallback.onLost(mBuilder.setRssi(-8).build());
195         mScanCallback.onError(ERROR_UNSUPPORTED);
196     }
197 
198     @Test
testsetPoweredOffFindingEphemeralIds()199     public void testsetPoweredOffFindingEphemeralIds() {
200         // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
201         assumeTrue(SdkLevel.isAtLeastV());
202         // Only test supporting devices.
203         if (mNearbyManager.getPoweredOffFindingMode()
204                 == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
205 
206         mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20], new byte[20]));
207     }
208 
209     @Test
testsetPoweredOffFindingEphemeralIds_noPrivilegedPermission()210     public void testsetPoweredOffFindingEphemeralIds_noPrivilegedPermission() {
211         // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
212         assumeTrue(SdkLevel.isAtLeastV());
213         // Only test supporting devices.
214         if (mNearbyManager.getPoweredOffFindingMode()
215                 == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
216 
217         mUiAutomation.dropShellPermissionIdentity();
218 
219         assertThrows(SecurityException.class,
220                 () -> mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20])));
221     }
222 
223 
224     @Test
testSetAndGetPoweredOffFindingMode_enabled()225     public void testSetAndGetPoweredOffFindingMode_enabled() {
226         // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
227         assumeTrue(SdkLevel.isAtLeastV());
228         // Only test supporting devices.
229         if (mNearbyManager.getPoweredOffFindingMode()
230                 == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
231 
232         enableLocation();
233         // enableLocation() has dropped shell permission identity.
234         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
235 
236         mNearbyManager.setPoweredOffFindingMode(
237                 NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
238         assertThat(mNearbyManager.getPoweredOffFindingMode())
239                 .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
240     }
241 
242     @Test
testSetAndGetPoweredOffFindingMode_disabled()243     public void testSetAndGetPoweredOffFindingMode_disabled() {
244         // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
245         assumeTrue(SdkLevel.isAtLeastV());
246         // Only test supporting devices.
247         if (mNearbyManager.getPoweredOffFindingMode()
248                 == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
249 
250         mNearbyManager.setPoweredOffFindingMode(
251                 NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
252         assertThat(mNearbyManager.getPoweredOffFindingMode())
253                 .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
254     }
255 
256     @Test
testSetPoweredOffFindingMode_noPrivilegedPermission()257     public void testSetPoweredOffFindingMode_noPrivilegedPermission() {
258         // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
259         assumeTrue(SdkLevel.isAtLeastV());
260         // Only test supporting devices.
261         if (mNearbyManager.getPoweredOffFindingMode()
262                 == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
263 
264         enableLocation();
265         mUiAutomation.dropShellPermissionIdentity();
266 
267         assertThrows(SecurityException.class, () -> mNearbyManager
268                 .setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED));
269     }
270 
271     @Test
testGetPoweredOffFindingMode_noPrivilegedPermission()272     public void testGetPoweredOffFindingMode_noPrivilegedPermission() {
273         // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
274         assumeTrue(SdkLevel.isAtLeastV());
275         // Only test supporting devices.
276         if (mNearbyManager.getPoweredOffFindingMode()
277                 == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
278 
279         mUiAutomation.dropShellPermissionIdentity();
280 
281         assertThrows(SecurityException.class, () -> mNearbyManager.getPoweredOffFindingMode());
282     }
283 
enableLocation()284     private void enableLocation() {
285         LocationManager locationManager = mContext.getSystemService(LocationManager.class);
286         UserHandle user = Process.myUserHandle();
287         SystemUtil.runWithShellPermissionIdentity(
288                 mUiAutomation, () -> locationManager.setLocationEnabledForUser(true, user));
289     }
290 
291     private static class OffloadCallback implements Consumer<OffloadCapability> {
292         @Override
accept(OffloadCapability aBoolean)293         public void accept(OffloadCapability aBoolean) {
294             // no-op for now
295         }
296     }
297 }
298