1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.project; 18 19 import com.android.ddmlib.AndroidDebugBridge; 20 import com.android.ddmlib.IDevice; 21 import com.android.ddmlib.MultiLineReceiver; 22 import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener; 23 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; 24 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; 25 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener; 26 27 import org.eclipse.core.resources.IProject; 28 import org.eclipse.core.runtime.IPath; 29 30 import java.util.HashSet; 31 import java.util.Iterator; 32 33 /** 34 * Registers which apk was installed on which device. 35 * <p/> 36 * The goal of this class is to remember the installation of APKs on devices, and provide 37 * information about whether a new APK should be installed on a device prior to running the 38 * application from a launch configuration. 39 * <p/> 40 * The manager uses {@link IProject} and {@link IDevice} to identify the target device and the 41 * (project generating the) APK. This ensures that disconnected and reconnected devices will 42 * always receive new APKs (since the version may not match). 43 * <p/> 44 * This is a singleton. To get the instance, use {@link #getInstance()} 45 */ 46 public final class ApkInstallManager { 47 48 private final static ApkInstallManager sThis = new ApkInstallManager(); 49 50 /** 51 * Internal struct to associate a project and a device. 52 */ 53 private final static class ApkInstall { 54 public ApkInstall(IProject project, String packageName, IDevice device) { 55 this.project = project; 56 this.packageName = packageName; 57 this.device = device; 58 } 59 60 @Override 61 public boolean equals(Object obj) { 62 if (obj instanceof ApkInstall) { 63 ApkInstall apkObj = (ApkInstall)obj; 64 65 return (device == apkObj.device && project.equals(apkObj.project) && 66 packageName.equals(apkObj.packageName)); 67 } 68 69 return false; 70 } 71 72 @Override 73 public int hashCode() { 74 return (device.getSerialNumber() + project.getName() + packageName).hashCode(); 75 } 76 77 final IProject project; 78 final String packageName; 79 final IDevice device; 80 } 81 82 /** 83 * Receiver and parser for the "pm path package" command. 84 */ 85 private final static class PmReceiver extends MultiLineReceiver { 86 boolean foundPackage = false; 87 @Override 88 public void processNewLines(String[] lines) { 89 // if the package if found, then pm will show a line starting with "package:/" 90 if (foundPackage == false) { // just in case this is called several times for multilines 91 for (String line : lines) { 92 if (line.startsWith("package:/")) { 93 foundPackage = true; 94 break; 95 } 96 } 97 } 98 } 99 100 public boolean isCancelled() { 101 return false; 102 } 103 } 104 105 /** 106 * Hashset of the list of installed package. Hashset used to ensure we don't re-add new 107 * objects for the same app. 108 */ 109 private final HashSet<ApkInstall> mInstallList = new HashSet<ApkInstall>(); 110 111 public static ApkInstallManager getInstance() { 112 return sThis; 113 } 114 115 /** 116 * Registers an installation of <var>project</var> onto <var>device</var> 117 * @param project The project that was installed. 118 * @param packageName the package name of the apk 119 * @param device The device that received the installation. 120 */ 121 public void registerInstallation(IProject project, String packageName, IDevice device) { 122 synchronized (mInstallList) { 123 mInstallList.add(new ApkInstall(project, packageName, device)); 124 } 125 } 126 127 /** 128 * Returns whether a <var>project</var> was installed on the <var>device</var>. 129 * @param project the project that may have been installed. 130 * @param device the device that may have received the installation. 131 * @return 132 */ 133 public boolean isApplicationInstalled(IProject project, String packageName, IDevice device) { 134 synchronized (mInstallList) { 135 ApkInstall found = null; 136 for (ApkInstall install : mInstallList) { 137 if (project.equals(install.project) && packageName.equals(install.packageName) && 138 device == install.device) { 139 found = install; 140 break; 141 } 142 } 143 144 // check the app is still installed. 145 if (found != null) { 146 try { 147 PmReceiver receiver = new PmReceiver(); 148 found.device.executeShellCommand("pm path " + packageName, receiver); 149 if (receiver.foundPackage == false) { 150 mInstallList.remove(found); 151 } 152 153 return receiver.foundPackage; 154 } catch (Exception e) { 155 // failed to query pm? force reinstall. 156 return false; 157 } 158 } 159 } 160 return false; 161 } 162 163 /** 164 * Resets registered installations for a specific {@link IProject}. 165 * <p/>This ensures that {@link #isApplicationInstalled(IProject, IDevice)} will always return 166 * <code>null</code> for this specified project, for any device. 167 * @param project the project for which to reset all installations. 168 */ 169 public void resetInstallationFor(IProject project) { 170 synchronized (mInstallList) { 171 Iterator<ApkInstall> iterator = mInstallList.iterator(); 172 while (iterator.hasNext()) { 173 ApkInstall install = iterator.next(); 174 if (install.project.equals(project)) { 175 iterator.remove(); 176 } 177 } 178 } 179 } 180 181 private ApkInstallManager() { 182 AndroidDebugBridge.addDeviceChangeListener(mDeviceChangeListener); 183 AndroidDebugBridge.addDebugBridgeChangeListener(mDebugBridgeListener); 184 GlobalProjectMonitor.getMonitor().addProjectListener(mProjectListener); 185 } 186 187 private IDebugBridgeChangeListener mDebugBridgeListener = new IDebugBridgeChangeListener() { 188 /** 189 * Responds to a bridge change by clearing the full installation list. 190 * 191 * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge) 192 */ 193 public void bridgeChanged(AndroidDebugBridge bridge) { 194 // the bridge changed, there is no way to know which IDevice will be which. 195 // We reset everything 196 synchronized (mInstallList) { 197 mInstallList.clear(); 198 } 199 } 200 }; 201 202 private IDeviceChangeListener mDeviceChangeListener = new IDeviceChangeListener() { 203 /** 204 * Responds to a device being disconnected by removing all installations related 205 * to this device. 206 * 207 * @see IDeviceChangeListener#deviceDisconnected(IDevice) 208 */ 209 public void deviceDisconnected(IDevice device) { 210 synchronized (mInstallList) { 211 Iterator<ApkInstall> iterator = mInstallList.iterator(); 212 while (iterator.hasNext()) { 213 ApkInstall install = iterator.next(); 214 if (install.device == device) { 215 iterator.remove(); 216 } 217 } 218 } 219 } 220 221 public void deviceChanged(IDevice device, int changeMask) { 222 // nothing to do. 223 } 224 225 public void deviceConnected(IDevice device) { 226 // nothing to do. 227 } 228 }; 229 230 private IProjectListener mProjectListener = new IProjectListener() { 231 /** 232 * Responds to a closed project by resetting all its installation. 233 * 234 * @see IProjectListener#projectClosed(IProject) 235 */ 236 public void projectClosed(IProject project) { 237 resetInstallationFor(project); 238 } 239 240 /** 241 * Responds to a deleted project by resetting all its installation. 242 * 243 * @see IProjectListener#projectDeleted(IProject) 244 */ 245 public void projectDeleted(IProject project) { 246 resetInstallationFor(project); 247 } 248 249 public void projectOpened(IProject project) { 250 // nothing to do. 251 } 252 253 public void projectOpenedWithWorkspace(IProject project) { 254 // nothing to do. 255 } 256 257 public void projectRenamed(IProject project, IPath from) { 258 // project renaming also triggers delete/open events so 259 // there's nothing to do here (since delete will remove 260 // whatever's linked to the project from the list). 261 } 262 }; 263 } 264