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 com.android.bluetooth.opp; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.mockito.ArgumentMatchers.argThat; 23 import static org.mockito.ArgumentMatchers.nullable; 24 import static org.mockito.Mockito.any; 25 import static org.mockito.Mockito.anyInt; 26 import static org.mockito.Mockito.doAnswer; 27 import static org.mockito.Mockito.doNothing; 28 import static org.mockito.Mockito.doReturn; 29 import static org.mockito.Mockito.eq; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.spy; 32 import static org.mockito.Mockito.verify; 33 34 import android.bluetooth.BluetoothAdapter; 35 import android.bluetooth.BluetoothDevice; 36 import android.content.Context; 37 import android.content.ContextWrapper; 38 import android.content.Intent; 39 import android.content.pm.PackageManager; 40 import android.content.pm.ResolveInfo; 41 import android.database.Cursor; 42 import android.net.Uri; 43 import android.os.ParcelFileDescriptor; 44 45 import androidx.test.platform.app.InstrumentationRegistry; 46 47 import com.android.bluetooth.BluetoothMethodProxy; 48 import com.android.bluetooth.R; 49 import com.android.bluetooth.TestUtils; 50 import com.android.bluetooth.opp.BluetoothOppTestUtils.CursorMockData; 51 52 import org.junit.After; 53 import org.junit.Before; 54 import org.junit.Rule; 55 import org.junit.Test; 56 import org.mockito.Mock; 57 import org.mockito.Spy; 58 import org.mockito.junit.MockitoJUnit; 59 import org.mockito.junit.MockitoRule; 60 61 import java.util.List; 62 import java.util.Objects; 63 import java.util.concurrent.atomic.AtomicInteger; 64 65 public class BluetoothOppUtilityTest { 66 67 private static final Uri CORRECT_FORMAT_BUT_INVALID_FILE_URI = 68 Uri.parse("content://com.android.bluetooth.opp/btopp/0123455343467"); 69 private static final Uri INCORRECT_FORMAT_URI = Uri.parse("www.google.com"); 70 71 Context mContext; 72 @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); 73 74 @Mock Cursor mCursor; 75 76 @Spy BluetoothMethodProxy mCallProxy = BluetoothMethodProxy.getInstance(); 77 78 @Before setUp()79 public void setUp() throws Exception { 80 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 81 BluetoothMethodProxy.setInstanceForTesting(mCallProxy); 82 TestUtils.setUpUiTest(); 83 } 84 85 @After tearDown()86 public void tearDown() throws Exception { 87 TestUtils.tearDownUiTest(); 88 89 BluetoothMethodProxy.setInstanceForTesting(null); 90 } 91 92 @Test isBluetoothShareUri_correctlyCheckUri()93 public void isBluetoothShareUri_correctlyCheckUri() { 94 assertThat(BluetoothOppUtility.isBluetoothShareUri(INCORRECT_FORMAT_URI)).isFalse(); 95 assertThat(BluetoothOppUtility.isBluetoothShareUri(CORRECT_FORMAT_BUT_INVALID_FILE_URI)) 96 .isTrue(); 97 } 98 99 @Test queryRecord_withInvalidFileUrl_returnsNull()100 public void queryRecord_withInvalidFileUrl_returnsNull() { 101 doReturn(null) 102 .when(mCallProxy) 103 .contentResolverQuery( 104 any(), 105 eq(CORRECT_FORMAT_BUT_INVALID_FILE_URI), 106 eq(null), 107 eq(null), 108 eq(null), 109 eq(null)); 110 assertThat(BluetoothOppUtility.queryRecord(mContext, CORRECT_FORMAT_BUT_INVALID_FILE_URI)) 111 .isNull(); 112 } 113 114 @Test queryRecord_mockCursor_returnsInstance()115 public void queryRecord_mockCursor_returnsInstance() { 116 String destinationValue = "AA:BB:CC:00:11:22"; 117 118 doReturn(mCursor) 119 .when(mCallProxy) 120 .contentResolverQuery( 121 any(), 122 eq(CORRECT_FORMAT_BUT_INVALID_FILE_URI), 123 eq(null), 124 eq(null), 125 eq(null), 126 eq(null)); 127 doReturn(true).when(mCursor).moveToFirst(); 128 doReturn(destinationValue).when(mCursor).getString(anyInt()); 129 assertThat(BluetoothOppUtility.queryRecord(mContext, CORRECT_FORMAT_BUT_INVALID_FILE_URI)) 130 .isInstanceOf(BluetoothOppTransferInfo.class); 131 } 132 133 @Test queryTransfersInBatch_returnsCorrectUrlArrayList()134 public void queryTransfersInBatch_returnsCorrectUrlArrayList() { 135 long timestampValue = 123456; 136 String where = BluetoothShare.TIMESTAMP + " == " + timestampValue; 137 AtomicInteger cnt = new AtomicInteger(1); 138 139 doReturn(mCursor) 140 .when(mCallProxy) 141 .contentResolverQuery( 142 any(), 143 eq(BluetoothShare.CONTENT_URI), 144 eq(new String[] {BluetoothShare._DATA}), 145 eq(where), 146 eq(null), 147 eq(BluetoothShare._ID)); 148 149 doAnswer(invocation -> cnt.incrementAndGet() > 5).when(mCursor).isAfterLast(); 150 doReturn(CORRECT_FORMAT_BUT_INVALID_FILE_URI.toString()).when(mCursor).getString(0); 151 152 List<String> answer = BluetoothOppUtility.queryTransfersInBatch(mContext, timestampValue); 153 for (String url : answer) { 154 assertThat(url).isEqualTo(CORRECT_FORMAT_BUT_INVALID_FILE_URI.toString()); 155 } 156 } 157 158 @Test openReceivedFile_fileNotExist()159 public void openReceivedFile_fileNotExist() { 160 161 Uri contentResolverUri = Uri.parse("content://com.android.bluetooth.opp/btopp/0123"); 162 Uri fileUri = Uri.parse("content:///tmp/randomFileName.txt"); 163 164 Context spiedContext = spy(new ContextWrapper(mContext)); 165 166 doReturn(0).when(mCallProxy).contentResolverDelete(any(), any(), any(), any()); 167 doReturn(mCursor) 168 .when(mCallProxy) 169 .contentResolverQuery( 170 any(), eq(contentResolverUri), any(), eq(null), eq(null), eq(null)); 171 172 doReturn(true).when(mCursor).moveToFirst(); 173 doReturn(fileUri.toString()).when(mCursor).getString(anyInt()); 174 175 doReturn(0) 176 .when(mCallProxy) 177 .contentResolverDelete( 178 any(), any(), nullable(String.class), nullable(String[].class)); 179 180 // Do nothing since we don't need the actual activity to be launched. 181 doNothing().when(spiedContext).startActivity(any()); 182 183 BluetoothOppUtility.openReceivedFile( 184 spiedContext, "randomFileName.txt", "text/plain", 0L, contentResolverUri); 185 186 verify(spiedContext) 187 .startActivity( 188 argThat( 189 argument -> 190 Objects.equals( 191 argument.getComponent().getClassName(), 192 BluetoothOppBtErrorActivity.class.getName()))); 193 } 194 195 @Test openReceivedFile_fileExist_HandlingApplicationExist()196 public void openReceivedFile_fileExist_HandlingApplicationExist() throws Exception { 197 Uri contentResolverUri = Uri.parse("content://com.android.bluetooth.opp/btopp/0123"); 198 Uri fileUri = Uri.parse("content:///tmp/randomFileName.txt"); 199 200 ParcelFileDescriptor pfd = mock(ParcelFileDescriptor.class); 201 202 Context spiedContext = spy(new ContextWrapper(mContext)); 203 // Control BluetoothOppUtility#fileExists flow 204 doReturn(mCursor) 205 .when(mCallProxy) 206 .contentResolverQuery( 207 any(), eq(contentResolverUri), any(), eq(null), eq(null), eq(null)); 208 209 doReturn(true).when(mCursor).moveToFirst(); 210 doReturn(fileUri.toString()).when(mCursor).getString(anyInt()); 211 212 doReturn(0).when(mCallProxy).contentResolverDelete(any(), any(), any(), any()); 213 doReturn(pfd).when(mCallProxy).contentResolverOpenFileDescriptor(any(), eq(fileUri), any()); 214 215 // Control BluetoothOppUtility#isRecognizedFileType flow 216 PackageManager mockManager = mock(PackageManager.class); 217 doReturn(mockManager).when(spiedContext).getPackageManager(); 218 doReturn(List.of(new ResolveInfo())) 219 .when(mockManager) 220 .queryIntentActivities(any(), anyInt()); 221 222 BluetoothOppUtility.openReceivedFile( 223 spiedContext, "randomFileName.txt", "text/plain", 0L, contentResolverUri); 224 225 verify(spiedContext) 226 .startActivity( 227 argThat( 228 argument -> 229 Objects.equals( 230 argument.getData(), 231 Uri.parse( 232 "content:///tmp/randomFileName.txt")) 233 && Objects.equals( 234 argument.getAction(), Intent.ACTION_VIEW))); 235 236 verify(pfd).close(); 237 } 238 239 @Test openReceivedFile_fileExist_HandlingApplicationNotExist()240 public void openReceivedFile_fileExist_HandlingApplicationNotExist() throws Exception { 241 Uri contentResolverUri = Uri.parse("content://com.android.bluetooth.opp/btopp/0123"); 242 Uri fileUri = Uri.parse("content:///tmp/randomFileName.txt"); 243 ParcelFileDescriptor pfd = mock(ParcelFileDescriptor.class); 244 245 Context spiedContext = spy(new ContextWrapper(mContext)); 246 // Control BluetoothOppUtility#fileExists flow 247 doReturn(mCursor) 248 .when(mCallProxy) 249 .contentResolverQuery( 250 any(), eq(contentResolverUri), any(), eq(null), eq(null), eq(null)); 251 252 doReturn(true).when(mCursor).moveToFirst(); 253 doReturn(fileUri.toString()).when(mCursor).getString(anyInt()); 254 255 doReturn(0).when(mCallProxy).contentResolverDelete(any(), any(), any(), any()); 256 doReturn(pfd).when(mCallProxy).contentResolverOpenFileDescriptor(any(), eq(fileUri), any()); 257 258 // Control BluetoothOppUtility#isRecognizedFileType flow 259 PackageManager mockManager = mock(PackageManager.class); 260 doReturn(mockManager).when(spiedContext).getPackageManager(); 261 doReturn(List.of()).when(mockManager).queryIntentActivities(any(), anyInt()); 262 263 // Do nothing since we don't need the actual activity to be launched. 264 doNothing().when(spiedContext).startActivity(any()); 265 266 BluetoothOppUtility.openReceivedFile( 267 spiedContext, "randomFileName.txt", "text/plain", 0L, contentResolverUri); 268 269 verify(spiedContext) 270 .startActivity( 271 argThat( 272 argument -> 273 argument.getComponent() 274 .getClassName() 275 .equals( 276 BluetoothOppBtErrorActivity.class 277 .getName()))); 278 verify(pfd).close(); 279 } 280 281 @Test fillRecord_filledAllProperties()282 public void fillRecord_filledAllProperties() { 283 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 284 int idValue = 1234; 285 int directionValue = BluetoothShare.DIRECTION_OUTBOUND; 286 long totalBytesValue = 10; 287 long currentBytesValue = 1; 288 int statusValue = BluetoothShare.STATUS_PENDING; 289 Long timestampValue = 123456789L; 290 String destinationValue = "AA:BB:CC:00:11:22"; 291 String fileNameValue = mContext.getString(R.string.unknown_file); 292 String fileTypeValue = "text/plain"; 293 BluetoothDevice remoteDevice = adapter.getRemoteDevice(destinationValue); 294 String deviceNameValue = 295 BluetoothOppManager.getInstance(mContext).getDeviceName(remoteDevice); 296 297 List<CursorMockData> cursorMockDataList = 298 List.of( 299 new CursorMockData(BluetoothShare._ID, 0, idValue), 300 new CursorMockData(BluetoothShare.STATUS, 1, statusValue), 301 new CursorMockData(BluetoothShare.DIRECTION, 2, directionValue), 302 new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, totalBytesValue), 303 new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, currentBytesValue), 304 new CursorMockData(BluetoothShare.TIMESTAMP, 5, timestampValue), 305 new CursorMockData(BluetoothShare.DESTINATION, 6, destinationValue), 306 new CursorMockData(BluetoothShare._DATA, 7, null), 307 new CursorMockData(BluetoothShare.FILENAME_HINT, 8, null), 308 new CursorMockData(BluetoothShare.MIMETYPE, 9, fileTypeValue)); 309 310 BluetoothOppTestUtils.setUpMockCursor(mCursor, cursorMockDataList); 311 312 BluetoothOppTransferInfo info = new BluetoothOppTransferInfo(); 313 BluetoothOppUtility.fillRecord(mContext, mCursor, info); 314 315 assertThat(info.mID).isEqualTo(idValue); 316 assertThat(info.mStatus).isEqualTo(statusValue); 317 assertThat(info.mDirection).isEqualTo(directionValue); 318 assertThat(info.mTotalBytes).isEqualTo(totalBytesValue); 319 assertThat(info.mCurrentBytes).isEqualTo(currentBytesValue); 320 assertThat(info.mTimeStamp).isEqualTo(timestampValue); 321 assertThat(info.mDestAddr).isEqualTo(destinationValue); 322 assertThat(info.mFileUri).isEqualTo(null); 323 assertThat(info.mFileType).isEqualTo(fileTypeValue); 324 assertThat(info.mDeviceName).isEqualTo(deviceNameValue); 325 assertThat(info.mHandoverInitiated).isEqualTo(false); 326 assertThat(info.mFileName).isEqualTo(fileNameValue); 327 } 328 329 @Test fileExists_returnFalse()330 public void fileExists_returnFalse() { 331 assertThat(BluetoothOppUtility.fileExists(mContext, CORRECT_FORMAT_BUT_INVALID_FILE_URI)) 332 .isFalse(); 333 } 334 335 @Test isRecognizedFileType_withWrongFileUriAndMimeType_returnFalse()336 public void isRecognizedFileType_withWrongFileUriAndMimeType_returnFalse() { 337 assertThat( 338 BluetoothOppUtility.isRecognizedFileType( 339 mContext, CORRECT_FORMAT_BUT_INVALID_FILE_URI, "aWrongMimeType")) 340 .isFalse(); 341 } 342 343 @Test formatProgressText()344 public void formatProgressText() { 345 assertThat(BluetoothOppUtility.formatProgressText(100, 42)).isEqualTo("42%"); 346 } 347 348 @Test formatResultText()349 public void formatResultText() { 350 String text = BluetoothOppUtility.formatResultText(1, 2, mContext); 351 352 assertThat(text.contains("1") && text.contains("2")).isTrue(); 353 } 354 355 @Test getStatusDescription_returnCorrectString()356 public void getStatusDescription_returnCorrectString() { 357 String deviceName = "randomName"; 358 assertThat( 359 BluetoothOppUtility.getStatusDescription( 360 mContext, BluetoothShare.STATUS_PENDING, deviceName)) 361 .isEqualTo(mContext.getString(R.string.status_pending)); 362 assertThat( 363 BluetoothOppUtility.getStatusDescription( 364 mContext, BluetoothShare.STATUS_RUNNING, deviceName)) 365 .isEqualTo(mContext.getString(R.string.status_running)); 366 assertThat( 367 BluetoothOppUtility.getStatusDescription( 368 mContext, BluetoothShare.STATUS_SUCCESS, deviceName)) 369 .isEqualTo(mContext.getString(R.string.status_success)); 370 assertThat( 371 BluetoothOppUtility.getStatusDescription( 372 mContext, BluetoothShare.STATUS_NOT_ACCEPTABLE, deviceName)) 373 .isEqualTo(mContext.getString(R.string.status_not_accept)); 374 assertThat( 375 BluetoothOppUtility.getStatusDescription( 376 mContext, BluetoothShare.STATUS_FORBIDDEN, deviceName)) 377 .isEqualTo(mContext.getString(R.string.status_forbidden)); 378 assertThat( 379 BluetoothOppUtility.getStatusDescription( 380 mContext, BluetoothShare.STATUS_CANCELED, deviceName)) 381 .isEqualTo(mContext.getString(R.string.status_canceled)); 382 assertThat( 383 BluetoothOppUtility.getStatusDescription( 384 mContext, BluetoothShare.STATUS_FILE_ERROR, deviceName)) 385 .isEqualTo(mContext.getString(R.string.status_file_error)); 386 assertThat( 387 BluetoothOppUtility.getStatusDescription( 388 mContext, BluetoothShare.STATUS_CONNECTION_ERROR, deviceName)) 389 .isEqualTo(mContext.getString(R.string.status_connection_error)); 390 assertThat( 391 BluetoothOppUtility.getStatusDescription( 392 mContext, BluetoothShare.STATUS_ERROR_NO_SDCARD, deviceName)) 393 .isEqualTo( 394 mContext.getString( 395 BluetoothOppUtility.deviceHasNoSdCard() 396 ? R.string.status_no_sd_card_nosdcard 397 : R.string.status_no_sd_card_default)); 398 assertThat( 399 BluetoothOppUtility.getStatusDescription( 400 mContext, BluetoothShare.STATUS_ERROR_SDCARD_FULL, deviceName)) 401 .isEqualTo( 402 mContext.getString( 403 BluetoothOppUtility.deviceHasNoSdCard() 404 ? R.string.bt_sm_2_1_nosdcard 405 : R.string.bt_sm_2_1_default)); 406 assertThat( 407 BluetoothOppUtility.getStatusDescription( 408 mContext, BluetoothShare.STATUS_BAD_REQUEST, deviceName)) 409 .isEqualTo(mContext.getString(R.string.status_protocol_error)); 410 assertThat(BluetoothOppUtility.getStatusDescription(mContext, 12345465, deviceName)) 411 .isEqualTo(mContext.getString(R.string.status_unknown_error)); 412 } 413 414 @Test originalUri_trimBeforeAt()415 public void originalUri_trimBeforeAt() { 416 Uri originalUri = Uri.parse("com.android.bluetooth.opp.BluetoothOppSendFileInfo"); 417 Uri uri = Uri.parse("com.android.bluetooth.opp.BluetoothOppSendFileInfo@dfe15a6"); 418 assertThat(BluetoothOppUtility.originalUri(uri)).isEqualTo(originalUri); 419 } 420 421 @Test fileInfo_testFileInfoFunctions()422 public void fileInfo_testFileInfoFunctions() { 423 assertThat(BluetoothOppUtility.getSendFileInfo(CORRECT_FORMAT_BUT_INVALID_FILE_URI)) 424 .isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR); 425 assertThat( 426 BluetoothOppUtility.generateUri( 427 CORRECT_FORMAT_BUT_INVALID_FILE_URI, 428 BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR) 429 .toString()) 430 .contains(CORRECT_FORMAT_BUT_INVALID_FILE_URI.toString()); 431 try { 432 BluetoothOppUtility.putSendFileInfo( 433 CORRECT_FORMAT_BUT_INVALID_FILE_URI, 434 BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR); 435 BluetoothOppUtility.closeSendFileInfo(CORRECT_FORMAT_BUT_INVALID_FILE_URI); 436 } catch (Exception e) { 437 assertWithMessage("Exception should not happen. " + e).fail(); 438 } 439 } 440 } 441