diff options
4 files changed, 236 insertions, 139 deletions
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java b/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java index d9d1275..e929431 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java @@ -16,8 +16,10 @@ 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(); + } } diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java b/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java index 3a2bd55..7e90878 100755 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java @@ -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; + } diff --git a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java index 04b42cc..29ec9fe 100644 --- a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java +++ b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java @@ -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(); + } + } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java index 5992fab..b1abba5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java @@ -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); } /** |