• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.mtp;
17 
18 import android.annotation.NonNull;
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapFactory;
22 import android.os.Build;
23 import android.os.FileUtils;
24 import android.os.UserHandle;
25 import android.os.storage.StorageManager;
26 import android.os.storage.StorageVolume;
27 import android.util.Log;
28 
29 import androidx.test.InstrumentationRegistry;
30 import androidx.test.filters.SmallTest;
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import com.android.internal.util.Preconditions;
34 
35 import org.junit.After;
36 import org.junit.Assert;
37 import org.junit.Assume;
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 
42 import java.io.ByteArrayOutputStream;
43 import java.io.File;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.OutputStream;
48 
49 /**
50  * Tests for MtpDatabase functionality.
51  */
52 @RunWith(AndroidJUnit4.class)
53 public class MtpDatabaseTest {
54     private static final String TAG = MtpDatabaseTest.class.getSimpleName();
55 
56     private final Context mContext = InstrumentationRegistry.getContext();
57 
58     private static final File mBaseDir = InstrumentationRegistry.getContext().getExternalCacheDir();
59     private static final String MAIN_STORAGE_DIR = mBaseDir.getPath() + "/" + TAG + "/";
60     private static final String TEST_DIRNAME = "/TestIs";
61 
62     private static final int MAIN_STORAGE_ID = 0x10001;
63     private static final int SCND_STORAGE_ID = 0x20001;
64     private static final String MAIN_STORAGE_ID_STR = Integer.toHexString(MAIN_STORAGE_ID);
65     private static final String SCND_STORAGE_ID_STR = Integer.toHexString(SCND_STORAGE_ID);
66 
67     private static final File mMainStorageDir = new File(MAIN_STORAGE_DIR);
68 
69     private static ServerHolder mServerHolder;
70     private MtpDatabase mMtpDatabase;
71 
logMethodName()72     private static void logMethodName() {
73         Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName());
74     }
75 
createNewDir(File parent, String name)76     private static File createNewDir(File parent, String name) {
77         File ret = new File(parent, name);
78         if (!ret.mkdir())
79             throw new AssertionError(
80                     "Failed to create file: name=" + name + ", " + parent.getPath());
81         return ret;
82     }
83 
writeNewFile(File newFile)84     private static void writeNewFile(File newFile) {
85         try {
86             new FileOutputStream(newFile).write(new byte[] {0, 0, 0});
87         } catch (IOException e) {
88             Assert.fail();
89         }
90     }
91 
writeNewFileFromByte(File newFile, byte[] byteData)92     private static void writeNewFileFromByte(File newFile, byte[] byteData) {
93         try {
94             new FileOutputStream(newFile).write(byteData);
95         } catch (IOException e) {
96             Assert.fail();
97         }
98     }
99 
100     private static class ServerHolder {
101         @NonNull final MtpServer server;
102         @NonNull final MtpDatabase database;
103 
ServerHolder(@onNull MtpServer server, @NonNull MtpDatabase database)104         ServerHolder(@NonNull MtpServer server, @NonNull MtpDatabase database) {
105             Preconditions.checkNotNull(server);
106             Preconditions.checkNotNull(database);
107             this.server = server;
108             this.database = database;
109         }
110 
close()111         void close() {
112             this.database.setServer(null);
113         }
114     }
115 
116     private class OnServerTerminated implements Runnable {
117         @Override
run()118         public void run() {
119             if (mServerHolder == null) {
120                 Log.e(TAG, "mServerHolder is unexpectedly null.");
121                 return;
122             }
123             mServerHolder.close();
124             mServerHolder = null;
125         }
126     }
127 
128     @Before
setUp()129     public void setUp() {
130         FileUtils.deleteContentsAndDir(mMainStorageDir);
131         Assert.assertTrue(mMainStorageDir.mkdir());
132 
133         StorageVolume mainStorage = new StorageVolume(MAIN_STORAGE_ID_STR,
134                 mMainStorageDir, mMainStorageDir, "Primary Storage",
135                 true, false, true, false, -1, UserHandle.CURRENT, null /* uuid */, "", "");
136 
137         final StorageVolume primary = mainStorage;
138 
139         mMtpDatabase = new MtpDatabase(mContext, null);
140 
141         final MtpServer server =
142                 new MtpServer(mMtpDatabase, null, false,
143                         new OnServerTerminated(), Build.MANUFACTURER,
144                         Build.MODEL, "1.0");
145         mMtpDatabase.setServer(server);
146         mServerHolder = new ServerHolder(server, mMtpDatabase);
147 
148         mMtpDatabase.addStorage(mainStorage);
149     }
150 
151     @After
tearDown()152     public void tearDown() {
153         FileUtils.deleteContentsAndDir(mMainStorageDir);
154     }
155 
stageFile(int resId, File file)156     private File stageFile(int resId, File file) throws IOException {
157         try (InputStream source = mContext.getResources().openRawResource(resId);
158                 OutputStream target = new FileOutputStream(file)) {
159             android.os.FileUtils.copy(source, target);
160         }
161         return file;
162     }
163 
164     /**
165      * Refer to BitmapUtilTests, but keep here,
166 	 * so as to be aware of the behavior or interface change there
167      */
assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap)168     private void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
169         Assert.assertTrue(
170                 "Abnormal bitmap.width: " + bitmap.getWidth(), bitmap.getWidth() >= expectedWidth);
171         Assert.assertTrue(
172                 "Abnormal bitmap.height: " + bitmap.getHeight(),
173                 bitmap.getHeight() >= expectedHeight);
174     }
175 
createJpegRawData(int sourceWidth, int sourceHeight)176     private byte[] createJpegRawData(int sourceWidth, int sourceHeight) throws IOException {
177         return createRawData(Bitmap.CompressFormat.JPEG, sourceWidth, sourceHeight);
178     }
179 
createPngRawData(int sourceWidth, int sourceHeight)180     private byte[] createPngRawData(int sourceWidth, int sourceHeight) throws IOException {
181         return createRawData(Bitmap.CompressFormat.PNG, sourceWidth, sourceHeight);
182     }
183 
createRawData(Bitmap.CompressFormat format, int sourceWidth, int sourceHeight)184     private byte[] createRawData(Bitmap.CompressFormat format, int sourceWidth, int sourceHeight)
185             throws IOException {
186         // Create a temp bitmap as our source
187         Bitmap b = Bitmap.createBitmap(sourceWidth, sourceHeight, Bitmap.Config.ARGB_8888);
188         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
189         b.compress(format, 50, outputStream);
190         final byte[] data = outputStream.toByteArray();
191         outputStream.close();
192         return data;
193     }
194 
195     /**
196      * Decodes the bitmap with the given sample size
197      */
decodeBitmapFromBytes(byte[] bytes, int sampleSize)198     public static Bitmap decodeBitmapFromBytes(byte[] bytes, int sampleSize) {
199         final BitmapFactory.Options options;
200         if (sampleSize <= 1) {
201             options = null;
202         } else {
203             options = new BitmapFactory.Options();
204             options.inSampleSize = sampleSize;
205         }
206         return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
207     }
208 
testThumbnail(int fileHandle, File imgFile, boolean isGoodThumb)209     private void testThumbnail(int fileHandle, File imgFile, boolean isGoodThumb)
210             throws IOException {
211         boolean isValidThumb;
212         byte[] byteArray;
213         long[] outLongs = new long[3];
214 
215         isValidThumb = mMtpDatabase.getThumbnailInfo(fileHandle, outLongs);
216         Assert.assertTrue(isValidThumb);
217 
218         byteArray = mMtpDatabase.getThumbnailData(fileHandle);
219 
220         if (isGoodThumb) {
221             Assert.assertNotNull("Fail to generate thumbnail:" + imgFile.getPath(), byteArray);
222 
223             Bitmap testBitmap = decodeBitmapFromBytes(byteArray, 4);
224             assertBitmapSize(32, 16, testBitmap);
225         } else Assert.assertNull("Bad image should return null:" + imgFile.getPath(), byteArray);
226     }
227 
228     @Test
229     @SmallTest
testMtpDatabaseThumbnail()230     public void testMtpDatabaseThumbnail() throws IOException {
231         int baseHandle;
232         int handleJpgBadThumb, handleJpgNoThumb, handleJpgBad;
233         int handlePng1, handlePngBad;
234         final String baseTestDirStr = mMainStorageDir.getPath() + TEST_DIRNAME;
235 
236         logMethodName();
237 
238         Log.d(TAG, "testMtpDatabaseThumbnail: Generate and insert tested files.");
239 
240         baseHandle = mMtpDatabase.beginSendObject(baseTestDirStr,
241                 MtpConstants.FORMAT_ASSOCIATION, 0, MAIN_STORAGE_ID);
242 
243         File baseDir = new File(baseTestDirStr);
244         baseDir.mkdirs();
245 
246         final File jpgfileBadThumb = new File(baseDir, "jpgfileBadThumb.jpg");
247         final File jpgFileNoThumb = new File(baseDir, "jpgFileNoThumb.jpg");
248         final File jpgfileBad = new File(baseDir, "jpgfileBad.jpg");
249         final File pngFile1 = new File(baseDir, "pngFile1.png");
250         final File pngFileBad = new File(baseDir, "pngFileBad.png");
251 
252         handleJpgBadThumb = mMtpDatabase.beginSendObject(jpgfileBadThumb.getPath(),
253                 MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
254         stageFile(R.raw.test_bad_thumb, jpgfileBadThumb);
255 
256         handleJpgNoThumb = mMtpDatabase.beginSendObject(jpgFileNoThumb.getPath(),
257                 MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
258         writeNewFileFromByte(jpgFileNoThumb, createJpegRawData(128, 64));
259 
260         handleJpgBad = mMtpDatabase.beginSendObject(jpgfileBad.getPath(),
261                 MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
262         writeNewFile(jpgfileBad);
263 
264         handlePng1 = mMtpDatabase.beginSendObject(pngFile1.getPath(),
265                 MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
266         writeNewFileFromByte(pngFile1, createPngRawData(128, 64));
267 
268         handlePngBad = mMtpDatabase.beginSendObject(pngFileBad.getPath(),
269                 MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
270         writeNewFile(pngFileBad);
271 
272         Log.d(TAG, "testMtpDatabaseThumbnail: Test bad JPG");
273 
274 // Now we support to generate thumbnail if embedded thumbnail is corrupted or not existed
275         testThumbnail(handleJpgBadThumb, jpgfileBadThumb, true);
276 
277         testThumbnail(handleJpgNoThumb, jpgFileNoThumb, true);
278 
279         testThumbnail(handleJpgBad, jpgfileBad, false);
280 
281         Log.d(TAG, "testMtpDatabaseThumbnail: Test PNG");
282 
283         testThumbnail(handlePng1, pngFile1, true);
284 
285         Log.d(TAG, "testMtpDatabaseThumbnail: Test bad PNG");
286 
287         testThumbnail(handlePngBad, pngFileBad, false);
288     }
289 
290     @Test
291     @SmallTest
testMtpDatabaseExtStorage()292     public void testMtpDatabaseExtStorage() throws IOException {
293         int numObj;
294         StorageVolume[] mVolumes;
295 
296         logMethodName();
297 
298         mVolumes = StorageManager.getVolumeList(UserHandle.myUserId(), 0);
299         // Currently it may need manual setup for 2nd storage on virtual device testing.
300         // Thus only run test when 2nd storage exists.
301         Assume.assumeTrue(
302                 "Skip when 2nd storage not available, volume numbers = " + mVolumes.length,
303                 mVolumes.length >= 2);
304 
305         for (int ii = 0; ii < mVolumes.length; ii++) {
306             StorageVolume volume = mVolumes[ii];
307             // Skip Actual Main storage (Internal Storage),
308             // since we use manipulated path as testing Main storage
309             if (ii > 0)
310                 mMtpDatabase.addStorage(volume);
311         }
312 
313         numObj = mMtpDatabase.getNumObjects(SCND_STORAGE_ID, 0, 0xFFFFFFFF);
314         Assert.assertTrue(
315                 "Fail to get objects in 2nd storage, object numbers = " + numObj, numObj >= 0);
316     }
317 }
318