Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tradefed.util;
     18 
     19 import com.android.tradefed.device.CollectingOutputReceiver;
     20 import com.android.tradefed.device.DeviceNotAvailableException;
     21 import com.android.tradefed.device.ITestDevice;
     22 import com.android.tradefed.log.LogUtil.CLog;
     23 import com.android.tradefed.result.FileInputStreamSource;
     24 import com.android.tradefed.result.ITestInvocationListener;
     25 import com.android.tradefed.result.InputStreamSource;
     26 import com.android.tradefed.result.LogDataType;
     27 import com.android.tradefed.util.sl4a.Sl4aClient;
     28 
     29 import java.io.File;
     30 import java.io.IOException;
     31 import java.util.HashSet;
     32 import java.util.Set;
     33 import java.util.regex.Matcher;
     34 import java.util.regex.Pattern;
     35 
     36 /**
     37  * Utility functions for calling BluetoothInstrumentation on device
     38  * <p>
     39  * Device side BluetoothInstrumentation code can be found in AOSP at:
     40  * <code>frameworks/base/core/tests/bluetoothtests</code>
     41  *
     42  */
     43 public class BluetoothUtils {
     44 
     45     private static final String BT_INSTR_CMD = "am instrument -w -r -e command %s "
     46             + "com.android.bluetooth.tests/android.bluetooth.BluetoothInstrumentation";
     47     private static final String SUCCESS_INSTR_OUTPUT = "INSTRUMENTATION_RESULT: result=SUCCESS";
     48     private static final String BT_GETADDR_HEADER = "INSTRUMENTATION_RESULT: address=";
     49     private static final long BASE_RETRY_DELAY_MS = 60 * 1000;
     50     private static final int MAX_RETRIES = 3;
     51     private static final Pattern BONDED_MAC_HEADER =
     52             Pattern.compile("INSTRUMENTATION_RESULT: device-\\d{2}=(.*)$");
     53     private static final String BT_STACK_CONF = "/etc/bluetooth/bt_stack.conf";
     54     public static final String BTSNOOP_API = "bluetoothConfigHciSnoopLog";
     55     public static final String BTSNOOP_CMD = "setprop persist.bluetooth.btsnoopenable ";
     56     public static final String BTSNOOP_ENABLE_CMD = BTSNOOP_CMD + "true";
     57     public static final String BTSNOOP_DISABLE_CMD = BTSNOOP_CMD + "false";
     58     public static final String GOLD_BTSNOOP_LOG_PATH = "/data/misc/bluetooth/logs/btsnoop_hci.log";
     59     public static final String O_BUILD = "O";
     60 
     61     /**
     62      * Convenience method to execute BT instrumentation command and return output
     63      *
     64      * @param device
     65      * @param command a command string sent over to BT instrumentation, currently supported:
     66      *                 enable, disable, unpairAll, getName, getAddress, getBondedDevices; refer to
     67      *                 AOSP source for more details
     68      * @return output of BluetoothInstrumentation
     69      * @throws DeviceNotAvailableException
     70      */
     71     public static String runBluetoothInstrumentation(ITestDevice device, String command)
     72             throws DeviceNotAvailableException {
     73         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
     74         device.executeShellCommand(String.format(BT_INSTR_CMD, command), receiver);
     75         String output = receiver.getOutput();
     76         CLog.v("bluetooth instrumentation sub command: %s\noutput:\n", command);
     77         CLog.v(output);
     78         return output;
     79     }
     80 
     81     public static boolean runBluetoothInstrumentationWithRetry(ITestDevice device, String command)
     82         throws DeviceNotAvailableException {
     83         for (int retry = 0; retry < MAX_RETRIES; retry++) {
     84             String output = runBluetoothInstrumentation(device, command);
     85             if (output.contains(SUCCESS_INSTR_OUTPUT)) {
     86                 return true;
     87             }
     88             RunUtil.getDefault().sleep(retry * BASE_RETRY_DELAY_MS);
     89         }
     90         return false;
     91     }
     92 
     93     /**
     94      * Retries clearing of BT pairing with linear backoff
     95      * @param device
     96      * @throws DeviceNotAvailableException
     97      */
     98     public static boolean unpairWithRetry(ITestDevice device)
     99             throws DeviceNotAvailableException {
    100         return runBluetoothInstrumentationWithRetry(device, "unpairAll");
    101     }
    102 
    103     /**
    104      * Retrieves BT mac of the given device
    105      *
    106      * @param device
    107      * @return BT mac or null if not found
    108      * @throws DeviceNotAvailableException
    109      */
    110     public static String getBluetoothMac(ITestDevice device) throws DeviceNotAvailableException {
    111         String lines[] = runBluetoothInstrumentation(device, "getAddress").split("\\r?\\n");
    112         for (String line : lines) {
    113             line = line.trim();
    114             if (line.startsWith(BT_GETADDR_HEADER)) {
    115                 return line.substring(BT_GETADDR_HEADER.length());
    116             }
    117         }
    118         return null;
    119     }
    120 
    121     /**
    122      * Enables bluetooth on the given device
    123      *
    124      * @param device
    125      * @return True if enable is successful, false otherwise
    126      * @throws DeviceNotAvailableException
    127      */
    128     public static boolean enable(ITestDevice device)
    129             throws DeviceNotAvailableException {
    130         return runBluetoothInstrumentationWithRetry(device, "enable");
    131     }
    132 
    133     /**
    134      * Disables bluetooth on the given device
    135      *
    136      * @param device
    137      * @return True if disable is successful, false otherwise
    138      * @throws DeviceNotAvailableException
    139      */
    140     public static boolean disable(ITestDevice device)
    141             throws DeviceNotAvailableException {
    142         return runBluetoothInstrumentationWithRetry(device, "disable");
    143     }
    144 
    145     /**
    146      * Returns bluetooth mac addresses of devices that the given device has bonded with
    147      *
    148      * @param device
    149      * @return bluetooth mac addresses
    150      * @throws DeviceNotAvailableException
    151      */
    152     public static Set<String> getBondedDevices(ITestDevice device)
    153             throws DeviceNotAvailableException {
    154         String lines[] = runBluetoothInstrumentation(device, "getBondedDevices").split("\\r?\\n");
    155         return parseBondedDeviceInstrumentationOutput(lines);
    156     }
    157 
    158     /** Parses instrumentation output into mac addresses */
    159     static Set<String> parseBondedDeviceInstrumentationOutput(String[] lines) {
    160         Set<String> ret = new HashSet<>();
    161         for (String line : lines) {
    162             Matcher m = BONDED_MAC_HEADER.matcher(line.trim());
    163             if (m.find()) {
    164                 ret.add(m.group(1));
    165             }
    166         }
    167         return ret;
    168     }
    169 
    170     /**
    171      * Confirm branch version if it is Gold or not based on build alias
    172      *
    173      * @param device Test device to check
    174      * @throws DeviceNotAvailableException
    175      */
    176     private static boolean isGoldAndAbove(ITestDevice device) throws DeviceNotAvailableException {
    177         // TODO: Use API level once it is bumped up
    178         int apiLevel = device.getApiLevel();
    179         if (!"REL".equals(device.getProperty("ro.build.version.codename"))) {
    180             apiLevel++;
    181         }
    182         return apiLevel >= 25;
    183     }
    184 
    185     /**
    186      * Enable btsnoop logging by sl4a call
    187      *
    188      * @param device
    189      * @return success or not
    190      * @throws DeviceNotAvailableException
    191      */
    192     public static boolean enableBtsnoopLogging(ITestDevice device)
    193             throws DeviceNotAvailableException {
    194         if (isGoldAndAbove(device)) {
    195             device.executeShellCommand(BTSNOOP_ENABLE_CMD);
    196             disable(device);
    197             enable(device);
    198             return true;
    199         }
    200         return enableBtsnoopLogging(device, null);
    201     }
    202 
    203     /**
    204      * Enable btsnoop logging by sl4a call
    205      *
    206      * @param device
    207      * @param sl4aApkFile sl4a.apk file location, null if it has been installed
    208      * @return success or not
    209      * @throws DeviceNotAvailableException
    210      */
    211     public static boolean enableBtsnoopLogging(ITestDevice device, File sl4aApkFile)
    212             throws DeviceNotAvailableException {
    213         Sl4aClient client = new Sl4aClient(device, sl4aApkFile);
    214         return toggleBtsnoopLogging(client, true);
    215     }
    216 
    217     /**
    218      * Disable btsnoop logging by sl4a call
    219      *
    220      * @param device
    221      * @return success or not
    222      * @throws DeviceNotAvailableException
    223      */
    224     public static boolean disableBtsnoopLogging(ITestDevice device)
    225             throws DeviceNotAvailableException {
    226         if (isGoldAndAbove(device)) {
    227             device.executeShellCommand(BTSNOOP_DISABLE_CMD);
    228             disable(device);
    229             enable(device);
    230             return true;
    231         }
    232         return disableBtsnoopLogging(device, null);
    233     }
    234 
    235     /**
    236      * Disable btsnoop logging by sl4a call
    237      *
    238      * @param device
    239      * @param sl4aApkFile sl4a.apk file location, null if it has been installed
    240      * @return success or not
    241      * @throws DeviceNotAvailableException
    242      */
    243     public static boolean disableBtsnoopLogging(ITestDevice device, File sl4aApkFile)
    244             throws DeviceNotAvailableException {
    245         Sl4aClient client = new Sl4aClient(device, sl4aApkFile);
    246         return toggleBtsnoopLogging(client, false);
    247     }
    248 
    249     public static boolean toggleBtsnoopLogging(Sl4aClient client, boolean onOff)
    250             throws DeviceNotAvailableException {
    251         try {
    252             client.startSl4A();
    253             client.rpcCall(BTSNOOP_API, onOff);
    254             return true;
    255         } catch (IOException | RuntimeException e) {
    256             CLog.e(e);
    257             return false;
    258         } finally {
    259             client.close();
    260         }
    261     }
    262 
    263     /**
    264      * Upload snoop log file for test results
    265      */
    266     public static void uploadLogFiles(ITestInvocationListener listener, ITestDevice device,
    267             String type, int iteration) throws DeviceNotAvailableException {
    268         File logFile = null;
    269         InputStreamSource logSource = null;
    270         String fileName = getBtSnoopLogFilePath(device);
    271         if (fileName != null) {
    272             try {
    273                 logFile = device.pullFile(fileName);
    274                 if (logFile != null) {
    275                     CLog.d(
    276                             "Sending %s %d byte file %s into the logosphere!",
    277                             type, logFile.length(), logFile);
    278                     logSource = new FileInputStreamSource(logFile);
    279                     listener.testLog(
    280                             String.format("%s_btsnoop_%d", type, iteration),
    281                             LogDataType.UNKNOWN,
    282                             logSource);
    283                 }
    284             } finally {
    285                 FileUtil.deleteFile(logFile);
    286                 StreamUtil.cancel(logSource);
    287             }
    288         } else {
    289             return;
    290         }
    291     }
    292 
    293     /**
    294      * Delete snoop log file from device
    295      */
    296     public static void cleanLogFile(ITestDevice device) throws DeviceNotAvailableException {
    297         String fileName = getBtSnoopLogFilePath(device);
    298         if (fileName != null) {
    299             device.executeShellCommand(String.format("rm %s", fileName));
    300         } else {
    301             CLog.e("Not able to delete BT snoop log, file not found");
    302         }
    303     }
    304 
    305     /**
    306      * Get bt snoop log file path from bt_stack.config file
    307      *
    308      * @param device
    309      * @return THe file name for bt_snoop_log or null if it is not found
    310      */
    311     public static String getBtSnoopLogFilePath(ITestDevice device)
    312             throws DeviceNotAvailableException {
    313         if (isGoldAndAbove(device)) {
    314             return GOLD_BTSNOOP_LOG_PATH;
    315         }
    316         String snoopfileSetting =
    317                 device.executeShellCommand(
    318                         String.format("cat %s | grep BtSnoopFileName", BT_STACK_CONF));
    319         String[] settingItems = snoopfileSetting.split("=");
    320         if (settingItems.length > 1) {
    321             return settingItems[1].trim();
    322         } else {
    323             CLog.e(String.format("Not able to local BT snoop log, '%s'", snoopfileSetting));
    324             return null;
    325         }
    326     }
    327 }
    328