aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java278
1 files changed, 278 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java
new file mode 100644
index 000000000..903914684
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java
@@ -0,0 +1,278 @@
+/*
+ * 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.internal.project;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * 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 version may not match).
+ * <p/>
+ * This is a singleton. To get the instance, use {@link #getInstance()}
+ */
+public final class ApkInstallManager {
+
+ private final static ApkInstallManager sThis = new ApkInstallManager();
+
+ /**
+ * Internal struct to associate a project and a device.
+ */
+ private final static class ApkInstall {
+ public ApkInstall(IProject project, String packageName, IDevice device) {
+ this.project = project;
+ this.packageName = packageName;
+ this.device = device;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ApkInstall) {
+ ApkInstall apkObj = (ApkInstall)obj;
+
+ return (device == apkObj.device && project.equals(apkObj.project) &&
+ packageName.equals(apkObj.packageName));
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (device.getSerialNumber() + project.getName() + packageName).hashCode();
+ }
+
+ final IProject project;
+ final String packageName;
+ final IDevice device;
+ }
+
+ /**
+ * Receiver and parser for the "pm path package" command.
+ */
+ private final static class PmReceiver extends MultiLineReceiver {
+ boolean foundPackage = false;
+ @Override
+ public void processNewLines(String[] lines) {
+ // if the package if found, then pm will show a line starting with "package:/"
+ if (foundPackage == false) { // just in case this is called several times for multilines
+ for (String line : lines) {
+ if (line.startsWith("package:/")) {
+ foundPackage = true;
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ }
+
+ /**
+ * Hashset of the list of installed package. Hashset used to ensure we don't re-add new
+ * objects for the same app.
+ */
+ private final HashSet<ApkInstall> mInstallList = new HashSet<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 packageName the package name of the apk
+ * @param device The device that received the installation.
+ */
+ public void registerInstallation(IProject project, String packageName, IDevice device) {
+ synchronized (mInstallList) {
+ mInstallList.add(new ApkInstall(project, packageName, 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, String packageName, IDevice device) {
+ synchronized (mInstallList) {
+ ApkInstall found = null;
+ for (ApkInstall install : mInstallList) {
+ if (project.equals(install.project) && packageName.equals(install.packageName) &&
+ device == install.device) {
+ found = install;
+ break;
+ }
+ }
+
+ // check the app is still installed.
+ if (found != null) {
+ try {
+ PmReceiver receiver = new PmReceiver();
+ found.device.executeShellCommand("pm path " + packageName, receiver);
+ if (receiver.foundPackage == false) {
+ mInstallList.remove(found);
+ }
+
+ return receiver.foundPackage;
+ } catch (Exception e) {
+ // failed to query pm? force reinstall.
+ return false;
+ }
+ }
+ }
+ 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) {
+ Iterator<ApkInstall> iterator = mInstallList.iterator();
+ while (iterator.hasNext()) {
+ ApkInstall install = iterator.next();
+ if (install.project.equals(project)) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ private ApkInstallManager() {
+ AndroidDebugBridge.addDeviceChangeListener(mDeviceChangeListener);
+ AndroidDebugBridge.addDebugBridgeChangeListener(mDebugBridgeListener);
+ GlobalProjectMonitor.getMonitor().addProjectListener(mProjectListener);
+ }
+
+ private IDebugBridgeChangeListener mDebugBridgeListener = new IDebugBridgeChangeListener() {
+ /**
+ * Responds to a bridge change by clearing the full installation list.
+ *
+ * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge)
+ */
+ @Override
+ 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();
+ }
+ }
+ };
+
+ private IDeviceChangeListener mDeviceChangeListener = new IDeviceChangeListener() {
+ /**
+ * Responds to a device being disconnected by removing all installations related
+ * to this device.
+ *
+ * @see IDeviceChangeListener#deviceDisconnected(IDevice)
+ */
+ @Override
+ public void deviceDisconnected(IDevice device) {
+ synchronized (mInstallList) {
+ Iterator<ApkInstall> iterator = mInstallList.iterator();
+ while (iterator.hasNext()) {
+ ApkInstall install = iterator.next();
+ if (install.device == device) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void deviceChanged(IDevice device, int changeMask) {
+ // nothing to do.
+ }
+
+ @Override
+ public void deviceConnected(IDevice device) {
+ // nothing to do.
+ }
+ };
+
+ private IProjectListener mProjectListener = new IProjectListener() {
+ /**
+ * Responds to a closed project by resetting all its installation.
+ *
+ * @see IProjectListener#projectClosed(IProject)
+ */
+ @Override
+ public void projectClosed(IProject project) {
+ resetInstallationFor(project);
+ }
+
+ /**
+ * Responds to a deleted project by resetting all its installation.
+ *
+ * @see IProjectListener#projectDeleted(IProject)
+ */
+ @Override
+ public void projectDeleted(IProject project) {
+ resetInstallationFor(project);
+ }
+
+ @Override
+ public void projectOpened(IProject project) {
+ // nothing to do.
+ }
+
+ @Override
+ public void projectOpenedWithWorkspace(IProject project) {
+ // nothing to do.
+ }
+
+ @Override
+ public void allProjectsOpenedWithWorkspace() {
+ // nothing to do.
+ }
+
+ @Override
+ public void projectRenamed(IProject project, IPath from) {
+ // project renaming also triggers delete/open events so
+ // there's nothing to do here (since delete will remove
+ // whatever's linked to the project from the list).
+ }
+ };
+}