com.motorola.studio.android.adt.DDMSFacade.java Source code

Java tutorial

Introduction

Here is the source code for com.motorola.studio.android.adt.DDMSFacade.java

Source

/*
 * Copyright (C) 2012 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.motorola.studio.android.adt;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;

import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData;
import com.android.ddmlib.EmulatorConsole;
import com.android.ddmlib.FileListingService;
import com.android.ddmlib.FileListingService.FileEntry;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IDevice.DeviceState;
import com.android.ddmlib.MultiLineReceiver;
import com.android.ddmlib.SyncException;
import com.android.ddmlib.SyncService;
import com.android.ddmlib.SyncService.ISyncProgressMonitor;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.motorola.studio.android.AndroidPlugin;
import com.motorola.studio.android.adt.StudioAndroidEventManager.EventType;
import com.motorola.studio.android.common.IAndroidConstants;
import com.motorola.studio.android.common.exception.AndroidException;
import com.motorola.studio.android.common.log.StudioLogger;
import com.motorola.studio.android.i18n.AndroidNLS;
import com.motorola.studio.android.utilities.TelnetFrameworkAndroid;

/**
 * DESCRIPTION:
 * This class is the common interface to functionalities from DDMS  
 *
 * RESPONSIBILITY:
 * Centralizes the access to DDMS. 
 *
 * COLABORATORS:
 * None. 
 *
 * USAGE:
 * Use the public methods to use DDMS
 */
public class DDMSFacade {
    /**
     * Command for switching back to USB connection mode.
     */
    private static final String USB_SWITCH_BACK_COMMAND = "usb"; //$NON-NLS-1$

    /**
     * Argument which indicates the device to apply a certain command. 
     */
    private static final String DEVICE_ID_INDICATOR = "-s"; //$NON-NLS-1$

    private static final String DEFAULT_WIRELESS_DEVICE_PROPERTY = "tiwlan0"; //$NON-NLS-1$

    /**
     * Map containing all connected devices.
     * It is being kept for us not to depend on ADT every time we need one, preventing 
     * deadlocks.
     */
    private static final Map<String, IDevice> connectedDevices = new HashMap<String, IDevice>();

    /**
     * Set containing the serial numbers of the devices completely loaded.
     * A device is considered completely loaded if it has already loaded the HOME application.
     */
    private static final Set<String> completelyUpDevices = new HashSet<String>();

    /**
     * Folder located inside the SDK folder containing some sdk tools.
     */
    static final String TOOLS_FOLDER = IAndroidConstants.FD_TOOLS;

    /**
     * Folder located inside the SDK folder and containing the ADB.
     */
    static final String PLATFORM_TOOLS_FOLDER = IAndroidConstants.FD_PLATFORM_TOOLS;

    /**
     * adb (android debug bridge) command.
     */
    static final String ADB_COMMAND = "adb"; //$NON-NLS-1$

    /**
     * Command to concatenate with "adb" to have the device shell.
     */
    static final String SHELL_CMD = "shell"; //$NON-NLS-1$

    /**
     * Options to be used with adb to indicate run operation.
     */
    private static final String AM_CMD = "am"; //$NON-NLS-1$

    /**
     * Command to concatenate with "am" to have an activity executed at the device.
     */
    private static final String START_CMD = "start"; //$NON-NLS-1$

    /**
     * Parameter for running in debug mode.
     */
    private static final String ADB_AM_DEBUG = "-D"; //$NON-NLS-1$

    /**
     * Parameter provided before the application package/name.
     */
    private static final String ADB_AM_NAME = "-n"; //$NON-NLS-1$

    /**
     * Parameter for selecting emulator instance.
     */
    static final String ADB_INSTANCE_PARAMETER = DEVICE_ID_INDICATOR; //$NON-NLS-1$

    /**
     * Folder for the SDK.
     */
    private static final String SDCARD_FOLDER = "sdcard"; //$NON-NLS-1$

    /**
     * Folder for the SDK.
     */
    private static final String MNT_SDCARD_FOLDER = "mnt/sdcard"; //$NON-NLS-1$

    /*
     * TCP/IP
     */
    private static final String CONNECT_TCPIP_CMD = "connect"; //$NON-NLS-1$

    private static final String DISCONNECT_TCPIP_CMD = "disconnect"; //$NON-NLS-1$

    private static final String TCPIP_CMD = "tcpip"; //$NON-NLS-1$

    private static final String IFCONFIG_CMD = "ifconfig"; //$NON-NLS-1$

    static Object consoleLock = new Object();

    private static Map<String, String> avdNameMap = new HashMap<String, String>();

    /**
     * Property from device which represents the wi-fi value to use ipconfig command.
     */
    private static final String WIFI_INTERFACE_DEVICE_PROPERTY = "wifi.interface"; //$NON-NLS-1$

    // IP validation
    private static final String ZERO_TO_255_PATTERN = "((\\d)|(\\d\\d)|([0-1]\\d\\d)|(2[0-4]\\d)|(25[0-5]))"; //$NON-NLS-1$

    private static final String IP_PATTERN = "(" + ZERO_TO_255_PATTERN + "\\." //$NON-NLS-1$ //$NON-NLS-2$
            + ZERO_TO_255_PATTERN + "\\." + ZERO_TO_255_PATTERN + "\\." + ZERO_TO_255_PATTERN //$NON-NLS-1$ //$NON-NLS-2$
            + ")+"; //$NON-NLS-1$

