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