AI 144303: am: CL 144302 CTS: Improve device restart behaviour (ADB workaround)
  - Instead of polling the log and searching for a string that indicates that the boot is complete, query the dev.bootcomplete property.
  - Use the ddmlib to poll the device boot status instead of a system call to adb, because of a suspected race condition when using both simultaneously.
  - If the device fails to register after a restart, kill the adb server and the ddmlib adb connection again.
  - Minor cleanups in comments and method names.
  Original author: phillipd
  Merged from: //branches/cupcake/...

Automated import of CL 144303
diff --git a/tools/host/src/com/android/cts/DeviceManager.java b/tools/host/src/com/android/cts/DeviceManager.java
index f43e6da..4e84058 100644
--- a/tools/host/src/com/android/cts/DeviceManager.java
+++ b/tools/host/src/com/android/cts/DeviceManager.java
@@ -16,19 +16,14 @@
 
 package com.android.cts;
 
-import com.android.cts.TestDevice.StdOutObserver;
 import com.android.ddmlib.AndroidDebugBridge;
 import com.android.ddmlib.Device;
+import com.android.ddmlib.NullOutputReceiver;
 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.util.regex.Pattern;
 
 /**
  * Initializing and managing devices.
@@ -37,6 +32,7 @@
 
     private static final int SHORT_DELAY = 1000 * 15; // 15 seconds
     private static final int LONG_DELAY = 1000 * 60 * 10; // 10 minutes
+    private static final int MAX_ADB_RESTART_ATTEMPTS = 10;
     ArrayList<TestDevice> mDevices;
     /** This is used during device restart for blocking until the device has been reconnected. */
     private Semaphore mSemaphore = new Semaphore(0);
