1# Copyright 2020 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15 16import logging 17import os 18import time 19import cv2 20 21import its_session_utils 22import lighting_control_utils 23from mobly import base_test 24from mobly import utils 25from mobly.controllers import android_device 26from snippet_uiautomator import uiautomator 27 28 29ADAPTIVE_BRIGHTNESS_OFF = '0' 30TABLET_CMD_DELAY_SEC = 0.5 # found empirically 31TABLET_DIMMER_TIMEOUT_MS = 1800000 # this is max setting possible 32CTS_VERIFIER_PKG = 'com.android.cts.verifier' 33WAIT_TIME_SEC = 5 34SCROLLER_TIMEOUT_MS = 3000 35VALID_NUM_DEVICES = (1, 2) 36FRONT_CAMERA_ID_PREFIX = '1' 37 38logging.getLogger('matplotlib.font_manager').disabled = True 39 40 41class ItsBaseTest(base_test.BaseTestClass): 42 """Base test for CameraITS tests. 43 44 Tests inherit from this class execute in the Camera ITS automation systems. 45 These systems consist of either: 46 1. a device under test (dut) and an external rotation controller 47 2. a device under test (dut) and one screen device(tablet) 48 3. a device under test (dut) and manual charts 49 50 Attributes: 51 dut: android_device.AndroidDevice, the device under test. 52 tablet: android_device.AndroidDevice, the tablet device used to display 53 scenes. 54 """ 55 56 def setup_class(self): 57 devices = self.register_controller(android_device, min_number=1) 58 self.dut = devices[0] 59 self.camera = str(self.user_params['camera']) 60 logging.debug('Camera_id: %s', self.camera) 61 if self.user_params.get('chart_distance'): 62 self.chart_distance = float(self.user_params['chart_distance']) 63 logging.debug('Chart distance: %s cm', self.chart_distance) 64 if (self.user_params.get('lighting_cntl') and 65 self.user_params.get('lighting_ch')): 66 self.lighting_cntl = self.user_params['lighting_cntl'] 67 self.lighting_ch = str(self.user_params['lighting_ch']) 68 else: 69 self.lighting_cntl = 'None' 70 self.lighting_ch = '1' 71 if (self.user_params.get('rotator_cntl') and 72 str(self.user_params.get('rotator_ch'))): 73 self.rotator_cntl = str(self.user_params['rotator_cntl']) 74 self.rotator_ch = str(self.user_params['rotator_ch']) 75 if self.user_params.get('tablet_device'): 76 self.tablet_device = self.user_params['tablet_device'] == 'True' 77 if self.user_params.get('debug_mode'): 78 self.debug_mode = self.user_params['debug_mode'] == 'True' 79 if self.user_params.get('scene'): 80 self.scene = self.user_params['scene'] 81 self.parallel_execution = ( 82 self.user_params.get('parallel_execution') == 'True' 83 ) 84 camera_id_combo = self.parse_hidden_camera_id() 85 self.camera_id = camera_id_combo[0] 86 if len(camera_id_combo) == 2: 87 self.hidden_physical_id = camera_id_combo[1] 88 else: 89 self.hidden_physical_id = None 90 91 num_devices = len(devices) 92 if num_devices == 2: # scenes [0,1,2,3,4,5,6] 93 try: 94 self.tablet = devices[1] 95 self.tablet_screen_brightness = self.user_params['brightness'] 96 tablet_name_unencoded = self.tablet.adb.shell( 97 ['getprop', 'ro.product.device'] 98 ) 99 tablet_name = str(tablet_name_unencoded.decode('utf-8')).strip() 100 logging.debug('tablet name: %s', tablet_name) 101 its_session_utils.validate_tablet( 102 tablet_name, self.tablet_screen_brightness, 103 self.tablet.serial) 104 except KeyError: 105 logging.debug('Not all tablet arguments set.') 106 else: # sensor_fusion or manual run 107 try: 108 self.fps = int(self.user_params['fps']) 109 img_size = self.user_params['img_size'].split(',') 110 self.img_w = int(img_size[0]) 111 self.img_h = int(img_size[1]) 112 self.test_length = float(self.user_params['test_length']) 113 self.rotator_cntl = str(self.user_params['rotator_cntl']) 114 self.rotator_ch = str(self.user_params['rotator_ch']) 115 except KeyError: 116 self.tablet = None 117 if self.scene == 'scene_ip': 118 logging.debug('Gen2 rig scene test run.') 119 else: 120 logging.debug('Not all arguments set. Manual run.') 121 122 self._setup_devices(num_devices) 123 124 if self.scene != 'scene_ip': 125 arduino_serial_port = lighting_control_utils.lighting_control( 126 self.lighting_cntl, self.lighting_ch) 127 if arduino_serial_port and self.scene != 'scene0': 128 lighting_control_utils.set_light_brightness( 129 self.lighting_ch, 255, arduino_serial_port) 130 logging.debug('Light is turned ON.') 131 132 # Check if current foldable state matches scene, if applicable 133 if self.user_params.get('foldable_device', 'False') == 'True': 134 foldable_state_unencoded = self.dut.adb.shell( 135 ['cmd', 'device_state', 'state'] 136 ) 137 foldable_state = str(foldable_state_unencoded.decode('utf-8')).strip() 138 is_folded = 'CLOSE' in foldable_state 139 scene_with_suffix = self.user_params.get('scene_with_suffix') 140 if scene_with_suffix: 141 if 'folded' in scene_with_suffix and not is_folded: 142 raise AssertionError( 143 f'Testing folded scene {scene_with_suffix} with unfolded device!') 144 if ('folded' not in scene_with_suffix and is_folded and 145 self.camera.startswith(FRONT_CAMERA_ID_PREFIX)): # Not rear camera 146 raise AssertionError( 147 f'Testing unfolded scene {scene_with_suffix} with a ' 148 'non-rear camera while device is folded!' 149 ) 150 else: 151 logging.debug('Testing without `run_all_tests`') 152 153 cv2_version = cv2.__version__ 154 logging.debug('cv2_version: %s', cv2_version) 155 156 def _setup_devices(self, num): 157 """Sets up each device in parallel if more than one device.""" 158 if num not in VALID_NUM_DEVICES: 159 raise AssertionError( 160 f'Incorrect number of devices! Must be in {str(VALID_NUM_DEVICES)}') 161 if num == 1: 162 self.setup_dut(self.dut) 163 else: 164 logic = lambda d: self.setup_dut(d) if d else self.setup_tablet() 165 utils.concurrent_exec( 166 logic, [(self.dut,), (None,)], 167 max_workers=2, 168 raise_on_exception=True) 169 170 def setup_dut(self, device): 171 self.dut.adb.shell( 172 'am start -n com.android.cts.verifier/.CtsVerifierActivity') 173 logging.debug('Setting up device: %s', str(device)) 174 # Wait for the app screen to appear. 175 time.sleep(WAIT_TIME_SEC) 176 177 def setup_tablet(self): 178 # KEYCODE_POWER to reset dimmer timer. KEYCODE_WAKEUP no effect if ON. 179 self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_POWER']) 180 time.sleep(TABLET_CMD_DELAY_SEC) 181 self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_WAKEUP']) 182 time.sleep(TABLET_CMD_DELAY_SEC) 183 # Dismiss keyguard 184 self.tablet.adb.shell(['wm', 'dismiss-keyguard']) 185 time.sleep(TABLET_CMD_DELAY_SEC) 186 # Turn off the adaptive brightness on tablet. 187 self.tablet.adb.shell( 188 ['settings', 'put', 'system', 'screen_brightness_mode', 189 ADAPTIVE_BRIGHTNESS_OFF]) 190 # Set the screen brightness 191 self.tablet.adb.shell( 192 ['settings', 'put', 'system', 'screen_brightness', 193 str(self.tablet_screen_brightness)]) 194 logging.debug('Tablet brightness set to: %s', 195 format(self.tablet_screen_brightness)) 196 self.tablet.adb.shell('settings put system screen_off_timeout {}'.format( 197 TABLET_DIMMER_TIMEOUT_MS)) 198 self.set_tablet_landscape_orientation() 199 self.tablet.adb.shell('am force-stop com.google.android.apps.docs') 200 self.tablet.adb.shell('am force-stop com.google.android.apps.photos') 201 self.tablet.adb.shell('am force-stop com.android.gallery3d') 202 self.tablet.adb.shell('am force-stop com.sec.android.gallery3d') 203 self.tablet.adb.shell('am force-stop com.miui.gallery') 204 self.tablet.adb.shell( 205 'settings put global policy_control immersive.full=*') 206 207 def set_tablet_landscape_orientation(self): 208 """Sets the screen orientation to landscape. 209 """ 210 # Get the landscape orientation value. 211 # This value is different for Pixel C/Huawei/Samsung tablets. 212 output = self.tablet.adb.shell('dumpsys window | grep mLandscapeRotation') 213 logging.debug('dumpsys window output: %s', output.decode('utf-8').strip()) 214 output_list = str(output.decode('utf-8')).strip().split(' ') 215 for val in output_list: 216 if 'LandscapeRotation' in val: 217 landscape_val = str(val.split('=')[-1]) 218 # For some tablets the values are in constant forms such as ROTATION_90 219 if 'ROTATION_90' in landscape_val: 220 landscape_val = '1' 221 elif 'ROTATION_0' in landscape_val: 222 landscape_val = '0' 223 logging.debug('Changing the orientation to landscape mode.') 224 self.tablet.adb.shell(['settings', 'put', 'system', 'user_rotation', 225 landscape_val]) 226 break 227 logging.debug('Reported tablet orientation is: %d', 228 int(self.tablet.adb.shell( 229 'settings get system user_rotation'))) 230 231 def set_screen_brightness(self, brightness_level): 232 """Sets the screen brightness to desired level. 233 234 Args: 235 brightness_level : brightness level to set. 236 """ 237 # Turn off the adaptive brightness on tablet. 238 self.tablet.adb.shell( 239 ['settings', 'put', 'system', 'screen_brightness_mode', '0']) 240 # Set the screen brightness 241 self.tablet.adb.shell([ 242 'settings', 'put', 'system', 'screen_brightness', 243 brightness_level 244 ]) 245 logging.debug('Tablet brightness set to: %s', brightness_level) 246 actual_brightness = self.tablet.adb.shell( 247 'settings get system screen_brightness') 248 if int(actual_brightness) != int(brightness_level): 249 raise AssertionError('Brightness was not set as expected! ' 250 f'Requested brightness: {brightness_level}, ' 251 f'Actual brightness: {actual_brightness}') 252 253 def turn_off_tablet(self): 254 """Turns off tablet, raising AssertionError if tablet is not found.""" 255 if self.tablet: 256 lighting_control_utils.turn_off_device_screen(self.tablet) 257 else: 258 raise AssertionError('Test must be run with tablet.') 259 260 def parse_hidden_camera_id(self): 261 """Parse the string of camera ID into an array. 262 263 Returns: 264 Array with camera id and hidden_physical camera id. 265 """ 266 camera_id_combo = self.camera.split(its_session_utils.SUB_CAMERA_SEPARATOR) 267 return camera_id_combo 268 269 def on_pass(self, record): 270 logging.debug('%s on PASS.', record.test_name) 271 272 def on_fail(self, record): 273 logging.debug('%s on FAIL.', record.test_name) 274 275 def teardown_class(self): 276 # edit root_output_path and summary_writer path 277 # to add test name to output directory 278 logging.debug('summary_writer._path: %s', self.summary_writer._path) 279 summary_head, summary_tail = os.path.split(self.summary_writer._path) 280 self.summary_writer._path = os.path.join( 281 f'{summary_head}_{self.__class__.__name__}', summary_tail) 282 os.rename(self.root_output_path, 283 f'{self.root_output_path}_{self.__class__.__name__}') 284 # print root_output_path so that it can be written to report log. 285 # Note: Do not replace print with logging.debug here. 286 print('root_output_path:', 287 f'{self.root_output_path}_{self.__class__.__name__}') 288 289 290class UiAutomatorItsBaseTest(ItsBaseTest): 291 def setup_class(self): 292 super().setup_class() 293 self.ui_app = None 294 self.dut.services.register( 295 uiautomator.ANDROID_SERVICE_NAME, uiautomator.UiAutomatorService 296 ) 297 298 def setup_test(self): 299 super().setup_test() 300 if not self.ui_app: 301 raise AssertionError( 302 'UiAutomator ITS tests must specify an app for UI interaction!') 303 its_session_utils.check_apk_installed(self.dut.serial, self.ui_app) 304