OSDN Git Service

AI 143410: am: CL 143408 am: CL 143407 Prevent reinstalling APKs during launch if...
authorXavier Ducrohet <>
Sat, 28 Mar 2009 03:06:23 +0000 (20:06 -0700)
committerThe Android Open Source Project <initial-contribution@android.com>
Sat, 28 Mar 2009 03:06:23 +0000 (20:06 -0700)
  Original author: xav
  Merged from: //branches/cupcake/...
  Original author: android-build
  Merged from: //branches/donutburger/...

Automated import of CL 143410

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ApkInstallManager.java [new file with mode: 0644]

index 1edcf79..47ea3e7 100644 (file)
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.build;
 
 import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.project.ApkInstallManager;
 import com.android.ide.eclipse.adt.project.ProjectHelper;
 import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
 import com.android.ide.eclipse.adt.sdk.Sdk;
@@ -551,6 +552,9 @@ public class ApkBuilder extends BaseBuilder {
             // and store it
             saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
             
+            // reset the installation manager to force new installs of this project
+            ApkInstallManager.getInstance().resetInstallationFor(project);
+            
             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
                     "Build Success!");
         }
index fafc402..b6c7640 100644 (file)
@@ -32,6 +32,7 @@ import com.android.ide.eclipse.adt.AdtPlugin;
 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;
@@ -762,13 +763,46 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
 
 
     /**
-     * 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();
@@ -812,12 +846,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
                 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;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ApkInstallManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ApkInstallManager.java
new file mode 100644 (file)
index 0000000..172c555
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * 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.
+    }
+}