Home | History | Annotate | Download | only in device
      1 /*
      2  * Copyright (C) 2016 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 package com.android.tradefed.device;
     17 
     18 import com.android.ddmlib.IDevice;
     19 import com.android.tradefed.log.LogUtil.CLog;
     20 import com.android.tradefed.util.CommandResult;
     21 import com.android.tradefed.util.CommandStatus;
     22 
     23 /**
     24  * Implementation of a {@link ITestDevice} for a full stack android device connected via
     25  * adb connect.
     26  * Assume the device serial will be in the format <hostname>:<portnumber> in adb.
     27  */
     28 public class RemoteAndroidDevice extends TestDevice {
     29     public static final long WAIT_FOR_ADB_CONNECT = 2 * 60 * 1000;
     30 
     31     protected static final long RETRY_INTERVAL_MS = 5000;
     32     protected static final int MAX_RETRIES = 5;
     33     protected static final long DEFAULT_SHORT_CMD_TIMEOUT = 20 * 1000;
     34 
     35     private static final String ADB_SUCCESS_CONNECT_TAG = "connected to";
     36     private static final String ADB_ALREADY_CONNECTED_TAG = "already";
     37     private static final String ADB_CONN_REFUSED = "Connection refused";
     38 
     39     /**
     40      * Creates a {@link RemoteAndroidDevice}.
     41      *
     42      * @param device the associated {@link IDevice}
     43      * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
     44      * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
     45      */
     46     public RemoteAndroidDevice(IDevice device, IDeviceStateMonitor stateMonitor,
     47             IDeviceMonitor allocationMonitor) {
     48         super(device, stateMonitor, allocationMonitor);
     49     }
     50 
     51     /**
     52      * {@inheritDoc}
     53      */
     54     @Override
     55     public void postAdbRootAction() throws DeviceNotAvailableException {
     56         // attempt to reconnect first to make sure we didn't loose the connection because of
     57         // adb root.
     58         adbTcpConnect(getHostName(), getPortNum());
     59         waitForAdbConnect(WAIT_FOR_ADB_CONNECT);
     60     }
     61 
     62     /**
     63      * {@inheritDoc}
     64      */
     65     @Override
     66     public void postAdbUnrootAction() throws DeviceNotAvailableException {
     67         // attempt to reconnect first to make sure we didn't loose the connection because of
     68         // adb unroot.
     69         adbTcpConnect(getHostName(), getPortNum());
     70         waitForAdbConnect(WAIT_FOR_ADB_CONNECT);
     71     }
     72 
     73     /**
     74      * Return the hostname associated with the device. Extracted from the serial.
     75      */
     76     public String getHostName() {
     77         if (!checkSerialFormatValid()) {
     78             throw new RuntimeException(
     79                     String.format("Serial Format is unexpected: %s "
     80                             + "should look like <hostname>:<port>", getSerialNumber()));
     81         }
     82         return getSerialNumber().split(":")[0];
     83     }
     84 
     85     /**
     86      * Return the port number asociated with the device. Extracted from the serial.
     87      */
     88     public String getPortNum() {
     89         if (!checkSerialFormatValid()) {
     90             throw new RuntimeException(
     91                     String.format("Serial Format is unexpected: %s "
     92                             + "should look like <hostname>:<port>", getSerialNumber()));
     93         }
     94         return getSerialNumber().split(":")[1];
     95     }
     96 
     97     /**
     98      * Check if the format of the serial is as expected <hostname>:port
     99      * @return true if the format is valid, false otherwise.
    100      */
    101     private boolean checkSerialFormatValid() {
    102         String[] serial =  getSerialNumber().split(":");
    103         if (serial.length == 2) {
    104             try {
    105                 Integer.parseInt(serial[1]);
    106                 return true;
    107             } catch (NumberFormatException nfe) {
    108                 return false;
    109             }
    110         }
    111         return false;
    112     }
    113 
    114     /**
    115      * Helper method to adb connect to a given tcp ip Android device
    116      *
    117      * @param host the hostname/ip of a tcp/ip Android device
    118      * @param port the port number of a tcp/ip device
    119      * @return true if we successfully connected to the device, false
    120      *         otherwise.
    121      */
    122     public boolean adbTcpConnect(String host, String port) {
    123         for (int i = 0; i < MAX_RETRIES; i++) {
    124             CommandResult result = getRunUtil().runTimedCmd(DEFAULT_SHORT_CMD_TIMEOUT, "adb",
    125                     "connect", String.format("%s:%s", host, port));
    126             if (CommandStatus.SUCCESS.equals(result.getStatus()) &&
    127                 result.getStdout().contains(ADB_SUCCESS_CONNECT_TAG)) {
    128                 CLog.d("adb connect output: status: %s stdout: %s stderr: %s",
    129                         result.getStatus(), result.getStdout(), result.getStderr());
    130 
    131                 // It is possible to get a positive result without it being connected because of
    132                 // the ssh bridge. Retrying to get confirmation, and expecting "already connected".
    133                 if(confirmAdbTcpConnect(host, port)) {
    134                     return true;
    135                 }
    136             } else if (CommandStatus.SUCCESS.equals(result.getStatus()) &&
    137                     result.getStdout().contains(ADB_CONN_REFUSED)) {
    138                 // If we find "Connection Refused", we bail out directly as more connect won't help
    139                 return false;
    140             }
    141             CLog.d("adb connect output: status: %s stdout: %s stderr: %s, retrying.",
    142                     result.getStatus(), result.getStdout(), result.getStderr());
    143             getRunUtil().sleep((i + 1) * RETRY_INTERVAL_MS);
    144         }
    145         return false;
    146     }
    147 
    148     private boolean confirmAdbTcpConnect(String host, String port) {
    149         CommandResult resultConfirmation =
    150                 getRunUtil().runTimedCmd(DEFAULT_SHORT_CMD_TIMEOUT, "adb", "connect",
    151                 String.format("%s:%s", host, port));
    152         if (CommandStatus.SUCCESS.equals(resultConfirmation.getStatus()) &&
    153                 resultConfirmation.getStdout().contains(ADB_ALREADY_CONNECTED_TAG)) {
    154             CLog.d("adb connect confirmed:\nstdout: %s\nsterr: %s",
    155                     resultConfirmation.getStdout(), resultConfirmation.getStderr());
    156             return true;
    157         } else {
    158             CLog.d("adb connect confirmation failed:\nstatus:%s\nstdout: %s\nsterr: %s",
    159                     resultConfirmation.getStatus(), resultConfirmation.getStdout(),
    160                     resultConfirmation.getStderr());
    161         }
    162         return false;
    163     }
    164 
    165     /**
    166      * Helper method to adb disconnect from a given tcp ip Android device
    167      *
    168      * @param host the hostname/ip of a tcp/ip Android device
    169      * @param port the port number of a tcp/ip device
    170      * @return true if we successfully disconnected to the device, false
    171      *         otherwise.
    172      */
    173     public boolean adbTcpDisconnect(String host, String port) {
    174         CommandResult result = getRunUtil().runTimedCmd(DEFAULT_SHORT_CMD_TIMEOUT, "adb",
    175                 "disconnect",
    176                 String.format("%s:%s", host, port));
    177         return CommandStatus.SUCCESS.equals(result.getStatus());
    178     }
    179 
    180     /**
    181      * Check if the adb connection is enabled.
    182      */
    183     public void waitForAdbConnect(final long waitTime) throws DeviceNotAvailableException {
    184         CLog.i("Waiting %d ms for adb connection.", waitTime);
    185         long startTime = System.currentTimeMillis();
    186         while (System.currentTimeMillis() - startTime < waitTime) {
    187             if (confirmAdbTcpConnect(getHostName(), getPortNum())) {
    188                 CLog.d("Adb connection confirmed.");
    189                 return;
    190             }
    191             getRunUtil().sleep(RETRY_INTERVAL_MS);
    192         }
    193         throw new DeviceNotAvailableException(
    194                 String.format("No adb connection after %sms.", waitTime), getSerialNumber());
    195     }
    196 
    197     /**
    198      * {@inheritDoc}
    199      */
    200     @Override
    201     public boolean isEncryptionSupported() {
    202         // Prevent device from being encrypted since we won't have a way to decrypt on Remote
    203         // devices since fastboot cannot be use remotely
    204         return false;
    205     }
    206 
    207     /**
    208      * {@inheritDoc}
    209      */
    210     @Override
    211     public String getMacAddress() {
    212         return null;
    213     }
    214 }
    215