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('tablet_device'): 72 self.tablet_device = self.user_params['tablet_device'] == 'True' 73 if self.user_params.get('debug_mode'): 74 self.debug_mode = self.user_params['debug_mode'] == 'True' 75 if self.user_params.get('scene'): 76 self.scene = self.user_params['scene'] 77 if self.user_params.get('log_feature_combo_support'): 78 self.log_feature_combo_support = ( 79 self.user_params['log_feature_combo_support'] == 'True' 80 ) 81 camera_id_combo = self.parse_hidden_camera_id() 82 self.camera_id = camera_id_combo[0] 83 if len(camera_id_combo) == 2: 84 self.hidden_physical_id = camera_id_combo[1] 85 else: 86 self.hidden_physical_id = None 87 88 num_devices = len(devices) 89 if num_devices == 2: # scenes [0,1,2,3,4,5,6] 90 try: 91 self.tablet = devices[1] 92 self.tablet_screen_brightness = self.user_params['brightness'] 93 tablet_name_unencoded = self.tablet.adb.shell( 94 ['getprop', 'ro.product.device'] 95 ) 96 tablet_name = str(tablet_name_unencoded.decode('utf-8')).strip() 97 logging.debug('tablet name: %s', tablet_name) 98 its_session_utils.validate_tablet( 99 tablet_name, self.tablet_screen_brightness, 100 self.tablet.serial) 101 except KeyError: 102 logging.debug('Not all tablet arguments set.') 103 else: # sensor_fusion or manual run 104 try: 105 self.fps = int(self.user_params['fps']) 106 img_size = self.user_params['img_size'].split(',') 107 self.img_w = int(img_size[0]) 108 self.img_h = int(img_size[1]) 109 self.test_length = float(self.user_params['test_length']) 110 self.rotator_cntl = self.user_params['rotator_cntl'] 111 self.rotator_ch = str(self.user_params['rotator_ch']) 112 except KeyError: 113 self.tablet = None 114 logging.debug('Not all arguments set. Manual run.') 115 116 self._setup_devices(num_devices) 117 118 arduino_serial_port = lighting_control_utils.lighting_control( 119 self.lighting_cntl, self.lighting_ch) 120 if arduino_serial_port and self.scene != 'scene0': 121 lighting_control_utils.set_light_brightness( 122 self.lighting_ch, 255, arduino_serial_port) 123 logging.debug('Light is turned ON.') 124 125 # Check if current foldable state matches scene, if applicable 126 if self.user_params.get('foldable_device', 'False') == 'True': 127 foldable_state_unencoded = self.dut.adb.shell( 128 ['cmd', 'device_state', 'state'] 129 ) 130 foldable_state = str(foldable_state_unencoded.decode('utf-8')).strip() 131 is_folded = 'CLOSE' in foldable_state 132 scene_with_suffix = self.user_params.get('scene_with_suffix') 133 if scene_with_suffix: 134 if 'folded' in scene_with_suffix and not is_folded: 135 raise AssertionError( 136 f'Testing folded scene {scene_with_suffix} with unfolded device!') 137 if ('folded' not in scene_with_suffix and is_folded and 138 self.camera.startswith(FRONT_CAMERA_ID_PREFIX)): # Not rear camera 139 raise AssertionError( 140 f'Testing unfolded scene {scene_with_suffix} with a ' 141 'non-rear camera while device is folded!' 142 ) 143 else: 144 logging.debug('Testing without `run_all_tests`') 145 146 cv2_version = cv2.__version__ 147 logging.debug('cv2_version: %s', cv2_version) 148 149 def _setup_devices(self, num): 150 """Sets up each device in parallel if more than one device.""" 151 if num not in VALID_NUM_DEVICES: 152 raise AssertionError( 153 f'Incorrect number of devices! Must be in {str(VALID_NUM_DEVICES)}') 154 if num == 1: 155 self.setup_dut(self.dut) 156 else: 157 logic = lambda d: self.setup_dut(d) if d else self.setup_tablet() 158 utils.concurrent_exec( 159 logic, [(self.dut,), (None,)], 160 max_workers=2, 161 raise_on_exception=True) 162 163 def setup_dut(self, device): 164 self.dut.adb.shell( 165 'am start -n com.android.cts.verifier/.CtsVerifierActivity') 166 logging.debug('Setting up device: %s', str(device)) 167 # Wait for the app screen to appear. 168 time.sleep(WAIT_TIME_SEC) 169 170 def setup_tablet(self): 171 # KEYCODE_POWER to reset dimmer timer. KEYCODE_WAKEUP no effect if ON. 172 self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_POWER']) 173 time.sleep(TABLET_CMD_DELAY_SEC) 174 self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_WAKEUP']) 175 time.sleep(TABLET_CMD_DELAY_SEC) 176 # Dismiss keyguard 177 self.tablet.adb.shell(['wm', 'dismiss-keyguard']) 178 time.sleep(TABLET_CMD_DELAY_SEC) 179 # Turn off the adaptive brightness on tablet. 180 self.tablet.adb.shell( 181 ['settings', 'put', 'system', 'screen_brightness_mode', 182 ADAPTIVE_BRIGHTNESS_OFF]) 183 # Set the screen brightness 184 self.tablet.adb.shell( 185 ['settings', 'put', 'system', 'screen_brightness', 186 str(self.tablet_screen_brightness)]) 187 logging.debug('Tablet brightness set to: %s', 188 format(self.tablet_screen_brightness)) 189 self.tablet.adb.shell('settings put system screen_off_timeout {}'.format( 190 TABLET_DIMMER_TIMEOUT_MS)) 191 self.set_tablet_landscape_orientation() 192 self.tablet.adb.shell('am force-stop com.google.android.apps.docs') 193 self.tablet.adb.shell('am force-stop com.google.android.apps.photos') 194 self.tablet.adb.shell('am force-stop com.android.gallery3d') 195 self.tablet.adb.shell('am force-stop com.sec.android.gallery3d') 196 self.tablet.adb.shell('am force-stop com.miui.gallery') 197 self.tablet.adb.shell( 198 'settings put global policy_control immersive.full=*') 199 200 def set_tablet_landscape_orientation(self): 201 """Sets the screen orientation to landscape. 202 """ 203 # Get the landscape orientation value. 204 # This value is different for Pixel C/Huawei/Samsung tablets. 205 output = self.tablet.adb.shell('dumpsys window | grep mLandscapeRotation') 206 logging.debug('dumpsys window output: %s', output.decode('utf-8').strip()) 207 output_list = str(output.decode('utf-8')).strip().split(' ') 208 for val in output_list: 209 if 'LandscapeRotation' in val: 210 landscape_val = str(val.split('=')[-1]) 211 # For some tablets the values are in constant forms such as ROTATION_90 212 if 'ROTATION_90' in landscape_val: 213 landscape_val = '1' 214 elif 'ROTATION_0' in landscape_val: 215 landscape_val = '0' 216 logging.debug('Changing the orientation to landscape mode.') 217 self.tablet.adb.shell(['settings', 'put', 'system', 'user_rotation', 218 landscape_val]) 219 break 220 logging.debug('Reported tablet orientation is: %d', 221 int(self.tablet.adb.shell( 222 'settings get system user_rotation'))) 223 224 def set_screen_brightness(self, brightness_level): 225 """Sets the screen brightness to desired level. 226 227 Args: 228 brightness_level : brightness level to set. 229 """ 230 # Turn off the adaptive brightness on tablet. 231 self.tablet.adb.shell( 232 ['settings', 'put', 'system', 'screen_brightness_mode', '0']) 233 # Set the screen brightness 234 self.tablet.adb.shell([ 235 'settings', 'put', 'system', 'screen_brightness', 236 brightness_level 237 ]) 238 logging.debug('Tablet brightness set to: %s', brightness_level) 239 actual_brightness = self.tablet.adb.shell( 240 'settings get system screen_brightness') 241 if int(actual_brightness) != int(brightness_level): 242 raise AssertionError('Brightness was not set as expected! ' 243 f'Requested brightness: {brightness_level}, ' 244 f'Actual brightness: {actual_brightness}') 245 246 def turn_off_tablet(self): 247 """Turns off tablet, raising AssertionError if tablet is not found.""" 248 if self.tablet: 249 lighting_control_utils.turn_off_device_screen(self.tablet) 250 else: 251 raise AssertionError('Test must be run with tablet.') 252 253 def parse_hidden_camera_id(self): 254 """Parse the string of camera ID into an array. 255 256 Returns: 257 Array with camera id and hidden_physical camera id. 258 """ 259 camera_id_combo = self.camera.split(its_session_utils.SUB_CAMERA_SEPARATOR) 260 return camera_id_combo 261 262 def on_pass(self, record): 263 logging.debug('%s on PASS.', record.test_name) 264 265 def on_fail(self, record): 266 logging.debug('%s on FAIL.', record.test_name) 267 268 def teardown_class(self): 269 # edit root_output_path and summary_writer path 270 # to add test name to output directory 271 logging.debug('summary_writer._path: %s', self.summary_writer._path) 272 summary_head, summary_tail = os.path.split(self.summary_writer._path) 273 self.summary_writer._path = os.path.join( 274 f'{summary_head}_{self.__class__.__name__}', summary_tail) 275 os.rename(self.root_output_path, 276 f'{self.root_output_path}_{self.__class__.__name__}') 277 # print root_output_path so that it can be written to report log. 278 # Note: Do not replace print with logging.debug here. 279 print('root_output_path:', 280 f'{self.root_output_path}_{self.__class__.__name__}') 281 282 283class UiAutomatorItsBaseTest(ItsBaseTest): 284 def setup_class(self): 285 super().setup_class() 286 self.ui_app = None 287 self.dut.services.register( 288 uiautomator.ANDROID_SERVICE_NAME, uiautomator.UiAutomatorService 289 ) 290 291 def setup_test(self): 292 super().setup_test() 293 if not self.ui_app: 294 raise AssertionError( 295 'UiAutomator ITS tests must specify an app for UI interaction!') 296 its_session_utils.check_apk_installed(self.dut.serial, self.ui_app) 297