1 /*
2  * Copyright (C) 2010 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 
17 package android.os.storage;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.when;
22 
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.test.InstrumentationTestCase;
26 import android.util.Log;
27 
28 import libcore.io.Streams;
29 
30 import org.junit.Test;
31 import org.mockito.Mock;
32 import org.mockito.MockitoAnnotations;
33 
34 import java.io.BufferedReader;
35 import java.io.DataInputStream;
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.FileNotFoundException;
39 import java.io.FileOutputStream;
40 import java.io.FileReader;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.OutputStream;
44 import java.io.StringReader;
45 
46 public class StorageManagerBaseTest extends InstrumentationTestCase {
47 
48     protected Context mContext = null;
49     protected StorageManager mSm = null;
50     @Mock private File mFile;
51     private static String LOG_TAG = "StorageManagerBaseTest";
52     protected static final long MAX_WAIT_TIME = 120*1000;
53     protected static final long WAIT_TIME_INCR = 5*1000;
54     protected static String OBB_FILE_1 = "obb_file1.obb";
55     protected static String OBB_FILE_1_CONTENTS_1 = "OneToOneThousandInts.bin";
56     protected static String OBB_FILE_2 = "obb_file2.obb";
57     protected static String OBB_FILE_3 = "obb_file3.obb";
58     protected static String OBB_FILE_2_UNSIGNED = "obb_file2_nosign.obb";
59     protected static String OBB_FILE_3_BAD_PACKAGENAME = "obb_file3_bad_packagename.obb";
60 
61     protected static boolean FORCE = true;
62     protected static boolean DONT_FORCE = false;
63 
64     private static final String SAMPLE1_TEXT = "This is sample text.\n\nTesting 1 2 3.";
65 
66     private static final String SAMPLE2_TEXT =
67         "We the people of the United States, in order to form a more perfect union,\n"
68         + "establish justice, insure domestic tranquility, provide for the common\n"
69         + "defense, promote the general welfare, and secure the blessings of liberty\n"
70         + "to ourselves and our posterity, do ordain and establish this Constitution\n"
71         + "for the United States of America.\n\n";
72 
73     public class ObbListener extends OnObbStateChangeListener {
74         private String LOG_TAG = "StorageManagerBaseTest.ObbListener";
75 
76         String mOfficialPath = null;
77         boolean mDone = false;
78         int mState = -1;
79 
80         /**
81          * {@inheritDoc}
82          */
83         @Override
onObbStateChange(String path, int state)84         public void onObbStateChange(String path, int state) {
85             Log.i(LOG_TAG, "Storage state changing to: " + state);
86 
87             synchronized (this) {
88                 Log.i(LOG_TAG, "OfficialPath is now: " + path);
89                 mState = state;
90                 mOfficialPath = path;
91                 mDone = true;
92                 notifyAll();
93             }
94         }
95 
96         /**
97          * Tells whether we are done or not (system told us the OBB has changed state)
98          *
99          * @return true if the system has told us this OBB's state has changed, false otherwise
100          */
isDone()101         public boolean isDone() {
102             return mDone;
103         }
104 
105         /**
106          * The last state of the OBB, according to the system
107          *
108          * @return A {@link String} representation of the state of the OBB
109          */
state()110         public int state() {
111             return mState;
112         }
113 
114         /**
115          * The normalized, official path to the OBB file (according to the system)
116          *
117          * @return A {@link String} representation of the official path to the OBB file
118          */
officialPath()119         public String officialPath() {
120             return mOfficialPath;
121         }
122     }
123 
124     /**
125      * {@inheritDoc}
126      */
127     @Override
setUp()128     public void setUp() throws Exception {
129         MockitoAnnotations.initMocks(this);
130         mContext = getInstrumentation().getContext();
131         mSm = (StorageManager)mContext.getSystemService(android.content.Context.STORAGE_SERVICE);
132 
133     }
134 
135     /**
136      * Tests the space reserved for cache when system has high free space i.e. more than
137      * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total space.
138      */
139     @Test
testGetStorageCacheBytesUnderHighStorage()140     public void testGetStorageCacheBytesUnderHighStorage() throws Exception {
141         when(mFile.getUsableSpace()).thenReturn(10000L);
142         when(mFile.getTotalSpace()).thenReturn(15000L);
143         long result = mSm.getStorageCacheBytes(mFile, 0);
144         assertThat(result).isEqualTo(1500L);
145     }
146 
147     /**
148      * Tests the space reserved for cache when system has low free space i.e. less than
149      * StorageManager.STORAGE_THRESHOLD_PERCENT_LOW of total space.
150      */
151     @Test
testGetStorageCacheBytesUnderLowStorage()152     public void testGetStorageCacheBytesUnderLowStorage() throws Exception {
153         when(mFile.getUsableSpace()).thenReturn(10000L);
154         when(mFile.getTotalSpace()).thenReturn(250000L);
155         long result = mSm.getStorageCacheBytes(mFile, 0);
156         assertThat(result).isEqualTo(5000L);
157     }
158 
159     /**
160      * Tests the space reserved for cache when system has moderate free space i.e.more than
161      * StorageManager.STORAGE_THRESHOLD_PERCENT_LOW of total space but less than
162      * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total space.
163      */
164     @Test
testGetStorageCacheBytesUnderModerateStorage()165     public void testGetStorageCacheBytesUnderModerateStorage() throws Exception {
166         when(mFile.getUsableSpace()).thenReturn(10000L);
167         when(mFile.getTotalSpace()).thenReturn(100000L);
168         long result = mSm.getStorageCacheBytes(mFile, 0);
169         assertThat(result).isEqualTo(4667L);
170     }
171 
172     /**
173      * Creates an OBB file (with the given name), into the app's standard files directory
174      *
175      * @param name The name of the OBB file we want to create/write to
176      * @param rawResId The raw resource ID of the OBB file in the package
177      * @return A {@link File} representing the file to write to
178      */
createObbFile(String name, int rawResId)179     protected File createObbFile(String name, int rawResId) throws IOException, Resources.NotFoundException {
180         final File outFile = new File(mContext.getObbDir(), name);
181         outFile.delete();
182 
183         try (InputStream in = mContext.getResources().openRawResource(rawResId);
184                 OutputStream out = new FileOutputStream(outFile)) {
185             Streams.copy(in, out);
186         }
187 
188         return outFile;
189     }
190 
191     /**
192      * Mounts an OBB file and opens a file located on it
193      *
194      * @param obbPath Path to OBB image
195      * @param fileName The full name and path to the file on the OBB to open once the OBB is mounted
196      * @return The {@link DataInputStream} representing the opened file, if successful in opening
197      *      the file, or null of unsuccessful.
198      */
openFileOnMountedObb(String obbPath, String fileName)199     protected DataInputStream openFileOnMountedObb(String obbPath, String fileName) {
200 
201         // get mSm obb mount path
202         assertTrue("Cannot open file when OBB is not mounted!", mSm.isObbMounted(obbPath));
203 
204         String path = mSm.getMountedObbPath(obbPath);
205         assertTrue("Path should not be null!", path != null);
206 
207         File inFile = new File(path, fileName);
208         DataInputStream inStream = null;
209         try {
210             inStream = new DataInputStream(new FileInputStream(inFile));
211             Log.i(LOG_TAG, "Opened file: " + fileName + " for read at path: " + path);
212         } catch (FileNotFoundException e) {
213             Log.e(LOG_TAG, e.toString());
214             return null;
215         } catch (SecurityException e) {
216             Log.e(LOG_TAG, e.toString());
217             return null;
218         }
219         return inStream;
220     }
221 
222     /**
223      * Mounts an OBB file
224      *
225      * @param obbFilePath The full path to the OBB file to mount
226      * @param expectedState The expected state resulting from trying to mount the OBB
227      * @return A {@link String} representing the normalized path to OBB file that was mounted
228      */
mountObb(String obbFilePath, int expectedState)229     protected String mountObb(String obbFilePath, int expectedState) {
230         return doMountObb(obbFilePath, expectedState);
231     }
232 
233     /**
234      * Mounts an OBB file with default options.
235      *
236      * @param obbFilePath The full path to the OBB file to mount
237      * @return A {@link String} representing the normalized path to OBB file that was mounted
238      */
mountObb(String obbFilePath)239     protected String mountObb(String obbFilePath) {
240         return doMountObb(obbFilePath, OnObbStateChangeListener.MOUNTED);
241     }
242 
243     /**
244      * Synchronously waits for an OBB listener to be signaled of a state change, but does not throw
245      *
246      * @param obbListener The listener for the OBB file
247      * @return true if the listener was signaled of a state change by the system, else returns
248      *      false if we time out.
249      */
doWaitForObbStateChange(ObbListener obbListener)250     protected boolean doWaitForObbStateChange(ObbListener obbListener) {
251         synchronized(obbListener) {
252             long waitTimeMillis = 0;
253             while (!obbListener.isDone()) {
254                 try {
255                     Log.i(LOG_TAG, "Waiting for listener...");
256                     obbListener.wait(WAIT_TIME_INCR);
257                     Log.i(LOG_TAG, "Awoke from waiting for listener...");
258                     waitTimeMillis += WAIT_TIME_INCR;
259                     if (waitTimeMillis > MAX_WAIT_TIME) {
260                         fail("Timed out waiting for OBB state to change!");
261                     }
262                 } catch (InterruptedException e) {
263                     Log.i(LOG_TAG, e.toString());
264                 }
265             }
266             return obbListener.isDone();
267             }
268     }
269 
270     /**
271      * Synchronously waits for an OBB listener to be signaled of a state change
272      *
273      * @param obbListener The listener for the OBB file
274      * @return true if the listener was signaled of a state change by the system; else a fail()
275      *      is triggered if we timed out
276      */
doMountObb_noThrow(String obbFilePath, int expectedState)277     protected String doMountObb_noThrow(String obbFilePath, int expectedState) {
278         Log.i(LOG_TAG, "doMountObb() on " + obbFilePath);
279         assertTrue ("Null path was passed in for OBB file!", obbFilePath != null);
280         assertTrue ("Null path was passed in for OBB file!", obbFilePath != null);
281 
282         ObbListener obbListener = new ObbListener();
283         boolean success = mSm.mountObb(obbFilePath, null, obbListener);
284         success &= obbFilePath.equals(doWaitForObbStateChange(obbListener));
285         success &= (expectedState == obbListener.state());
286 
287         if (OnObbStateChangeListener.MOUNTED == expectedState) {
288             success &= obbFilePath.equals(obbListener.officialPath());
289             success &= mSm.isObbMounted(obbListener.officialPath());
290         } else {
291             success &= !mSm.isObbMounted(obbListener.officialPath());
292         }
293 
294         if (success) {
295             return obbListener.officialPath();
296         } else {
297             return null;
298         }
299     }
300 
301     /**
302      * Mounts an OBB file without throwing and synchronously waits for it to finish mounting
303      *
304      * @param obbFilePath The full path to the OBB file to mount
305      * @param expectedState The expected state resulting from trying to mount the OBB
306      * @return A {@link String} representing the actual normalized path to OBB file that was
307      *      mounted, or null if the mounting failed
308      */
doMountObb(String obbFilePath, int expectedState)309     protected String doMountObb(String obbFilePath, int expectedState) {
310         Log.i(LOG_TAG, "doMountObb() on " + obbFilePath);
311         assertTrue ("Null path was passed in for OBB file!", obbFilePath != null);
312 
313         ObbListener obbListener = new ObbListener();
314         assertTrue("mountObb call failed", mSm.mountObb(obbFilePath, null, obbListener));
315         assertTrue("Failed to get OBB mount status change for file: " + obbFilePath,
316                 doWaitForObbStateChange(obbListener));
317         assertEquals("OBB mount state not what was expected!", expectedState,
318                 obbListener.state());
319 
320         if (OnObbStateChangeListener.MOUNTED == expectedState) {
321             assertEquals(obbFilePath, obbListener.officialPath());
322             assertTrue("Obb should be mounted, but SM reports it is not!",
323                     mSm.isObbMounted(obbListener.officialPath()));
324         } else if (OnObbStateChangeListener.UNMOUNTED == expectedState) {
325             assertFalse("Obb should not be mounted, but SM reports it is!",
326                     mSm.isObbMounted(obbListener.officialPath()));
327         }
328 
329         assertEquals("Mount state is not what was expected!", expectedState,
330                 obbListener.state());
331         return obbListener.officialPath();
332     }
333 
334     /**
335      * Unmounts an OBB file without throwing, and synchronously waits for it to finish unmounting
336      *
337      * @param obbFilePath The full path to the OBB file to mount
338      * @param force true if we shuold force the unmount, false otherwise
339      * @return true if the unmount was successful, false otherwise
340      */
unmountObb_noThrow(String obbFilePath, boolean force)341     protected boolean unmountObb_noThrow(String obbFilePath, boolean force) {
342         Log.i(LOG_TAG, "doUnmountObb_noThrow() on " + obbFilePath);
343         assertTrue ("Null path was passed in for OBB file!", obbFilePath != null);
344         boolean success = true;
345 
346         ObbListener obbListener = new ObbListener();
347         assertTrue("unmountObb call failed", mSm.unmountObb(obbFilePath, force, obbListener));
348 
349         boolean stateChanged = doWaitForObbStateChange(obbListener);
350         if (force) {
351             success &= stateChanged;
352             success &= (OnObbStateChangeListener.UNMOUNTED == obbListener.state());
353             success &= !mSm.isObbMounted(obbFilePath);
354         }
355         return success;
356     }
357 
358     /**
359      * Unmounts an OBB file and synchronously waits for it to finish unmounting
360      *
361      * @param obbFilePath The full path to the OBB file to mount
362      * @param force true if we shuold force the unmount, false otherwise
363      */
unmountObb(String obbFilePath, boolean force)364     protected void unmountObb(String obbFilePath, boolean force) {
365         Log.i(LOG_TAG, "doUnmountObb() on " + obbFilePath);
366         assertTrue ("Null path was passed in for OBB file!", obbFilePath != null);
367 
368         ObbListener obbListener = new ObbListener();
369         assertTrue("unmountObb call failed", mSm.unmountObb(obbFilePath, force, obbListener));
370 
371         boolean stateChanged = doWaitForObbStateChange(obbListener);
372         if (force) {
373             assertTrue("Timed out waiting to unmount OBB file " + obbFilePath, stateChanged);
374             assertEquals("OBB failed to unmount", OnObbStateChangeListener.UNMOUNTED,
375                     obbListener.state());
376             assertFalse("Obb should NOT be mounted, but SM reports it is!", mSm.isObbMounted(
377                     obbFilePath));
378         }
379     }
380 
381     /**
382      * Helper to validate the contents of an "int" file in an OBB.
383      *
384      * The format of the files are sequential int's, in the range of: [start..end)
385      *
386      * @param path The full path to the file (path to OBB)
387      * @param filename The filename containing the ints to validate
388      * @param start The first int expected to be found in the file
389      * @param end The last int + 1 expected to be found in the file
390      */
doValidateIntContents(String path, String filename, int start, int end)391     protected void doValidateIntContents(String path, String filename, int start, int end) {
392         File inFile = new File(path, filename);
393         DataInputStream inStream = null;
394         Log.i(LOG_TAG, "Validating file " + filename + " at " + path);
395         try {
396             inStream = new DataInputStream(new FileInputStream(inFile));
397 
398             for (int i = start; i < end; ++i) {
399                 if (inStream.readInt() != i) {
400                     fail("Unexpected value read in OBB file");
401                 }
402             }
403             if (inStream != null) {
404                 inStream.close();
405             }
406             Log.i(LOG_TAG, "Successfully validated file " + filename);
407         } catch (FileNotFoundException e) {
408             fail("File " + inFile + " not found: " + e.toString());
409         } catch (IOException e) {
410             fail("IOError with file " + inFile + ":" + e.toString());
411         }
412     }
413 
414     /**
415      * Helper to validate the contents of a text file in an OBB
416      *
417      * @param path The full path to the file (path to OBB)
418      * @param filename The filename containing the ints to validate
419      * @param contents A {@link String} containing the expected contents of the file
420      */
doValidateTextContents(String path, String filename, String contents)421     protected void doValidateTextContents(String path, String filename, String contents) {
422         File inFile = new File(path, filename);
423         BufferedReader fileReader = null;
424         BufferedReader textReader = null;
425         Log.i(LOG_TAG, "Validating file " + filename + " at " + path);
426         try {
427             fileReader = new BufferedReader(new FileReader(inFile));
428             textReader = new BufferedReader(new StringReader(contents));
429             String actual = null;
430             String expected = null;
431             while ((actual = fileReader.readLine()) != null) {
432                 expected = textReader.readLine();
433                 if (!actual.equals(expected)) {
434                     fail("File " + filename + " in OBB " + path + " does not match expected value");
435                 }
436             }
437             fileReader.close();
438             textReader.close();
439             Log.i(LOG_TAG, "File " + filename + " successfully verified.");
440         } catch (IOException e) {
441             fail("IOError with file " + inFile + ":" + e.toString());
442         }
443     }
444 
445     /**
446      * Helper to validate the contents of a "long" file on our OBBs
447      *
448      * The format of the files are sequential 0's of type long
449      *
450      * @param path The full path to the file (path to OBB)
451      * @param filename The filename containing the ints to validate
452      * @param size The number of zero's expected in the file
453      * @param checkContents If true, the contents of the file are actually verified; if false,
454      *      we simply verify that the file can be opened
455      */
doValidateZeroLongFile(String path, String filename, long size, boolean checkContents)456     protected void doValidateZeroLongFile(String path, String filename, long size,
457             boolean checkContents) {
458         File inFile = new File(path, filename);
459         DataInputStream inStream = null;
460         Log.i(LOG_TAG, "Validating file " + filename + " at " + path);
461         try {
462             inStream = new DataInputStream(new FileInputStream(inFile));
463 
464             if (checkContents) {
465                 for (long i = 0; i < size; ++i) {
466                     if (inStream.readLong() != 0) {
467                         fail("Unexpected value read in OBB file" + filename);
468                     }
469                 }
470             }
471 
472             if (inStream != null) {
473                 inStream.close();
474             }
475             Log.i(LOG_TAG, "File " + filename + " successfully verified for " + size + " zeros");
476         } catch (IOException e) {
477             fail("IOError with file " + inFile + ":" + e.toString());
478         }
479     }
480 
481     /**
482      * Helper to synchronously wait until we can get a path for a given OBB file
483      *
484      * @param filePath The full normalized path to the OBB file
485      * @return The mounted path of the OBB, used to access contents in it
486      */
doWaitForPath(String filePath)487     protected String doWaitForPath(String filePath) {
488         String path = null;
489 
490         long waitTimeMillis = 0;
491         assertTrue("OBB " + filePath + " is not currently mounted!", mSm.isObbMounted(filePath));
492         while (path == null) {
493             try {
494                 Thread.sleep(WAIT_TIME_INCR);
495                 waitTimeMillis += WAIT_TIME_INCR;
496                 if (waitTimeMillis > MAX_WAIT_TIME) {
497                     fail("Timed out waiting to get path of OBB file " + filePath);
498                 }
499             } catch (InterruptedException e) {
500                 // do nothing
501             }
502             path = mSm.getMountedObbPath(filePath);
503         }
504         Log.i(LOG_TAG, "Got OBB path: " + path);
505         return path;
506     }
507 
508     /**
509      * Verifies the pre-defined contents of our first OBB (OBB_FILE_1)
510      *
511      * The OBB contains 4 files and no subdirectories
512      *
513      * @param filePath The normalized path to the already-mounted OBB file
514      */
verifyObb1Contents(String filePath)515     protected void verifyObb1Contents(String filePath) {
516         String path = null;
517         path = doWaitForPath(filePath);
518 
519         // Validate contents of 2 files in this obb
520         doValidateIntContents(path, "OneToOneThousandInts.bin", 0, 1000);
521         doValidateIntContents(path, "SevenHundredInts.bin", 0, 700);
522         doValidateZeroLongFile(path, "FiveLongs.bin", 5, true);
523     }
524 
525     /**
526      * Verifies the pre-defined contents of our second OBB (OBB_FILE_2)
527      *
528      * The OBB contains 2 files and no subdirectories
529      *
530      * @param filePath The normalized path to the already-mounted OBB file
531      */
verifyObb2Contents(String filename)532     protected void verifyObb2Contents(String filename) {
533         String path = null;
534         path = doWaitForPath(filename);
535 
536         // Validate contents of file
537         doValidateTextContents(path, "sample.txt", SAMPLE1_TEXT);
538         doValidateTextContents(path, "sample2.txt", SAMPLE2_TEXT);
539     }
540 
541     /**
542      * Verifies the pre-defined contents of our third OBB (OBB_FILE_3)
543      *
544      * The OBB contains nested files and subdirectories
545      *
546      * @param filePath The normalized path to the already-mounted OBB file
547      */
verifyObb3Contents(String filename)548     protected void verifyObb3Contents(String filename) {
549         String path = null;
550         path = doWaitForPath(filename);
551 
552         // Validate contents of file
553         doValidateIntContents(path, "OneToOneThousandInts.bin", 0, 1000);
554         doValidateZeroLongFile(path, "TwoHundredLongs", 200, true);
555 
556         // validate subdirectory 1
557         doValidateZeroLongFile(path + File.separator + "subdir1", "FiftyLongs", 50, true);
558 
559         // validate subdirectory subdir2/
560         doValidateIntContents(path + File.separator + "subdir2", "OneToOneThousandInts", 0, 1000);
561 
562         // validate subdirectory subdir2/subdir2a/
563         doValidateZeroLongFile(path + File.separator + "subdir2" + File.separator + "subdir2a",
564                 "TwoHundredLongs", 200, true);
565 
566         // validate subdirectory subdir2/subdir2a/subdir2a1/
567         doValidateIntContents(path + File.separator + "subdir2" + File.separator + "subdir2a"
568                 + File.separator + "subdir2a1", "OneToOneThousandInts", 0, 1000);
569     }
570 }
571