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 19 20import its_session_utils 21import lighting_control_utils 22from mobly import asserts 23from mobly import base_test 24from mobly import utils 25from mobly.controllers import android_device 26 27 28ADAPTIVE_BRIGHTNESS_OFF = '0' 29TABLET_CMD_DELAY_SEC = 0.5 # found empirically 30TABLET_DIMMER_TIMEOUT_MS = 1800000 # this is max setting possible 31CTS_VERIFIER_PKG = 'com.android.cts.verifier' 32WAIT_TIME_SEC = 5 33SCROLLER_TIMEOUT_MS = 3000 34VALID_NUM_DEVICES = (1, 2) 35NOT_YET_MANDATED_ALL = 100 36FRONT_CAMERA_ID_PREFIX = '1' 37 38# Not yet mandated tests ['test', first_api_level not yet mandatory] 39# ie. ['test_test_patterns', 30] is MANDATED for first_api_level > 30 40NOT_YET_MANDATED = { 41 'scene0': [['test_test_patterns', 30], 42 ['test_tonemap_curve', 30]], 43 'scene1_1': [['test_ae_precapture_trigger', 28]], 44 'scene1_2': [], 45 'scene2_a': [['test_jpeg_quality', 30]], 46 'scene2_b': [['test_auto_per_frame_control', NOT_YET_MANDATED_ALL]], 47 'scene2_c': [], 48 'scene2_d': [['test_num_faces', 30]], 49 'scene2_e': [['test_num_faces', 30], ['test_continuous_picture', 30]], 50 'scene2_f': [['test_num_faces', 30]], 51 'scene3': [], 52 'scene4': [], 53 'scene5': [], 54 'scene6': [['test_zoom', 30]], 55 'sensor_fusion': [], 56 'scene_hdr': [], 57 'scene_night': [], 58} 59 60logging.getLogger('matplotlib.font_manager').disabled = True 61 62 63class ItsBaseTest(base_test.BaseTestClass): 64 """Base test for CameraITS tests. 65 66 Tests inherit from this class execute in the Camera ITS automation systems. 67 These systems consist of either: 68 1. a device under test (dut) and an external rotation controller 69 2. a device under test (dut) and one screen device(tablet) 70 3. a device under test (dut) and manual charts 71 72 Attributes: 73 dut: android_device.AndroidDevice, the device under test. 74 tablet: android_device.AndroidDevice, the tablet device used to display 75 scenes. 76 """ 77 78 def setup_class(self): 79 devices = self.register_controller(android_device, min_number=1) 80 self.dut = devices[0] 81 self.camera = str(self.user_params['camera']) 82 logging.debug('Camera_id: %s', self.camera) 83 if self.user_params.get('chart_distance'): 84 self.chart_distance = float(self.user_params['chart_distance']) 85 logging.debug('Chart distance: %s cm', self.chart_distance) 86 if (self.user_params.get('lighting_cntl') and 87 self.user_params.get('lighting_ch')): 88 self.lighting_cntl = self.user_params['lighting_cntl'] 89 self.lighting_ch = str(self.user_params['lighting_ch']) 90 else: 91 self.lighting_cntl = 'None' 92 self.lighting_ch = '1' 93 if self.user_params.get('tablet_device'): 94 self.tablet_device = self.user_params['tablet_device'] == 'True' 95 if self.user_params.get('debug_mode'): 96 self.debug_mode = self.user_params['debug_mode'] == 'True' 97 if self.user_params.get('scene'): 98 self.scene = self.user_params['scene'] 99 camera_id_combo = self.parse_hidden_camera_id() 100 self.camera_id = camera_id_combo[0] 101 if len(camera_id_combo) == 2: 102 self.hidden_physical_id = camera_id_combo[1] 103 else: 104 self.hidden_physical_id = None 105 106 num_devices = len(devices) 107 if num_devices == 2: # scenes [0,1,2,3,4,5,6] 108 try: 109 self.tablet = devices[1] 110 self.tablet_screen_brightness = self.user_params['brightness'] 111 tablet_name_unencoded = self.tablet.adb.shell( 112 ['getprop', 'ro.build.product'] 113 ) 114 tablet_name = str(tablet_name_unencoded.decode('utf-8')).strip() 115 logging.debug('tablet name: %s', tablet_name) 116 its_session_utils.validate_tablet_brightness( 117 tablet_name, self.tablet_screen_brightness) 118 except KeyError: 119 logging.debug('Not all tablet arguments set.') 120 else: # sensor_fusion or manual run 121 try: 122 self.fps = int(self.user_params['fps']) 123 img_size = self.user_params['img_size'].split(',') 124 self.img_w = int(img_size[0]) 125 self.img_h = int(img_size[1]) 126 self.test_length = float(self.user_params['test_length']) 127 self.rotator_cntl = self.user_params['rotator_cntl'] 128 self.rotator_ch = str(self.user_params['rotator_ch']) 129 except KeyError: 130 self.tablet = None 131 logging.debug('Not all arguments set. Manual run.') 132 133 self._setup_devices(num_devices) 134 135 arduino_serial_port = lighting_control_utils.lighting_control( 136 self.lighting_cntl, self.lighting_ch) 137 if arduino_serial_port and self.scene != 'scene0': 138 lighting_control_utils.set_light_brightness( 139 self.lighting_ch, 255, arduino_serial_port) 140 logging.debug('Light is turned ON.') 141 142 # Check if current foldable state matches scene, if applicable 143 if self.user_params.get('foldable_device', 'False') == 'True': 144 foldable_state_unencoded = tablet_name_unencoded = self.dut.adb.shell( 145 ['cmd', 'device_state', 'state'] 146 ) 147 foldable_state = str(foldable_state_unencoded.decode('utf-8')).strip() 148 is_folded = 'CLOSE' in foldable_state 149 scene_with_suffix = self.user_params.get('scene_with_suffix') 150 if scene_with_suffix: 151 if 'folded' in scene_with_suffix and not is_folded: 152 raise AssertionError( 153 f'Testing folded scene {scene_with_suffix} with unfolded device!') 154 if ('folded' not in scene_with_suffix and is_folded and 155 self.camera.startswith(FRONT_CAMERA_ID_PREFIX)): # Not rear camera 156 raise AssertionError( 157 f'Testing unfolded scene {scene_with_suffix} with a ' 158 'non-rear camera while device is folded!' 159 ) 160 else: 161 logging.debug('Testing without `run_all_tests`') 162 163 def _setup_devices(self, num): 164 """Sets up each device in parallel if more than one device.""" 165 if num not in VALID_NUM_DEVICES: 166 raise AssertionError( 167 f'Incorrect number of devices! Must be in {str(VALID_NUM_DEVICES)}') 168 if num == 1: 169 self.setup_dut(self.dut) 170 else: 171 logic = lambda d: self.setup_dut(d) if d else self.setup_tablet() 172 utils.concurrent_exec( 173 logic, [(self.dut,), (None,)], 174 max_workers=2, 175 raise_on_exception=True) 176 177 def setup_dut(self, device): 178 self.dut.adb.shell( 179 'am start -n com.android.cts.verifier/.CtsVerifierActivity') 180 logging.debug('Setting up device: %s', str(device)) 181 # Wait for the app screen to appear. 182 time.sleep(WAIT_TIME_SEC) 183 184 def setup_tablet(self): 185 # KEYCODE_POWER to reset dimmer timer. KEYCODE_WAKEUP no effect if ON. 186 self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_POWER']) 187 time.sleep(TABLET_CMD_DELAY_SEC) 188 self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_WAKEUP']) 189 time.sleep(TABLET_CMD_DELAY_SEC) 190 # Dismiss keyguard 191 self.tablet.adb.shell(['wm', 'dismiss-keyguard']) 192 time.sleep(TABLET_CMD_DELAY_SEC) 193 # Turn off the adaptive brightness on tablet. 194 self.tablet.adb.shell( 195 ['settings', 'put', 'system', 'screen_brightness_mode', 196 ADAPTIVE_BRIGHTNESS_OFF]) 197 # Set the screen brightness 198 self.tablet.adb.shell( 199 ['settings', 'put', 'system', 'screen_brightness', 200 str(self.tablet_screen_brightness)]) 201 logging.debug('Tablet brightness set to: %s', 202 format(self.tablet_screen_brightness)) 203 self.tablet.adb.shell('settings put system screen_off_timeout {}'.format( 204 TABLET_DIMMER_TIMEOUT_MS)) 205 self.set_tablet_landscape_orientation() 206 self.tablet.adb.shell('am force-stop com.google.android.apps.docs') 207 self.tablet.adb.shell('am force-stop com.google.android.apps.photos') 208 self.tablet.adb.shell('am force-stop com.android.gallery3d') 209 self.tablet.adb.shell('am force-stop com.sec.android.gallery3d') 210 self.tablet.adb.shell('am force-stop com.miui.gallery') 211 self.tablet.adb.shell( 212 'settings put global policy_control immersive.full=*') 213 214 def set_tablet_landscape_orientation(self): 215 """Sets the screen orientation to landscape. 216 """ 217 # Get the landscape orientation value. 218 # This value is different for Pixel C/Huawei/Samsung tablets. 219 output = self.tablet.adb.shell('dumpsys window | grep mLandscapeRotation') 220 logging.debug('dumpsys window output: %s', output.decode('utf-8').strip()) 221 output_list = str(output.decode('utf-8')).strip().split(' ') 222 for val in output_list: 223 if 'LandscapeRotation' in val: 224 landscape_val = str(val.split('=')[-1]) 225 # For some tablets the values are in constant forms such as ROTATION_90 226 if 'ROTATION_90' in landscape_val: 227 landscape_val = '1' 228 elif 'ROTATION_0' in landscape_val: 229 landscape_val = '0' 230 logging.debug('Changing the orientation to landscape mode.') 231 self.tablet.adb.shell(['settings', 'put', 'system', 'user_rotation', 232 landscape_val]) 233 break 234 logging.debug('Reported tablet orientation is: %d', 235 int(self.tablet.adb.shell( 236 'settings get system user_rotation'))) 237 238 def set_screen_brightness(self, brightness_level): 239 """Sets the screen brightness to desired level. 240 241 Args: 242 brightness_level : brightness level to set. 243 """ 244 # Turn off the adaptive brightness on tablet. 245 self.tablet.adb.shell( 246 ['settings', 'put', 'system', 'screen_brightness_mode', '0']) 247 # Set the screen brightness 248 self.tablet.adb.shell([ 249 'settings', 'put', 'system', 'screen_brightness', 250 brightness_level 251 ]) 252 logging.debug('Tablet brightness set to: %s', brightness_level) 253 actual_brightness = self.tablet.adb.shell( 254 'settings get system screen_brightness') 255 if int(actual_brightness) != int(brightness_level): 256 raise AssertionError('Brightness was not set as expected! ' 257 'Requested brightness: {brightness_level}, ' 258 'Actual brightness: {actual_brightness}') 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 determine_not_yet_mandated_tests(self, device_id, scene): 270 """Determine not_yet_mandated tests from NOT_YET_MANDATED list & phone info. 271 272 Args: 273 device_id: string of device id number. 274 scene: scene to which tests belong to. 275 276 Returns: 277 dict of not yet mandated tests 278 """ 279 # Initialize not_yet_mandated. 280 not_yet_mandated = {} 281 not_yet_mandated[scene] = [] 282 283 # Determine first API level for device. 284 first_api_level = its_session_utils.get_first_api_level(device_id) 285 286 # Determine which test are not yet mandated for first api level. 287 tests = NOT_YET_MANDATED[scene] 288 for [test, first_api_level_not_mandated] in tests: 289 logging.debug('First API level %s NOT MANDATED: %d', 290 test, first_api_level_not_mandated) 291 if first_api_level <= first_api_level_not_mandated: 292 not_yet_mandated[scene].append(test) 293 return not_yet_mandated 294 295 def on_pass(self, record): 296 logging.debug('%s on PASS.', record.test_name) 297 298 def on_fail(self, record): 299 logging.debug('%s on FAIL.', record.test_name) 300 if self.user_params.get('scene'): 301 not_yet_mandated_tests = self.determine_not_yet_mandated_tests( 302 self.dut.serial, self.scene) 303 if self.current_test_info.name in not_yet_mandated_tests[self.scene]: 304 logging.debug('%s is not yet mandated.', self.current_test_info.name) 305 asserts.fail('Not yet mandated test', extras='Not yet mandated test') 306 307 def teardown_class(self): 308 # edit root_output_path and summary_writer path 309 # to add test name to output directory 310 logging.debug('summary_writer._path: %s', self.summary_writer._path) 311 summary_head, summary_tail = os.path.split(self.summary_writer._path) 312 self.summary_writer._path = os.path.join( 313 f'{summary_head}_{self.__class__.__name__}', summary_tail) 314 os.rename(self.root_output_path, 315 f'{self.root_output_path}_{self.__class__.__name__}') 316 # print root_output_path so that it can be written to report log. 317 # Note: Do not replace print with logging.debug here. 318 print('root_output_path:', 319 f'{self.root_output_path}_{self.__class__.__name__}') 320