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