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