• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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