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.io.IOException; 31 import java.util.HashSet; 32 import java.util.Iterator; 33 34 /** 35 * Registers which apk was installed on which device. 36 * <p/> 37 * The goal of this class is to remember the installation of APKs on devices, and provide 38 * information about whether a new APK should be installed on a device prior to running the 39 * application from a launch configuration. 40 * <p/> 41 * The manager uses {@link IProject} and {@link IDevice} to identify the target device and the 42 * (project generating the) APK. This ensures that disconnected and reconnected devices will 43 * always receive new APKs (since the version may not match). 44 * <p/> 45 * This is a singleton. To get the instance, use {@link #getInstance()} 46 */ 47 public final class ApkInstallManager { 48 49 private final static ApkInstallManager sThis = new ApkInstallManager(); 50 51 /** 52 * Internal struct to associate a project and a device. 53 */ 54 private final static class ApkInstall { ApkInstall(IProject project, String packageName, IDevice device)55 public ApkInstall(IProject project, String packageName, IDevice device) { 56 this.project = project; 57 this.packageName = packageName; 58 this.device = device; 59 } 60 61 @Override equals(Object obj)62 public boolean equals(Object obj) { 63 if (obj instanceof ApkInstall) { 64 ApkInstall apkObj = (ApkInstall)obj; 65 66 return (device == apkObj.device && project.equals(apkObj.project) && 67 packageName.equals(apkObj.packageName)); 68 } 69 70 return false; 71 } 72 73 @Override hashCode()74 public int hashCode() { 75 return (device.getSerialNumber() + project.getName() + packageName).hashCode(); 76 } 77 78 final IProject project; 79 final String packageName; 80 final IDevice device; 81 } 82 83 /** 84 * Receiver and parser for the "pm path package" command. 85 */ 86 private final static class PmReceiver extends MultiLineReceiver { 87 boolean foundPackage = false; 88 @Override processNewLines(String[] lines)89 public void processNewLines(String[] lines) { 90 // if the package if found, then pm will show a line starting with "package:/" 91 if (foundPackage == false) { // just in case this is called several times for multilines 92 for (String line : lines) { 93 if (line.startsWith("package:/")) { 94 foundPackage = true; 95 break; 96 } 97 } 98 } 99 } 100 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 (IOException 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 public void bridgeChanged(AndroidDebugBridge bridge) { 195 // the bridge changed, there is no way to know which IDevice will be which. 196 // We reset everything 197 synchronized (mInstallList) { 198 mInstallList.clear(); 199 } 200 } 201 }; 202 203 private IDeviceChangeListener mDeviceChangeListener = new IDeviceChangeListener() { 204 /** 205 * Responds to a device being disconnected by removing all installations related 206 * to this device. 207 * 208 * @see IDeviceChangeListener#deviceDisconnected(IDevice) 209 */ 210 public void deviceDisconnected(IDevice device) { 211 synchronized (mInstallList) { 212 Iterator<ApkInstall> iterator = mInstallList.iterator(); 213 while (iterator.hasNext()) { 214 ApkInstall install = iterator.next(); 215 if (install.device == device) { 216 iterator.remove(); 217 } 218 } 219 } 220 } 221 222 public void deviceChanged(IDevice device, int changeMask) { 223 // nothing to do. 224 } 225 226 public void deviceConnected(IDevice device) { 227 // nothing to do. 228 } 229 }; 230 231 private IProjectListener mProjectListener = new IProjectListener() { 232 /** 233 * Responds to a closed project by resetting all its installation. 234 * 235 * @see IProjectListener#projectClosed(IProject) 236 */ 237 public void projectClosed(IProject project) { 238 resetInstallationFor(project); 239 } 240 241 /** 242 * Responds to a deleted project by resetting all its installation. 243 * 244 * @see IProjectListener#projectDeleted(IProject) 245 */ 246 public void projectDeleted(IProject project) { 247 resetInstallationFor(project); 248 } 249 250 public void projectOpened(IProject project) { 251 // nothing to do. 252 } 253 254 public void projectOpenedWithWorkspace(IProject project) { 255 // nothing to do. 256 } 257 258 public void projectRenamed(IProject project, IPath from) { 259 // project renaming also triggers delete/open events so 260 // there's nothing to do here (since delete will remove 261 // whatever's linked to the project from the list). 262 } 263 }; 264 } 265