import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration.TargetMode;
import com.android.ide.eclipse.adt.launch.DelayedLaunchInfo.InstallRetryMode;
import com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserResponse;
+import com.android.ide.eclipse.adt.project.ApkInstallManager;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
/**
- * Syncs the application on the device/emulator.
+ * If needed, syncs the application and all its dependencies on the device/emulator.
*
* @param launchInfo The Launch information object.
* @param device the device on which to sync the application
* @return true if the install succeeded.
*/
private boolean syncApp(DelayedLaunchInfo launchInfo, IDevice device) {
+ boolean alreadyInstalled = ApkInstallManager.getInstance().isApplicationInstalled(
+ launchInfo.getProject(), device);
+
+ if (alreadyInstalled) {
+ AdtPlugin.printToConsole(launchInfo.getProject(),
+ "Application already deployed. No need to reinstall.");
+ } else {
+ if (doSyncApp(launchInfo, device) == false) {
+ return false;
+ }
+ }
+
+ // The app is now installed, now try the dependent projects
+ for (DelayedLaunchInfo dependentLaunchInfo : getDependenciesLaunchInfo(launchInfo)) {
+ String msg = String.format("Project dependency found, installing: %s",
+ dependentLaunchInfo.getProject().getName());
+ AdtPlugin.printToConsole(launchInfo.getProject(), msg);
+ if (syncApp(dependentLaunchInfo, device) == false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Syncs the application on the device/emulator.
+ *
+ * @param launchInfo The Launch information object.
+ * @param device the device on which to sync the application
+ * @return true if the install succeeded.
+ */
+ private boolean doSyncApp(DelayedLaunchInfo launchInfo, IDevice device) {
SyncService sync = device.getSyncService();
if (sync != null) {
IPath path = launchInfo.getPackageFile().getLocation();
return false;
}
- // The app is now installed, now try the dependent projects
- for (DelayedLaunchInfo dependentLaunchInfo : getDependenciesLaunchInfo(launchInfo)) {
- String msg = String.format("Project dependency found, syncing: %s",
- dependentLaunchInfo.getProject().getName());
- AdtPlugin.printToConsole(launchInfo.getProject(), msg);
- syncApp(dependentLaunchInfo, device);
+ // if the installation succeeded, we register it.
+ if (installResult) {
+ ApkInstallManager.getInstance().registerInstallation(
+ launchInfo.getProject(), device);
}
return installResult;
--- /dev/null
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.project;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.Device;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor;
+import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener;
+
+import org.eclipse.core.resources.IProject;
+
+import java.util.ArrayList;
+
+/**
+ * Registers which apk was installed on which device.
+ * <p/>
+ * The goal of this class is to remember the installation of APKs on devices, and provide
+ * information about whether a new APK should be installed on a device prior to running the
+ * application from a launch configuration.
+ * <p/>
+ * The manager uses {@link IProject} and {@link IDevice} to identify the target device and the
+ * (project generating the) APK. This ensures that disconnected and reconnected devices will
+ * always receive new APKs (since the APK could be uninstalled manually).
+ * <p/>
+ * Manually uninstalling an APK from a connected device will still be a problem, but this should
+ * be a limited use case.
+ * <p/>
+ * This is a singleton. To get the instance, use {@link #getInstance()}
+ */
+public class ApkInstallManager implements IDeviceChangeListener, IDebugBridgeChangeListener,
+ IProjectListener {
+
+ private final static ApkInstallManager sThis = new ApkInstallManager();
+
+ /**
+ * Internal struct to associate a project and a device.
+ */
+ private static class ApkInstall {
+ public ApkInstall(IProject project, IDevice device) {
+ this.project = project;
+ this.device = device;
+ }
+ IProject project;
+ IDevice device;
+ }
+
+ private final ArrayList<ApkInstall> mInstallList = new ArrayList<ApkInstall>();
+
+ public static ApkInstallManager getInstance() {
+ return sThis;
+ }
+
+ /**
+ * Registers an installation of <var>project</var> onto <var>device</var>
+ * @param project The project that was installed.
+ * @param device The device that received the installation.
+ */
+ public void registerInstallation(IProject project, IDevice device) {
+ synchronized (mInstallList) {
+ mInstallList.add(new ApkInstall(project, device));
+ }
+ }
+
+ /**
+ * Returns whether a <var>project</var> was installed on the <var>device</var>.
+ * @param project the project that may have been installed.
+ * @param device the device that may have received the installation.
+ * @return
+ */
+ public boolean isApplicationInstalled(IProject project, IDevice device) {
+ synchronized (mInstallList) {
+ for (ApkInstall install : mInstallList) {
+ if (project == install.project && device == install.device) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Resets registered installations for a specific {@link IProject}.
+ * <p/>This ensures that {@link #isApplicationInstalled(IProject, IDevice)} will always return
+ * <code>null</code> for this specified project, for any device.
+ * @param project the project for which to reset all installations.
+ */
+ public void resetInstallationFor(IProject project) {
+ synchronized (mInstallList) {
+ for (int i = 0 ; i < mInstallList.size() ;) {
+ ApkInstall install = mInstallList.get(i);
+ if (install.project == project) {
+ mInstallList.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+ }
+
+ private ApkInstallManager() {
+ AndroidDebugBridge.addDeviceChangeListener(this);
+ AndroidDebugBridge.addDebugBridgeChangeListener(this);
+ ResourceMonitor.getMonitor().addProjectListener(this);
+ }
+
+ /*
+ * Responds to a bridge change by clearing the full installation list.
+ * (non-Javadoc)
+ * @see com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener#bridgeChanged(com.android.ddmlib.AndroidDebugBridge)
+ */
+ public void bridgeChanged(AndroidDebugBridge bridge) {
+ // the bridge changed, there is no way to know which IDevice will be which.
+ // We reset everything
+ synchronized (mInstallList) {
+ mInstallList.clear();
+ }
+ }
+
+ /*
+ * Responds to a device being disconnected by removing all installations related to this device.
+ * (non-Javadoc)
+ * @see com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener#deviceDisconnected(com.android.ddmlib.Device)
+ */
+ public void deviceDisconnected(Device device) {
+ synchronized (mInstallList) {
+ for (int i = 0 ; i < mInstallList.size() ;) {
+ ApkInstall install = mInstallList.get(i);
+ if (install.device == device) {
+ mInstallList.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+ }
+
+ /*
+ * Responds to a close project by resetting all its installation.
+ * (non-Javadoc)
+ * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener#projectClosed(org.eclipse.core.resources.IProject)
+ */
+ public void projectClosed(IProject project) {
+ resetInstallationFor(project);
+ }
+
+ /*
+ * Responds to a close project by resetting all its installation.
+ * (non-Javadoc)
+ * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener#projectDeleted(org.eclipse.core.resources.IProject)
+ */
+ public void projectDeleted(IProject project) {
+ resetInstallationFor(project);
+ }
+
+ /*
+ * Does nothing
+ * (non-Javadoc)
+ * @see com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener#deviceChanged(com.android.ddmlib.Device, int)
+ */
+ public void deviceChanged(Device device, int changeMask) {
+ // nothing to do.
+ }
+
+ /*
+ * Does nothing
+ * (non-Javadoc)
+ * @see com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener#deviceConnected(com.android.ddmlib.Device)
+ */
+ public void deviceConnected(Device device) {
+ // nothing to do.
+ }
+
+ /*
+ * Does nothing
+ * (non-Javadoc)
+ * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener#projectOpened(org.eclipse.core.resources.IProject)
+ */
+ public void projectOpened(IProject project) {
+ // nothing to do.
+ }
+
+ /*
+ * Does nothing
+ * (non-Javadoc)
+ * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener#projectOpenedWithWorkspace(org.eclipse.core.resources.IProject)
+ */
+ public void projectOpenedWithWorkspace(IProject project) {
+ // nothing to do.
+ }
+}