Merge changes I7da699ca,I5a09efef into rvc-dev am: f21e3a96d3 am: 0865548a55
Change-Id: Ie254417bf1d604f82b1140ff8da19021af9373c6
This commit is contained in:
@@ -46,6 +46,7 @@ import android.view.Display;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForNative;
|
import com.android.internal.annotations.VisibleForNative;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import dalvik.system.CloseGuard;
|
import dalvik.system.CloseGuard;
|
||||||
|
|
||||||
@@ -408,7 +409,8 @@ public class MtpDatabase implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForNative
|
@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 =
|
MtpStorageManager.MtpObject parentObj =
|
||||||
parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
|
parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
|
||||||
if (parentObj == null) {
|
if (parentObj == null) {
|
||||||
@@ -452,7 +454,8 @@ public class MtpDatabase implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForNative
|
@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,
|
List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
|
||||||
format, storageID);
|
format, storageID);
|
||||||
if (objs == null) {
|
if (objs == null) {
|
||||||
@@ -830,7 +833,8 @@ public class MtpDatabase implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForNative
|
@VisibleForNative
|
||||||
private boolean getThumbnailInfo(int handle, long[] outLongs) {
|
@VisibleForTesting
|
||||||
|
public boolean getThumbnailInfo(int handle, long[] outLongs) {
|
||||||
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
|
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -866,7 +870,8 @@ public class MtpDatabase implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForNative
|
@VisibleForNative
|
||||||
private byte[] getThumbnailData(int handle) {
|
@VisibleForTesting
|
||||||
|
public byte[] getThumbnailData(int handle) {
|
||||||
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
|
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class MtpStorage {
|
|||||||
|
|
||||||
public MtpStorage(StorageVolume volume, int storageId) {
|
public MtpStorage(StorageVolume volume, int storageId) {
|
||||||
mStorageId = storageId;
|
mStorageId = storageId;
|
||||||
mPath = volume.getInternalPath();
|
mPath = volume.getPath();
|
||||||
mDescription = volume.getDescription(null);
|
mDescription = volume.getDescription(null);
|
||||||
mRemovable = volume.isRemovable();
|
mRemovable = volume.isRemovable();
|
||||||
mMaxFileSize = volume.getMaxFileSize();
|
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