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.AndroidDebugBridge.IDebugBridgeChangeListener; 21 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; 22 import com.android.ddmlib.IDevice; 23 import com.android.ddmlib.MultiLineReceiver; 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 { ApkInstall(IProject project, String packageName, IDevice device)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 equals(Object obj)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 hashCode()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 processNewLines(String[] lines)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 @Override isCancelled()101 public boolean isCancelled() { 102 return false; 103 } 104 } 105 106 /** 107 * Hashset of the list of installed package. Hashset used to ensure we don't re-add new 108 * objects for the same app. 109 */ 110 private final HashSet<ApkInstall> mInstallList = new HashSet<ApkInstall>(); 111 getInstance()112 public static ApkInstallManager getInstance() { 113 return sThis; 114 } 115 116 /** 117 * Registers an installation of <var>project</var> onto <var>device</var> 118 * @param project The project that was installed. 119 * @param packageName the package name of the apk 120 * @param device The device that received the installation. 121 */ registerInstallation(IProject project, String packageName, IDevice device)122 public void registerInstallation(IProject project, String packageName, IDevice device) { 123 synchronized (mInstallList) { 124 mInstallList.add(new ApkInstall(project, packageName, device)); 125 } 126 } 127 128 /** 129 * Returns whether a <var>project</var> was installed on the <var>device</var>. 130 * @param project the project that may have been installed. 131 * @param device the device that may have received the installation. 132 * @return 133 */ isApplicationInstalled(IProject project, String packageName, IDevice device)134 public boolean isApplicationInstalled(IProject project, String packageName, IDevice device) { 135 synchronized (mInstallList) { 136 ApkInstall found = null; 137 for (ApkInstall install : mInstallList) { 138 if (project.equals(install.project) && packageName.equals(install.packageName) && 139 device == install.device) { 140 found = install; 141 break; 142 } 143 } 144 145 // check the app is still installed. 146 if (found != null) { 147 try { 148 PmReceiver receiver = new PmReceiver(); 149 found.device.executeShellCommand("pm path " + packageName, receiver); 150 if (receiver.foundPackage == false) { 151 mInstallList.remove(found); 152 } 153 154 return receiver.foundPackage; 155 } catch (Exception e) { 156 // failed to query pm? force reinstall. 157 return false; 158 } 159 } 160 } 161 return false; 162 } 163 164 /** 165 * Resets registered installations for a specific {@link IProject}. 166 * <p/>This ensures that {@link #isApplicationInstalled(IProject, IDevice)} will always return 167 * <code>null</code> for this specified project, for any device. 168 * @param project the project for which to reset all installations. 169 */ resetInstallationFor(IProject project)170 public void resetInstallationFor(IProject project) { 171 synchronized (mInstallList) { 172 Iterator<ApkInstall> iterator = mInstallList.iterator(); 173 while (iterator.hasNext()) { 174 ApkInstall install = iterator.next(); 175 if (install.project.equals(project)) { 176 iterator.remove(); 177 } 178 } 179 } 180 } 181 ApkInstallManager()182 private ApkInstallManager() { 183 AndroidDebugBridge.addDeviceChangeListener(mDeviceChangeListener); 184 AndroidDebugBridge.addDebugBridgeChangeListener(mDebugBridgeListener); 185 GlobalProjectMonitor.getMonitor().addProjectListener(mProjectListener); 186 } 187 188 private IDebugBridgeChangeListener mDebugBridgeListener = new IDebugBridgeChangeListener() { 189 /** 190 * Responds to a bridge change by clearing the full installation list. 191 * 192 * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge) 193 */ 194 @Override 195 public void bridgeChanged(AndroidDebugBridge bridge) { 196 // the bridge changed, there is no way to know which IDevice will be which. 197 // We reset everything 198 synchronized (mInstallList) { 199 mInstallList.clear(); 200 } 201 } 202 }; 203 204 private IDeviceChangeListener mDeviceChangeListener = new IDeviceChangeListener() { 205 /** 206 * Responds to a device being disconnected by removing all installations related 207 * to this device. 208 * 209 * @see IDeviceChangeListener#deviceDisconnected(IDevice) 210 */ 211 @Override 212 public void deviceDisconnected(IDevice device) { 213 synchronized (mInstallList) { 214 Iterator<ApkInstall> iterator = mInstallList.iterator(); 215 while (iterator.hasNext()) { 216 ApkInstall install = iterator.next(); 217 if (install.device == device) { 218 iterator.remove(); 219 } 220 } 221 } 222 } 223 224 @Override 225 public void deviceChanged(IDevice device, int changeMask) { 226 // nothing to do. 227 } 228 229 @Override 230 public void deviceConnected(IDevice device) { 231 // nothing to do. 232 } 233 }; 234 235 private IProjectListener mProjectListener = new IProjectListener() { 236 /** 237 * Responds to a closed project by resetting all its installation. 238 * 239 * @see IProjectListener#projectClosed(IProject) 240 */ 241 @Override 242 public void projectClosed(IProject project) { 243 resetInstallationFor(project); 244 } 245 246 /** 247 * Responds to a deleted project by resetting all its installation. 248 * 249 * @see IProjectListener#projectDeleted(IProject) 250 */ 251 @Override 252 public void projectDeleted(IProject project) { 253 resetInstallationFor(project); 254 } 255 256 @Override 257 public void projectOpened(IProject project) { 258 // nothing to do. 259 } 260 261 @Override 262 public void projectOpenedWithWorkspace(IProject project) { 263 // nothing to do. 264 } 265 266 @Override 267 public void allProjectsOpenedWithWorkspace() { 268 // nothing to do. 269 } 270 271 @Override 272 public void projectRenamed(IProject project, IPath from) { 273 // project renaming also triggers delete/open events so 274 // there's nothing to do here (since delete will remove 275 // whatever's linked to the project from the list). 276 } 277 }; 278 } 279