/** Ensure that the flasher instance limiting machinery is working as expected. */
  public void testFlashLimit() throws Exception {
    final DeviceFlashPreparer dfp = mDeviceFlashPreparer;
    try {
      Thread waiter =
          new Thread() {
            @Override
            public void run() {
              dfp.takeFlashingPermit();
              dfp.returnFlashingPermit();
            }
          };
      dfp.setConcurrentFlashSettings(1, new Semaphore(1), true);
      // take the permit; the next attempt to take the permit should block
      dfp.takeFlashingPermit();
      assertFalse(dfp.getConcurrentFlashLock().hasQueuedThreads());

      waiter.start();
      RunUtil.getDefault().sleep(100); // Thread start should take <100ms
      assertTrue("Invalid state: waiter thread is not alive", waiter.isAlive());
      assertTrue("No queued threads", dfp.getConcurrentFlashLock().hasQueuedThreads());

      dfp.returnFlashingPermit();
      RunUtil.getDefault().sleep(100); // Thread start should take <100ms
      assertFalse("Unexpected queued threads", dfp.getConcurrentFlashLock().hasQueuedThreads());

      waiter.join(1000);
      assertFalse("waiter thread has not returned", waiter.isAlive());
    } finally {
      // Attempt to reset concurrent flash settings to defaults
      dfp.setConcurrentFlashSettings(null, null, true);
    }
  }
  /** Ensure that the flasher instance limiting machinery is working as expected. */
  public void testUnlimitedFlashLimit() throws Exception {
    final DeviceFlashPreparer dfp = mDeviceFlashPreparer;
    try {
      Thread waiter =
          new Thread() {
            @Override
            public void run() {
              dfp.takeFlashingPermit();
              dfp.returnFlashingPermit();
            }
          };
      dfp.setConcurrentFlashSettings(null, null, true);
      // take a permit; the next attempt to take the permit should proceed without blocking
      dfp.takeFlashingPermit();
      assertNull("Flash lock is non-null", dfp.getConcurrentFlashLock());

      waiter.start();
      RunUtil.getDefault().sleep(100); // Thread start should take <100ms
      Thread.State waiterState = waiter.getState();
      assertTrue(
          "Invalid state: waiter thread hasn't started",
          waiter.isAlive() || Thread.State.TERMINATED.equals(waiterState));
      assertNull("Flash lock is non-null", dfp.getConcurrentFlashLock());

      dfp.returnFlashingPermit();
      RunUtil.getDefault().sleep(100); // Thread start should take <100ms
      assertNull("Flash lock is non-null", dfp.getConcurrentFlashLock());

      waiter.join(1000);
      assertFalse("waiter thread has not returned", waiter.isAlive());
    } finally {
      // Attempt to reset concurrent flash settings to defaults
      dfp.setConcurrentFlashSettings(null, null, true);
    }
  }
  /** {@inheritDoc} */
  @Override
  public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    Assert.assertNotNull(mTestDevice);

    mAppListPath =
        new File(mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE), APP_LIST_FILE)
            .getAbsolutePath();
    mAppOutputPath =
        new File(mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE), APP_OUTPUT_FILE)
            .getAbsolutePath();

    setupAppInfos();

    // Setup the device
    mTestDevice.executeShellCommand(String.format("rm %s %s", mAppListPath, mAppOutputPath));
    mTestDevice.pushString(generateAppList(), mAppListPath);
    mTestDevice.executeShellCommand(String.format("chmod 750 %s", APP_LAUNCH));

    // Sleep 30 seconds to let device settle.
    RunUtil.getDefault().sleep(30 * 1000);

    // Run the test
    String output = mTestDevice.executeShellCommand(APP_LAUNCH);

    CLog.d("App launch output: %s", output);
    logOutputFile(listener);
  }