blob: 8f4b4491e3d5d7615af105333cd7c2961a58c4f7 [file] [log] [blame]
Qi Jiang73b25352017-08-02 19:56:31 +00001#!/usr/bin/env python3.4
2#
3# Copyright 2017 Google, Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Qi Jiang1c542f62018-04-09 19:50:43 -070017import acts
Qi Jiang9bbaee62018-03-23 00:03:18 +000018import json
Qi Jiang73b25352017-08-02 19:56:31 +000019import logging
20import os
21import time
Qi Jiang8f9c7582017-09-06 21:00:14 +000022from acts import asserts
Qi Jiang73b25352017-08-02 19:56:31 +000023from acts import utils
24from acts.controllers import monsoon
Qi Jiangcaca55e2017-10-10 23:10:40 +000025from acts.libs.proc import job
Qi Jiang7f6dc982018-02-24 01:29:29 +000026from acts.controllers.ap_lib import bridge_interface as bi
Qi Jiang73b25352017-08-02 19:56:31 +000027from acts.test_utils.wifi import wifi_test_utils as wutils
Qi Jiang8f9c7582017-09-06 21:00:14 +000028from bokeh.layouts import layout
Qi Jiang73b25352017-08-02 19:56:31 +000029from bokeh.models import CustomJS, ColumnDataSource
Omar El Ayachf4f132b2018-01-26 03:37:08 +000030from bokeh.models import tools as bokeh_tools
Qi Jiang73b25352017-08-02 19:56:31 +000031from bokeh.models.widgets import DataTable, TableColumn
Qi Jiang8f9c7582017-09-06 21:00:14 +000032from bokeh.plotting import figure, output_file, save
Qi Jiang73b25352017-08-02 19:56:31 +000033from acts.controllers.ap_lib import hostapd_security
34from acts.controllers.ap_lib import hostapd_ap_preset
Daniel Barros43760302017-12-06 04:16:03 +000035from acts.test_utils.bt.bt_test_utils import enable_bluetooth
36from acts.test_utils.bt.bt_test_utils import disable_bluetooth
37
Daniel Barrosfefd8c32017-08-04 15:36:40 -070038# http://www.secdev.org/projects/scapy/
39# On ubuntu, sudo pip3 install scapy-python3
40import scapy.all as scapy
Qi Jiang73b25352017-08-02 19:56:31 +000041
42SETTINGS_PAGE = "am start -n com.android.settings/.Settings"
43SCROLL_BOTTOM = "input swipe 0 2000 0 0"
44UNLOCK_SCREEN = "input keyevent 82"
45SCREENON_USB_DISABLE = "dumpsys battery unplug"
46RESET_BATTERY_STATS = "dumpsys batterystats --reset"
47AOD_OFF = "settings put secure doze_always_on 0"
48MUSIC_IQ_OFF = "pm disable-user com.google.intelligence.sense"
49# Command to disable gestures
50LIFT = "settings put secure doze_pulse_on_pick_up 0"
51DOUBLE_TAP = "settings put secure doze_pulse_on_double_tap 0"
52JUMP_TO_CAMERA = "settings put secure camera_double_tap_power_gesture_disabled 1"
53RAISE_TO_CAMERA = "settings put secure camera_lift_trigger_enabled 0"
54FLIP_CAMERA = "settings put secure camera_double_twist_to_flip_enabled 0"
55ASSIST_GESTURE = "settings put secure assist_gesture_enabled 0"
56ASSIST_GESTURE_ALERT = "settings put secure assist_gesture_silence_alerts_enabled 0"
57ASSIST_GESTURE_WAKE = "settings put secure assist_gesture_wake_enabled 0"
58SYSTEM_NAVI = "settings put secure system_navigation_keys_enabled 0"
59# End of command to disable gestures
60AUTO_TIME_OFF = "settings put global auto_time 0"
61AUTO_TIMEZONE_OFF = "settings put global auto_time_zone 0"
Qi Jiang50733382017-08-22 01:11:58 +000062FORCE_YOUTUBE_STOP = "am force-stop com.google.android.youtube"
63FORCE_DIALER_STOP = "am force-stop com.google.android.dialer"
Qi Jiang73b25352017-08-02 19:56:31 +000064IPERF_TIMEOUT = 180
Daniel Barros7cc34e82017-09-22 02:41:32 +000065THRESHOLD_TOLERANCE = 0.2
Daniel Barrosebbf91a2017-10-04 01:08:40 +000066GET_FROM_PHONE = 'get_from_dut'
67GET_FROM_AP = 'get_from_ap'
Daniel Barros35c901a2017-10-25 13:30:57 +000068PHONE_BATTERY_VOLTAGE = 4.2
69MONSOON_MAX_CURRENT = 8.0
Qi Jiang9c752562018-03-05 07:04:38 +000070MONSOON_RETRY_INTERVAL = 300
Qi Jiang36a7c732018-02-25 05:05:40 +000071MEASUREMENT_RETRY_COUNT = 3
Qi Jianga5f9a0d2018-03-03 02:54:02 +000072RECOVER_MONSOON_RETRY_COUNT = 3
Qi Jiangce0b1272018-02-28 04:39:23 +000073MIN_PERCENT_SAMPLE = 95
Qi Jiang487e7582018-04-24 08:10:12 -070074ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM='
75MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM='
Qi Jiang73b25352017-08-02 19:56:31 +000076
77
78def dut_rockbottom(ad):
79 """Set the phone into Rock-bottom state.
80
81 Args:
82 ad: the target android device, AndroidDevice object
83
84 """
85 ad.log.info("Now set the device to Rockbottom State")
86 utils.require_sl4a((ad, ))
Daniel Barros7cc34e82017-09-22 02:41:32 +000087 ad.droid.connectivityToggleAirplaneMode(False)
88 time.sleep(5)
89 ad.droid.connectivityToggleAirplaneMode(True)
Qi Jiang73b25352017-08-02 19:56:31 +000090 utils.set_ambient_display(ad, False)
91 utils.set_auto_rotate(ad, False)
92 utils.set_adaptive_brightness(ad, False)
93 utils.sync_device_time(ad)
94 utils.set_location_service(ad, False)
95 utils.set_mobile_data_always_on(ad, False)
96 utils.disable_doze_light(ad)
97 utils.disable_doze(ad)
98 wutils.reset_wifi(ad)
99 wutils.wifi_toggle_state(ad, False)
Qi Jiang1c542f62018-04-09 19:50:43 -0700100 try:
101 ad.droid.nfcDisable()
102 except acts.controllers.sl4a_lib.rpc_client.Sl4aApiError:
103 ad.log.info('NFC is not available')
Qi Jiang73b25352017-08-02 19:56:31 +0000104 ad.droid.setScreenBrightness(0)
105 ad.adb.shell(AOD_OFF)
106 ad.droid.setScreenTimeout(2200)
Qi Jiang73b25352017-08-02 19:56:31 +0000107 ad.droid.wakeUpNow()
108 ad.adb.shell(LIFT)
109 ad.adb.shell(DOUBLE_TAP)
110 ad.adb.shell(JUMP_TO_CAMERA)
111 ad.adb.shell(RAISE_TO_CAMERA)
112 ad.adb.shell(FLIP_CAMERA)
113 ad.adb.shell(ASSIST_GESTURE)
114 ad.adb.shell(ASSIST_GESTURE_ALERT)
115 ad.adb.shell(ASSIST_GESTURE_WAKE)
116 ad.adb.shell(SCREENON_USB_DISABLE)
117 ad.adb.shell(UNLOCK_SCREEN)
118 ad.adb.shell(SETTINGS_PAGE)
119 ad.adb.shell(SCROLL_BOTTOM)
120 ad.adb.shell(MUSIC_IQ_OFF)
121 ad.adb.shell(AUTO_TIME_OFF)
122 ad.adb.shell(AUTO_TIMEZONE_OFF)
Qi Jiang50733382017-08-22 01:11:58 +0000123 ad.adb.shell(FORCE_YOUTUBE_STOP)
124 ad.adb.shell(FORCE_DIALER_STOP)
Qi Jiang487e7582018-04-24 08:10:12 -0700125 ad.droid.wifiSetCountryCode('US')
Qi Jiang50733382017-08-22 01:11:58 +0000126 ad.droid.wakeUpNow()
Qi Jiang73b25352017-08-02 19:56:31 +0000127 ad.log.info('Device has been set to Rockbottom state')
Daniel Barros35c901a2017-10-25 13:30:57 +0000128 ad.log.info('Screen is ON')
Qi Jiang73b25352017-08-02 19:56:31 +0000129
130
Qi Jiang9bbaee62018-03-23 00:03:18 +0000131def unpack_custom_file(file, test_class=None):
132 """Unpack the pass_fail_thresholds from a common file.
133
134 Args:
135 file: the common file containing pass fail threshold.
136 """
137 with open(file, 'r') as f:
138 params = json.load(f)
139 if test_class:
140 return params[test_class]
141 else:
142 return params
143
144
Qi Jiang8f9c7582017-09-06 21:00:14 +0000145def pass_fail_check(test_class, test_result):
146 """Check the test result and decide if it passed or failed.
147 The threshold is provided in the config file
148
149 Args:
150 test_class: the specific test class where test is running
Qi Jiang7f6dc982018-02-24 01:29:29 +0000151 test_result: the average current as the test result
Qi Jiang8f9c7582017-09-06 21:00:14 +0000152 """
153 test_name = test_class.current_test_name
154 current_threshold = test_class.threshold[test_name]
Qi Jiang7f6dc982018-02-24 01:29:29 +0000155 if test_result:
156 asserts.assert_true(
157 abs(test_result - current_threshold) / current_threshold <
158 THRESHOLD_TOLERANCE,
159 ("Measured average current in [%s]: %s, which is "
160 "more than %d percent off than acceptable threshold %.2fmA") %
Qi Jiang9bbaee62018-03-23 00:03:18 +0000161 (test_name, test_result, test_class.pass_fail_tolerance * 100,
Qi Jiang7f6dc982018-02-24 01:29:29 +0000162 current_threshold))
163 asserts.explicit_pass("Measurement finished for %s." % test_name)
164 else:
165 asserts.fail(
166 "Something happened, measurement is not complete, test failed")
Qi Jiang8f9c7582017-09-06 21:00:14 +0000167
168
Qi Jiang9c752562018-03-05 07:04:38 +0000169def monsoon_recover(mon):
Qi Jiang36a7c732018-02-25 05:05:40 +0000170 """Test loop to wait for monsoon recover from unexpected error.
171
Qi Jiangce0b1272018-02-28 04:39:23 +0000172 Wait for a certain time duration, then quit.0
Qi Jiang36a7c732018-02-25 05:05:40 +0000173 Args:
174 mon: monsoon object
175 """
Qi Jiang9c752562018-03-05 07:04:38 +0000176 try:
177 mon.reconnect_monsoon()
178 time.sleep(2)
179 mon.usb('on')
180 logging.info("Monsoon recovered from unexpected error")
181 time.sleep(2)
182 return True
183 except monsoon.MonsoonError:
184 logging.info(mon.mon.ser.in_waiting)
185 logging.warning("Unable to recover monsoon from unexpected error")
186 return False
Qi Jiang36a7c732018-02-25 05:05:40 +0000187
188
Qi Jiangc64e51b2018-02-09 04:02:47 +0000189def monsoon_data_collect_save(ad, mon_info, test_name):
Qi Jiang73b25352017-08-02 19:56:31 +0000190 """Current measurement and save the log file.
191
192 Collect current data using Monsoon box and return the path of the
193 log file. Take bug report if requested.
194
195 Args:
196 ad: the android device under test
197 mon_info: dict with information of monsoon measurement, including
198 monsoon device object, measurement frequency, duration and
199 offset etc.
200 test_name: current test name, used to contruct the result file name
201 bug_report: indicator to take bug report or not, 0 or 1
202 Returns:
203 data_path: the absolute path to the log file of monsoon current
204 measurement
205 avg_current: the average current of the test
206 """
Qi Jiang36a7c732018-02-25 05:05:40 +0000207
208 log = logging.getLogger()
209 tag = (test_name + '_' + ad.model + '_' + ad.build_info['build_id'])
Qi Jiangce0b1272018-02-28 04:39:23 +0000210 data_path = os.path.join(mon_info['data_path'], "%s.txt" % tag)
211 total_expected_samples = mon_info['freq'] * (
212 mon_info['duration'] + mon_info['offset'])
213 min_required_samples = total_expected_samples * MIN_PERCENT_SAMPLE / 100
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000214 # Retry counter for monsoon data aquisition
215 retry_measure = 1
Qi Jiangce0b1272018-02-28 04:39:23 +0000216 # Indicator that need to re-collect data
217 need_collect_data = 1
Qi Jiang94b289a2018-03-01 03:50:15 +0000218 result = None
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000219 while retry_measure <= MEASUREMENT_RETRY_COUNT:
Qi Jiang36a7c732018-02-25 05:05:40 +0000220 try:
Qi Jiangce0b1272018-02-28 04:39:23 +0000221 # If need to retake data
222 if need_collect_data == 1:
Qi Jiangce0b1272018-02-28 04:39:23 +0000223 #Resets the battery status right before the test started
224 ad.adb.shell(RESET_BATTERY_STATS)
225 log.info(
226 "Starting power measurement with monsoon box, try #{}".
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000227 format(retry_measure))
Qi Jiangce0b1272018-02-28 04:39:23 +0000228 #Start the power measurement using monsoon
Qi Jiang6cc758e2018-03-08 01:18:44 +0000229 mon_info['dut'].monsoon_usb_auto()
Qi Jiangce0b1272018-02-28 04:39:23 +0000230 result = mon_info['dut'].measure_power(
231 mon_info['freq'],
232 mon_info['duration'],
233 tag=tag,
234 offset=mon_info['offset'])
Qi Jiang94b289a2018-03-01 03:50:15 +0000235 mon_info['dut'].reconnect_dut()
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000236 # Reconnect to dut
237 else:
238 mon_info['dut'].reconnect_dut()
239 # Reconnect and return measurement results if no error happens
Qi Jiang36a7c732018-02-25 05:05:40 +0000240 avg_current = result.average_current
241 monsoon.MonsoonData.save_to_text_file([result], data_path)
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000242 log.info(
243 "Power measurement done within {} try".format(retry_measure))
Qi Jiang36a7c732018-02-25 05:05:40 +0000244 return data_path, avg_current
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000245 # Catch monsoon errors during measurement
Qi Jiang94b289a2018-03-01 03:50:15 +0000246 except monsoon.MonsoonError:
Qi Jiang9c752562018-03-05 07:04:38 +0000247 log.info(mon_info['dut'].mon.ser.in_waiting)
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000248 # Break early if it's one count away from limit
249 if retry_measure == MEASUREMENT_RETRY_COUNT:
250 log.error('Test failed after maximum measurement retry')
251 break
252
253 log.warning('Monsoon error happened, now try to recover')
254 # Retry loop to recover monsoon from error
255 retry_monsoon = 1
256 while retry_monsoon <= RECOVER_MONSOON_RETRY_COUNT:
Qi Jiang9c752562018-03-05 07:04:38 +0000257 mon_status = monsoon_recover(mon_info['dut'])
Qi Jiangce0b1272018-02-28 04:39:23 +0000258 if mon_status:
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000259 break
Qi Jiangce0b1272018-02-28 04:39:23 +0000260 else:
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000261 retry_monsoon += 1
Qi Jiang6cc758e2018-03-08 01:18:44 +0000262 log.warning('Wait for {} second then try again'.format(
263 MONSOON_RETRY_INTERVAL))
Qi Jiang9c752562018-03-05 07:04:38 +0000264 time.sleep(MONSOON_RETRY_INTERVAL)
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000265
266 # Break the loop to end test if failed to recover monsoon
267 if not mon_status:
268 log.error('Tried our best, still failed to recover monsoon')
269 break
Qi Jiang36a7c732018-02-25 05:05:40 +0000270 else:
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000271 # If there is no data or captured samples are less than min
272 # required, re-take
273 if not result:
274 log.warning('No data taken, need to remeasure')
275 elif len(result._data_points) <= min_required_samples:
276 log.warning(
277 'More than {} percent of samples are missing due to monsoon error. Need to remeasure'.
278 format(100 - MIN_PERCENT_SAMPLE))
Qi Jiangce0b1272018-02-28 04:39:23 +0000279 else:
Qi Jianga5f9a0d2018-03-03 02:54:02 +0000280 need_collect_data = 0
281 log.warning(
282 'Data collected is valid, try reconnect to DUT to finish test'
283 )
284 retry_measure += 1
285
286 if retry_measure > MEASUREMENT_RETRY_COUNT:
287 log.error('Test failed after maximum measurement retry')
Qi Jiang73b25352017-08-02 19:56:31 +0000288
289
290def monsoon_data_plot(mon_info, file_path, tag=""):
291 """Plot the monsoon current data using bokeh interactive plotting tool.
292
293 Plotting power measurement data with bokeh to generate interactive plots.
294 You can do interactive data analysis on the plot after generating with the
295 provided widgets, which make the debugging much easier. To realize that,
296 bokeh callback java scripting is used. View a sample html output file:
297 https://drive.google.com/open?id=0Bwp8Cq841VnpT2dGUUxLYWZvVjA
298
299 Args:
300 mon_info: dict with information of monsoon measurement, including
301 monsoon device object, measurement frequency, duration and
302 offset etc.
303 file_path: the path to the monsoon log file with current data
304
305 Returns:
306 plot: the plotting object of bokeh, optional, will be needed if multiple
307 plots will be combined to one html file.
308 dt: the datatable object of bokeh, optional, will be needed if multiple
309 datatables will be combined to one html file.
310 """
311
312 log = logging.getLogger()
313 log.info("Plot the power measurement data")
314 #Get results as monsoon data object from the input file
315 results = monsoon.MonsoonData.from_text_file(file_path)
316 #Decouple current and timestamp data from the monsoon object
317 current_data = []
318 timestamps = []
319 voltage = results[0].voltage
320 [current_data.extend(x.data_points) for x in results]
321 [timestamps.extend(x.timestamps) for x in results]
322 period = 1 / float(mon_info['freq'])
323 time_relative = [x * period for x in range(len(current_data))]
324 #Calculate the average current for the test
325 current_data = [x * 1000 for x in current_data]
326 avg_current = sum(current_data) / len(current_data)
327 color = ['navy'] * len(current_data)
328
329 #Preparing the data and source link for bokehn java callback
Qi Jiang94b289a2018-03-01 03:50:15 +0000330 source = ColumnDataSource(
331 data=dict(x0=time_relative, y0=current_data, color=color))
332 s2 = ColumnDataSource(
333 data=dict(
334 z0=[mon_info['duration']],
335 y0=[round(avg_current, 2)],
336 x0=[round(avg_current * voltage, 2)],
337 z1=[round(avg_current * voltage * mon_info['duration'], 2)],
338 z2=[round(avg_current * mon_info['duration'], 2)]))
Qi Jiang73b25352017-08-02 19:56:31 +0000339 #Setting up data table for the output
340 columns = [
341 TableColumn(field='z0', title='Total Duration (s)'),
342 TableColumn(field='y0', title='Average Current (mA)'),
343 TableColumn(field='x0', title='Average Power (4.2v) (mW)'),
344 TableColumn(field='z1', title='Average Energy (mW*s)'),
345 TableColumn(field='z2', title='Normalized Average Energy (mA*s)')
346 ]
347 dt = DataTable(
348 source=s2, columns=columns, width=1300, height=60, editable=True)
349
350 plot_title = file_path[file_path.rfind('/') + 1:-4] + tag
351 output_file("%s/%s.html" % (mon_info['data_path'], plot_title))
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000352 TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
Qi Jiang73b25352017-08-02 19:56:31 +0000353 # Create a new plot with the datatable above
354 plot = figure(
355 plot_width=1300,
356 plot_height=700,
357 title=plot_title,
358 tools=TOOLS,
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000359 output_backend="webgl")
360 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
361 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
Qi Jiang73b25352017-08-02 19:56:31 +0000362 plot.line('x0', 'y0', source=source, line_width=2)
363 plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color')
364 plot.xaxis.axis_label = 'Time (s)'
365 plot.yaxis.axis_label = 'Current (mA)'
366 plot.title.text_font_size = {'value': '15pt'}
367
368 #Callback Java scripting
369 source.callback = CustomJS(
370 args=dict(mytable=dt),
371 code="""
372 var inds = cb_obj.get('selected')['1d'].indices;
373 var d1 = cb_obj.get('data');
374 var d2 = mytable.get('source').get('data');
375 ym = 0
376 ts = 0
377 d2['x0'] = []
378 d2['y0'] = []
379 d2['z1'] = []
380 d2['z2'] = []
381 d2['z0'] = []
382 min=max=d1['x0'][inds[0]]
383 if (inds.length==0) {return;}
384 for (i = 0; i < inds.length; i++) {
385 ym += d1['y0'][inds[i]]
386 d1['color'][inds[i]] = "red"
387 if (d1['x0'][inds[i]] < min) {
388 min = d1['x0'][inds[i]]}
389 if (d1['x0'][inds[i]] > max) {
390 max = d1['x0'][inds[i]]}
391 }
392 ym /= inds.length
393 ts = max - min
394 dx0 = Math.round(ym*4.2*100.0)/100.0
395 dy0 = Math.round(ym*100.0)/100.0
396 dz1 = Math.round(ym*4.2*ts*100.0)/100.0
397 dz2 = Math.round(ym*ts*100.0)/100.0
398 dz0 = Math.round(ts*1000.0)/1000.0
399 d2['z0'].push(dz0)
400 d2['x0'].push(dx0)
401 d2['y0'].push(dy0)
402 d2['z1'].push(dz1)
403 d2['z2'].push(dz2)
404 mytable.trigger('change');
405 """)
406
407 #Layout the plot and the datatable bar
408 l = layout([[dt], [plot]])
409 save(l)
410 return [plot, dt]
411
412
Qi Jiang43c80e12017-11-04 03:05:21 +0000413def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10):
Qi Jiang73b25352017-08-02 19:56:31 +0000414 """Function to change the DTIM setting in the phone.
415
416 Args:
417 ad: the target android device, AndroidDevice object
418 gEnableModulatedDTIM: Modulated DTIM, int
419 gMaxLIModulatedDTIM: Maximum modulated DTIM, int
420 """
Qi Jiang487e7582018-04-24 08:10:12 -0700421 # First trying to find the ini file with DTIM settings
422 ini_file_phone = ad.adb.shell('ls /vendor/firmware/wlan/*/*.ini')
423 ini_file_local = ini_file_phone.split('/')[-1]
424
425 # Pull the file and change the DTIM to desired value
426 ad.adb.pull('{} {}'.format(ini_file_phone, ini_file_local))
Qi Jiang73b25352017-08-02 19:56:31 +0000427
428 with open(ini_file_local, 'r') as fin:
429 for line in fin:
Qi Jiang487e7582018-04-24 08:10:12 -0700430 if ENABLED_MODULATED_DTIM in line:
431 gE_old = line.strip('\n')
432 gEDTIM_old = line.strip(ENABLED_MODULATED_DTIM).strip('\n')
433 if MAX_MODULATED_DTIM in line:
434 gM_old = line.strip('\n')
435 gMDTIM_old = line.strip(MAX_MODULATED_DTIM).strip('\n')
436 fin.close()
Qi Jiang43c80e12017-11-04 03:05:21 +0000437 if int(gEDTIM_old) == gEnableModulatedDTIM and int(
438 gMDTIM_old) == gMaxLIModulatedDTIM:
Qi Jiang73b25352017-08-02 19:56:31 +0000439 ad.log.info('Current DTIM is already the desired value,'
440 'no need to reset it')
441 return
442
Qi Jiang487e7582018-04-24 08:10:12 -0700443 gE_new = ENABLED_MODULATED_DTIM + str(gEnableModulatedDTIM)
444 gM_new = MAX_MODULATED_DTIM + str(gMaxLIModulatedDTIM)
Qi Jiang73b25352017-08-02 19:56:31 +0000445
Qi Jiang487e7582018-04-24 08:10:12 -0700446 sed_gE = 'sed -i \'s/{}/{}/g\' {}'.format(gE_old, gE_new, ini_file_local)
447 sed_gM = 'sed -i \'s/{}/{}/g\' {}'.format(gM_old, gM_new, ini_file_local)
448 job.run(sed_gE)
449 job.run(sed_gM)
Qi Jiang73b25352017-08-02 19:56:31 +0000450
Qi Jiang487e7582018-04-24 08:10:12 -0700451 # Push the file to the phone
452 push_file_to_phone(ad, ini_file_local, ini_file_phone)
453 ad.log.info('DTIM changes checked in and rebooting...')
454 ad.reboot()
455 ad.log.info('DTIM updated and device back from reboot')
456
457
458def push_file_to_phone(ad, file_local, file_phone):
459 """Function to push local file to android phone.
460
461 Args:
462 ad: the target android device
463 file_local: the locla file to push
464 file_phone: the file/directory on the phone to be pushed
465 """
466 ad.adb.root()
467 cmd_out = ad.adb.remount()
468 if 'Permission denied' in cmd_out:
Qi Jiang73b25352017-08-02 19:56:31 +0000469 ad.log.info('Need to disable verity first and reboot')
Qi Jiang487e7582018-04-24 08:10:12 -0700470 ad.adb.disable_verity()
Qi Jiang73b25352017-08-02 19:56:31 +0000471 time.sleep(1)
472 ad.reboot()
473 ad.log.info('Verity disabled and device back from reboot')
Qi Jiang487e7582018-04-24 08:10:12 -0700474 ad.adb.root()
475 ad.adb.remount()
Qi Jiang73b25352017-08-02 19:56:31 +0000476 time.sleep(1)
Qi Jiang487e7582018-04-24 08:10:12 -0700477 ad.adb.push('{} {}'.format(file_local, file_phone))
Qi Jiang73b25352017-08-02 19:56:31 +0000478
479
Qi Jiangdc073e52017-12-19 19:12:35 +0000480def ap_setup(ap, network, bandwidth=80):
Qi Jiang73b25352017-08-02 19:56:31 +0000481 """Set up the whirlwind AP with provided network info.
482
483 Args:
484 ap: access_point object of the AP
485 network: dict with information of the network, including ssid, password
486 bssid, channel etc.
Qi Jiangdc073e52017-12-19 19:12:35 +0000487 bandwidth: the operation bandwidth for the AP, default 80MHz
Qi Jiang7f6dc982018-02-24 01:29:29 +0000488 Returns:
489 brconfigs: the bridge interface configs
Qi Jiang73b25352017-08-02 19:56:31 +0000490 """
Qi Jiang73b25352017-08-02 19:56:31 +0000491 log = logging.getLogger()
492 bss_settings = []
493 ssid = network[wutils.WifiEnums.SSID_KEY]
Qi Jiangcaca55e2017-10-10 23:10:40 +0000494 if "password" in network.keys():
495 password = network["password"]
496 security = hostapd_security.Security(
497 security_mode="wpa", password=password)
498 else:
499 security = hostapd_security.Security(security_mode=None, password=None)
Qi Jiang73b25352017-08-02 19:56:31 +0000500 channel = network["channel"]
Qi Jiang73b25352017-08-02 19:56:31 +0000501 config = hostapd_ap_preset.create_ap_preset(
502 channel=channel,
503 ssid=ssid,
504 security=security,
505 bss_settings=bss_settings,
Qi Jiangdc073e52017-12-19 19:12:35 +0000506 vht_bandwidth=bandwidth,
507 profile_name='whirlwind',
508 iface_wlan_2g=ap.wlan_2g,
509 iface_wlan_5g=ap.wlan_5g)
Qi Jiang7f6dc982018-02-24 01:29:29 +0000510 config_bridge = ap.generate_bridge_configs(channel)
511 brconfigs = bi.BridgeInterfaceConfigs(config_bridge[0], config_bridge[1],
512 config_bridge[2])
513 ap.bridge.startup(brconfigs)
Qi Jiang73b25352017-08-02 19:56:31 +0000514 ap.start_ap(config)
515 log.info("AP started on channel {} with SSID {}".format(channel, ssid))
Qi Jiang7f6dc982018-02-24 01:29:29 +0000516 return brconfigs
Qi Jiang73b25352017-08-02 19:56:31 +0000517
518
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000519def bokeh_plot(data_sets,
520 legends,
521 fig_property,
522 shaded_region=None,
523 output_file_path=None):
Qi Jiang73b25352017-08-02 19:56:31 +0000524 """Plot bokeh figs.
525 Args:
526 data_sets: data sets including lists of x_data and lists of y_data
527 ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]]
528 legends: list of legend for each curve
529 fig_property: dict containing the plot property, including title,
530 lables, linewidth, circle size, etc.
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000531 shaded_region: optional dict containing data for plot shading
532 output_file_path: optional path at which to save figure
Qi Jiang73b25352017-08-02 19:56:31 +0000533 Returns:
534 plot: bokeh plot figure object
535 """
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000536 TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
Qi Jiang73b25352017-08-02 19:56:31 +0000537 plot = figure(
538 plot_width=1300,
539 plot_height=700,
540 title=fig_property['title'],
541 tools=TOOLS,
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000542 output_backend="webgl")
543 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
544 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
Qi Jiang73b25352017-08-02 19:56:31 +0000545 colors = [
546 'red', 'green', 'blue', 'olive', 'orange', 'salmon', 'black', 'navy',
547 'yellow', 'darkred', 'goldenrod'
548 ]
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000549 if shaded_region:
550 band_x = shaded_region["x_vector"]
551 band_x.extend(shaded_region["x_vector"][::-1])
552 band_y = shaded_region["lower_limit"]
553 band_y.extend(shaded_region["upper_limit"][::-1])
554 plot.patch(
555 band_x, band_y, color='#7570B3', line_alpha=0.1, fill_alpha=0.1)
556
Qi Jiang73b25352017-08-02 19:56:31 +0000557 for x_data, y_data, legend in zip(data_sets[0], data_sets[1], legends):
558 index_now = legends.index(legend)
559 color = colors[index_now % len(colors)]
560 plot.line(
561 x_data, y_data, legend=str(legend), line_width=3, color=color)
562 plot.circle(
563 x_data, y_data, size=10, legend=str(legend), fill_color=color)
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000564
Qi Jiang73b25352017-08-02 19:56:31 +0000565 #Plot properties
566 plot.xaxis.axis_label = fig_property['x_label']
567 plot.yaxis.axis_label = fig_property['y_label']
568 plot.legend.location = "top_right"
569 plot.legend.click_policy = "hide"
570 plot.title.text_font_size = {'value': '15pt'}
Omar El Ayachded64232017-12-08 00:53:12 +0000571 if output_file_path is not None:
572 output_file(output_file_path)
573 save(plot)
Qi Jiang73b25352017-08-02 19:56:31 +0000574 return plot
575
576
577def run_iperf_client_nonblocking(ad, server_host, extra_args=""):
578 """Start iperf client on the device with nohup.
579
580 Return status as true if iperf client start successfully.
581 And data flow information as results.
582
583 Args:
584 ad: the android device under test
585 server_host: Address of the iperf server.
586 extra_args: A string representing extra arguments for iperf client,
587 e.g. "-i 1 -t 30".
588
589 """
590 log = logging.getLogger()
591 ad.adb.shell_nb("nohup iperf3 -c {} {} &".format(server_host, extra_args))
592 log.info("IPerf client started")
593
594
595def get_wifi_rssi(ad):
596 """Get the RSSI of the device.
597
598 Args:
599 ad: the android device under test
600 Returns:
601 RSSI: the rssi level of the device
602 """
603 RSSI = ad.droid.wifiGetConnectionInfo()['rssi']
604 return RSSI
Qi Jiang50733382017-08-22 01:11:58 +0000605
606
607def get_phone_ip(ad):
608 """Get the WiFi IP address of the phone.
609
610 Args:
611 ad: the android device under test
612 Returns:
613 IP: IP address of the phone for WiFi, as a string
614 """
615 IP = ad.droid.connectivityGetIPv4Addresses('wlan0')[0]
616
617 return IP
618
619
620def get_phone_mac(ad):
621 """Get the WiFi MAC address of the phone.
622
623 Args:
624 ad: the android device under test
625 Returns:
626 mac: MAC address of the phone for WiFi, as a string
627 """
628 mac = ad.droid.wifiGetConnectionInfo()["mac_address"]
629
630 return mac
631
632
633def get_phone_ipv6(ad):
634 """Get the WiFi IPV6 address of the phone.
635
636 Args:
637 ad: the android device under test
638 Returns:
639 IPv6: IPv6 address of the phone for WiFi, as a string
640 """
641 IPv6 = ad.droid.connectivityGetLinkLocalIpv6Address('wlan0')[:-6]
642
643 return IPv6
Daniel Barrosfefd8c32017-08-04 15:36:40 -0700644
645
646def get_if_addr6(intf, address_type):
647 """Returns the Ipv6 address from a given local interface.
648
649 Returns the desired IPv6 address from the interface 'intf' in human
650 readable form. The address type is indicated by the IPv6 constants like
651 IPV6_ADDR_LINKLOCAL, IPV6_ADDR_GLOBAL, etc. If no address is found,
652 None is returned.
653
654 Args:
655 intf: desired interface name
656 address_type: addrees typle like LINKLOCAL or GLOBAL
657
658 Returns:
659 Ipv6 address of the specified interface in human readable format
660 """
661 for if_list in scapy.in6_getifaddr():
662 if if_list[2] == intf and if_list[1] == address_type:
663 return if_list[0]
664
665 return None
Daniel Barrosebbf91a2017-10-04 01:08:40 +0000666
667
Qi Jiangcaca55e2017-10-10 23:10:40 +0000668@utils.timeout(60)
669def wait_for_dhcp(intf):
670 """Wait the DHCP address assigned to desired interface.
671
672 Getting DHCP address takes time and the wait time isn't constant. Utilizing
673 utils.timeout to keep trying until success
674
675 Args:
676 intf: desired interface name
677 Returns:
678 ip: ip address of the desired interface name
679 Raise:
680 TimeoutError: After timeout, if no DHCP assigned, raise
681 """
682 log = logging.getLogger()
683 reset_host_interface(intf)
684 ip = '0.0.0.0'
685 while ip == '0.0.0.0':
686 ip = scapy.get_if_addr(intf)
Qi Jiang0ded53a2018-02-03 06:23:11 +0000687 log.info('DHCP address assigned to {} as {}'.format(intf, ip))
Qi Jiangcaca55e2017-10-10 23:10:40 +0000688 return ip
689
690
691def reset_host_interface(intf):
692 """Reset the host interface.
693
694 Args:
695 intf: the desired interface to reset
696 """
697 log = logging.getLogger()
698 intf_down_cmd = 'ifconfig %s down' % intf
699 intf_up_cmd = 'ifconfig %s up' % intf
700 try:
701 job.run(intf_down_cmd)
Qi Jiang0ded53a2018-02-03 06:23:11 +0000702 time.sleep(10)
Qi Jiangcaca55e2017-10-10 23:10:40 +0000703 job.run(intf_up_cmd)
Qi Jiangcaca55e2017-10-10 23:10:40 +0000704 log.info('{} has been reset'.format(intf))
705 except job.Error:
706 raise Exception('No such interface')
707
708
Daniel Barrosebbf91a2017-10-04 01:08:40 +0000709def create_pkt_config(test_class):
710 """Creates the config for generating multicast packets
711
712 Args:
713 test_class: object with all networking paramters
714
715 Returns:
716 Dictionary with the multicast packet config
717 """
718 addr_type = (scapy.IPV6_ADDR_LINKLOCAL
719 if test_class.ipv6_src_type == 'LINK_LOCAL' else
720 scapy.IPV6_ADDR_GLOBAL)
721
722 mac_dst = test_class.mac_dst
723 if GET_FROM_PHONE in test_class.mac_dst:
724 mac_dst = get_phone_mac(test_class.dut)
725
726 ipv4_dst = test_class.ipv4_dst
727 if GET_FROM_PHONE in test_class.ipv4_dst:
728 ipv4_dst = get_phone_ip(test_class.dut)
729
730 ipv6_dst = test_class.ipv6_dst
731 if GET_FROM_PHONE in test_class.ipv6_dst:
732 ipv6_dst = get_phone_ipv6(test_class.dut)
733
734 ipv4_gw = test_class.ipv4_gwt
735 if GET_FROM_AP in test_class.ipv4_gwt:
736 ipv4_gw = test_class.access_point.ssh_settings.hostname
737
738 pkt_gen_config = {
739 'interf': test_class.pkt_sender.interface,
740 'subnet_mask': test_class.sub_mask,
741 'src_mac': test_class.mac_src,
742 'dst_mac': mac_dst,
743 'src_ipv4': test_class.ipv4_src,
744 'dst_ipv4': ipv4_dst,
745 'src_ipv6': test_class.ipv6_src,
746 'src_ipv6_type': addr_type,
747 'dst_ipv6': ipv6_dst,
748 'gw_ipv4': ipv4_gw
749 }
750 return pkt_gen_config
751
752
753def create_monsoon_info(test_class):
754 """Creates the config dictionary for monsoon
755
756 Args:
757 test_class: object with all the parameters
758
759 Returns:
760 Dictionary with the monsoon packet config
761 """
762 mon_info = {
763 'dut': test_class.mon,
764 'freq': test_class.mon_freq,
765 'duration': test_class.mon_duration,
766 'offset': test_class.mon_offset,
767 'data_path': test_class.mon_data_path
768 }
769 return mon_info
Daniel Barros43760302017-12-06 04:16:03 +0000770
771
772def setup_phone_wireless(test_class,
773 bt_on,
774 wifi_on,
775 screen_status,
776 network=None,
777 regular_mode=False):
778 """Sets the phone in rock-bottom and in the desired wireless mode
779
780 Args:
781 test_class: the specific test class where test is running
782 bt_on: Enable/Disable BT
783 wifi_on: Enable/Disable WiFi
784 screen_status: screen ON or OFF
785 network: a dict of information for the WiFi network to connect
786 regular_mode: enable cellular data (i.e., disable airplane mode)
787 """
788 # Initialize the dut to rock-bottom state
789 dut_rockbottom(test_class.dut)
Qi Jiang93b2d5b2018-02-25 03:30:46 +0000790 brconfigs = None
Daniel Barros43760302017-12-06 04:16:03 +0000791 time.sleep(1)
792
793 if regular_mode:
794 test_class.dut.droid.connectivityToggleAirplaneMode(False)
795 utils.set_mobile_data_always_on(test_class.dut, True)
796 time.sleep(2)
797
798 # Turn ON/OFF BT
799 if bt_on == 'ON':
800 enable_bluetooth(test_class.dut.droid, test_class.dut.ed)
801 test_class.dut.log.info('BT is ON')
802 else:
803 disable_bluetooth(test_class.dut.droid)
804 test_class.dut.droid.bluetoothDisableBLE()
805 test_class.dut.log.info('BT is OFF')
806 time.sleep(2)
807
808 # Turn ON/OFF Wifi
809 if wifi_on == 'ON':
810 wutils.wifi_toggle_state(test_class.dut, True)
811 test_class.dut.log.info('WiFi is ON')
812 if network:
813 # Set attenuation and connect to AP
814 for attn in range(test_class.num_atten):
815 test_class.attenuators[attn].set_atten(
816 test_class.atten_level['zero_atten'][attn])
817 test_class.log.info('Set attenuation level to all zero')
Qi Jiang93b2d5b2018-02-25 03:30:46 +0000818 brconfigs = ap_setup(test_class.access_point, network)
Daniel Barros43760302017-12-06 04:16:03 +0000819 wutils.wifi_connect(test_class.dut, network)
820 else:
821 wutils.wifi_toggle_state(test_class.dut, False)
822 test_class.dut.log.info('WiFi is OFF')
823 time.sleep(1)
824
825 # Set the desired screen status
826 if screen_status == 'OFF':
827 test_class.dut.droid.goToSleepNow()
828 test_class.dut.log.info('Screen is OFF')
829 time.sleep(1)
Qi Jiang93b2d5b2018-02-25 03:30:46 +0000830
831 if brconfigs:
832 return brconfigs
833 else:
834 return None