@@ -129,10 +125,10 @@
     /**
      * Append the device to the device list.
      *
-     * @param device The devie to be appended to the device list.
+     * @param device The device to be appended to the device list.
      */
     private void appendDevice(final Device device) {
-        if (-1 == searchDevice(device)) {
+        if (-1 == getDeviceIndex(device)) {
             TestDevice td = new TestDevice(device);
             mDevices.add(td);
         }
@@ -144,7 +140,7 @@
      * @param device The device to be removed from the device list.
      */
     private void removeDevice(final Device device) {
-        int index = searchDevice(device);
+        int index = getDeviceIndex(device);
         if (index == -1) {
             Log.d("Can't find " + device + " in device list of DeviceManager");
             return;
@@ -154,12 +150,12 @@
     }
 
     /**
-     * Search a specific device.
+     * Get the index of the specified device in the device array.
      *
      * @param device The device to be found.
-     * @return The index of the specific device if exits; else -1.
+     * @return The index of the device if it exists; else -1.
      */
-    private int searchDevice(final Device device) {
+    private int getDeviceIndex(final Device device) {
         TestDevice td;
 
         for (int index = 0; index < mDevices.size(); index++) {
@@ -170,12 +166,12 @@
         }
         return -1;
     }
-
+    
     /**
-     * Search a specific device.
+     * Search a <code>TestDevice</code> by serial number.
      *
-     * @param deviceSerialNumber The device to be found.
-     * @return The the specific test device if exits; else null.
+     * @param deviceSerialNumber The serial number of the device to be found.
+     * @return The test device, if it exists, otherwise null.
      */
     private TestDevice searchTestDevice(final String deviceSerialNumber) {
         for (TestDevice td : mDevices) {
@@ -291,42 +287,44 @@
 
         String deviceSerialNumber = ts.getDeviceId();
         if (!deviceSerialNumber.toLowerCase().startsWith("emulator")) {
-            AndroidDebugBridge.disconnectBridge();
-            executeCommand("adb shell reboot");
-            // kill the server while the device is rebooting
-            executeCommand("adb kill-server");
-            
-            // Reset the device counter semaphore. We will wait below until at least one device
-            // has come online. This can happen any time during or after the call to
-            // createBridge(). The counter gets increased by the DeviceServiceMonitor when a
-            // device is added.
-            mSemaphore.drainPermits();
-            AndroidDebugBridge.createBridge(getAdbLocation(), true);
-            
-            // wait until at least one device has been added
-            mSemaphore.tryAcquire(LONG_DELAY, TimeUnit.MILLISECONDS);
-
-            // wait until the device has started up
-            // TODO: Can we use 'adb shell getprop dev.bootcomplete' instead?
-            boolean started = false;
-            while (!started) {
-                Thread.sleep(SHORT_DELAY);
-                // dump log and exit
-                RestartADBServerObserver ro = executeCommand("adb logcat -d");
-                started = ro.hasStarted();
+            try {
+                Device dev = searchTestDevice(deviceSerialNumber).getDevice();
+                dev.executeShellCommand("reboot", new NullOutputReceiver());
+            } catch (Exception e) {
+                Log.d("Could not issue reboot command through ddmlib: " + e);
+                // try to reboot the device using the command line adb
+                executeCommand("adb -s " + deviceSerialNumber + " shell reboot");
             }
+            int attempts = 0;
+            boolean deviceConnected = false;
+            while (!deviceConnected && (attempts < MAX_ADB_RESTART_ATTEMPTS)) {
+                AndroidDebugBridge.disconnectBridge();
 
-            TestDevice device = searchTestDevice(deviceSerialNumber);
-            if (device != null) {
-                ts.setTestDevice(device);
-                ts.getSessionLog().setDeviceInfo(device.getDeviceInfo());
-                Thread.sleep(SHORT_DELAY);
-                device.probeDeviceStatus();
-                Thread.sleep(SHORT_DELAY * 2);
+                // kill the server while the device is rebooting
+                executeCommand("adb kill-server");
+                
+                // Reset the device counter semaphore. We will wait below until at least one device
+                // has come online. This can happen any time during or after the call to
+                // createBridge(). The counter gets increased by the DeviceServiceMonitor when a
+                // device is added.
+                mSemaphore.drainPermits();
+                AndroidDebugBridge.createBridge(getAdbLocation(), true);
+                
+                boolean deviceFound = false;
+                while (!deviceFound) {
+                    // wait until at least one device has been added
+                    mSemaphore.tryAcquire(LONG_DELAY, TimeUnit.MILLISECONDS);
+                    TestDevice device = searchTestDevice(deviceSerialNumber);
+                    if (device != null) {
+                        ts.setTestDevice(device);
+                        deviceFound = true;
+                        deviceConnected = device.waitForBootComplete();
+                    }
+                }
+                attempts += 1;
             }
-            
             // dismiss the screen lock by sending a MENU key event
-            executeCommand("adb shell input keyevent 82");
+            executeCommand("adb -s " + deviceSerialNumber + " shell input keyevent 82");
         }
     }
     
@@ -334,92 +332,41 @@
      * Execute the given command and wait for its completion.
      *
      * @param command The command to be executed.
+     * @return True if the command was executed successfully, otherwise false.
      */
-    private RestartADBServerObserver executeCommand(String command) {
-        Log.d("restartADBServer(): cmd=" + command);
-        RestartADBServerObserver ro = new RestartADBServerObserver();
+    private boolean executeCommand(String command) {
+        Log.d("executeCommand(): cmd=" + command);
         try {
             Process proc = Runtime.getRuntime().exec(command);
-            ro.setInputStream(proc.getInputStream());
-            ro.waitToFinish();
-        } catch (IOException e) {
-            e.printStackTrace();
+            TimeoutThread tt = new TimeoutThread(proc, SHORT_DELAY);
+            tt.start();
+            proc.waitFor(); // ignore exit value
+            tt.interrupt(); // wake timeout thread
+        } catch (Exception e) {
+            return false;
         }
-        return ro;
+        return true; 
     }
-
-
-    /**
-     * The observer of restarting ADB server.
-     * TODO: This is a workaround to reset the device after a certain number of tests have been run.
-     * This should be removed as soon as the ADB stability problems have been fixed.
-     */
-    final class RestartADBServerObserver implements StdOutObserver, Runnable {
-
-        private BufferedReader mReader;
-        private final Pattern mPattern = 
-            Pattern.compile("I/SurfaceFlinger.*Boot is finished.*");
-        private boolean mFoundPattern;
-        private Thread mThread;
-        private boolean mKillThread;
+    
+    class TimeoutThread extends Thread {
+        Process mProcess;
+        long mTimeout;
         
-        /**
-         * Wait for the observer to process all lines.
-         * @return True if the observer finished normally, false if a timeout occurred.
-         */
-        public boolean waitToFinish() {
-            // wait for thread to terminate first
-            try {
-                mThread.join(LONG_DELAY);
-                // set the kill flag, just in case we timed out
-                mKillThread = true;
-                return true;
-            } catch (InterruptedException e) {
-                mKillThread = true;
-                return false;
-            }
+        TimeoutThread(Process process, long timeout) {
+            mProcess = process;
+            mTimeout = timeout;
         }
         
-        public boolean hasStarted() {
-            waitToFinish();
-            return mFoundPattern;
-        }
-        
-        /** {@inheritDoc} */
+        @Override
         public void run() {
             try {
-                processLines();
-            } catch (IOException e) {
-                e.printStackTrace();
-            } finally {
-                try {
-                    mReader.close();
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
+                Thread.sleep(mTimeout);
+            } catch (InterruptedException e) {
+                // process has already completed
+                return;
             }
-        }
-
-        /**
-         * Parse the standard out.
-         */
-        public void processLines() throws IOException {
-            String line = mReader.readLine();
-            
-            while (line != null && !mKillThread) {
-                Log.d("line=" + line);
-                if (!mFoundPattern) {
-                    mFoundPattern = mPattern.matcher(line.trim()).matches();
-                }
-                line = mReader.readLine();
-            }
-        }
-
-        /** {@inheritDoc} */
-        public void setInputStream(InputStream is) {
-            mReader = new BufferedReader(new InputStreamReader(is));
-            mThread = new Thread(this);
-            mThread.start();
+            // destroy process and wake up thread waiting for its completion
+            mProcess.destroy();
         }
     }
 }
diff --git a/tools/host/src/com/android/cts/TestDevice.java b/tools/host/src/com/android/cts/TestDevice.java
index acb4952..14ef06a 100644
--- a/tools/host/src/com/android/cts/TestDevice.java
+++ b/tools/host/src/com/android/cts/TestDevice.java
@@ -61,6 +61,13 @@
     private static final String STATUS_STR_IDLE = "idle";
     private static final String STATUS_STR_IN_USE = "in use";
     private static final String STATUS_STR_OFFLINE = "offline";
+    
+    /** Interval [ms] for polling a device until boot is completed. */
+    private static final int REBOOT_POLL_INTERVAL = 5000;
+    /** Number of times a booting device should be polled before we give up. */
+    private static final int REBOOT_POLL_COUNT = 10 * 60 * 1000 / REBOOT_POLL_INTERVAL;
+    /** Max time [ms] to wait for <code>adb shell getprop</code> to return a result. */
+    private static final int GETPROP_TIMEOUT = 5000;
 
     public static final Pattern INSTRUMENT_RESULT_PATTERN;
 
@@ -80,7 +87,6 @@
     private PackageActionTimer mPackageActionTimer;
 
     private ObjectSync mObjectSync;
-    private int mPackageActionResultCode;
 
     static {
         INSTRUMENT_RESULT_PATTERN = Pattern.compile(sInstrumentResultExpr);
@@ -120,46 +126,91 @@
         }
         return mDeviceInfo;
     }
-
+    
     /**
-     * Probe device status by pushing apk to device.
+     * Return the Device instance associated with this TestDevice.
      */
-    public void probeDeviceStatus() throws DeviceDisconnectedException {
-        String apkName = "DeviceInfoCollector";
+    public Device getDevice() {
+        return mDevice;
+    }
+
+    class RestartPropReceiver extends MultiLineReceiver {
+        private boolean mRestarted;
+        private boolean mCancelled;
+        private boolean mDone;
+        
+        @Override
+        public void processNewLines(String[] lines) {
+            for (String line : lines) {
+                if (line.trim().equals("1")) {
+                    mRestarted = true;
+                }
+            }
+        }
+        @Override
+        public void done() {
+            synchronized(this) {
+                mDone = true;
+                this.notifyAll();
+            }
+        }
+        
+        public boolean isCancelled() {
+            return mCancelled;
+        }
+        
+        boolean hasRestarted(long timeout) {
+            try {
+                synchronized (this) {
+                    if (!mDone) {
+                        this.wait(timeout);
+                    }
+                }
+            } catch (InterruptedException e) {
+                // ignore
+            }
+            mCancelled = true;
+            return mRestarted;
+        }
+    }
+    
+    /**
+     * Wait until device indicates that boot is complete.
+     * 
+     * @return true if the device has completed the boot process, false if it does not, or the
+     * device does not respond.
+     */
+    public boolean waitForBootComplete() throws DeviceDisconnectedException {
         Log.d("probe device status...");
 
-        String apkPath = HostConfig.getInstance().getCaseRepository().getApkPath(apkName);
-        if (!HostUtils.isFileExist(apkPath)) {
-            Log.e("File doesn't exist: " + apkPath, null);
-            return;
-        }
-
         mDeviceInfo.set(DeviceParameterCollector.SERIAL_NUMBER, getSerialNumber());
-        Log.d("installing " + apkName + " apk");
         mObjectSync = new ObjectSync();
 
         // reset device observer
         DeviceObserver tmpDeviceObserver = mDeviceObserver;
         mDeviceObserver = this;
 
+        int retries = 0;
         boolean success = false;
-        while (!success) {
-            Log.d("install get info ...");
-            mPackageActionResultCode = 0;
-            installAPK(apkPath);
-            waitForCommandFinish();
-            if (mPackageActionResultCode == DeviceObserver.SUCCESS) {
-                success = true;
-                break;
-            }
-
-            try {
-                Thread.sleep(5000);
-            } catch (InterruptedException e) {
+        while (!success && (retries < REBOOT_POLL_COUNT)) {
+            Log.d("Waiting for device to complete boot");
+            RestartPropReceiver rpr = new RestartPropReceiver();
+            this.executeShellCommand("getprop dev.bootcomplete", rpr);
+            success = rpr.hasRestarted(GETPROP_TIMEOUT);
+            if (!success) {
+                try {
+                    Thread.sleep(REBOOT_POLL_INTERVAL);
+                } catch (InterruptedException e) {
+                    // ignore and retry
+                }
+                retries += 1;
             }
         }
         mDeviceObserver = tmpDeviceObserver;
-        Log.d("probe device status succeds.");
+        if (success) {
+            Log.d("Device boot complete");
+        }
+        return success;
     }
 
     /**
@@ -1546,7 +1597,6 @@
      * Notify install complete.
      */
     public void notifyInstallingComplete(int resultCode) {
-        mPackageActionResultCode = resultCode;
         synchronized (mObjectSync) {
             mObjectSync.sendNotify();
             mPackageActionTimer.stop();
@@ -1555,7 +1605,6 @@
 
     /** {@inheritDoc} */
     public void notifyInstallingTimeout(TestDevice testDevice) {
-        mPackageActionResultCode = DeviceObserver.FAIL;
         synchronized (mObjectSync) {
             mObjectSync.sendNotify();
         }