blob: e23d8177a66ff465d162e1580fbf5b57a171988a [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.ddmlib;
import com.android.ddmlib.SyncService.SyncResult;
import com.android.ddmlib.log.LogReceiver;
import java.io.File;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A Device. It can be a physical device or an emulator.
*/
final class Device implements IDevice {
/** Emulator Serial Number regexp. */
final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
/** Serial number of the device */
private String mSerialNumber = null;
/** Name of the AVD */
private String mAvdName = null;
/** State of the device. */
private DeviceState mState = null;
/** Device properties. */
private final Map<String, String> mProperties = new HashMap<String, String>();
private final ArrayList<Client> mClients = new ArrayList<Client>();
private DeviceMonitor mMonitor;
private static final String LOG_TAG = "Device";
/**
* Socket for the connection monitoring client connection/disconnection.
*/
private SocketChannel mSocketChannel;
/**
* Output receiver for "pm install package.apk" command line.
*/
private static final class InstallReceiver extends MultiLineReceiver {
private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
private String mErrorMessage = null;
public InstallReceiver() {
}
@Override
public void processNewLines(String[] lines) {
for (String line : lines) {
if (line.length() > 0) {
if (line.startsWith(SUCCESS_OUTPUT)) {
mErrorMessage = null;
} else {
Matcher m = FAILURE_PATTERN.matcher(line);
if (m.matches()) {
mErrorMessage = m.group(1);
}
}
}
}
}
public boolean isCancelled() {
return false;
}
public String getErrorMessage() {
return mErrorMessage;
}
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSerialNumber()
*/
public String getSerialNumber() {
return mSerialNumber;
}
/** {@inheritDoc} */
public String getAvdName() {
return mAvdName;
}
/**
* Sets the name of the AVD
*/
void setAvdName(String avdName) {
if (isEmulator() == false) {
throw new IllegalArgumentException(
"Cannot set the AVD name of the device is not an emulator");
}
mAvdName = avdName;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getState()
*/
public DeviceState getState() {
return mState;
}
/**
* Changes the state of the device.
*/
void setState(DeviceState state) {
mState = state;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getProperties()
*/
public Map<String, String> getProperties() {
return Collections.unmodifiableMap(mProperties);
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getPropertyCount()
*/
public int getPropertyCount() {
return mProperties.size();
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getProperty(java.lang.String)
*/
public String getProperty(String name) {
return mProperties.get(name);
}
@Override
public String toString() {
return mSerialNumber;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isOnline()
*/
public boolean isOnline() {
return mState == DeviceState.ONLINE;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isEmulator()
*/
public boolean isEmulator() {
return mSerialNumber.matches(RE_EMULATOR_SN);
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isOffline()
*/
public boolean isOffline() {
return mState == DeviceState.OFFLINE;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isBootLoader()
*/
public boolean isBootLoader() {
return mState == DeviceState.BOOTLOADER;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#hasClients()
*/
public boolean hasClients() {
return mClients.size() > 0;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClients()
*/
public Client[] getClients() {
synchronized (mClients) {
return mClients.toArray(new Client[mClients.size()]);
}
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClient(java.lang.String)
*/
public Client getClient(String applicationName) {
synchronized (mClients) {
for (Client c : mClients) {
if (applicationName.equals(c.getClientData().getClientDescription())) {
return c;
}
}
}
return null;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSyncService()
*/
public SyncService getSyncService() throws IOException {
SyncService syncService = new SyncService(AndroidDebugBridge.sSocketAddr, this);
if (syncService.openSync()) {
return syncService;
}
return null;
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getFileListingService()
*/
public FileListingService getFileListingService() {
return new FileListingService(this);
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getScreenshot()
*/
public RawImage getScreenshot() throws IOException {
return AdbHelper.getFrameBuffer(AndroidDebugBridge.sSocketAddr, this);
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#executeShellCommand(java.lang.String, com.android.ddmlib.IShellOutputReceiver)
*/
public void executeShellCommand(String command, IShellOutputReceiver receiver)
throws IOException {
AdbHelper.executeRemoteCommand(AndroidDebugBridge.sSocketAddr, command, this,
receiver);
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#runEventLogService(com.android.ddmlib.log.LogReceiver)
*/
public void runEventLogService(LogReceiver receiver) throws IOException {
AdbHelper.runEventLogService(AndroidDebugBridge.sSocketAddr, this, receiver);
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#runLogService(com.android.ddmlib.log.LogReceiver)
*/
public void runLogService(String logname,
LogReceiver receiver) throws IOException {
AdbHelper.runLogService(AndroidDebugBridge.sSocketAddr, this, logname, receiver);
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#createForward(int, int)
*/
public boolean createForward(int localPort, int remotePort) {
try {
return AdbHelper.createForward(AndroidDebugBridge.sSocketAddr, this,
localPort, remotePort);
} catch (IOException e) {
Log.e("adb-forward", e); //$NON-NLS-1$
return false;
}
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#removeForward(int, int)
*/
public boolean removeForward(int localPort, int remotePort) {
try {
return AdbHelper.removeForward(AndroidDebugBridge.sSocketAddr, this,
localPort, remotePort);
} catch (IOException e) {
Log.e("adb-remove-forward", e); //$NON-NLS-1$
return false;
}
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClientName(int)
*/
public String getClientName(int pid) {
synchronized (mClients) {
for (Client c : mClients) {
if (c.getClientData().getPid() == pid) {
return c.getClientData().getClientDescription();
}
}
}
return null;
}
Device(DeviceMonitor monitor, String serialNumber, DeviceState deviceState) {
mMonitor = monitor;
mSerialNumber = serialNumber;
mState = deviceState;
}
DeviceMonitor getMonitor() {
return mMonitor;
}
void addClient(Client client) {
synchronized (mClients) {
mClients.add(client);
}
}
List<Client> getClientList() {
return mClients;
}
boolean hasClient(int pid) {
synchronized (mClients) {
for (Client client : mClients) {
if (client.getClientData().getPid() == pid) {
return true;
}
}
}
return false;
}
void clearClientList() {
synchronized (mClients) {
mClients.clear();
}
}
/**
* Sets the client monitoring socket.
* @param socketChannel the sockets
*/
void setClientMonitoringSocket(SocketChannel socketChannel) {
mSocketChannel = socketChannel;
}
/**
* Returns the client monitoring socket.
*/
SocketChannel getClientMonitoringSocket() {
return mSocketChannel;
}
/**
* Removes a {@link Client} from the list.
* @param client the client to remove.
* @param notify Whether or not to notify the listeners of a change.
*/
void removeClient(Client client, boolean notify) {
mMonitor.addPortToAvailableList(client.getDebuggerListenPort());
synchronized (mClients) {
mClients.remove(client);
}
if (notify) {
mMonitor.getServer().deviceChanged(this, CHANGE_CLIENT_LIST);
}
}
void update(int changeMask) {
mMonitor.getServer().deviceChanged(this, changeMask);
}
void update(Client client, int changeMask) {
mMonitor.getServer().clientChanged(client, changeMask);
}
void addProperty(String label, String value) {
mProperties.put(label, value);
}
/**
* {@inheritDoc}
*/
public String installPackage(String packageFilePath, boolean reinstall)
throws IOException {
String remoteFilePath = syncPackageToDevice(packageFilePath);
String result = installRemotePackage(remoteFilePath, reinstall);
removeRemotePackage(remoteFilePath);
return result;
}
/**
* {@inheritDoc}
*/
public String syncPackageToDevice(String localFilePath)
throws IOException {
try {
String packageFileName = getFileName(localFilePath);
String remoteFilePath = String.format("/data/local/tmp/%1$s", packageFileName); //$NON-NLS-1$
Log.d(packageFileName, String.format("Uploading %1$s onto device '%2$s'",
packageFileName, getSerialNumber()));
SyncService sync = getSyncService();
if (sync != null) {
String message = String.format("Uploading file onto device '%1$s'",
getSerialNumber());
Log.d(LOG_TAG, message);
SyncResult result = sync.pushFile(localFilePath, remoteFilePath,
SyncService.getNullProgressMonitor());
if (result.getCode() != SyncService.RESULT_OK) {
throw new IOException(String.format("Unable to upload file: %1$s",
result.getMessage()));
}
} else {
throw new IOException("Unable to open sync connection!");
}
return remoteFilePath;
} catch (IOException e) {
Log.e(LOG_TAG, String.format("Unable to open sync connection! reason: %1$s",
e.getMessage()));
throw e;
}
}
/**
* Helper method to retrieve the file name given a local file path
* @param filePath full directory path to file
* @return {@link String} file name
*/
private String getFileName(String filePath) {
return new File(filePath).getName();
}
/**
* {@inheritDoc}
*/
public String installRemotePackage(String remoteFilePath, boolean reinstall)
throws IOException {
InstallReceiver receiver = new InstallReceiver();
String cmd = String.format(reinstall ? "pm install -r \"%1$s\"" : "pm install \"%1$s\"",
remoteFilePath);
executeShellCommand(cmd, receiver);
return receiver.getErrorMessage();
}
/**
* {@inheritDoc}
*/
public void removeRemotePackage(String remoteFilePath) throws IOException {
// now we delete the app we sync'ed
try {
executeShellCommand("rm " + remoteFilePath, new NullOutputReceiver());
} catch (IOException e) {
Log.e(LOG_TAG, String.format("Failed to delete temporary package: %1$s",
e.getMessage()));
throw e;
}
}
/**
* {@inheritDoc}
*/
public String uninstallPackage(String packageName) throws IOException {
InstallReceiver receiver = new InstallReceiver();
executeShellCommand("pm uninstall " + packageName, receiver);
return receiver.getErrorMessage();
}
}