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 }