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