OSDN Git Service

Move package installation logic from ADT to ddmlib.
authorBrett Chabot <brettchabot@google.com>
Fri, 12 Jun 2009 22:30:55 +0000 (15:30 -0700)
committerBrett Chabot <brettchabot@google.com>
Tue, 16 Jun 2009 02:25:48 +0000 (19:25 -0700)
ddms/libs/ddmlib/src/com/android/ddmlib/Device.java
ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java

index d9d1275..e929431 100644 (file)
 
 package com.android.ddmlib;
 
+import com.android.ddmlib.SyncService.SyncResult;
 import com.android.ddmlib.log.LogReceiver;
 
+import java.io.File;
 import java.io.IOException;
 import java.nio.channels.SocketChannel;
 import java.util.ArrayList;
@@ -25,6 +27,8 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 
 /**
@@ -50,11 +54,51 @@ final class Device implements IDevice {
     private final ArrayList<Client> mClients = new ArrayList<Client>();
     private DeviceMonitor mMonitor;
 
+    private static final String LOG_TAG = "Device";
+
     /**
      * Socket for the connection monitoring client connection/disconnection.
      */
     private SocketChannel mSocketChannel;
 
+    /**
+     * Output receiver for "pm install package.apk" command line.
+     */
+    private static final class InstallReceiver extends MultiLineReceiver {
+
+        private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
+        private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
+
+        private String mErrorMessage = null;
+
+        public InstallReceiver() {
+        }
+
+        @Override
+        public void processNewLines(String[] lines) {
+            for (String line : lines) {
+                if (line.length() > 0) {
+                    if (line.startsWith(SUCCESS_OUTPUT)) {
+                        mErrorMessage = null;
+                    } else {
+                        Matcher m = FAILURE_PATTERN.matcher(line);
+                        if (m.matches()) {
+                            mErrorMessage = m.group(1);
+                        }
+                    }
+                }
+            }
+        }
+
+        public boolean isCancelled() {
+            return false;
+        }
+
+        public String getErrorMessage() {
+            return mErrorMessage;
+        }
+    }
+
     /*
      * (non-Javadoc)
      * @see com.android.ddmlib.IDevice#getSerialNumber()
@@ -373,4 +417,94 @@ final class Device implements IDevice {
     void addProperty(String label, String value) {
         mProperties.put(label, value);
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String installPackage(String packageFilePath, boolean reinstall)
+           throws IOException {
+       String remoteFilePath = syncPackageToDevice(packageFilePath);
+       String result = installRemotePackage(remoteFilePath, reinstall);
+       removeRemotePackage(remoteFilePath);
+       return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String syncPackageToDevice(String localFilePath)
+            throws IOException {
+        try {
+            String packageFileName = getFileName(localFilePath);
+            String remoteFilePath = String.format("/data/local/tmp/%1$s", packageFileName); //$NON-NLS-1$
+
+            Log.i(packageFileName, String.format("Uploading %1$s onto device '%2$s'",
+                    packageFileName, getSerialNumber()));
+
+            SyncService sync = getSyncService();
+            if (sync != null) {
+                String message = String.format("Uploading file onto device '%1$s'",
+                        getSerialNumber());
+                Log.i(LOG_TAG, message);
+                SyncResult result = sync.pushFile(localFilePath, remoteFilePath,
+                        SyncService.getNullProgressMonitor());
+
+                if (result.getCode() != SyncService.RESULT_OK) {
+                    throw new IOException(String.format("Unable to upload file: %1$s",
+                            result.getMessage()));
+                }
+            } else {
+                throw new IOException("Unable to open sync connection!");
+            }
+            return remoteFilePath;
+        } catch (IOException e) {
+            Log.e(LOG_TAG, String.format("Unable to open sync connection! reason: %1$s",
+                    e.getMessage()));
+            throw e;
+        }
+    }
+
+    /**
+     * Helper method to retrieve the file name given a local file path
+     * @param filePath full directory path to file
+     * @return {@link String} file name
+     */
+    private String getFileName(String filePath) {
+        return new File(filePath).getName();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String installRemotePackage(String remoteFilePath, boolean reinstall)
+            throws IOException {
+        InstallReceiver receiver = new InstallReceiver();
+        String cmd = String.format(reinstall ? "pm install -r \"%1$s\"" : "pm install \"%1$s\"",
+                            remoteFilePath);
+        executeShellCommand(cmd, receiver);
+        return receiver.getErrorMessage();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeRemotePackage(String remoteFilePath) throws IOException {
+        // now we delete the app we sync'ed
+        try {
+            executeShellCommand("rm " + remoteFilePath, new NullOutputReceiver());
+        } catch (IOException e) {
+            Log.e(LOG_TAG, String.format("Failed to delete temporary package: %1$s",
+                    e.getMessage()));
+            throw e;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String uninstallPackage(String packageName) throws IOException {
+        InstallReceiver receiver = new InstallReceiver();
+        executeShellCommand("pm uninstall " + packageName, receiver);
+        return receiver.getErrorMessage();
+    }
 }
index 3a2bd55..7e90878 100755 (executable)
@@ -146,7 +146,7 @@ public interface IDevice {
 
     /**
      * Returns a {@link SyncService} object to push / pull files to and from the device.
-     * @return <code>null</code> if the SyncService couldn't be created. This can happen if abd
+     * @return <code>null</code> if the SyncService couldn't be created. This can happen if adb
      * refuse to open the connection because the {@link IDevice} is invalid (or got disconnected).
      * @throws IOException if the connection with adb failed.
      */
@@ -211,4 +211,48 @@ public interface IDevice {
      */
     public String getClientName(int pid);
 
+    /**
+     * Installs an Android application on device.
+     * This is a helper method that combines the syncPackageToDevice, installRemotePackage,
+     * and removePackage steps
+     * @param packageFilePath the absolute file system path to file on local host to install
+     * @param reinstall set to <code>true</code> if re-install of app should be performed
+     * @return a {@link String} with an error code, or <code>null</code> if success.
+     * @throws IOException
+     */
+    public String installPackage(String packageFilePath, boolean reinstall)  throws IOException;
+
+    /**
+     * Pushes a file to device
+     * @param localFilePath the absolute path to file on local host
+     * @return {@link String} destination path on device for file
+     * @throws IOException if fatal error occurred when pushing file
+     */
+    public String syncPackageToDevice(String localFilePath)
+            throws IOException;
+
+    /**
+     * Installs the application package that was pushed to a temporary location on the device.
+     * @param remoteFilePath absolute file path to package file on device
+     * @param reinstall set to <code>true</code> if re-install of app should be performed
+     * @throws InstallException if installation failed
+     */
+    public String installRemotePackage(String remoteFilePath, boolean reinstall)
+            throws IOException;
+
+    /**
+     * Remove a file from device
+     * @param remoteFilePath path on device of file to remove
+     * @throws IOException if file removal failed
+     */
+    public void removeRemotePackage(String remoteFilePath) throws IOException;
+
+    /**
+     * Uninstall an package from the device.
+     * @param packageName the Android application package name to uninstall
+     * @return a {@link String} with an error code, or <code>null</code> if success.
+     * @throws IOException
+     */
+    public String uninstallPackage(String packageName) throws IOException;
+
 }
index 04b42cc..29ec9fe 100644 (file)
@@ -220,6 +220,30 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
             return "";
         }
 
+        public String installPackage(String packageFilePath, boolean reinstall)
+                throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        public String uninstallPackage(String packageName) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        public String installRemotePackage(String remoteFilePath,
+                boolean reinstall) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        public void removeRemotePackage(String remoteFilePath)
+                throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        public String syncPackageToDevice(String localFilePath)
+                throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
     }
 
     /**
index 5992fab..b1abba5 100644 (file)
@@ -21,12 +21,9 @@ import com.android.ddmlib.Client;
 import com.android.ddmlib.ClientData;
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.Log;
-import com.android.ddmlib.MultiLineReceiver;
-import com.android.ddmlib.SyncService;
 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
 import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
-import com.android.ddmlib.SyncService.SyncResult;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration.TargetMode;
 import com.android.ide.eclipse.adt.internal.launch.DelayedLaunchInfo.InstallRetryMode;
@@ -74,8 +71,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map.Entry;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Controls the launch of Android application either on a device or on the
@@ -134,47 +129,6 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
     /** static instance for singleton */
     private static AndroidLaunchController sThis = new AndroidLaunchController();
 
-
-
-    /**
-     * Output receiver for "pm install package.apk" command line.
-     */
-    private static final class InstallReceiver extends MultiLineReceiver {
-
-        private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
-        private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
-
-        private String mSuccess = null;
-
-        public InstallReceiver() {
-        }
-
-        @Override
-        public void processNewLines(String[] lines) {
-            for (String line : lines) {
-                if (line.length() > 0) {
-                    if (line.startsWith(SUCCESS_OUTPUT)) {
-                        mSuccess = null;
-                    } else {
-                        Matcher m = FAILURE_PATTERN.matcher(line);
-                        if (m.matches()) {
-                            mSuccess = m.group(1);
-                        }
-                    }
-                }
-            }
-        }
-
-        public boolean isCancelled() {
-            return false;
-        }
-
-        public String getSuccess() {
-            return mSuccess;
-        }
-    }
-
-
     /** private constructor to enforce singleton */
     private AndroidLaunchController() {
         AndroidDebugBridge.addDebugBridgeChangeListener(this);
@@ -847,70 +801,29 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
      * @return true if the install succeeded.
      */
     private boolean doSyncApp(DelayedLaunchInfo launchInfo, IDevice device) {
+        IPath path = launchInfo.getPackageFile().getLocation();
+        String fileName = path.lastSegment();
         try {
-            SyncService sync = device.getSyncService();
-            if (sync != null) {
-                IPath path = launchInfo.getPackageFile().getLocation();
-                String message = String.format("Uploading %1$s onto device '%2$s'",
-                        path.lastSegment(), device.getSerialNumber());
-                AdtPlugin.printToConsole(launchInfo.getProject(), message);
-
-                String osLocalPath = path.toOSString();
-                String apkName = launchInfo.getPackageFile().getName();
-                String remotePath = "/data/local/tmp/" + apkName; //$NON-NLS-1$
-
-                SyncResult result = sync.pushFile(osLocalPath, remotePath,
-                        SyncService.getNullProgressMonitor());
-
-                if (result.getCode() != SyncService.RESULT_OK) {
-                    String msg = String.format("Failed to upload %1$s on '%2$s': %3$s",
-                            apkName, device.getSerialNumber(), result.getMessage());
-                    AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
-                    return false;
-                }
-
-                // Now that the package is uploaded, we can install it properly.
-                // This will check that there isn't another apk declaring the same package, or
-                // that another install used a different key.
-                boolean installResult =  installPackage(launchInfo, remotePath, device);
-
-                // now we delete the app we sync'ed
-                try {
-                    device.executeShellCommand("rm " + remotePath, new MultiLineReceiver() { //$NON-NLS-1$
-                        @Override
-                        public void processNewLines(String[] lines) {
-                            // pass
-                        }
-                        public boolean isCancelled() {
-                            return false;
-                        }
-                    });
-                } catch (IOException e) {
-                    AdtPlugin.printErrorToConsole(launchInfo.getProject(), String.format(
-                            "Failed to delete temporary package: %1$s", e.getMessage()));
-                    return false;
-                }
-
-                // if the installation succeeded, we register it.
-                if (installResult) {
-                    ApkInstallManager.getInstance().registerInstallation(
-                            launchInfo.getProject(), device);
-                }
-
-                return installResult;
-            } else {
-                String msg = String.format(
-                        "Failed to upload %1$s on device '%2$s': Unable to open sync connection!",
-                        launchInfo.getPackageFile().getName(), device.getSerialNumber());
-                AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
+            String message = String.format("Uploading %1$s onto device '%2$s'",
+                    fileName, device.getSerialNumber());
+            AdtPlugin.printToConsole(launchInfo.getProject(), message);
+
+            String remotePackagePath = device.syncPackageToDevice(path.toOSString());
+            boolean installResult = installPackage(launchInfo, remotePackagePath, device);
+            device.removeRemotePackage(remotePackagePath);
+
+            // if the installation succeeded, we register it.
+            if (installResult) {
+               ApkInstallManager.getInstance().registerInstallation(
+                       launchInfo.getProject(), device);
             }
-        } catch (IOException e) {
-            String msg = String.format(
-                    "Failed to upload %1$s on device '%2$s': Unable to open sync connection!",
-                    launchInfo.getPackageFile().getName(), device.getSerialNumber());
-            AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e.getMessage());
+            return installResult;
+        }
+        catch (IOException e) {
+            String msg = String.format("Failed to upload %1$s on device '%2$s'", fileName,
+                    device.getSerialNumber());
+            AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e);
         }
-
         return false;
     }
 
@@ -988,22 +901,19 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
         return dependencies;
     }
 
-
-
     /**
-     * Installs the application package that was pushed to a temporary location on the device.
+     * Installs the application package on the device, and handles return result
      * @param launchInfo The launch information
      * @param remotePath The remote path of the package.
      * @param device The device on which the launch is done.
      */
     private boolean installPackage(DelayedLaunchInfo launchInfo, final String remotePath,
             final IDevice device) {
-
         String message = String.format("Installing %1$s...", launchInfo.getPackageFile().getName());
         AdtPlugin.printToConsole(launchInfo.getProject(), message);
-
         try {
-            String result = doInstall(launchInfo, remotePath, device, false /* reinstall */);
+            // try a reinstall first, because the most common case is the app is already installed
+            String result = doInstall(launchInfo, remotePath, device, true /* reinstall */);
 
             /* For now we force to retry the install (after uninstalling) because there's no
              * other way around it: adb install does not want to update a package w/o uninstalling
@@ -1012,7 +922,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
             return checkInstallResult(result, device, launchInfo, remotePath,
                     InstallRetryMode.ALWAYS);
         } catch (IOException e) {
-            // do nothing, we'll return false
+            String msg = String.format(
+                    "Failed to install %1$s on device '%2$s!",
+                    launchInfo.getPackageFile().getName(), device.getSerialNumber());
+            AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e.getMessage());
         }
 
         return false;
@@ -1033,7 +946,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
         if (result == null) {
             AdtPlugin.printToConsole(launchInfo.getProject(), "Success!");
             return true;
-        } else if (result.equals("INSTALL_FAILED_ALREADY_EXISTS")) { //$NON-NLS-1$
+        }
+        else if (result.equals("INSTALL_FAILED_ALREADY_EXISTS")) { //$NON-NLS-1$
+            // this should never happen, since reinstall mode is used on the first attempt
             if (retryMode == InstallRetryMode.PROMPT) {
                 boolean prompt = AdtPlugin.displayPrompt("Application Install",
                         "A previous installation needs to be uninstalled before the new package can be installed.\nDo you want to uninstall?");
@@ -1067,11 +982,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
 
                 AdtPlugin.printToConsole(launchInfo.getProject(),
                         "Application already exists. Attempting to re-install instead...");
-                String res = doInstall(launchInfo, remotePath, device, true /* reinstall */);
+                String res = doInstall(launchInfo, remotePath, device, true /* reinstall */ );
                 return checkInstallResult(res, device, launchInfo, remotePath,
                         InstallRetryMode.NEVER);
             }
-
             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
                     "Installation error! The package already exists.");
         } else if (result.equals("INSTALL_FAILED_INVALID_APK")) { //$NON-NLS-1$
@@ -1110,18 +1024,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
      */
     @SuppressWarnings("unused")
     private String doUninstall(IDevice device, DelayedLaunchInfo launchInfo) throws IOException {
-        InstallReceiver receiver = new InstallReceiver();
         try {
-            device.executeShellCommand("pm uninstall " + launchInfo.getPackageName(), //$NON-NLS-1$
-                    receiver);
+            return device.uninstallPackage(launchInfo.getPackageName());
         } catch (IOException e) {
             String msg = String.format(
                     "Failed to uninstall %1$s: %2$s", launchInfo.getPackageName(), e.getMessage());
             AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
             throw e;
         }
-
-        return receiver.getSuccess();
     }
 
     /**
@@ -1136,22 +1046,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
      */
     private String doInstall(DelayedLaunchInfo launchInfo, final String remotePath,
             final IDevice device, boolean reinstall) throws IOException {
-        InstallReceiver receiver = new InstallReceiver();
-        try {
-            String cmd = String.format(
-                    reinstall ? "pm install -r \"%1$s\"" : "pm install \"%1$s\"", //$NON-NLS-1$ //$NON-NLS-2$
-                    remotePath); //$NON-NLS-1$ //$NON-NLS-2$
-            device.executeShellCommand(cmd, receiver);
-        } catch (IOException e) {
-            String msg = String.format(
-                    "Failed to install %1$s on device '%2$s': %3$s",
-                    launchInfo.getPackageFile().getName(), device.getSerialNumber(),
-                    e.getMessage());
-            AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
-            throw e;
-        }
-
-        return receiver.getSuccess();
+        return device.installRemotePackage(remotePath, reinstall);
     }
 
     /**