Loading android/app/src/com/android/bluetooth/BluetoothMethodProxy.java +19 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.ContentResolver; 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; Loading @@ -34,6 +35,7 @@ import com.android.obex.HeaderSet; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; /** * Proxy class for method calls to help with unit testing Loading Loading @@ -132,6 +134,23 @@ public class BluetoothMethodProxy { return contentResolver.openFileDescriptor(uri, mode); } /** * 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)}. */ Loading android/app/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java +13 −7 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.provider.OpenableColumns; import android.util.EventLog; import android.util.Log; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.R; import java.io.File; Loading Loading @@ -119,7 +120,8 @@ public class BluetoothOppSendFileInfo { contentType = contentResolver.getType(uri); Cursor metadataCursor; try { metadataCursor = contentResolver.query(uri, new String[]{ metadataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery( contentResolver, uri, new String[]{ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }, null, null, null); } catch (SQLiteException e) { Loading Loading @@ -180,7 +182,8 @@ public class BluetoothOppSendFileInfo { // 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) Loading @@ -200,7 +203,8 @@ public class BluetoothOppSendFileInfo { 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) { Loading @@ -219,14 +223,16 @@ public class BluetoothOppSendFileInfo { 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; Loading android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppSendFileInfoTest.java 0 → 100644 +220 −0 Original line number Diff line number Diff line /* * 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); } } Loading
android/app/src/com/android/bluetooth/BluetoothMethodProxy.java +19 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.ContentResolver; 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; Loading @@ -34,6 +35,7 @@ import com.android.obex.HeaderSet; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; /** * Proxy class for method calls to help with unit testing Loading Loading @@ -132,6 +134,23 @@ public class BluetoothMethodProxy { return contentResolver.openFileDescriptor(uri, mode); } /** * 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)}. */ Loading
android/app/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java +13 −7 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.provider.OpenableColumns; import android.util.EventLog; import android.util.Log; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.R; import java.io.File; Loading Loading @@ -119,7 +120,8 @@ public class BluetoothOppSendFileInfo { contentType = contentResolver.getType(uri); Cursor metadataCursor; try { metadataCursor = contentResolver.query(uri, new String[]{ metadataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery( contentResolver, uri, new String[]{ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }, null, null, null); } catch (SQLiteException e) { Loading Loading @@ -180,7 +182,8 @@ public class BluetoothOppSendFileInfo { // 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) Loading @@ -200,7 +203,8 @@ public class BluetoothOppSendFileInfo { 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) { Loading @@ -219,14 +223,16 @@ public class BluetoothOppSendFileInfo { 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; Loading
android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppSendFileInfoTest.java 0 → 100644 +220 −0 Original line number Diff line number Diff line /* * 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); } }