summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java10
-rw-r--r--media/tests/MtpTests/res/raw/test_bad_thumb.jpg0
-rw-r--r--media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java284
3 files changed, 291 insertions, 3 deletions
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index e6962a14a2d0..a01f0c6bfa38 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -46,6 +46,7 @@ import android.view.Display;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForNative;
+import com.android.internal.annotations.VisibleForTesting;
import dalvik.system.CloseGuard;
@@ -408,7 +409,8 @@ public class MtpDatabase implements AutoCloseable {
}
@VisibleForNative
- private int beginSendObject(String path, int format, int parent, int storageId) {
+ @VisibleForTesting
+ public int beginSendObject(String path, int format, int parent, int storageId) {
MtpStorageManager.MtpObject parentObj =
parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
if (parentObj == null) {
@@ -830,7 +832,8 @@ public class MtpDatabase implements AutoCloseable {
}
@VisibleForNative
- private boolean getThumbnailInfo(int handle, long[] outLongs) {
+ @VisibleForTesting
+ public boolean getThumbnailInfo(int handle, long[] outLongs) {
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
if (obj == null) {
return false;
@@ -866,7 +869,8 @@ public class MtpDatabase implements AutoCloseable {
}
@VisibleForNative
- private byte[] getThumbnailData(int handle) {
+ @VisibleForTesting
+ public byte[] getThumbnailData(int handle) {
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
if (obj == null) {
return null;
diff --git a/media/tests/MtpTests/res/raw/test_bad_thumb.jpg b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg
diff --git a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
new file mode 100644
index 000000000000..e0cd1917cff6
--- /dev/null
+++ b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2020 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 android.mtp;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.os.storage.StorageVolume;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Tests for MtpDatabase functionality.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MtpDatabaseTest {
+ private static final String TAG = MtpDatabaseTest.class.getSimpleName();
+
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ private static final File mBaseDir = InstrumentationRegistry.getContext().getExternalCacheDir();
+ private static final String MAIN_STORAGE_DIR = mBaseDir.getPath() + "/" + TAG + "/";
+ private static final String TEST_DIRNAME = "/TestIs";
+
+ private static final int MAIN_STORAGE_ID = 0x10001;
+ private static final String MAIN_STORAGE_ID_STR = Integer.toHexString(MAIN_STORAGE_ID);
+
+ private static final File mMainStorageDir = new File(MAIN_STORAGE_DIR);
+
+ private static ServerHolder mServerHolder;
+ private MtpDatabase mMtpDatabase;
+
+ private static void logMethodName() {
+ Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName());
+ }
+
+ private static File createNewDir(File parent, String name) {
+ File ret = new File(parent, name);
+ if (!ret.mkdir())
+ throw new AssertionError(
+ "Failed to create file: name=" + name + ", " + parent.getPath());
+ return ret;
+ }
+
+ private static void writeNewFile(File newFile) {
+ try {
+ new FileOutputStream(newFile).write(new byte[] {0, 0, 0});
+ } catch (IOException e) {
+ Assert.fail();
+ }
+ }
+
+ private static void writeNewFileFromByte(File newFile, byte[] byteData) {
+ try {
+ new FileOutputStream(newFile).write(byteData);
+ } catch (IOException e) {
+ Assert.fail();
+ }
+ }
+
+ private static class ServerHolder {
+ @NonNull final MtpServer server;
+ @NonNull final MtpDatabase database;
+
+ ServerHolder(@NonNull MtpServer server, @NonNull MtpDatabase database) {
+ Preconditions.checkNotNull(server);
+ Preconditions.checkNotNull(database);
+ this.server = server;
+ this.database = database;
+ }
+
+ void close() {
+ this.database.setServer(null);
+ }
+ }
+
+ private class OnServerTerminated implements Runnable {
+ @Override
+ public void run() {
+ if (mServerHolder == null) {
+ Log.e(TAG, "mServerHolder is unexpectedly null.");
+ return;
+ }
+ mServerHolder.close();
+ mServerHolder = null;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ FileUtils.deleteContentsAndDir(mMainStorageDir);
+ Assert.assertTrue(mMainStorageDir.mkdir());
+
+ StorageVolume mainStorage = new StorageVolume(MAIN_STORAGE_ID_STR,
+ mMainStorageDir, mMainStorageDir, "Primary Storage",
+ true, false, true, false, -1, UserHandle.CURRENT, "", "");
+
+ final StorageVolume primary = mainStorage;
+
+ mMtpDatabase = new MtpDatabase(mContext, null);
+
+ final MtpServer server =
+ new MtpServer(mMtpDatabase, null, false,
+ new OnServerTerminated(), Build.MANUFACTURER,
+ Build.MODEL, "1.0");
+ mMtpDatabase.setServer(server);
+ mServerHolder = new ServerHolder(server, mMtpDatabase);
+
+ mMtpDatabase.addStorage(mainStorage);
+ }
+
+ @After
+ public void tearDown() {
+ FileUtils.deleteContentsAndDir(mMainStorageDir);
+ }
+
+ private File stageFile(int resId, File file) throws IOException {
+ try (InputStream source = mContext.getResources().openRawResource(resId);
+ OutputStream target = new FileOutputStream(file)) {
+ android.os.FileUtils.copy(source, target);
+ }
+ return file;
+ }
+
+ /**
+ * Refer to BitmapUtilTests, but keep here,
+ * so as to be aware of the behavior or interface change there
+ */
+ private void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
+ Assert.assertTrue(
+ "Abnormal bitmap.width: " + bitmap.getWidth(), bitmap.getWidth() >= expectedWidth);
+ Assert.assertTrue(
+ "Abnormal bitmap.height: " + bitmap.getHeight(),
+ bitmap.getHeight() >= expectedHeight);
+ }
+
+ private byte[] createJpegRawData(int sourceWidth, int sourceHeight) throws IOException {
+ return createRawData(Bitmap.CompressFormat.JPEG, sourceWidth, sourceHeight);
+ }
+
+ private byte[] createPngRawData(int sourceWidth, int sourceHeight) throws IOException {
+ return createRawData(Bitmap.CompressFormat.PNG, sourceWidth, sourceHeight);
+ }
+
+ private byte[] createRawData(Bitmap.CompressFormat format, int sourceWidth, int sourceHeight)
+ throws IOException {
+ // Create a temp bitmap as our source
+ Bitmap b = Bitmap.createBitmap(sourceWidth, sourceHeight, Bitmap.Config.ARGB_8888);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ b.compress(format, 50, outputStream);
+ final byte[] data = outputStream.toByteArray();
+ outputStream.close();
+ return data;
+ }
+
+ /**
+ * Decodes the bitmap with the given sample size
+ */
+ public static Bitmap decodeBitmapFromBytes(byte[] bytes, int sampleSize) {
+ final BitmapFactory.Options options;
+ if (sampleSize <= 1) {
+ options = null;
+ } else {
+ options = new BitmapFactory.Options();
+ options.inSampleSize = sampleSize;
+ }
+ return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
+ }
+
+ private void testThumbnail(int fileHandle, File imgFile, boolean isGoodThumb)
+ throws IOException {
+ boolean isValidThumb;
+ byte[] byteArray;
+ long[] outLongs = new long[3];
+
+ isValidThumb = mMtpDatabase.getThumbnailInfo(fileHandle, outLongs);
+ Assert.assertTrue(isValidThumb);
+
+ byteArray = mMtpDatabase.getThumbnailData(fileHandle);
+
+ if (isGoodThumb) {
+ Assert.assertNotNull("Fail to generate thumbnail:" + imgFile.getPath(), byteArray);
+
+ Bitmap testBitmap = decodeBitmapFromBytes(byteArray, 4);
+ assertBitmapSize(32, 16, testBitmap);
+ } else Assert.assertNull("Bad image should return null:" + imgFile.getPath(), byteArray);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpDatabaseThumbnail() throws IOException {
+ int baseHandle;
+ int handleJpgBadThumb, handleJpgNoThumb, handleJpgBad;
+ int handlePng1, handlePngBad;
+ final String baseTestDirStr = mMainStorageDir.getPath() + TEST_DIRNAME;
+
+ logMethodName();
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Generate and insert tested files.");
+
+ baseHandle = mMtpDatabase.beginSendObject(baseTestDirStr,
+ MtpConstants.FORMAT_ASSOCIATION, 0, MAIN_STORAGE_ID);
+
+ File baseDir = new File(baseTestDirStr);
+ baseDir.mkdirs();
+
+ final File jpgfileBadThumb = new File(baseDir, "jpgfileBadThumb.jpg");
+ final File jpgFileNoThumb = new File(baseDir, "jpgFileNoThumb.jpg");
+ final File jpgfileBad = new File(baseDir, "jpgfileBad.jpg");
+ final File pngFile1 = new File(baseDir, "pngFile1.png");
+ final File pngFileBad = new File(baseDir, "pngFileBad.png");
+
+ handleJpgBadThumb = mMtpDatabase.beginSendObject(jpgfileBadThumb.getPath(),
+ MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+ stageFile(R.raw.test_bad_thumb, jpgfileBadThumb);
+
+ handleJpgNoThumb = mMtpDatabase.beginSendObject(jpgFileNoThumb.getPath(),
+ MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFileFromByte(jpgFileNoThumb, createJpegRawData(128, 64));
+
+ handleJpgBad = mMtpDatabase.beginSendObject(jpgfileBad.getPath(),
+ MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFile(jpgfileBad);
+
+ handlePng1 = mMtpDatabase.beginSendObject(pngFile1.getPath(),
+ MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFileFromByte(pngFile1, createPngRawData(128, 64));
+
+ handlePngBad = mMtpDatabase.beginSendObject(pngFileBad.getPath(),
+ MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFile(pngFileBad);
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Test bad JPG");
+
+ testThumbnail(handleJpgBadThumb, jpgfileBadThumb, false);
+
+ testThumbnail(handleJpgNoThumb, jpgFileNoThumb, false);
+
+ testThumbnail(handleJpgBad, jpgfileBad, false);
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Test PNG");
+
+ testThumbnail(handlePng1, pngFile1, true);
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Test bad PNG");
+
+ testThumbnail(handlePngBad, pngFileBad, false);
+ }
+}