Merge changes I7da699ca,I5a09efef into rvc-dev am: f21e3a96d3 am: 0865548a55 am: 9d3d199ebe
Change-Id: I34cbe6a5fc8686d18f7561eeee2a9a721e0bcd73
This commit is contained in:
@@ -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) {
|
||||
@@ -452,7 +454,8 @@ public class MtpDatabase implements AutoCloseable {
|
||||
}
|
||||
|
||||
@VisibleForNative
|
||||
private int getNumObjects(int storageID, int format, int parent) {
|
||||
@VisibleForTesting
|
||||
public int getNumObjects(int storageID, int format, int parent) {
|
||||
List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
|
||||
format, storageID);
|
||||
if (objs == null) {
|
||||
@@ -830,7 +833,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 +870,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;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class MtpStorage {
|
||||
|
||||
public MtpStorage(StorageVolume volume, int storageId) {
|
||||
mStorageId = storageId;
|
||||
mPath = volume.getInternalPath();
|
||||
mPath = volume.getPath();
|
||||
mDescription = volume.getDescription(null);
|
||||
mRemovable = volume.isRemovable();
|
||||
mMaxFileSize = volume.getMaxFileSize();
|
||||
|
||||
0
media/tests/MtpTests/res/raw/test_bad_thumb.jpg
Normal file
0
media/tests/MtpTests/res/raw/test_bad_thumb.jpg
Normal file
316
media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
Normal file
316
media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* 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.StorageManager;
|
||||
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.Assume;
|
||||
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 int SCND_STORAGE_ID = 0x20001;
|
||||
private static final String MAIN_STORAGE_ID_STR = Integer.toHexString(MAIN_STORAGE_ID);
|
||||
private static final String SCND_STORAGE_ID_STR = Integer.toHexString(SCND_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);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testMtpDatabaseExtStorage() throws IOException {
|
||||
int numObj;
|
||||
StorageVolume[] mVolumes;
|
||||
|
||||
logMethodName();
|
||||
|
||||
mVolumes = StorageManager.getVolumeList(UserHandle.myUserId(), 0);
|
||||
// Currently it may need manual setup for 2nd storage on virtual device testing.
|
||||
// Thus only run test when 2nd storage exists.
|
||||
Assume.assumeTrue(
|
||||
"Skip when 2nd storage not available, volume numbers = " + mVolumes.length,
|
||||
mVolumes.length >= 2);
|
||||
|
||||
for (int ii = 0; ii < mVolumes.length; ii++) {
|
||||
StorageVolume volume = mVolumes[ii];
|
||||
// Skip Actual Main storage (Internal Storage),
|
||||
// since we use manipulated path as testing Main storage
|
||||
if (ii > 0)
|
||||
mMtpDatabase.addStorage(volume);
|
||||
}
|
||||
|
||||
numObj = mMtpDatabase.getNumObjects(SCND_STORAGE_ID, 0, 0xFFFFFFFF);
|
||||
Assert.assertTrue(
|
||||
"Fail to get objects in 2nd storage, object numbers = " + numObj, numObj >= 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user