2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android
.os
.storage
;
19 import android
.content
.Context
;
20 import android
.content
.res
.Resources
;
21 import android
.content
.res
.Resources
.NotFoundException
;
22 import android
.os
.Environment
;
23 import android
.os
.SystemClock
;
24 import android
.test
.InstrumentationTestCase
;
25 import android
.util
.Log
;
26 import android
.os
.Environment
;
27 import android
.os
.FileUtils
;
28 import android
.os
.storage
.OnObbStateChangeListener
;
29 import android
.os
.storage
.StorageManager
;
31 import java
.io
.BufferedReader
;
32 import java
.io
.DataInputStream
;
34 import java
.io
.FileInputStream
;
35 import java
.io
.FileNotFoundException
;
36 import java
.io
.FileReader
;
37 import java
.io
.InputStream
;
38 import java
.io
.IOException
;
39 import java
.io
.StringReader
;
41 public class StorageManagerBaseTest
extends InstrumentationTestCase
{
43 protected Context mContext
= null;
44 protected StorageManager mSm
= null;
45 private static String LOG_TAG
= "StorageManagerBaseTest";
46 protected static final long MAX_WAIT_TIME
= 120*1000;
47 protected static final long WAIT_TIME_INCR
= 5*1000;
48 protected static String OBB_FILE_1
= "obb_file1.obb";
49 protected static String OBB_FILE_1_CONTENTS_1
= "OneToOneThousandInts.bin";
50 protected static String OBB_FILE_2
= "obb_file2.obb";
51 protected static String OBB_FILE_3
= "obb_file3.obb";
52 protected static String OBB_FILE_1_PASSWORD
= "password1";
53 protected static String OBB_FILE_1_ENCRYPTED
= "obb_enc_file100_orig1.obb";
54 protected static String OBB_FILE_2_UNSIGNED
= "obb_file2_nosign.obb";
55 protected static String OBB_FILE_3_PASSWORD
= "password3";
56 protected static String OBB_FILE_3_ENCRYPTED
= "obb_enc_file100_orig3.obb";
57 protected static String OBB_FILE_3_BAD_PACKAGENAME
= "obb_file3_bad_packagename.obb";
59 protected static boolean FORCE
= true;
60 protected static boolean DONT_FORCE
= false;
62 private static final String SAMPLE1_TEXT
= "This is sample text.\n\nTesting 1 2 3.";
64 private static final String SAMPLE2_TEXT
=
65 "We the people of the United States, in order to form a more perfect union,\n"
66 + "establish justice, insure domestic tranquility, provide for the common\n"
67 + "defense, promote the general welfare, and secure the blessings of liberty\n"
68 + "to ourselves and our posterity, do ordain and establish this Constitution\n"
69 + "for the United States of America.\n\n";
71 class MountingObbThread
extends Thread
{
72 boolean mStop
= false;
73 volatile boolean mFileOpenOnObb
= false;
74 private String mObbFilePath
= null;
75 private String mPathToContentsFile
= null;
76 private String mOfficialObbFilePath
= null;
81 * @param obbFilePath path to the OBB image file
82 * @param pathToContentsFile path to a file on the mounted OBB volume to open after the OBB
85 public MountingObbThread (String obbFilePath
, String pathToContentsFile
) {
86 assertTrue("obbFilePath cannot be null!", obbFilePath
!= null);
87 mObbFilePath
= obbFilePath
;
88 assertTrue("path to contents file cannot be null!", pathToContentsFile
!= null);
89 mPathToContentsFile
= pathToContentsFile
;
95 * Mounts OBB_FILE_1, and tries to open a file on the mounted OBB (specified in the
96 * constructor). Once it's open, it waits until someone calls its doStop(), after which it
97 * closes the opened file.
100 // the official OBB file path and the mount-request file path should be the same, but
101 // let's distinguish the two as they may make for some interesting tests later
102 mOfficialObbFilePath
= mountObb(mObbFilePath
);
103 assertEquals("Expected and actual OBB file paths differ!", mObbFilePath
,
104 mOfficialObbFilePath
);
106 // open a file on OBB 1...
107 DataInputStream inputFile
= openFileOnMountedObb(mOfficialObbFilePath
,
108 mPathToContentsFile
);
109 assertTrue("Failed to open file!", inputFile
!= null);
111 synchronized (this) {
112 mFileOpenOnObb
= true;
118 Thread
.sleep(WAIT_TIME_INCR
);
119 } catch (InterruptedException e
) {
120 // nothing special to be done for interruptions
125 } catch (IOException e
) {
126 fail("Failed to close file on OBB due to error: " + e
.toString());
131 * Tells whether a file has yet been successfully opened on the OBB or not
133 * @return true if the specified file on the OBB was opened; false otherwise
135 public boolean isFileOpenOnObb() {
136 return mFileOpenOnObb
;
140 * Returns the official path of the OBB file that was mounted
142 * This is not the mount path, but the normalized path to the actual OBB file
144 * @return a {@link String} representation of the path to the OBB file that was mounted
146 public String
officialObbFilePath() {
147 return mOfficialObbFilePath
;
151 * Requests the thread to stop running
153 * Closes the opened file and returns
155 public void doStop() {
160 public class ObbListener
extends OnObbStateChangeListener
{
161 private String LOG_TAG
= "StorageManagerBaseTest.ObbListener";
163 String mOfficialPath
= null;
164 boolean mDone
= false;
171 public void onObbStateChange(String path
, int state
) {
172 Log
.i(LOG_TAG
, "Storage state changing to: " + state
);
174 synchronized (this) {
175 Log
.i(LOG_TAG
, "OfficialPath is now: " + path
);
177 mOfficialPath
= path
;
184 * Tells whether we are done or not (system told us the OBB has changed state)
186 * @return true if the system has told us this OBB's state has changed, false otherwise
188 public boolean isDone() {
193 * The last state of the OBB, according to the system
195 * @return A {@link String} representation of the state of the OBB
202 * The normalized, official path to the OBB file (according to the system)
204 * @return A {@link String} representation of the official path to the OBB file
206 public String
officialPath() {
207 return mOfficialPath
;
215 public void setUp() throws Exception
{
216 mContext
= getInstrumentation().getContext();
217 mSm
= (StorageManager
)mContext
.getSystemService(android
.content
.Context
.STORAGE_SERVICE
);
222 * Helper to copy a raw resource file to an actual specified file
224 * @param rawResId The raw resource ID of the OBB resource file
225 * @param outFile A File representing the file we want to copy the OBB to
226 * @throws NotFoundException If the resource file could not be found
228 private void copyRawToFile(int rawResId
, File outFile
) throws NotFoundException
{
229 Resources res
= mContext
.getResources();
230 InputStream is
= null;
232 is
= res
.openRawResource(rawResId
);
233 } catch (NotFoundException e
) {
234 Log
.i(LOG_TAG
, "Failed to load resource with id: " + rawResId
);
237 FileUtils
.setPermissions(outFile
.getPath(), FileUtils
.S_IRWXU
| FileUtils
.S_IRWXG
238 | FileUtils
.S_IRWXO
, -1, -1);
239 assertTrue(FileUtils
.copyToFile(is
, outFile
));
240 FileUtils
.setPermissions(outFile
.getPath(), FileUtils
.S_IRWXU
| FileUtils
.S_IRWXG
241 | FileUtils
.S_IRWXO
, -1, -1);
245 * Creates an OBB file (with the given name), into the app's standard files directory
247 * @param name The name of the OBB file we want to create/write to
248 * @param rawResId The raw resource ID of the OBB file in the package
249 * @return A {@link File} representing the file to write to
251 protected File
createObbFile(String name
, int rawResId
) {
254 final File filesDir
= mContext
.getFilesDir();
255 outFile
= new File(filesDir
, name
);
256 copyRawToFile(rawResId
, outFile
);
257 } catch (NotFoundException e
) {
258 if (outFile
!= null) {
266 * Mounts an OBB file and opens a file located on it
268 * @param obbPath Path to OBB image
269 * @param fileName The full name and path to the file on the OBB to open once the OBB is mounted
270 * @return The {@link DataInputStream} representing the opened file, if successful in opening
271 * the file, or null of unsuccessful.
273 protected DataInputStream
openFileOnMountedObb(String obbPath
, String fileName
) {
275 // get mSm obb mount path
276 assertTrue("Cannot open file when OBB is not mounted!", mSm
.isObbMounted(obbPath
));
278 String path
= mSm
.getMountedObbPath(obbPath
);
279 assertTrue("Path should not be null!", path
!= null);
281 File inFile
= new File(path
, fileName
);
282 DataInputStream inStream
= null;
284 inStream
= new DataInputStream(new FileInputStream(inFile
));
285 Log
.i(LOG_TAG
, "Opened file: " + fileName
+ " for read at path: " + path
);
286 } catch (FileNotFoundException e
) {
287 Log
.e(LOG_TAG
, e
.toString());
289 } catch (SecurityException e
) {
290 Log
.e(LOG_TAG
, e
.toString());
299 * @param obbFilePath The full path to the OBB file to mount
300 * @param key (optional) The key to use to unencrypt the OBB; pass null for no encryption
301 * @param expectedState The expected state resulting from trying to mount the OBB
302 * @return A {@link String} representing the normalized path to OBB file that was mounted
304 protected String
mountObb(String obbFilePath
, String key
, int expectedState
) {
305 return doMountObb(obbFilePath
, key
, expectedState
);
309 * Mounts an OBB file with default options (no encryption, mounting succeeds)
311 * @param obbFilePath The full path to the OBB file to mount
312 * @return A {@link String} representing the normalized path to OBB file that was mounted
314 protected String
mountObb(String obbFilePath
) {
315 return doMountObb(obbFilePath
, null, OnObbStateChangeListener
.MOUNTED
);
319 * Synchronously waits for an OBB listener to be signaled of a state change, but does not throw
321 * @param obbListener The listener for the OBB file
322 * @return true if the listener was signaled of a state change by the system, else returns
323 * false if we time out.
325 protected boolean doWaitForObbStateChange(ObbListener obbListener
) {
326 synchronized(obbListener
) {
327 long waitTimeMillis
= 0;
328 while (!obbListener
.isDone()) {
330 Log
.i(LOG_TAG
, "Waiting for listener...");
331 obbListener
.wait(WAIT_TIME_INCR
);
332 Log
.i(LOG_TAG
, "Awoke from waiting for listener...");
333 waitTimeMillis
+= WAIT_TIME_INCR
;
334 if (waitTimeMillis
> MAX_WAIT_TIME
) {
335 fail("Timed out waiting for OBB state to change!");
337 } catch (InterruptedException e
) {
338 Log
.i(LOG_TAG
, e
.toString());
341 return obbListener
.isDone();
346 * Synchronously waits for an OBB listener to be signaled of a state change
348 * @param obbListener The listener for the OBB file
349 * @return true if the listener was signaled of a state change by the system; else a fail()
350 * is triggered if we timed out
352 protected String
doMountObb_noThrow(String obbFilePath
, String key
, int expectedState
) {
353 Log
.i(LOG_TAG
, "doMountObb() on " + obbFilePath
+ " using key: " + key
);
354 assertTrue ("Null path was passed in for OBB file!", obbFilePath
!= null);
355 assertTrue ("Null path was passed in for OBB file!", obbFilePath
!= null);
357 ObbListener obbListener
= new ObbListener();
358 boolean success
= mSm
.mountObb(obbFilePath
, key
, obbListener
);
359 success
&= obbFilePath
.equals(doWaitForObbStateChange(obbListener
));
360 success
&= (expectedState
== obbListener
.state());
362 if (OnObbStateChangeListener
.MOUNTED
== expectedState
) {
363 success
&= obbFilePath
.equals(obbListener
.officialPath());
364 success
&= mSm
.isObbMounted(obbListener
.officialPath());
366 success
&= !mSm
.isObbMounted(obbListener
.officialPath());
370 return obbListener
.officialPath();
377 * Mounts an OBB file without throwing and synchronously waits for it to finish mounting
379 * @param obbFilePath The full path to the OBB file to mount
380 * @param key (optional) The key to use to unencrypt the OBB; pass null for no encryption
381 * @param expectedState The expected state resulting from trying to mount the OBB
382 * @return A {@link String} representing the actual normalized path to OBB file that was
383 * mounted, or null if the mounting failed
385 protected String
doMountObb(String obbFilePath
, String key
, int expectedState
) {
386 Log
.i(LOG_TAG
, "doMountObb() on " + obbFilePath
+ " using key: " + key
);
387 assertTrue ("Null path was passed in for OBB file!", obbFilePath
!= null);
389 ObbListener obbListener
= new ObbListener();
390 assertTrue("mountObb call failed", mSm
.mountObb(obbFilePath
, key
, obbListener
));
391 assertTrue("Failed to get OBB mount status change for file: " + obbFilePath
,
392 doWaitForObbStateChange(obbListener
));
393 assertEquals("OBB mount state not what was expected!", expectedState
, obbListener
.state());
395 if (OnObbStateChangeListener
.MOUNTED
== expectedState
) {
396 assertEquals(obbFilePath
, obbListener
.officialPath());
397 assertTrue("Obb should be mounted, but SM reports it is not!",
398 mSm
.isObbMounted(obbListener
.officialPath()));
399 } else if (OnObbStateChangeListener
.UNMOUNTED
== expectedState
) {
400 assertFalse("Obb should not be mounted, but SM reports it is!",
401 mSm
.isObbMounted(obbListener
.officialPath()));
404 assertEquals("Mount state is not what was expected!", expectedState
, obbListener
.state());
405 return obbListener
.officialPath();
409 * Unmounts an OBB file without throwing, and synchronously waits for it to finish unmounting
411 * @param obbFilePath The full path to the OBB file to mount
412 * @param force true if we shuold force the unmount, false otherwise
413 * @return true if the unmount was successful, false otherwise
415 protected boolean unmountObb_noThrow(String obbFilePath
, boolean force
) {
416 Log
.i(LOG_TAG
, "doUnmountObb_noThrow() on " + obbFilePath
);
417 assertTrue ("Null path was passed in for OBB file!", obbFilePath
!= null);
418 boolean success
= true;
420 ObbListener obbListener
= new ObbListener();
421 assertTrue("unmountObb call failed", mSm
.unmountObb(obbFilePath
, force
, obbListener
));
423 boolean stateChanged
= doWaitForObbStateChange(obbListener
);
425 success
&= stateChanged
;
426 success
&= (OnObbStateChangeListener
.UNMOUNTED
== obbListener
.state());
427 success
&= !mSm
.isObbMounted(obbFilePath
);
433 * Unmounts an OBB file and synchronously waits for it to finish unmounting
435 * @param obbFilePath The full path to the OBB file to mount
436 * @param force true if we shuold force the unmount, false otherwise
438 protected void unmountObb(String obbFilePath
, boolean force
) {
439 Log
.i(LOG_TAG
, "doUnmountObb() on " + obbFilePath
);
440 assertTrue ("Null path was passed in for OBB file!", obbFilePath
!= null);
442 ObbListener obbListener
= new ObbListener();
443 assertTrue("unmountObb call failed", mSm
.unmountObb(obbFilePath
, force
, obbListener
));
445 boolean stateChanged
= doWaitForObbStateChange(obbListener
);
447 assertTrue("Timed out waiting to unmount OBB file " + obbFilePath
, stateChanged
);
448 assertEquals("OBB failed to unmount", OnObbStateChangeListener
.UNMOUNTED
,
449 obbListener
.state());
450 assertFalse("Obb should NOT be mounted, but SM reports it is!", mSm
.isObbMounted(
456 * Helper to validate the contents of an "int" file in an OBB.
458 * The format of the files are sequential int's, in the range of: [start..end)
460 * @param path The full path to the file (path to OBB)
461 * @param filename The filename containing the ints to validate
462 * @param start The first int expected to be found in the file
463 * @param end The last int + 1 expected to be found in the file
465 protected void doValidateIntContents(String path
, String filename
, int start
, int end
) {
466 File inFile
= new File(path
, filename
);
467 DataInputStream inStream
= null;
468 Log
.i(LOG_TAG
, "Validating file " + filename
+ " at " + path
);
470 inStream
= new DataInputStream(new FileInputStream(inFile
));
472 for (int i
= start
; i
< end
; ++i
) {
473 if (inStream
.readInt() != i
) {
474 fail("Unexpected value read in OBB file");
477 if (inStream
!= null) {
480 Log
.i(LOG_TAG
, "Successfully validated file " + filename
);
481 } catch (FileNotFoundException e
) {
482 fail("File " + inFile
+ " not found: " + e
.toString());
483 } catch (IOException e
) {
484 fail("IOError with file " + inFile
+ ":" + e
.toString());
489 * Helper to validate the contents of a text file in an OBB
491 * @param path The full path to the file (path to OBB)
492 * @param filename The filename containing the ints to validate
493 * @param contents A {@link String} containing the expected contents of the file
495 protected void doValidateTextContents(String path
, String filename
, String contents
) {
496 File inFile
= new File(path
, filename
);
497 BufferedReader fileReader
= null;
498 BufferedReader textReader
= null;
499 Log
.i(LOG_TAG
, "Validating file " + filename
+ " at " + path
);
501 fileReader
= new BufferedReader(new FileReader(inFile
));
502 textReader
= new BufferedReader(new StringReader(contents
));
503 String actual
= null;
504 String expected
= null;
505 while ((actual
= fileReader
.readLine()) != null) {
506 expected
= textReader
.readLine();
507 if (!actual
.equals(expected
)) {
508 fail("File " + filename
+ " in OBB " + path
+ " does not match expected value");
513 Log
.i(LOG_TAG
, "File " + filename
+ " successfully verified.");
514 } catch (IOException e
) {
515 fail("IOError with file " + inFile
+ ":" + e
.toString());
520 * Helper to validate the contents of a "long" file on our OBBs
522 * The format of the files are sequential 0's of type long
524 * @param path The full path to the file (path to OBB)
525 * @param filename The filename containing the ints to validate
526 * @param size The number of zero's expected in the file
527 * @param checkContents If true, the contents of the file are actually verified; if false,
528 * we simply verify that the file can be opened
530 protected void doValidateZeroLongFile(String path
, String filename
, long size
,
531 boolean checkContents
) {
532 File inFile
= new File(path
, filename
);
533 DataInputStream inStream
= null;
534 Log
.i(LOG_TAG
, "Validating file " + filename
+ " at " + path
);
536 inStream
= new DataInputStream(new FileInputStream(inFile
));
539 for (long i
= 0; i
< size
; ++i
) {
540 if (inStream
.readLong() != 0) {
541 fail("Unexpected value read in OBB file" + filename
);
546 if (inStream
!= null) {
549 Log
.i(LOG_TAG
, "File " + filename
+ " successfully verified for " + size
+ " zeros");
550 } catch (IOException e
) {
551 fail("IOError with file " + inFile
+ ":" + e
.toString());
556 * Helper to synchronously wait until we can get a path for a given OBB file
558 * @param filePath The full normalized path to the OBB file
559 * @return The mounted path of the OBB, used to access contents in it
561 protected String
doWaitForPath(String filePath
) {
564 long waitTimeMillis
= 0;
565 assertTrue("OBB " + filePath
+ " is not currently mounted!", mSm
.isObbMounted(filePath
));
566 while (path
== null) {
568 Thread
.sleep(WAIT_TIME_INCR
);
569 waitTimeMillis
+= WAIT_TIME_INCR
;
570 if (waitTimeMillis
> MAX_WAIT_TIME
) {
571 fail("Timed out waiting to get path of OBB file " + filePath
);
573 } catch (InterruptedException e
) {
576 path
= mSm
.getMountedObbPath(filePath
);
578 Log
.i(LOG_TAG
, "Got OBB path: " + path
);
583 * Verifies the pre-defined contents of our first OBB (OBB_FILE_1)
585 * The OBB contains 4 files and no subdirectories
587 * @param filePath The normalized path to the already-mounted OBB file
589 protected void verifyObb1Contents(String filePath
) {
591 path
= doWaitForPath(filePath
);
593 // Validate contents of 2 files in this obb
594 doValidateIntContents(path
, "OneToOneThousandInts.bin", 0, 1000);
595 doValidateIntContents(path
, "SevenHundredInts.bin", 0, 700);
596 doValidateZeroLongFile(path
, "FiveLongs.bin", 5, true);
600 * Verifies the pre-defined contents of our second OBB (OBB_FILE_2)
602 * The OBB contains 2 files and no subdirectories
604 * @param filePath The normalized path to the already-mounted OBB file
606 protected void verifyObb2Contents(String filename
) {
608 path
= doWaitForPath(filename
);
610 // Validate contents of file
611 doValidateTextContents(path
, "sample.txt", SAMPLE1_TEXT
);
612 doValidateTextContents(path
, "sample2.txt", SAMPLE2_TEXT
);
616 * Verifies the pre-defined contents of our third OBB (OBB_FILE_3)
618 * The OBB contains nested files and subdirectories
620 * @param filePath The normalized path to the already-mounted OBB file
622 protected void verifyObb3Contents(String filename
) {
624 path
= doWaitForPath(filename
);
626 // Validate contents of file
627 doValidateIntContents(path
, "OneToOneThousandInts.bin", 0, 1000);
628 doValidateZeroLongFile(path
, "TwoHundredLongs", 200, true);
630 // validate subdirectory 1
631 doValidateZeroLongFile(path
+ File
.separator
+ "subdir1", "FiftyLongs", 50, true);
633 // validate subdirectory subdir2/
634 doValidateIntContents(path
+ File
.separator
+ "subdir2", "OneToOneThousandInts", 0, 1000);
636 // validate subdirectory subdir2/subdir2a/
637 doValidateZeroLongFile(path
+ File
.separator
+ "subdir2" + File
.separator
+ "subdir2a",
638 "TwoHundredLongs", 200, true);
640 // validate subdirectory subdir2/subdir2a/subdir2a1/
641 doValidateIntContents(path
+ File
.separator
+ "subdir2" + File
.separator
+ "subdir2a"
642 + File
.separator
+ "subdir2a1", "OneToOneThousandInts", 0, 1000);