Add BluetoothOppSendFileInfoTest
Test: atest BluetoothInstrumentionTests
Bug: 237467631
Tag: #refactor
Change-Id: I8f25a113cb700fe545a525d7cf76753b751a27a4
(cherry picked from commit 8d86ccbad1272648b237f9c15ff140e8ad31403c)
Merged-In: I8f25a113cb700fe545a525d7cf76753b751a27a4
diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
index 8c494ed..8ce48c4 100644
--- a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -22,6 +22,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@@ -34,6 +35,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
/**
* Proxy class for method calls to help with unit testing
@@ -133,6 +135,23 @@
}
/**
+ * Proxies {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
+ */
+ public AssetFileDescriptor contentResolverOpenAssetFileDescriptor(
+ ContentResolver contentResolver, final Uri uri, final String mode)
+ throws FileNotFoundException {
+ return contentResolver.openAssetFileDescriptor(uri, mode);
+ }
+
+ /**
+ * Proxies {@link ContentResolver#openInputStream(Uri)}.
+ */
+ public InputStream contentResolverOpenInputStream(ContentResolver contentResolver,
+ final Uri uri) throws FileNotFoundException {
+ return contentResolver.openInputStream(uri);
+ }
+
+ /**
* Proxies {@link Context#sendBroadcast(Intent)}.
*/
public void contextSendBroadcast(Context context, @RequiresPermission Intent intent) {
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
index 46e3ba1..2adb8e5 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
@@ -42,6 +42,7 @@
import android.util.EventLog;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.R;
import java.io.File;
@@ -119,9 +120,10 @@
contentType = contentResolver.getType(uri);
Cursor metadataCursor;
try {
- metadataCursor = contentResolver.query(uri, new String[]{
- OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
- }, null, null, null);
+ metadataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
+ contentResolver, uri, new String[]{
+ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
+ }, null, null, null);
} catch (SQLiteException e) {
// some content providers don't support the DISPLAY_NAME or SIZE columns
metadataCursor = null;
@@ -180,7 +182,8 @@
// right size in _OpenableColumns.SIZE
// As a second source of getting the correct file length,
// get a file descriptor and get the stat length
- AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r");
+ AssetFileDescriptor fd = BluetoothMethodProxy.getInstance()
+ .contentResolverOpenAssetFileDescriptor(contentResolver, uri, "r");
long statLength = fd.getLength();
if (length != statLength && statLength > 0) {
Log.e(TAG, "Content provider length is wrong (" + Long.toString(length)
@@ -200,7 +203,8 @@
length = getStreamSize(is);
Log.w(TAG, "File length not provided. Length from stream = " + length);
// Reset the stream
- fd = contentResolver.openAssetFileDescriptor(uri, "r");
+ fd = BluetoothMethodProxy.getInstance()
+ .contentResolverOpenAssetFileDescriptor(contentResolver, uri, "r");
is = fd.createInputStream();
}
} catch (IOException e) {
@@ -219,14 +223,16 @@
if (is == null) {
try {
- is = (FileInputStream) contentResolver.openInputStream(uri);
+ is = (FileInputStream) BluetoothMethodProxy.getInstance()
+ .contentResolverOpenInputStream(contentResolver, uri);
// If the database doesn't contain the file size, get the size
// by reading through the entire stream
if (length == 0) {
length = getStreamSize(is);
// Reset the stream
- is = (FileInputStream) contentResolver.openInputStream(uri);
+ is = (FileInputStream) BluetoothMethodProxy.getInstance()
+ .contentResolverOpenInputStream(contentResolver, uri);
}
} catch (FileNotFoundException e) {
return SEND_FILE_INFO_ERROR;
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppSendFileInfoTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppSendFileInfoTest.java
new file mode 100644
index 0000000..756836a
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppSendFileInfoTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.bluetooth.opp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.provider.OpenableColumns;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.BluetoothMethodProxy;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothOppSendFileInfoTest {
+ Context mContext;
+ MatrixCursor mCursor;
+
+ @Mock
+ BluetoothMethodProxy mCallProxy;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ BluetoothMethodProxy.setInstanceForTesting(mCallProxy);
+ }
+
+ @After
+ public void tearDown() {
+ BluetoothMethodProxy.setInstanceForTesting(null);
+ }
+
+ @Test
+ public void createInstance_withFileInputStream() {
+ String fileName = "abc.txt";
+ String type = "text/plain";
+ long length = 10000;
+ FileInputStream inputStream = mock(FileInputStream.class);
+ int status = BluetoothShare.STATUS_SUCCESS;
+ BluetoothOppSendFileInfo info =
+ new BluetoothOppSendFileInfo(fileName, type, length, inputStream, status);
+
+ assertThat(info.mStatus).isEqualTo(status);
+ assertThat(info.mFileName).isEqualTo(fileName);
+ assertThat(info.mLength).isEqualTo(length);
+ assertThat(info.mInputStream).isEqualTo(inputStream);
+ assertThat(info.mMimetype).isEqualTo(type);
+ }
+
+ @Test
+ public void createInstance_withoutFileInputStream() {
+ String type = "text/plain";
+ long length = 10000;
+ int status = BluetoothShare.STATUS_SUCCESS;
+ String data = "Testing is boring";
+ BluetoothOppSendFileInfo info =
+ new BluetoothOppSendFileInfo(data, type, length, status);
+
+ assertThat(info.mStatus).isEqualTo(status);
+ assertThat(info.mData).isEqualTo(data);
+ assertThat(info.mLength).isEqualTo(length);
+ assertThat(info.mMimetype).isEqualTo(type);
+ }
+
+ @Test
+ public void generateFileInfo_withUnsupportedScheme_returnsSendFileInfoError() {
+ String type = "text/plain";
+ Uri uri = Uri.parse("https://www.google.com");
+
+ BluetoothOppSendFileInfo info = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri,
+ type, true);
+ assertThat(info).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+ }
+
+ @Test
+ public void generateFileInfo_withForbiddenExternalUri_returnsSendFileInfoError() {
+ String type = "text/plain";
+ Uri uri = Uri.parse("content://com.android.bluetooth.map.MmsFileProvider:8080");
+
+ BluetoothOppSendFileInfo info = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri,
+ type, true);
+ assertThat(info).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+ }
+
+ @Test
+ public void generateFileInfo_withoutPermissionForAccessingUri_returnsSendFileInfoError() {
+ String type = "text/plain";
+ Uri uri = Uri.parse("content:///hello/world");
+
+ doThrow(new SecurityException()).when(mCallProxy).contentResolverQuery(
+ any(), eq(uri), any(), any(), any(),
+ any());
+
+ BluetoothOppSendFileInfo info = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri,
+ type, true);
+ assertThat(info).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+ }
+
+ @Test
+ public void generateFileInfo_withUncorrectableMismatch_returnsSendFileInfoError()
+ throws IOException {
+ String type = "text/plain";
+ Uri uri = Uri.parse("content:///hello/world");
+
+ long fileLength = 0;
+ String fileName = "coolName.txt";
+
+ AssetFileDescriptor fd = mock(AssetFileDescriptor.class);
+ FileInputStream fs = mock(FileInputStream.class);
+
+ mCursor = new MatrixCursor(new String[]{
+ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
+ });
+ mCursor.addRow(new Object[]{fileName, fileLength});
+
+ doReturn(mCursor).when(mCallProxy).contentResolverQuery(
+ any(), eq(uri), any(), any(), any(),
+ any());
+
+ doReturn(fd).when(mCallProxy).contentResolverOpenAssetFileDescriptor(
+ any(), eq(uri), any());
+ doReturn(0L).when(fd).getLength();
+ doThrow(new IOException()).when(fd).createInputStream();
+ doReturn(fs).when(mCallProxy).contentResolverOpenInputStream(any(), eq(uri));
+ doReturn(0, -1).when(fs).read(any(), anyInt(), anyInt());
+
+ BluetoothOppSendFileInfo info = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri,
+ type, true);
+
+ assertThat(info).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+ }
+
+ @Test
+ public void generateFileInfo_withCorrectableMismatch_returnInfoWithCorrectLength()
+ throws IOException {
+ String type = "text/plain";
+ Uri uri = Uri.parse("content:///hello/world");
+
+ long fileLength = 0;
+ long correctFileLength = 1000;
+ String fileName = "coolName.txt";
+
+ AssetFileDescriptor fd = mock(AssetFileDescriptor.class);
+ FileInputStream fs = mock(FileInputStream.class);
+
+ mCursor = new MatrixCursor(new String[]{
+ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
+ });
+ mCursor.addRow(new Object[]{fileName, fileLength});
+
+ doReturn(mCursor).when(mCallProxy).contentResolverQuery(
+ any(), eq(uri), any(), any(), any(),
+ any());
+
+ doReturn(fd).when(mCallProxy).contentResolverOpenAssetFileDescriptor(
+ any(), eq(uri), any());
+ doReturn(0L).when(fd).getLength();
+ doReturn(fs).when(fd).createInputStream();
+
+ // the real size will be returned in getStreamSize(fs)
+ doReturn((int) correctFileLength, -1).when(fs).read(any(), anyInt(), anyInt());
+
+ BluetoothOppSendFileInfo info = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri,
+ type, true);
+
+ assertThat(info.mInputStream).isEqualTo(fs);
+ assertThat(info.mFileName).isEqualTo(fileName);
+ assertThat(info.mLength).isEqualTo(correctFileLength);
+ assertThat(info.mStatus).isEqualTo(0);
+ }
+
+ @Test
+ public void generateFileInfo_withFileUriNotInExternalStorageDir_returnFileErrorInfo() {
+ String type = "text/plain";
+ Uri uri = Uri.parse("file:///obviously/not/in/external/storage");
+
+ BluetoothOppSendFileInfo info = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri,
+ type, true);
+
+ assertThat(info).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+ }
+}