    /**
     * Must be called only once, during AndroidPlugin start-up.
     * This method configures all necessary device listeners. 
     */
    public static void setup() {
        AndroidPlugin.getDefault().addSDKLoaderListener(new Runnable() {

            public void run() {
                AndroidDebugBridge adb = AndroidDebugBridge.getBridge();
                if (adb == null) {
                    AndroidDebugBridge.disconnectBridge();
                    DdmsPlugin.setToolsLocation(AdtPlugin.getOsAbsoluteAdb(), true,
                            AdtPlugin.getOsAbsoluteHprofConv(), AdtPlugin.getOsAbsoluteTraceview());
                }

                if (adb != null) {
                    IDevice[] x = adb.getDevices();
                    IDevice[] newDevices = x;
                    List<IDevice> oldDevList = new ArrayList<IDevice>(connectedDevices.values());

                    for (IDevice newDev : newDevices) {
                        String serialNum = newDev.getSerialNumber();
                        if (connectedDevices.containsKey(serialNum)) {
                            IDevice oldDev = connectedDevices.get(serialNum);
                            oldDevList.remove(oldDev);
                            if (oldDev.getState().compareTo((newDev).getState()) != 0) {
                                if ((newDev).getState() == DeviceState.OFFLINE) {
                                    deviceDisconnected(newDev);
                                } else if ((newDev).getState() == DeviceState.ONLINE) {
                                    deviceConnected(newDev);
                                }
                            }
                        } else {
                            deviceConnected(newDev);
                        }
                    }

                    for (IDevice oldDev : oldDevList) {
                        deviceDisconnected(oldDev);
                    }
                }

            }
        });

        // Adds listener for the HOME application. It adds the serial number of the 
        // device to a collection when it identifies that the HOME application has
        // loaded
        AndroidDebugBridge.addClientChangeListener(new IClientChangeListener() {

            public void clientChanged(Client client, int changeMask) {
                if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) {
                    final Client finalClient = client;
                    Thread t = new Thread() {

                        @Override
                        public void run() {
                            String applicationName = finalClient.getClientData().getClientDescription();
                            if (applicationName != null) {
                                IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
                                String home = store.getString(AdtPrefs.PREFS_HOME_PACKAGE);
                                if (home.equals(applicationName)) {
                                    String serialNum = finalClient.getDevice().getSerialNumber();
                                    synchronized (completelyUpDevices) {
                                        StudioLogger.debug("Completely Up Device: " + serialNum); //$NON-NLS-1$
                                        completelyUpDevices.add(serialNum);
                                    }
                                }
                            }
                        }
                    };
                    t.start();
                }
            }
        });
    }

    static void deviceStatusChanged(IDevice device) {
        StudioLogger.debug("Device changed: " + device.getSerialNumber()); //$NON-NLS-1$
        synchronized (connectedDevices) {
            connectedDevices.put(device.getSerialNumber(), device);
        }
        if ((device).getState() == DeviceState.ONLINE) {
            IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
            String home = store.getString(AdtPrefs.PREFS_HOME_PACKAGE);
            if (device.getClient(home) != null) {
                synchronized (completelyUpDevices) {
                    StudioLogger.debug("Completely Up Device: " + device.getSerialNumber()); //$NON-NLS-1$
                    if (!completelyUpDevices.contains(device.getSerialNumber())) {
                        completelyUpDevices.add(device.getSerialNumber());
                    }
                }
            }
        }
    }

    /**
     * Registers a device as connected
     * 
     * @param device
     */
    static void deviceConnected(IDevice device) {
        final String serialNumber = device.getSerialNumber();
        StudioLogger.debug("Device connected: " + serialNumber); //$NON-NLS-1$
        synchronized (connectedDevices) {
            connectedDevices.put(serialNumber, device);
        }

        if (!device.isEmulator() && !device.hasClients()) {
            boolean timeout = false;
            long startTime = System.currentTimeMillis();
            int maxInterval = 10000;
            do {
                try {
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                    //do nothing
                }
                long currentTime = System.currentTimeMillis();
                timeout = ((startTime + maxInterval) < currentTime);

            } while (!device.hasClients() && !timeout);
            if (timeout) {
                synchronized (completelyUpDevices) {
                    //put the device up anyway.
                    completelyUpDevices.add(serialNumber);
                }
            }
        }

        if (device.hasClients()) {
            // When a device is connected, look for the HOME application and add
            // the device serial number to a collection if it is already running.
            IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
            String home = store.getString(AdtPrefs.PREFS_HOME_PACKAGE);
            if (device.getClient(home) != null) {
                StudioLogger.debug("Completely Up Device: " + serialNumber); //$NON-NLS-1$
                synchronized (completelyUpDevices) {
                    completelyUpDevices.add(serialNumber);
                }
            }
        }

        StudioAndroidEventManager.fireEvent(EventType.DEVICE_CONNECTED, serialNumber);
    }

    /**
     * Unregisters a device as connected
     * 
     * @param device
     */
    static void deviceDisconnected(IDevice device) {
        final String serialNumber = device.getSerialNumber();
        StudioLogger.debug("Device disconnected: " + serialNumber); //$NON-NLS-1$
        synchronized (completelyUpDevices) {
            completelyUpDevices.remove(serialNumber);
        }
        synchronized (connectedDevices) {
            connectedDevices.remove(serialNumber);
        }
        StudioAndroidEventManager.fireEvent(EventType.DEVICE_DISCONNECTED, serialNumber);
        avdNameMap.remove(device.getSerialNumber());

    }

    /**
     * Get all connected device serial numbers
     * 
     * @return
     */
    public static Collection<String> getConnectedSerialNumbers() {
        return connectedDevices.keySet();
    }

    /**
     * Get the Device associated with the given serial number
     * 
     * @param serialNumber Serial number of the device to retrieve
     * @return Device associated with the given serial number
     */
    public static IDevice getDeviceBySerialNumber(String serialNumber) {
        return connectedDevices.get(serialNumber);
    }

    /**
     * Runs an activity at the given device
     * 
     * @param serialNumber The serial number of the device to have the activity executed
     * @param activityName The activity to execute
     * @param debugMode Whether the activity shall be run in debug mode or not
     * @param processOut The output stream of the process running "adb"
     * 
     * @return An IStatus object with the result of the operation
     */
    public static IStatus runActivity(String serialNumber, String activityName, boolean debugMode,
            OutputStream processOut) {
        IStatus status = Status.OK_STATUS;

        // Return if no instance is selected
        if (serialNumber == null) {
            StudioLogger.error("Abort run operation. Serial number is null."); //$NON-NLS-1$
            return new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID,
                    AndroidNLS.ERR_DDMSFacade_SerialNumberNullPointer);
        }

        // Return if instance is not started
        if (!isDeviceOnline(serialNumber)) {
            StudioLogger.error("Abort run operation. Device is not online."); //$NON-NLS-1$
            return new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, ""); //$NON-NLS-1$
        }

        try {
            String[] cmd = createRunCommand(serialNumber, activityName, debugMode);
            executeCommand(cmd, processOut);
        } catch (IOException e) {
            StudioLogger.error("Deploy: Could not execute adb install command."); //$NON-NLS-1$
            status = new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, e.getMessage());
        }

        return status;
    }

    static String executeCommand(String[] cmd, OutputStream out) throws IOException {
        return executeCommand(cmd, out, null);
    }

    /**
     * DOCUMENT ME!!
     * @param cmd
     * @param out
     * @param serialNumber 
     * @return
     * @throws IOException
     */
    static String executeCommand(String[] cmd, OutputStream out, String serialNumber) throws IOException {
        String fullCmd = ""; //$NON-NLS-1$
        if (out != null) {
            for (String cmdArg : cmd) {
                fullCmd += cmdArg + " "; //$NON-NLS-1$
            }
            out.write(fullCmd.getBytes());
            out.write("\n".getBytes()); //$NON-NLS-1$
        }

        Runtime r = Runtime.getRuntime();
        Process p = r.exec(cmd);

        String command_results = ""; //$NON-NLS-1$
        InputStream processIn = p.getInputStream();
        final BufferedReader br = new BufferedReader(new InputStreamReader(processIn));
        String line;
        try {
            while ((line = br.readLine()) != null) {
                command_results += line;
                command_results += "\n"; //$NON-NLS-1$
                if (out != null) {
                    if (serialNumber != null) {
                        out.write((serialNumber + ": ").getBytes()); //$NON-NLS-1$
                    }
                    out.write(line.getBytes());
                    out.write("\n".getBytes()); //$NON-NLS-1$
                }
            }
        } finally {
            br.close();
        }

        return command_results;
    }

    /**
     * See http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
     * to understand how Process.exec works and its problems
     * 
     * @param cmd Command to be executed.
     * @param out Output Stream.
     * @param timeout Timeout (secs.)
     * @param monitor {@link IProgressMonitor}
     * 
     * @return the {@link IStatus} of this process execution.
     * 
     * @throws IOException Exception thrown in case there is any problem
     * executing the command.
     */
    private static IStatus executeRemoteDevicesCommand(String[] cmd, OutputStream out, int timeout,
            String timeoutMsg, IStopCondition stopCondition, IProgressMonitor monitor) throws IOException {

        IStatus status = Status.OK_STATUS;

        long timeoutLimit = -1;
        if (timeout != 0) {
            timeoutLimit = System.currentTimeMillis() + (timeout * 1000);
        }

        String fullCmd = ""; //$NON-NLS-1$
        for (String cmdArg : cmd) {
            fullCmd += cmdArg + " "; //$NON-NLS-1$
        }
        if (out != null) {
            out.write(fullCmd.getBytes());
            out.write("\n".getBytes()); //$NON-NLS-1$
        }

        Runtime r = Runtime.getRuntime();
        Process p = r.exec(cmd);

        int errorCode = 0;

        //  inputStream / errorStream;
        String[] commandResults = new String[] { "", "" //$NON-NLS-1$ //$NON-NLS-2$
        };

        commandResults = readCmdOutputFromStreams(commandResults[0], commandResults[1], p.getInputStream(),
                p.getErrorStream(), out);

        while (!stopCondition.canStop()) {
            if ((monitor != null) && (monitor.isCanceled())) {
                p.destroy();
                return Status.CANCEL_STATUS;
            }

            try {
                errorCode = p.exitValue();
                if (errorCode != 0) {
                    break;
                }

            } catch (IllegalThreadStateException e) {
                // Process is still running... Proceed with loop
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                StudioLogger.error("Execute command: thread has been interrupted"); //$NON-NLS-1$
            }

            if (timeout > 0) {
                try {
                    testTimeout(timeoutLimit, ((timeoutMsg != null) ? timeoutMsg : AndroidNLS.ERR_GenericTimeout));
                } catch (TimeoutException e) {
                    p.destroy();
                    StudioLogger.debug("The timeout " + timeout //$NON-NLS-1$
                            + " has been reached when executing the command " + fullCmd); //$NON-NLS-1$
                    return new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, e.getMessage(), e);
                }
            }

        }

        commandResults = readCmdOutputFromStreams(commandResults[0], commandResults[1], p.getInputStream(),
                p.getErrorStream(), out);

        if (errorCode != 0) {
            StudioLogger.debug("Command " + cmd + " returned an error code: " + errorCode); //$NON-NLS-1$ //$NON-NLS-2$
            status = new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID,
                    NLS.bind(AndroidNLS.ERR_CommandError, errorCode) + "\n" //$NON-NLS-1$
                            + ((!commandResults[1].equals("")) ? commandResults[1] //$NON-NLS-1$
                                    : commandResults[0]));
        } else {
            status = new Status(IStatus.OK, AndroidPlugin.PLUGIN_ID, commandResults[0]);
        }

        return status;
    }

    /**
     * Defines a stop condition
     */
    interface IStopCondition {
        public boolean canStop();
    }

    /**
     * @param commandResults
     * @param errorResults
     * @param inputStream
     * @param errorStream
     * @param out
     */
    private static String[] readCmdOutputFromStreams(String commandResults, String errorResults,
            InputStream inputStream, InputStream errorStream, OutputStream out) {
        String[] results = new String[2];
        String line = ""; //$NON-NLS-1$

        BufferedReader brInput = new BufferedReader(new InputStreamReader(inputStream));
        BufferedReader brError = new BufferedReader(new InputStreamReader(errorStream));

        try {

            // input stream
            if (brInput.ready()) {
                while ((line = brInput.readLine()) != null) {
                    commandResults += line;
                    commandResults += "\n"; //$NON-NLS-1$
                    if (out != null) {
                        out.write(line.getBytes());
                        out.write("\n".getBytes()); //$NON-NLS-1$
                    }
                }
            }

            // error stream
            if (brError.ready()) {
                while ((line = brError.readLine()) != null) {
                    errorResults += "\n"; //$NON-NLS-1$
                    if (out != null) {
                        out.write(line.getBytes());
                        out.write("\n".getBytes()); //$NON-NLS-1$
                    }
                }
            }
        } catch (IOException e) {
            StudioLogger.error("Cannot read command outputs"); //$NON-NLS-1$
        } finally {
            try {
                brInput.close();
                brError.close();
            } catch (IOException e) {
                StudioLogger.error("Could not close console stream: " + e.getMessage());
            }
        }

        results[0] = commandResults;
        results[1] = errorResults;

        return results;

    }

    /**
     * Checks if the timeout limit has reached.
     * 
     * @param timeoutLimit The system time limit that cannot be overtaken, in milliseconds.
     * @throws StartTimeoutException When the system time limit is overtaken. 
     */
    private static void testTimeout(long timeoutLimit, String timeoutErrorMessage) throws TimeoutException {
        if (System.currentTimeMillis() > timeoutLimit) {
            throw new TimeoutException(timeoutErrorMessage);
        }
    }

    /**
     * Creates a string with the command that should
     * be called in order to run the application.
     */
    private static String[] createRunCommand(String serialNumber, String activityName, boolean debugMode) {
        String cmd[];
        String sdkPath = SdkUtils.getSdkPath();

        // The tools folder should exist and be here, but double-cheking
        // once more wont kill
        File f = new File(sdkPath + PLATFORM_TOOLS_FOLDER + File.separator);
        if (!f.exists()) {
            StudioLogger.error("Run: Could not find tools folder on " + sdkPath + PLATFORM_TOOLS_FOLDER //$NON-NLS-1$
                    + File.separator);
        } else {
            if (!f.isDirectory()) {
                StudioLogger.error("Run: Invalid tools folder " + sdkPath + PLATFORM_TOOLS_FOLDER //$NON-NLS-1$
                        + File.separator);
            }
        }

        String completeAppPath = activityName.substring(0, activityName.lastIndexOf(".")) + "/" + activityName; //$NON-NLS-1$ //$NON-NLS-2$
        if (debugMode) {
            // If debugMode option is checked, create command with the -D paramater
            String cmdTemp[] = { sdkPath + PLATFORM_TOOLS_FOLDER + File.separator + ADB_COMMAND,
                    ADB_INSTANCE_PARAMETER, serialNumber, SHELL_CMD, AM_CMD, START_CMD, ADB_AM_DEBUG, ADB_AM_NAME,
                    completeAppPath };
            cmd = cmdTemp;
        } else {
            // If debugMode option is unchecked, create command without the -D paramater
            String cmdTemp[] = { sdkPath + PLATFORM_TOOLS_FOLDER + File.separator + ADB_COMMAND,
                    ADB_INSTANCE_PARAMETER, serialNumber, SHELL_CMD, AM_CMD, START_CMD, ADB_AM_NAME,
                    completeAppPath };
            cmd = cmdTemp;
        }

        return cmd;
    }

    /**
     * Check if the device is Online (i.e. if it's possible to communicate with it)
     * Notice it is a verification of the status of the Device which may be different than the status of the Tml Instance.
     * 
     * @param serialNumber
     * @return true if the Device is online, false otherwise
     */
    public static boolean isDeviceOnline(String serialNumber) {
        IDevice device = getDeviceBySerialNumber(serialNumber);
        if ((device == null) || !device.isOnline()) {
            return false;
        }
        return true;
    }

    /**
     * Return true if the Device is being shown on the OFFLINE state.
     * 
     * @param serialNumber Devices serial number.
     * 
     * @return <code>true</code> in case the Device if offline,
     * <code>false</code> otherwise.
     */
    public static boolean isDeviceOffline(String serialNumber) {

        IDevice device = getDeviceBySerialNumber(serialNumber);
        return ((device == null) || ((device != null) && device.isOffline()));
    }

    /**
     * Check if the device is completely loaded
     * A device is completely loaded when it loads the HOME application
     *  
     * @param serialNumber
     * @return true if the Device has completely loaded; false otherwise
     */
    public static boolean isDeviceCompletelyLoaded(String serialNumber) {
        return completelyUpDevices.contains(serialNumber);
    }

    /**
     * Tests if the device represented by the serial number (if it exists) is an emulator
     * 
     * @param serialNumber
     * @return true if it is an emulator, false if not or non existent
     */
    public static boolean isEmulator(String serialNumber) {
        IDevice device = getDeviceBySerialNumber(serialNumber);
        if ((device != null) && device.isEmulator()) {
            return true;
        }
        return false;
    }

    public static boolean isRemote(String serialNumber) {
        // firstly, test if the serial number has the format "anything:digits"
        Pattern p = Pattern.compile("(.)+:(\\d)+"); //$NON-NLS-1$
        Matcher m = p.matcher(serialNumber);
        if (m.matches()) {
            IDevice device = getDeviceBySerialNumber(serialNumber);
            if ((device != null) && !device.isEmulator()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Execute an app in the Device
     * 
     * @param serialNumber Serial number of the device where to execute the command
     * @param remoteCommand command to be executed remotely on the Device
     * @param monitor monitor associated with the operation
     * 
     * @return The lines read from the command output 
     * 
     * @throws IOException
     */
    public static Collection<String> execRemoteApp(String serialNumber, String remoteCommand,
            final IProgressMonitor monitor) throws IOException {
        return executeShellCmd(serialNumber, remoteCommand, monitor);
    }

    /**
     * Execute an app in the Device
     * 
     * @param serialNumber Serial number of the device where to execute the command
     * @param remoteCommands commands to be executed remotely on the Device
     * @param monitor monitor associated with the operation
     * 
     * @throws IOException
     */
    public static Map<String, Collection<String>> execRemoteApp(String serialNumber,
            Collection<String> remoteCommands, final IProgressMonitor monitor) throws IOException {
        Map<String, Collection<String>> cmdAnswers = new LinkedHashMap<String, Collection<String>>();
        for (String remoteCommand : remoteCommands) {
            StudioLogger.debug(remoteCommand);
            Collection<String> answers = executeShellCmd(serialNumber, remoteCommand, monitor);
            cmdAnswers.put(remoteCommand, answers);
        }

        return cmdAnswers;
    }

    private static Collection<String> executeShellCmd(String serialNumber, final String cmd,
            final IProgressMonitor monitor) {
        final Collection<String> results = new ArrayList<String>();
        IDevice d = getDeviceBySerialNumber(serialNumber);
        if (d != null) {
            try {
                d.executeShellCommand(cmd, new MultiLineReceiver() {
                    public boolean isCancelled() {
                        return monitor.isCanceled();
                    }

                    @Override
                    public void processNewLines(String[] lines) {
                        for (String line : lines) {
                            if ((!line.equals("")) && (!line.equals(cmd))) //$NON-NLS-1$
                            {
                                results.add(line);
                            }
                        }
                    }
                }, 0);
            } catch (Exception e) {
                StudioLogger.error(DDMSFacade.class, "Error executing shell command " + cmd //$NON-NLS-1$
                        + " at device " + serialNumber, e); //$NON-NLS-1$
            }
        }
        return results;
    }

    /**
     * Retrieves all properties from the device with provided serial number.
     * @param serialNumber
     * @return
     */
    public static Properties getDeviceProperties(String serialNumber) {
        Properties instanceProperties = new Properties();
        if (serialNumber != null) {
            String key = ""; //$NON-NLS-1$
            String value = ""; //$NON-NLS-1$
            Collection<String> lines;
            try {
                lines = execRemoteApp(serialNumber, "getprop", new NullProgressMonitor()); //$NON-NLS-1$

                for (String line : lines) {
                    String[] split = line.split("]:"); //$NON-NLS-1$
                    if (split.length >= 2) {
                        if (!split[0].equals("")) //$NON-NLS-1$
                        {
                            key = split[0].substring(1, split[0].length());
                            if (!split[1].equals("")) //$NON-NLS-1$
                            {
                                value = split[1].substring(2, split[1].length() - 1);
                                instanceProperties.setProperty(key, value);
                            }
                        }

                    }
                }
            } catch (IOException e) {
                StudioLogger.error("IOException while executing an app on device. " + e.getMessage());
            }
        }
        return instanceProperties;
    }

    /**
    * Retrieves a given property from the device with provided serial number.
    * 
    * @param serialNumber
    * @param propertyName
    * 
    * @return
    */
    public static String getDeviceProperty(String serialNumber, String propertyName) {
        String result = null;
        IDevice device = DDMSFacade.getDeviceBySerialNumber(serialNumber);
        if (device != null) {
            result = device.getProperty(propertyName);
        }
        return result;
    }

    /**
     * Get the name of the VM associated to the emulator running in the given deviceSerial identification.
     * 
     * @param deviceSerial identification of the emulator whose vm name must be retrieved.
     * @return the name of the VM used by the emulator running with the given id, 
     * or null if the vmname could be retrieved.
     */
    public static String getVmName(final IDevice d) {
        String vmName = null;
        String serialNumber = d.getSerialNumber();
        int MAX_TRIES = 120;
        int tries = 0;

        while ((vmName == null) && (tries < MAX_TRIES)) {
            synchronized (avdNameMap) {
                vmName = avdNameMap.get(serialNumber);
            }

            if (vmName == null) {
                vmName = d.getAvdName();
            }

            if (vmName == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //do nothing
                } finally {
                    tries++;
                }
            }

            //try to get vmname by telnet as last option
            if (vmName == null) {
                vmName = getVmNameByTelnet(serialNumber);
            }

        }

        if (vmName != null) {
            synchronized (avdNameMap) {
                if (avdNameMap.get(serialNumber) == null) {
                    avdNameMap.put(serialNumber, vmName);
                }
            }

        }

        return vmName;
    }

    private static String getVmNameByTelnet(String serialNumber) {
        Pattern pattern = Pattern.compile("emulator-([0-9]+)"); //$NON-NLS-1$
        TelnetFrameworkAndroid telnet = new TelnetFrameworkAndroid();
        Matcher matcher = pattern.matcher(serialNumber);
        matcher.matches();
        String avdName = null;

        try {
            Integer telnetPort = Integer.valueOf(matcher.group(1));
            telnet.connect("localhost", telnetPort); //$NON-NLS-1$
            String avdNameRaw = telnet.write("avd name\r\n", new String[] //$NON-NLS-1$
            { "KO" //$NON-NLS-1$
            });

            String split = avdNameRaw.contains("\r\n") ? "\r\n" : "\n";

            String[] outputArray = avdNameRaw.split(split); //$NON-NLS-1$
            if (outputArray.length > 2) {
                avdName = outputArray[2];
            }

            if (avdName != null) {
                avdNameMap.put(serialNumber, avdName);
            }
        } catch (NumberFormatException e) {
            avdName = serialNumber;
        } catch (IOException e) {
            avdName = serialNumber;
        } finally {
            try {
                telnet.disconnect();
            } catch (IOException e) {
                //Do nothing.
            }
        }
        return avdName;
    }

    /**
     * Get the Device associated with the Android VM
     * 
     * @param vmName Android VM name
     * @return Device associated with the given Android VM
     */
    public static IDevice getDeviceWithVmName(String vmName) {
        IDevice toReturn = null;
        if (vmName != null) {
            Collection<IDevice> devices = connectedDevices.values();
            for (IDevice d : devices) {
                if (d.isEmulator()) {
                    String deviceVmName = DDMSFacade.getVmName(d);
                    if (vmName.equals(deviceVmName)) {
                        toReturn = d;
                        break;
                    }
                }
            }
        }
        return toReturn;
    }

    /**
     * Securely get the name of the AVD associated to the given device.
     * 
     * @param serialNumber The serialNumber of the device that we want the AVD name for
     * @return the name of the AVD used by the emulator running with the given device, 
     * or null if the vmname could be retrieved.
     */
    public static String getNameBySerialNumber(String serialNumber) {
        String avdName = null;
        IDevice d = getDeviceBySerialNumber(serialNumber);
        avdName = getNameByDevice(d);

        return avdName;
    }

    /**
     * Securely get the serial number of the given instance.
     * 
     * @param instanceName The name of the instance we want the serial number of
     * @return the serial number of the given instance, or <code>null</code> if no instance with the 
     * given name is online
     */
    public static String getSerialNumberByName(String instanceName) {
        String serialNumber = null;
        if (instanceName != null) {
            List<IDevice> devices = null;
            synchronized (connectedDevices) {
                devices = new ArrayList<IDevice>(connectedDevices.size());
                devices.addAll(connectedDevices.values());
            }
            if (devices != null) {
                for (IDevice dev : devices) {
                    if (instanceName.equals(getNameByDevice(dev))) {
                        serialNumber = dev.getSerialNumber();
                        break;
                    }
                }
            }
        }

        return serialNumber;
    }

    public static Collection<String> getRunningApplications(String serialNumber) {
        Collection<String> apps = new ArrayList<String>();
        if (serialNumber != null) {
            IDevice dev = getDeviceBySerialNumber(serialNumber);
            if (dev != null) {
                Client[] clients = dev.getClients();
                if ((clients != null) && (clients.length > 0)) {
                    for (Client c : clients) {
                        apps.add(c.getClientData().getClientDescription());
                    }
                }
            }
        }

        return apps;
    }

    /**
     * Dumps a HPROF file based on a client description and a device serial number
     * @param clientDescription A client description of a running application
     */
    public static IStatus dumpHprofFile(String clientDescription, String serialNumber, IProgressMonitor monitor) {
        IStatus status = Status.OK_STATUS;
        monitor.beginTask(AndroidNLS.DumpHprofFile_GeneratingMemoryAnalysisOutput, 100);

        // Retrive running apps
        monitor.setTaskName(AndroidNLS.DumpHprofFile_GettingRunningApplications);
        IDevice dev = getDeviceBySerialNumber(serialNumber);
        Client[] clients = dev.getClients();
        monitor.worked(25);

        // Store the shell
        final Shell[] shell = new Shell[1];

        PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
            public void run() {

                shell[0] = new Shell(PlatformUI.getWorkbench().getDisplay());

            }
        });

        MotodevHProfDumpHandler hprofHandler = new MotodevHProfDumpHandler(shell[0], monitor);
        monitor.setTaskName(AndroidNLS.DumpHprofFile_SettingApplicationToAnalyse);
        hprofHandler.setSelectedApp(clientDescription);
        monitor.worked(25);

        try {
            // Find a client with matching description and dum the HPROF file
            for (Client c : clients) {
                if (c.getClientData().getClientDescription().equals(clientDescription)) {
                    // Set our handler as the HprofDumpHandler
                    ClientData.setHprofDumpHandler(hprofHandler);
                    monitor.setTaskName(AndroidNLS.DumpHprofFile_DumpingHprofFile);
                    c.dumpHprof();
                    synchronized (DDMSFacade.class) {
                        DDMSFacade.class.wait();
                    }

                    monitor.worked(50);
                }
            }
        } catch (Exception e) {
            // Status not ok
            status = Status.CANCEL_STATUS;
        } finally {
            monitor.done();
        }

        return status;

    }

    /**
     * Gets the AVD name of the device
     * 
     * @param d The device to be searched for the AVD name
     * 
     * @return The AVD name
     */
    private static String getNameByDevice(final IDevice d) {
        String name = null;
        if (d != null) {
            if (d.isEmulator()) {

                name = getVmName(d);
            } else {
                name = d.getSerialNumber();
            }
        }

        return name;
    }

    /**
     * Create port forward for a given VM
     * 
     * @param serialNumber Android serial number
     * @param from port number from
     * @param to port number to
     * @return true is the port forward was successful, false otherwise
     */
    public static boolean createForward(String serialNumber, int from, int to) {
        boolean ok = true;
        IDevice device = getDeviceBySerialNumber(serialNumber);
        try {
            device.createForward(from, to);
        } catch (Exception e) {
            StudioLogger.error(DDMSFacade.class, "Error creating forward of device: " //$NON-NLS-1$
                    + serialNumber + " from " + from + " to " + to, e); //$NON-NLS-1$ //$NON-NLS-2$
            ok = false;
        }
        return ok;
    }

    /**
     * Kill the communication channel
     * 
     * @param serialNumber The serial number of the device to kill
     */
    public static void kill(String serialNumber) {
        if (isDeviceOnline(serialNumber)) {
            IDevice deviceToKill = getDeviceBySerialNumber(serialNumber);
            if (deviceToKill != null) {
                synchronized (consoleLock) {
                    EmulatorConsole console = EmulatorConsole.getConsole(deviceToKill);
                    if (console != null) {
                        console.kill();
                    }
                }
            }
        }
    }

    /**
     * Push files to device
     * 
     * @param serialNumber Android device serial number
     * @param localDir local folder path
     * @param fileNames files to transfer
     * @param remoteDir destination folder path
     * @param timeout timeout for the operation
     * @param monitor monitor associated with the operation
     */
    public static IStatus pushFiles(String serialNumber, String localDir, Collection<String> fileNames,
            String remoteDir, int timeout, final IProgressMonitor monitor, OutputStream outputStream) {
        return transferFiles(true, serialNumber, localDir, fileNames, remoteDir, timeout, monitor, outputStream);
    }

    /**
     * Push files to device
     * 
     * @param serialNumber Android device serial number
     * @param localFiles destination local files 
     * @param remoteFiles remote files to transfer as localFiles to desktop
     * @param timeout timeout for the operation
     * @param monitor monitor associated with the operation
     */
    public static IStatus pushFiles(String serialNumber, List<File> localFiles, List<String> remoteFiles,
            int timeout, final IProgressMonitor monitor, OutputStream outputStream) {
        return transferFiles(true, serialNumber, localFiles, remoteFiles, timeout, monitor, outputStream);
    }

    /**
     * Pull files from device
     * 
     * @param serialNumber Android device serial number
     * @param localDir local folder path
     * @param fileNames files to transfer
     * @param remoteDir destination folder path
     * @param timeout timeout for the operation
     * @param monitor monitor associated with the operation
     */
    public static IStatus pullFiles(String serialNumber, String localDir, Collection<String> fileNames,
            String remoteDir, int timeout, final IProgressMonitor monitor, OutputStream outputStream) {
        return transferFiles(false, serialNumber, localDir, fileNames, remoteDir, timeout, monitor, outputStream);
    }

    /**
     * Pull files from device
     * 
     * @param serialNumber Android device serial number
     * @param localFiles local files to transfer as remoteFiles to device
     * @param remoteFiles destination remote files
     * @param timeout timeout for the operation
     * @param monitor monitor associated with the operation
     */
    public static IStatus pullFiles(String serialNumber, List<File> localFiles, List<String> remoteFiles,
            int timeout, final IProgressMonitor monitor, OutputStream outputStream) {
        return transferFiles(false, serialNumber, localFiles, remoteFiles, timeout, monitor, outputStream);
    }

    /**
     * Get the service used to transfer files to the Device
     * 
     * @param device Device
     * @param timeout timeout for the operation
     * @param monitor monitor associated with the operation
     * @return The service used to transfer files to the Device
     * 
     * @throws AndroidException
     */
    private static SyncService getSyncService(IDevice device, int timeout, final IProgressMonitor monitor)
            throws AndroidException {

        SyncService service = null;
        long timeoutLimit = System.currentTimeMillis() + timeout;
        do {
            if ((device != null) && (device.isOnline())) {
                try {
                    service = device.getSyncService();
                } catch (IOException e) {
                    StudioLogger.debug("Couldn't get sync service; cause: " + e.getMessage()); //$NON-NLS-1$
                } catch (com.android.ddmlib.TimeoutException e) {
                    StudioLogger.debug("Couldn't get sync service; cause: " + e.getMessage()); //$NON-NLS-1$
                } catch (AdbCommandRejectedException e) {
                    StudioLogger.debug("Couldn't get sync service; cause: " + e.getMessage()); //$NON-NLS-1$
                }
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                //do nothing
            }

            if (monitor.isCanceled()) {
                StudioLogger.info("Operation canceled by the user"); //$NON-NLS-1$
                return null;
            }

            if (System.currentTimeMillis() > timeoutLimit) {
                StudioLogger.error("The emulator was not up within the set timeout"); //$NON-NLS-1$
                throw new AndroidException("Timeout while preparing to transfer files to the Device. " + device); //$NON-NLS-1$
            }
        } while (service == null);

        return service;
    }

    private static IStatus transferFiles(boolean isPush, String serialNumber, String localDir,
            Collection<String> fileNames, String remoteDir, int timeout, final IProgressMonitor monitor,
            OutputStream outputStream) {
        List<File> localList = new ArrayList<File>();
        List<String> remoteList = new ArrayList<String>();
        for (String name : fileNames) {
            localList.add(new File(localDir, name));
            remoteList.add(remoteDir + "/" + name); //$NON-NLS-1$
        }
        return transferFiles(isPush, serialNumber, localList, remoteList, timeout, monitor, outputStream);
    }

    private static IStatus transferFiles(boolean isPush, String serialNumber, List<File> localFiles,
            List<String> remoteFiles, int timeout, final IProgressMonitor monitor, OutputStream outputStream) {
        if (localFiles.size() != remoteFiles.size()) {
            return new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID,
                    AndroidNLS.ERR_DDMSFacade_IncompatibleFileLists);
        }

        IStatus status = Status.OK_STATUS;
        IDevice device = DDMSFacade.getDeviceBySerialNumber(serialNumber);

        SyncService service = null;
        try {
            service = getSyncService(device, timeout, monitor);
            if (service == null) {
                status = Status.CANCEL_STATUS;
            } else {
                final ISyncProgressMonitor syncMonitor = new ISyncProgressMonitor() {
                    public void start(int i) {
                        //do nothing
                    }

                    public void stop() {
                        //do nothing
                    }

                    public void advance(int i) {
                        //do nothing
                    }

                    public boolean isCanceled() {
                        return monitor.isCanceled();
                    }

                    public void startSubTask(String s) {
                        //do nothing
                    }
                };

                FileListingService flService = device.getFileListingService();

                for (int i = 0; i < localFiles.size(); i++) {
                    File localFile = localFiles.get(i);
                    String remotePath = remoteFiles.get(i);
                    String absLocalFile = localFile.getAbsolutePath();

                    String resultMessage = null;
                    if (isPush) {
                        StudioLogger.debug("Push " + absLocalFile + " to " + remotePath); //$NON-NLS-1$ //$NON-NLS-2$
                        try {
                            service.pushFile(absLocalFile, remotePath, syncMonitor);
                        } catch (SyncException e1) {
                            StudioLogger.debug("Push result: " + "SyncException occured " + e1.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
                            resultMessage = NLS.bind(AndroidNLS.CON_ConsolePush, absLocalFile, remotePath) + ": " //$NON-NLS-1$
                                    + e1.getLocalizedMessage();
                        } catch (FileNotFoundException e1) {
                            StudioLogger.debug("Push result: " + "FileNotFoundException occured " //$NON-NLS-1$ //$NON-NLS-2$
                                    + e1.getMessage());
                            resultMessage = NLS.bind(AndroidNLS.CON_ConsolePush, absLocalFile, remotePath) + ": " //$NON-NLS-1$
                                    + e1.getLocalizedMessage();
                        } catch (IOException e1) {
                            StudioLogger.debug("Push result: " + "IOException occured " + e1.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
                            resultMessage = NLS.bind(AndroidNLS.CON_ConsolePush, absLocalFile, remotePath) + ": " //$NON-NLS-1$
                                    + e1.getLocalizedMessage();
                        } catch (com.android.ddmlib.TimeoutException e1) {
                            StudioLogger.debug("Push result: " + "TimeoutException occured " + e1.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
                            resultMessage = NLS.bind(AndroidNLS.CON_ConsolePush, absLocalFile, remotePath) + ": " //$NON-NLS-1$
                                    + e1.getLocalizedMessage();
                        }

                        if ((outputStream != null) && (resultMessage != null)) {
                            try {
                                outputStream.write(resultMessage.getBytes());
                                outputStream.write('\n');
                                outputStream.flush();
                            } catch (Exception e) {
                                // Do nothing
                            }
                        }

                    } else {
                        FileEntry f1 = null;
                        FileEntry f2 = null;

                        f2 = flService.getRoot();
                        flService.getChildren(f2, false, null);
                        String[] dirs = remotePath.split("/"); //$NON-NLS-1$

                        for (int j = 1; j < (dirs.length - 1); j++) {
                            f1 = f2.findChild(dirs[j]);
                            flService.getChildren(f1, false, null);
                            f2 = f1;
                        }

                        final FileEntry fileToPull = f2.findChild(dirs[dirs.length - 1]);

                        if (fileToPull != null) {
                            try {
                                service.pullFile(fileToPull, absLocalFile, syncMonitor);
                            } catch (FileNotFoundException e) {
                                resultMessage = e.getLocalizedMessage();
                                StudioLogger.debug("Pull result: " + e.getMessage()); //$NON-NLS-1$
                            } catch (SyncException e) {
                                resultMessage = e.getLocalizedMessage();
                                StudioLogger.debug("Pull result: " + e.getMessage()); //$NON-NLS-1$
                            } catch (IOException e) {
                                resultMessage = e.getLocalizedMessage();
                                StudioLogger.debug("Pull result: " + e.getMessage()); //$NON-NLS-1$
                            } catch (com.android.ddmlib.TimeoutException e) {
                                resultMessage = e.getLocalizedMessage();
                                StudioLogger.debug("Pull result: " + e.getMessage()); //$NON-NLS-1$
                            }

                            if ((outputStream != null) && (resultMessage != null)) {
                                String message = NLS.bind(AndroidNLS.CON_ConsolePull, fileToPull.getFullPath(),
                                        absLocalFile) + ": " + resultMessage; //$NON-NLS-1$
                                try {
                                    outputStream.write(message.getBytes());
                                    outputStream.write('\n');
                                    outputStream.flush();
                                } catch (IOException e) {
                                    //do nothing
                                }
                            }
                        } else {
                            resultMessage = NLS.bind(AndroidNLS.DDMSFacade_Remote_File_Not_Found, remotePath);
                            StudioLogger.debug("Pull result: File not found " + remotePath); //$NON-NLS-1$
                        }
                    }

                    if (resultMessage != null) {
                        status = new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, resultMessage);
                    }
                    if (syncMonitor.isCanceled()) {
                        status = Status.CANCEL_STATUS;
                        break;
                    }
                }
            }
        } catch (AndroidException e) {
            status = new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, e.getMessage());
        } catch (NullPointerException e1) {
            status = new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, AndroidNLS.ERR_DDMSFacade_FileNotFound);
        } finally {
            if (service != null) {
                service.close();
            }
        }

        return status;
    }

    /**
     * Check if the application is running in the device with specified serial number
     * @param serialNumber
     * @param applicationName
     * @return true if it is running, false otherwise
     */
    public static boolean isApplicationRunning(String serialNumber, String applicationName) {
        IDevice dev = null;
        boolean running = false;
        dev = connectedDevices.get(serialNumber);
        if (dev != null) {
            running = dev.getClient(applicationName) != null;
        }
        return running;
    }

    /**
     * Connect to a Remote Device given its IP/Port
     * 
     * @param device the Remote Device Instance
     * @param host device host (IP)
     * @param port device port
     * @param timeout the maximum time allowed to successfully connect to the device
     * @param monitor the monitor of the operation
     * @return the status of the operation
     * @throws IOException
     */
    public static IStatus connectTcpIp(final ISerialNumbered device, String host, String port, int timeout,
            IProgressMonitor monitor) throws IOException {
        SubMonitor subMonitor = SubMonitor.convert(monitor, 1000);

        subMonitor.beginTask(AndroidNLS.DDMSFacade_MsgConnectingToDeviceViaTCPIP, 10);

        IStatus status = Status.OK_STATUS;

        String serialNumber = device.getSerialNumber();
        if (!isDeviceOnline(serialNumber)) // check if it's already connected
        {
            String[] cmd = createConnectTcpIpCommand(host, port);

            status = executeRemoteDevicesCommand(cmd, null, timeout,
                    NLS.bind(AndroidNLS.ERR_RemoteDevice_TimeoutWhileConnecting, device.getDeviceName()),
                    new IStopCondition() {

                        public boolean canStop() {
                            String serialNumber = device.getSerialNumber();
                            if (serialNumber != null) {
                                return isDeviceOnline(serialNumber);
                            } else {
                                return false;
                            }
                        }
                    }, subMonitor.newChild(1000));

        }

        subMonitor.worked(1000);

        return status;
    }

    /**
     * Method which switches the device connection mode from TCP/IP
     * to USB.
     * 
     * @param device {@link ISerialNumbered} device to have its connection mode changed.
     * @param host The IP of the device.
     * @param port The port in which the TCP/IP connection is established.
     * @param timeout The maximum time which the switching operation is attempted.
     * @param monitor The {@link IProgressMonitor} which this operation is being computed.
     * 
     * @return Returns the {@link IStatus} of this operation.
     * 
     * @throws IOException Exception thrown in case something goes wrong while trying
     * to switch the device connection mode from TCP/IP to USB.
     */
    public static IStatus switchFromTCPConnectionModeToUSBConnectionMode(final ISerialNumbered device, String host,
            String port, int timeout, IProgressMonitor monitor) throws IOException {
        SubMonitor subMonitor = SubMonitor.convert(monitor, 1000);

        subMonitor.beginTask(AndroidNLS.DDMSFacade_MsgSwitchingDeviceFromTCPIPToUSB, 10);

        IStatus status = Status.OK_STATUS;

        String serialNumber = device.getSerialNumber();
        if (isDeviceOnline(serialNumber)) // check if it's already connected
        {
            String[] cmd = createSwitchToUSBConnectionModeCommand(host, port);

            status = executeRemoteDevicesCommand(cmd, null, timeout,
                    NLS.bind(AndroidNLS.DDMSFacade_MsgTimeoutReachedSwitchingFromTCPToUSB, device.getDeviceName()),
                    new IStopCondition() {

                        public boolean canStop() {
                            String serialNumber = device.getSerialNumber();
                            if (serialNumber != null) {
                                return isDeviceOnline(serialNumber);
                            } else {
                                return false;
                            }
                        }
                    }, subMonitor.newChild(1000));

        }

        subMonitor.worked(1000);

        if (status.isOK()) {
            StudioLogger.collectUsageData(StudioLogger.WHAT_REMOTE_USB, StudioLogger.KIND_REMOTE_DEVICE,
                    StudioLogger.DESCRIPTION_DEFAULT, AndroidPlugin.PLUGIN_ID,
                    AndroidPlugin.getDefault().getBundle().getVersion().toString());
        }

        return status;
    }

    /**
     * Get the wireless ip from the connected handset
     * @param serialNumber
     * @param monitor
     * @return the ip or null if not possible to retrieve it
     */
    public static String getWirelessIPfromHandset(String serialNumber, IProgressMonitor monitor) {
        String handset_wireless_ip = null;
        IDevice device = null;

        device = connectedDevices.get(serialNumber);
        if (device != null) {
            // get the wi-fi name for executing the ipconfig command
            String wifiProperty = device.getProperty(WIFI_INTERFACE_DEVICE_PROPERTY);
            if (wifiProperty == null) {
                wifiProperty = DEFAULT_WIRELESS_DEVICE_PROPERTY;
            }
            // execute ipconfig command
            Collection<String> answers = executeShellCmd(serialNumber, IFCONFIG_CMD + " " + wifiProperty, monitor); //$NON-NLS-1$

            // Success message - for example
            // [tiwlan0: ip 192.168.0.174 mask 255.255.255.0 flags [up broadcast running multicast]]

            if (answers != null) {
                String result = answers.toString();
                if (result != null) {
                    // splits the result of the shell command and gets the third position 
                    // that should be the IP number
                    String[] result_splited = result.split(" "); //$NON-NLS-1$
                    if (result_splited.length >= 3) {
                        // check whether there is an IP
                        Pattern pattern = Pattern.compile(IP_PATTERN);
                        Matcher matcher = pattern.matcher(result);
                        if (matcher.find()) {
                            handset_wireless_ip = result_splited[2];
                        }
                    }
                }
            }
        }
        return handset_wireless_ip;
    }

    /**
     * Switch adb connection mode of an specific device to TCPIP
     * 
     * @param deviceName name of the handset instance
     * @param host wireless ip of the handset instance
     * @param port number of the port to be using during the connection
     * @param timeout the maximum time allowed to successfully connect to the device
     * @param monitor the monitor of the operation
     * @return the status of the operation
     * 
     * @throws IOException Exception thrown in case there are problems switching the device.
     */
    public static IStatus switchUSBtoTcpIp(String deviceName, final String serialNumber, String port, int timeout,
            IProgressMonitor monitor) throws IOException {
        SubMonitor subMonitor = SubMonitor.convert(monitor, 1000);

        subMonitor.beginTask(AndroidNLS.DDMSFacade_MsgSwitchingFromUSBConnection, 10);

        IStatus status = Status.OK_STATUS;

        if (isDeviceOnline(serialNumber)) {
            String[] cmd = createSwitchToTcpIpCommand(serialNumber, port);

            status = executeRemoteDevicesCommand(cmd, null, timeout,
                    NLS.bind(AndroidNLS.ERR_WirelessRemoteDevice_TimeoutWhileConnecting, deviceName),
                    new IStopCondition() {

                        public boolean canStop() {
                            if (serialNumber != null) {
                                return isDeviceOffline(serialNumber);
                            } else {
                                return false;
                            }
                        }
                    }, subMonitor.newChild(1000));

        }

        monitor.worked(1000);

        return status;
    }

    /**
     * Disconnect from a Remote Device given its IP/Port
     * 
     * @param device the Remote Device Instance
     * @param host device host (IP)
     * @param port device port
     * @param timeout the maximum time allowed to successfully disconnect from the device
     * @param monitor the monitor of the operation
     * @return the status of the operation
     * @throws IOException
     */
    public static IStatus disconnectTcpIp(final ISerialNumbered device, String host, String port, int timeout,
            IProgressMonitor monitor) throws IOException {
        IStatus status = Status.OK_STATUS;

        String serialNumber = device.getSerialNumber();
        if (isDeviceOnline(serialNumber)) // check if it's already disconnected
        {
            String[] cmd = createDisconnectTcpIpCommand(host, port);

            status = executeRemoteDevicesCommand(cmd, null, timeout,
                    NLS.bind(AndroidNLS.ERR_RemoteDevice_TimeoutWhileDisconnecting, device.getDeviceName()),
                    new IStopCondition() {

                        public boolean canStop() {
                            String serialNumber = device.getSerialNumber();
                            return !isDeviceOnline(serialNumber);
                        }
                    }, monitor);

        }

        return status;
    }

    /**
     * Creates a string with the command that should
     * be called in order to connect to an IP/Port
     * 
     * @param host device host (IP)
     * @param port device port
     * @return the command to be used to connect to an IP/Port
     */
    private static String[] createConnectTcpIpCommand(String host, String port) {

        String hostPort = host + ":" + port; //$NON-NLS-1$

        String sdkPath = SdkUtils.getSdkPath();

        String cmd[] = { sdkPath + PLATFORM_TOOLS_FOLDER + File.separator + ADB_COMMAND, CONNECT_TCPIP_CMD,
                hostPort };

        return cmd;
    }

    /**
     * Creates a string with the command switches
     * a device from the TCP/IP connection mode
     * to the USB connection mode.
     * 
     * @param host Device host (IP).
     * @param port Device port.
     * 
     * @return The command to be used to switch back to USB connection mode.
     */
    private static String[] createSwitchToUSBConnectionModeCommand(String host, String port) {

        String hostPort = host + ":" + port; //$NON-NLS-1$

        String sdkPath = SdkUtils.getSdkPath();

        String cmd[] = { sdkPath + PLATFORM_TOOLS_FOLDER + File.separator + ADB_COMMAND, DEVICE_ID_INDICATOR,
                hostPort, USB_SWITCH_BACK_COMMAND };

        return cmd;
    }

    /**
     * Creates a string with the command that should
     * be called in order to switch adb connection from USB to TPCIP mode
     * 
     * @param serialNumber device serial number
     * @param port device port
     * @return the command to be used to switch adb connection to TCPIP mode
     */
    private static String[] createSwitchToTcpIpCommand(String serialNumber, String port) {
        String sdkPath = SdkUtils.getSdkPath();

        String cmd[] = { sdkPath + PLATFORM_TOOLS_FOLDER + File.separator + ADB_COMMAND, ADB_INSTANCE_PARAMETER,
                serialNumber, TCPIP_CMD, port };

        return cmd;
    }

    /**
     * Creates a string with the command that should
     * be called to delete a file from device
     * 
     * @param serialNumber
     * @param file
     * @param folder
     * @return
     */
    private static String[] createDeleteFileFromDeviceCommand(String serialNumber, String file, String folder) {
        String sdkPath = SdkUtils.getSdkPath();

        // The tools folder should exist and be here, but double-cheking
        // once more wont kill
        File f = new File(sdkPath + PLATFORM_TOOLS_FOLDER + File.separator);
        if (!f.exists()) {
            StudioLogger.error("Run: Could not find tools folder on " + sdkPath + PLATFORM_TOOLS_FOLDER //$NON-NLS-1$
                    + File.separator);
        } else {
            if (!f.isDirectory()) {
                StudioLogger.error("Run: Invalid tools folder " + sdkPath + PLATFORM_TOOLS_FOLDER //$NON-NLS-1$
                        + File.separator);
            }
        }

        String cmd[] = { sdkPath + PLATFORM_TOOLS_FOLDER + File.separator + ADB_COMMAND, ADB_INSTANCE_PARAMETER,
                serialNumber, SHELL_CMD, "rm /" + folder + "/" + file //$NON-NLS-1$ //$NON-NLS-2$
        };

        return cmd;
    }

    /**
     * Uses the ADB shell command to remove a file from the device
     * 
     * @param serialNumber
     * @param fileName
     * @param sdCardFolder
     * @return
     * @throws IOException
     */
    private static boolean deleteFileFromDevice(String serialNumber, String fileName, String folder)
            throws IOException {

        String command[] = createDeleteFileFromDeviceCommand(serialNumber, fileName, folder);
        IStatus status = executeRemoteDevicesCommand(command, null, 1000, "", new IStopCondition() //$NON-NLS-1$
        {

            public boolean canStop() {
                return true;
            }
        }, null);

        return status.isOK();
    }

    /**
     * Check if a device identified by the serial number has a mounted SDCard
     * @param serialNumber the serial number
     * @return true if the device has a SDCard
     * @throws IOException 
     */
    public static boolean hasSDCard(String serialNumber) throws IOException {
        boolean hasSdCard = false;
        File tempSdCardFile = File.createTempFile("SDcheck", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$
        boolean tempCopiedOnSdCardFile = pushFileToDevice(serialNumber, SDCARD_FOLDER, tempSdCardFile);

        if (tempCopiedOnSdCardFile) {
            //trying to write on /sdcard folder (it works for phones previous from Platform 2.2)
            if (!deleteFileFromDevice(serialNumber, tempSdCardFile.getName(), SDCARD_FOLDER)) {
                StudioLogger.error(
                        "DDMSFacade: Could not delete tempfile from /sdcard when checking if card is enabled"); //$NON-NLS-1$
            }
            hasSdCard = true;
            tempSdCardFile.delete();
        } else {

            File tempMntFile = File.createTempFile("SDcheck", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$
            boolean tempCopiedOnMntFile = pushFileToDevice(serialNumber, MNT_SDCARD_FOLDER, tempSdCardFile);

            if (tempCopiedOnMntFile) {
                //trying to write on /mnt/sdcard folder (it works for phones since Platform 2.2)
                if (!deleteFileFromDevice(serialNumber, tempMntFile.getName(), MNT_SDCARD_FOLDER)) {
                    StudioLogger.error(
                            "DDMSFacade: Could not delete tempfile from /mnt/sdcard when checking if card is enabled"); //$NON-NLS-1$
                }
                hasSdCard = true;
                tempMntFile.delete();
            }

        }

        return hasSdCard;
    }

    /**
     * 
     * @param serialNumber
     * @param sdCardFolder
     * @param tempFile
     * @return true if manages to push file into the folder specified on device
     */
    private static boolean pushFileToDevice(String serialNumber, String folder, File file) {
        Collection<String> files = new ArrayList<String>();
        files.add(file.getName());
        Path path = new Path(file.getAbsolutePath());

        IStatus status = pushFiles(serialNumber, path.removeLastSegments(1).toString(), files, folder, 2000,
                new NullProgressMonitor(), null);

        return status.isOK();
    }

    /**
     * Creates a string with the command that should
     * be called in order to disconnect from an IP/Port
     * 
     * @param host device host (IP)
     * @param port device port
     * @return the command to be used to disconnect from an IP/Port
     */
    private static String[] createDisconnectTcpIpCommand(String host, String port) {

        String hostPort = host + ":" + port; //$NON-NLS-1$

        String sdkPath = SdkUtils.getSdkPath();
        String cmd[] = { sdkPath + PLATFORM_TOOLS_FOLDER + File.separator + ADB_COMMAND, DISCONNECT_TCPIP_CMD,
                hostPort };

        return cmd;
    }

    public static void deleteFile(String serialNumber, String path) throws IOException {
        DDMSFacade.execRemoteApp(serialNumber, "rm " + path, //$NON-NLS-1$
                new NullProgressMonitor());
    }
}