1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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.ddmlib; 18 19 import com.android.ddmlib.SyncService.SyncResult; 20 import com.android.ddmlib.log.LogReceiver; 21 22 import java.io.File; 23 import java.io.IOException; 24 import java.nio.channels.SocketChannel; 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.HashMap; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 34 /** 35 * A Device. It can be a physical device or an emulator. 36 */ 37 final class Device implements IDevice { 38 39 /** Emulator Serial Number regexp. */ 40 final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$ 41 42 /** Serial number of the device */ 43 private String mSerialNumber = null; 44 45 /** Name of the AVD */ 46 private String mAvdName = null; 47 48 /** State of the device. */ 49 private DeviceState mState = null; 50 51 /** Device properties. */ 52 private final Map<String, String> mProperties = new HashMap<String, String>(); 53 private final Map<String, String> mMountPoints = new HashMap<String, String>(); 54 55 private final ArrayList<Client> mClients = new ArrayList<Client>(); 56 private DeviceMonitor mMonitor; 57 58 private static final String LOG_TAG = "Device"; 59 60 /** 61 * Socket for the connection monitoring client connection/disconnection. 62 */ 63 private SocketChannel mSocketChannel; 64 65 /** 66 * Output receiver for "pm install package.apk" command line. 67 */ 68 private static final class InstallReceiver extends MultiLineReceiver { 69 70 private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$ 71 private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$ 72 73 private String mErrorMessage = null; 74 75 public InstallReceiver() { 76 } 77 78 @Override 79 public void processNewLines(String[] lines) { 80 for (String line : lines) { 81 if (line.length() > 0) { 82 if (line.startsWith(SUCCESS_OUTPUT)) { 83 mErrorMessage = null; 84 } else { 85 Matcher m = FAILURE_PATTERN.matcher(line); 86 if (m.matches()) { 87 mErrorMessage = m.group(1); 88 } 89 } 90 } 91 } 92 } 93 94 public boolean isCancelled() { 95 return false; 96 } 97 98 public String getErrorMessage() { 99 return mErrorMessage; 100 } 101 } 102 103 /* 104 * (non-Javadoc) 105 * @see com.android.ddmlib.IDevice#getSerialNumber() 106 */ 107 public String getSerialNumber() { 108 return mSerialNumber; 109 } 110 111 /** {@inheritDoc} */ 112 public String getAvdName() { 113 return mAvdName; 114 } 115 116 /** 117 * Sets the name of the AVD 118 */ 119 void setAvdName(String avdName) { 120 if (isEmulator() == false) { 121 throw new IllegalArgumentException( 122 "Cannot set the AVD name of the device is not an emulator"); 123 } 124 125 mAvdName = avdName; 126 } 127 128 /* 129 * (non-Javadoc) 130 * @see com.android.ddmlib.IDevice#getState() 131 */ 132 public DeviceState getState() { 133 return mState; 134 } 135 136 /** 137 * Changes the state of the device. 138 */ 139 void setState(DeviceState state) { 140 mState = state; 141 } 142 143 144 /* 145 * (non-Javadoc) 146 * @see com.android.ddmlib.IDevice#getProperties() 147 */ 148 public Map<String, String> getProperties() { 149 return Collections.unmodifiableMap(mProperties); 150 } 151 152 /* 153 * (non-Javadoc) 154 * @see com.android.ddmlib.IDevice#getPropertyCount() 155 */ 156 public int getPropertyCount() { 157 return mProperties.size(); 158 } 159 160 /* 161 * (non-Javadoc) 162 * @see com.android.ddmlib.IDevice#getProperty(java.lang.String) 163 */ 164 public String getProperty(String name) { 165 return mProperties.get(name); 166 } 167 168 public String getMountPoint(String name) { 169 return mMountPoints.get(name); 170 } 171 172 173 @Override 174 public String toString() { 175 return mSerialNumber; 176 } 177 178 /* 179 * (non-Javadoc) 180 * @see com.android.ddmlib.IDevice#isOnline() 181 */ 182 public boolean isOnline() { 183 return mState == DeviceState.ONLINE; 184 } 185 186 /* 187 * (non-Javadoc) 188 * @see com.android.ddmlib.IDevice#isEmulator() 189 */ 190 public boolean isEmulator() { 191 return mSerialNumber.matches(RE_EMULATOR_SN); 192 } 193 194 /* 195 * (non-Javadoc) 196 * @see com.android.ddmlib.IDevice#isOffline() 197 */ 198 public boolean isOffline() { 199 return mState == DeviceState.OFFLINE; 200 } 201 202 /* 203 * (non-Javadoc) 204 * @see com.android.ddmlib.IDevice#isBootLoader() 205 */ 206 public boolean isBootLoader() { 207 return mState == DeviceState.BOOTLOADER; 208 } 209 210 /* 211 * (non-Javadoc) 212 * @see com.android.ddmlib.IDevice#hasClients() 213 */ 214 public boolean hasClients() { 215 return mClients.size() > 0; 216 } 217 218 /* 219 * (non-Javadoc) 220 * @see com.android.ddmlib.IDevice#getClients() 221 */ 222 public Client[] getClients() { 223 synchronized (mClients) { 224 return mClients.toArray(new Client[mClients.size()]); 225 } 226 } 227 228 /* 229 * (non-Javadoc) 230 * @see com.android.ddmlib.IDevice#getClient(java.lang.String) 231 */ 232 public Client getClient(String applicationName) { 233 synchronized (mClients) { 234 for (Client c : mClients) { 235 if (applicationName.equals(c.getClientData().getClientDescription())) { 236 return c; 237 } 238 } 239 240 } 241 242 return null; 243 } 244 245 /* 246 * (non-Javadoc) 247 * @see com.android.ddmlib.IDevice#getSyncService() 248 */ 249 public SyncService getSyncService() throws IOException { 250 SyncService syncService = new SyncService(AndroidDebugBridge.sSocketAddr, this); 251 if (syncService.openSync()) { 252 return syncService; 253 } 254 255 return null; 256 } 257 258 /* 259 * (non-Javadoc) 260 * @see com.android.ddmlib.IDevice#getFileListingService() 261 */ 262 public FileListingService getFileListingService() { 263 return new FileListingService(this); 264 } 265 266 /* 267 * (non-Javadoc) 268 * @see com.android.ddmlib.IDevice#getScreenshot() 269 */ 270 public RawImage getScreenshot() throws IOException { 271 return AdbHelper.getFrameBuffer(AndroidDebugBridge.sSocketAddr, this); 272 } 273 274 /* 275 * (non-Javadoc) 276 * @see com.android.ddmlib.IDevice#executeShellCommand(java.lang.String, com.android.ddmlib.IShellOutputReceiver) 277 */ 278 public void executeShellCommand(String command, IShellOutputReceiver receiver) 279 throws IOException { 280 AdbHelper.executeRemoteCommand(AndroidDebugBridge.sSocketAddr, command, this, 281 receiver); 282 } 283 284 /* 285 * (non-Javadoc) 286 * @see com.android.ddmlib.IDevice#runEventLogService(com.android.ddmlib.log.LogReceiver) 287 */ 288 public void runEventLogService(LogReceiver receiver) throws IOException { 289 AdbHelper.runEventLogService(AndroidDebugBridge.sSocketAddr, this, receiver); 290 } 291 292 /* 293 * (non-Javadoc) 294 * @see com.android.ddmlib.IDevice#runLogService(com.android.ddmlib.log.LogReceiver) 295 */ 296 public void runLogService(String logname, 297 LogReceiver receiver) throws IOException { 298 AdbHelper.runLogService(AndroidDebugBridge.sSocketAddr, this, logname, receiver); 299 } 300 301 /* 302 * (non-Javadoc) 303 * @see com.android.ddmlib.IDevice#createForward(int, int) 304 */ 305 public boolean createForward(int localPort, int remotePort) { 306 try { 307 return AdbHelper.createForward(AndroidDebugBridge.sSocketAddr, this, 308 localPort, remotePort); 309 } catch (IOException e) { 310 Log.e("adb-forward", e); //$NON-NLS-1$ 311 return false; 312 } 313 } 314 315 /* 316 * (non-Javadoc) 317 * @see com.android.ddmlib.IDevice#removeForward(int, int) 318 */ 319 public boolean removeForward(int localPort, int remotePort) { 320 try { 321 return AdbHelper.removeForward(AndroidDebugBridge.sSocketAddr, this, 322 localPort, remotePort); 323 } catch (IOException e) { 324 Log.e("adb-remove-forward", e); //$NON-NLS-1$ 325 return false; 326 } 327 } 328 329 /* 330 * (non-Javadoc) 331 * @see com.android.ddmlib.IDevice#getClientName(int) 332 */ 333 public String getClientName(int pid) { 334 synchronized (mClients) { 335 for (Client c : mClients) { 336 if (c.getClientData().getPid() == pid) { 337 return c.getClientData().getClientDescription(); 338 } 339 } 340 } 341 342 return null; 343 } 344 345 346 Device(DeviceMonitor monitor, String serialNumber, DeviceState deviceState) { 347 mMonitor = monitor; 348 mSerialNumber = serialNumber; 349 mState = deviceState; 350 } 351 352 DeviceMonitor getMonitor() { 353 return mMonitor; 354 } 355 356 void addClient(Client client) { 357 synchronized (mClients) { 358 mClients.add(client); 359 } 360 } 361 362 List<Client> getClientList() { 363 return mClients; 364 } 365 366 boolean hasClient(int pid) { 367 synchronized (mClients) { 368 for (Client client : mClients) { 369 if (client.getClientData().getPid() == pid) { 370 return true; 371 } 372 } 373 } 374 375 return false; 376 } 377 378 void clearClientList() { 379 synchronized (mClients) { 380 mClients.clear(); 381 } 382 } 383 384 /** 385 * Sets the client monitoring socket. 386 * @param socketChannel the sockets 387 */ 388 void setClientMonitoringSocket(SocketChannel socketChannel) { 389 mSocketChannel = socketChannel; 390 } 391 392 /** 393 * Returns the client monitoring socket. 394 */ 395 SocketChannel getClientMonitoringSocket() { 396 return mSocketChannel; 397 } 398 399 /** 400 * Removes a {@link Client} from the list. 401 * @param client the client to remove. 402 * @param notify Whether or not to notify the listeners of a change. 403 */ 404 void removeClient(Client client, boolean notify) { 405 mMonitor.addPortToAvailableList(client.getDebuggerListenPort()); 406 synchronized (mClients) { 407 mClients.remove(client); 408 } 409 if (notify) { 410 mMonitor.getServer().deviceChanged(this, CHANGE_CLIENT_LIST); 411 } 412 } 413 414 void update(int changeMask) { 415 mMonitor.getServer().deviceChanged(this, changeMask); 416 } 417 418 void update(Client client, int changeMask) { 419 mMonitor.getServer().clientChanged(client, changeMask); 420 } 421 422 void addProperty(String label, String value) { 423 mProperties.put(label, value); 424 } 425 426 void setMountingPoint(String name, String value) { 427 mMountPoints.put(name, value); 428 } 429 430 /** 431 * {@inheritDoc} 432 */ 433 public String installPackage(String packageFilePath, boolean reinstall) 434 throws IOException { 435 String remoteFilePath = syncPackageToDevice(packageFilePath); 436 String result = installRemotePackage(remoteFilePath, reinstall); 437 removeRemotePackage(remoteFilePath); 438 return result; 439 } 440 441 /** 442 * {@inheritDoc} 443 */ 444 public String syncPackageToDevice(String localFilePath) 445 throws IOException { 446 try { 447 String packageFileName = getFileName(localFilePath); 448 String remoteFilePath = String.format("/data/local/tmp/%1$s", packageFileName); //$NON-NLS-1$ 449 450 Log.d(packageFileName, String.format("Uploading %1$s onto device '%2$s'", 451 packageFileName, getSerialNumber())); 452 453 SyncService sync = getSyncService(); 454 if (sync != null) { 455 String message = String.format("Uploading file onto device '%1$s'", 456 getSerialNumber()); 457 Log.d(LOG_TAG, message); 458 SyncResult result = sync.pushFile(localFilePath, remoteFilePath, 459 SyncService.getNullProgressMonitor()); 460 461 if (result.getCode() != SyncService.RESULT_OK) { 462 throw new IOException(String.format("Unable to upload file: %1$s", 463 result.getMessage())); 464 } 465 } else { 466 throw new IOException("Unable to open sync connection!"); 467 } 468 return remoteFilePath; 469 } catch (IOException e) { 470 Log.e(LOG_TAG, String.format("Unable to open sync connection! reason: %1$s", 471 e.getMessage())); 472 throw e; 473 } 474 } 475 476 /** 477 * Helper method to retrieve the file name given a local file path 478 * @param filePath full directory path to file 479 * @return {@link String} file name 480 */ 481 private String getFileName(String filePath) { 482 return new File(filePath).getName(); 483 } 484 485 /** 486 * {@inheritDoc} 487 */ 488 public String installRemotePackage(String remoteFilePath, boolean reinstall) 489 throws IOException { 490 InstallReceiver receiver = new InstallReceiver(); 491 String cmd = String.format(reinstall ? "pm install -r \"%1$s\"" : "pm install \"%1$s\"", 492 remoteFilePath); 493 executeShellCommand(cmd, receiver); 494 return receiver.getErrorMessage(); 495 } 496 497 /** 498 * {@inheritDoc} 499 */ 500 public void removeRemotePackage(String remoteFilePath) throws IOException { 501 // now we delete the app we sync'ed 502 try { 503 executeShellCommand("rm " + remoteFilePath, new NullOutputReceiver()); 504 } catch (IOException e) { 505 Log.e(LOG_TAG, String.format("Failed to delete temporary package: %1$s", 506 e.getMessage())); 507 throw e; 508 } 509 } 510 511 /** 512 * {@inheritDoc} 513 */ 514 public String uninstallPackage(String packageName) throws IOException { 515 InstallReceiver receiver = new InstallReceiver(); 516 executeShellCommand("pm uninstall " + packageName, receiver); 517 return receiver.getErrorMessage(); 518 } 519 } 520