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.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;
     30 
     31 import java.io.BufferedReader;
     32 import java.io.DataInputStream;
     33 import java.io.File;
     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;
     40 
     41 public class StorageManagerBaseTest extends InstrumentationTestCase {
     42 
     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";
     58 
     59     protected static boolean FORCE = true;
     60     protected static boolean DONT_FORCE = false;
     61 
     62     private static final String SAMPLE1_TEXT = "This is sample text.\n\nTesting 1 2 3.";
     63 
     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";
     70 
     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;
     77 
     78         /**
     79          * Constructor
     80          *
     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
     83          *      has been mounted
     84          */
     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;
     90         }
     91 
     92         /**
     93          * Runs the thread
     94          *
     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.
     98          */
     99         public void run() {
    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);
    105 
    106             // open a file on OBB 1...
    107             DataInputStream inputFile = openFileOnMountedObb(mOfficialObbFilePath,
    108                     mPathToContentsFile);
    109             assertTrue("Failed to open file!", inputFile != null);
    110 
    111             synchronized (this) {
    112                 mFileOpenOnObb = true;
    113                 notifyAll();
    114             }
    115 
    116             while (!mStop) {
    117                 try {
    118                     Thread.sleep(WAIT_TIME_INCR);
    119                 } catch (InterruptedException e) {
    120                     // nothing special to be done for interruptions
    121                 }
    122             }
    123             try {
    124                 inputFile.close();
    125             } catch (IOException e) {
    126                 fail("Failed to close file on OBB due to error: " + e.toString());
    127             }
    128         }
    129 
    130         /**
    131          * Tells whether a file has yet been successfully opened on the OBB or not
    132          *
    133          * @return true if the specified file on the OBB was opened; false otherwise
    134          */
    135         public boolean isFileOpenOnObb() {
    136             return mFileOpenOnObb;
    137         }
    138 
    139         /**
    140          * Returns the official path of the OBB file that was mounted
    141          *
    142          * This is not the mount path, but the normalized path to the actual OBB file
    143          *
    144          * @return a {@link String} representation of the path to the OBB file that was mounted
    145          */
    146         public String officialObbFilePath() {
    147             return mOfficialObbFilePath;
    148         }
    149 
    150         /**
    151          * Requests the thread to stop running
    152          *
    153          * Closes the opened file and returns
    154          */
    155         public void doStop() {
    156             mStop = true;
    157         }
    158     }
    159 
    160     public class ObbListener extends OnObbStateChangeListener {
    161         private String LOG_TAG = "StorageManagerBaseTest.ObbListener";
    162 
    163         String mOfficialPath = null;
    164         boolean mDone = false;
    165         int mState = -1;
    166 
    167         /**
    168          * {@inheritDoc}
    169          */
    170         @Override
    171         public void onObbStateChange(String path, int state) {
    172             Log.i(LOG_TAG, "Storage state changing to: " + state);
    173 
    174             synchronized (this) {
    175                 Log.i(LOG_TAG, "OfficialPath is now: " + path);
    176                 mState = state;
    177                 mOfficialPath = path;
    178                 mDone = true;
    179                 notifyAll();
    180             }
    181         }
    182 
    183         /**
    184          * Tells whether we are done or not (system told us the OBB has changed state)
    185          *
    186          * @return true if the system has told us this OBB's state has changed, false otherwise
    187          */
    188         public boolean isDone() {
    189             return mDone;
    190         }
    191 
    192         /**
    193          * The last state of the OBB, according to the system
    194          *
    195          * @return A {@link String} representation of the state of the OBB
    196          */
    197         public int state() {
    198             return mState;
    199         }
    200 
    201         /**
    202          * The normalized, official path to the OBB file (according to the system)
    203          *
    204          * @return A {@link String} representation of the official path to the OBB file
    205          */
    206         public String officialPath() {
    207             return mOfficialPath;
    208         }
    209     }
    210 
    211     /**
    212      * {@inheritDoc}
    213      */
    214     @Override
    215     public void setUp() throws Exception {
    216         mContext = getInstrumentation().getContext();
    217         mSm = (StorageManager)mContext.getSystemService(android.content.Context.STORAGE_SERVICE);
    218 
    219     }
    220 
    221     /**
    222      * Helper to copy a raw resource file to an actual specified file
    223      *
    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
    227      */
    228     private void copyRawToFile(int rawResId, File outFile) throws NotFoundException {
    229         Resources res = mContext.getResources();
    230         InputStream is = null;
    231         try {
    232             is = res.openRawResource(rawResId);
    233         } catch (NotFoundException e) {
    234             Log.i(LOG_TAG, "Failed to load resource with id: " + rawResId);
    235             throw e;
    236         }
    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);
    242     }
    243 
    244     /**
    245      * Creates an OBB file (with the given name), into the app's standard files directory
    246      *
    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
    250      */
    251     protected File createObbFile(String name, int rawResId) {
    252         File outFile = null;
    253         try {
    254             final File filesDir = mContext.getFilesDir();
    255             outFile = new File(filesDir, name);
    256             copyRawToFile(rawResId, outFile);
    257         } catch (NotFoundException e) {
    258             if (outFile != null) {
    259                 outFile.delete();
    260             }
    261         }
    262         return outFile;
    263     }
    264 
    265     /**
    266      * Mounts an OBB file and opens a file located on it
    267      *
    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.
    272      */
    273     protected DataInputStream openFileOnMountedObb(String obbPath, String fileName) {
    274 
    275         // get mSm obb mount path
    276         assertTrue("Cannot open file when OBB is not mounted!", mSm.isObbMounted(obbPath));
    277 
    278         String path = mSm.getMountedObbPath(obbPath);
    279         assertTrue("Path should not be null!", path != null);
    280 
    281         File inFile = new File(path, fileName);
    282         DataInputStream inStream = null;
    283         try {
    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());
    288             return null;
    289         } catch (SecurityException e) {
    290             Log.e(LOG_TAG, e.toString());
    291             return null;
    292         }
    293         return inStream;
    294     }
    295 
    296     /**
    297      * Mounts an OBB file
    298      *
    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
    303      */
    304     protected String mountObb(String obbFilePath, String key, int expectedState) {
    305         return doMountObb(obbFilePath, key, expectedState);
    306     }
    307 
    308     /**
    309      * Mounts an OBB file with default options (no encryption, mounting succeeds)
    310      *
    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
    313      */
    314     protected String mountObb(String obbFilePath) {
    315         return doMountObb(obbFilePath, null, OnObbStateChangeListener.MOUNTED);
    316     }
    317 
    318     /**
    319      * Synchronously waits for an OBB listener to be signaled of a state change, but does not throw
    320      *
    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.
    324      */
    325     protected boolean doWaitForObbStateChange(ObbListener obbListener) {
    326         synchronized(obbListener) {
    327             long waitTimeMillis = 0;
    328             while (!obbListener.isDone()) {
    329                 try {
    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!");
    336                     }
    337                 } catch (InterruptedException e) {
    338                     Log.i(LOG_TAG, e.toString());
    339                 }
    340             }
    341             return obbListener.isDone();
    342             }
    343     }
    344 
    345     /**
    346      * Synchronously waits for an OBB listener to be signaled of a state change
    347      *
    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
    351      */
    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);
    356 
    357         ObbListener obbListener = new ObbListener();
    358         boolean success = mSm.mountObb(obbFilePath, key, obbListener);
    359         success &= obbFilePath.equals(doWaitForObbStateChange(obbListener));
    360         success &= (expectedState == obbListener.state());
    361 
    362         if (OnObbStateChangeListener.MOUNTED == expectedState) {
    363             success &= obbFilePath.equals(obbListener.officialPath());
    364             success &= mSm.isObbMounted(obbListener.officialPath());
    365         } else {
    366             success &= !mSm.isObbMounted(obbListener.officialPath());
    367         }
    368 
    369         if (success) {
    370             return obbListener.officialPath();
    371         } else {
    372             return null;
    373         }
    374     }
    375 
    376     /**
    377      * Mounts an OBB file without throwing and synchronously waits for it to finish mounting
    378      *
    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
    384      */
    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);
    388 
    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());
    394 
    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()));
    402         }
    403 
    404         assertEquals("Mount state is not what was expected!", expectedState, obbListener.state());
    405         return obbListener.officialPath();
    406     }
    407 
    408     /**
    409      * Unmounts an OBB file without throwing, and synchronously waits for it to finish unmounting
    410      *
    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
    414      */
    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;
    419 
    420         ObbListener obbListener = new ObbListener();
    421         assertTrue("unmountObb call failed", mSm.unmountObb(obbFilePath, force, obbListener));
    422 
    423         boolean stateChanged = doWaitForObbStateChange(obbListener);
    424         if (force) {
    425             success &= stateChanged;
    426             success &= (OnObbStateChangeListener.UNMOUNTED == obbListener.state());
    427             success &= !mSm.isObbMounted(obbFilePath);
    428         }
    429         return success;
    430     }
    431 
    432     /**
    433      * Unmounts an OBB file and synchronously waits for it to finish unmounting
    434      *
    435      * @param obbFilePath The full path to the OBB file to mount
    436      * @param force true if we shuold force the unmount, false otherwise
    437      */
    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);
    441 
    442         ObbListener obbListener = new ObbListener();
    443         assertTrue("unmountObb call failed", mSm.unmountObb(obbFilePath, force, obbListener));
    444 
    445         boolean stateChanged = doWaitForObbStateChange(obbListener);
    446         if (force) {
    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(
    451                     obbFilePath));
    452         }
    453     }
    454 
    455     /**
    456      * Helper to validate the contents of an "int" file in an OBB.
    457      *
    458      * The format of the files are sequential int's, in the range of: [start..end)
    459      *
    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
    464      */
    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);
    469         try {
    470             inStream = new DataInputStream(new FileInputStream(inFile));
    471 
    472             for (int i = start; i < end; ++i) {
    473                 if (inStream.readInt() != i) {
    474                     fail("Unexpected value read in OBB file");
    475                 }
    476             }
    477             if (inStream != null) {
    478                 inStream.close();
    479             }
    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());
    485         }
    486     }
    487 
    488     /**
    489      * Helper to validate the contents of a text file in an OBB
    490      *
    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
    494      */
    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);
    500         try {
    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");
    509                 }
    510             }
    511             fileReader.close();
    512             textReader.close();
    513             Log.i(LOG_TAG, "File " + filename + " successfully verified.");
    514         } catch (IOException e) {
    515             fail("IOError with file " + inFile + ":" + e.toString());
    516         }
    517     }
    518 
    519     /**
    520      * Helper to validate the contents of a "long" file on our OBBs
    521      *
    522      * The format of the files are sequential 0's of type long
    523      *
    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
    529      */
    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);
    535         try {
    536             inStream = new DataInputStream(new FileInputStream(inFile));
    537 
    538             if (checkContents) {
    539                 for (long i = 0; i < size; ++i) {
    540                     if (inStream.readLong() != 0) {
    541                         fail("Unexpected value read in OBB file" + filename);
    542                     }
    543                 }
    544             }
    545 
    546             if (inStream != null) {
    547                 inStream.close();
    548             }
    549             Log.i(LOG_TAG, "File " + filename + " successfully verified for " + size + " zeros");
    550         } catch (IOException e) {
    551             fail("IOError with file " + inFile + ":" + e.toString());
    552         }
    553     }
    554 
    555     /**
    556      * Helper to synchronously wait until we can get a path for a given OBB file
    557      *
    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
    560      */
    561     protected String doWaitForPath(String filePath) {
    562         String path = null;
    563 
    564         long waitTimeMillis = 0;
    565         assertTrue("OBB " + filePath + " is not currently mounted!", mSm.isObbMounted(filePath));
    566         while (path == null) {
    567             try {
    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);
    572                 }
    573             } catch (InterruptedException e) {
    574                 // do nothing
    575             }
    576             path = mSm.getMountedObbPath(filePath);
    577         }
    578         Log.i(LOG_TAG, "Got OBB path: " + path);
    579         return path;
    580     }
    581 
    582     /**
    583      * Verifies the pre-defined contents of our first OBB (OBB_FILE_1)
    584      *
    585      * The OBB contains 4 files and no subdirectories
    586      *
    587      * @param filePath The normalized path to the already-mounted OBB file
    588      */
    589     protected void verifyObb1Contents(String filePath) {
    590         String path = null;
    591         path = doWaitForPath(filePath);
    592 
    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);
    597     }
    598 
    599     /**
    600      * Verifies the pre-defined contents of our second OBB (OBB_FILE_2)
    601      *
    602      * The OBB contains 2 files and no subdirectories
    603      *
    604      * @param filePath The normalized path to the already-mounted OBB file
    605      */
    606     protected void verifyObb2Contents(String filename) {
    607         String path = null;
    608         path = doWaitForPath(filename);
    609 
    610         // Validate contents of file
    611         doValidateTextContents(path, "sample.txt", SAMPLE1_TEXT);
    612         doValidateTextContents(path, "sample2.txt", SAMPLE2_TEXT);
    613     }
    614 
    615     /**
    616      * Verifies the pre-defined contents of our third OBB (OBB_FILE_3)
    617      *
    618      * The OBB contains nested files and subdirectories
    619      *
    620      * @param filePath The normalized path to the already-mounted OBB file
    621      */
    622     protected void verifyObb3Contents(String filename) {
    623         String path = null;
    624         path = doWaitForPath(filename);
    625 
    626         // Validate contents of file
    627         doValidateIntContents(path, "OneToOneThousandInts.bin", 0, 1000);
    628         doValidateZeroLongFile(path, "TwoHundredLongs", 200, true);
    629 
    630         // validate subdirectory 1
    631         doValidateZeroLongFile(path + File.separator + "subdir1", "FiftyLongs", 50, true);
    632 
    633         // validate subdirectory subdir2/
    634         doValidateIntContents(path + File.separator + "subdir2", "OneToOneThousandInts", 0, 1000);
    635 
    636         // validate subdirectory subdir2/subdir2a/
    637         doValidateZeroLongFile(path + File.separator + "subdir2" + File.separator + "subdir2a",
    638                 "TwoHundredLongs", 200, true);
    639 
    640         // validate subdirectory subdir2/subdir2a/subdir2a1/
    641         doValidateIntContents(path + File.separator + "subdir2" + File.separator + "subdir2a"
    642                 + File.separator + "subdir2a1", "OneToOneThousandInts", 0, 1000);
    643     }
    644 }