1 # Copyright 2016 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Resource manager to access the ARC-related functionality.""" 6 7 import logging 8 import os 9 import pipes 10 import time 11 12 from autotest_lib.client.bin import utils 13 from autotest_lib.client.common_lib import error 14 from autotest_lib.client.common_lib.cros import arc 15 from autotest_lib.client.cros.multimedia import arc_resource_common 16 from autotest_lib.client.cros.input_playback import input_playback 17 18 19 def set_tag(tag): 20 """Sets a tag file. 21 22 @param tag: Path to the tag file. 23 24 """ 25 open(tag, 'w').close() 26 27 28 def tag_exists(tag): 29 """Checks if a tag exists. 30 31 @param tag: Path to the tag file. 32 33 """ 34 return os.path.exists(tag) 35 36 37 class ArcMicrophoneResourceException(Exception): 38 """Exceptions in ArcResource.""" 39 pass 40 41 42 class ArcMicrophoneResource(object): 43 """Class to manage microphone app in container.""" 44 _MICROPHONE_ACTIVITY = 'org.chromium.arc.testapp.microphone/.MainActivity' 45 _MICROPHONE_PACKAGE = 'org.chromium.arc.testapp.microphone' 46 _MICROPHONE_RECORD_PATH = '/storage/emulated/0/recorded.amr-nb' 47 _MICROPHONE_PERMISSIONS = ['RECORD_AUDIO', 'WRITE_EXTERNAL_STORAGE', 48 'READ_EXTERNAL_STORAGE'] 49 50 def __init__(self): 51 """Initializes a ArcMicrophoneResource.""" 52 self._mic_app_start_time = None 53 54 55 def start_microphone_app(self): 56 """Starts microphone app to start recording. 57 58 Starts microphone app. The app starts recorder itself after start up. 59 60 @raises: ArcMicrophoneResourceException if microphone app is not ready 61 yet. 62 63 """ 64 if not tag_exists(arc_resource_common.MicrophoneProps.READY_TAG_FILE): 65 raise ArcMicrophoneResourceException( 66 'Microphone app is not ready yet.') 67 68 if self._mic_app_start_time: 69 raise ArcMicrophoneResourceException( 70 'Microphone app is already started.') 71 72 # In case the permissions are cleared, set the permission again before 73 # each start of the app. 74 self._set_permission() 75 self._start_app() 76 self._mic_app_start_time = time.time() 77 78 79 def stop_microphone_app(self, dest_path): 80 """Stops microphone app and gets recorded audio file from container. 81 82 Stops microphone app. 83 Copies the recorded file from container to Cros device. 84 Deletes the recorded file in container. 85 86 @param dest_path: Destination path of the recorded file on Cros device. 87 88 @raises: ArcMicrophoneResourceException if microphone app is not started 89 yet or is still recording. 90 91 """ 92 if not self._mic_app_start_time: 93 raise ArcMicrophoneResourceException( 94 'Recording is not started yet') 95 96 if self._is_recording(): 97 raise ArcMicrophoneResourceException('Still recording') 98 99 self._stop_app() 100 self._get_file(dest_path) 101 self._delete_file() 102 103 self._mic_app_start_time = None 104 105 106 def _is_recording(self): 107 """Checks if microphone app is recording audio. 108 109 We use the time stamp of app start up time to determine if app is still 110 recording audio. 111 112 @returns: True if microphone app is recording, False otherwise. 113 114 """ 115 if not self._mic_app_start_time: 116 return False 117 118 return (time.time() - self._mic_app_start_time < 119 (arc_resource_common.MicrophoneProps.RECORD_SECS + 120 arc_resource_common.MicrophoneProps.RECORD_FUZZ_SECS)) 121 122 123 def _set_permission(self): 124 """Grants permissions to microphone app.""" 125 for permission in self._MICROPHONE_PERMISSIONS: 126 arc.adb_shell('pm grant %s android.permission.%s' % ( 127 pipes.quote(self._MICROPHONE_PACKAGE), 128 pipes.quote(permission))) 129 130 131 def _start_app(self): 132 """Starts microphone app.""" 133 arc.adb_shell('am start -W %s' % pipes.quote(self._MICROPHONE_ACTIVITY)) 134 135 136 def _stop_app(self): 137 """Stops microphone app. 138 139 Stops the microphone app process. 140 141 """ 142 arc.adb_shell( 143 'am force-stop %s' % pipes.quote(self._MICROPHONE_PACKAGE)) 144 145 146 def _get_file(self, dest_path): 147 """Gets recorded audio file from container. 148 149 Copies the recorded file from container to Cros device. 150 151 @dest_path: Destination path of the recorded file on Cros device. 152 153 """ 154 arc.adb_cmd('pull %s %s' % (pipes.quote(self._MICROPHONE_RECORD_PATH), 155 pipes.quote(dest_path))) 156 157 158 def _delete_file(self): 159 """Removes the recorded file in container.""" 160 arc.adb_shell('rm %s' % pipes.quote(self._MICROPHONE_RECORD_PATH)) 161 162 163 class ArcPlayMusicResourceException(Exception): 164 """Exceptions in ArcPlayMusicResource.""" 165 pass 166 167 168 class ArcPlayMusicResource(object): 169 """Class to manage Play Music app in container.""" 170 _PLAYMUSIC_PACKAGE = 'com.google.android.music' 171 _PLAYMUSIC_FILE_FOLDER = '/storage/emulated/0/' 172 _PLAYMUSIC_PERMISSIONS = ['WRITE_EXTERNAL_STORAGE', 'READ_EXTERNAL_STORAGE'] 173 _PLAYMUSIC_ACTIVITY = '.AudioPreview' 174 _KEYCODE_MEDIA_STOP = 86 175 176 def __init__(self): 177 """Initializes an ArcPlayMusicResource.""" 178 self._files_pushed = [] 179 180 181 def set_playback_file(self, file_path): 182 """Copies file into container. 183 184 @param file_path: Path to the file to play on Cros host. 185 186 @returns: Path to the file in container. 187 188 """ 189 file_name = os.path.basename(file_path) 190 dest_path = os.path.join(self._PLAYMUSIC_FILE_FOLDER, file_name) 191 192 # pipes.quote is deprecated in 2.7 (but still available). 193 # It should be replaced by shlex.quote in python 3.3. 194 arc.adb_cmd('push %s %s' % (pipes.quote(file_path), 195 pipes.quote(dest_path))) 196 197 self._files_pushed.append(dest_path) 198 199 return dest_path 200 201 202 def start_playback(self, dest_path): 203 """Starts Play Music app to play an audio file. 204 205 @param dest_path: The file path in container. 206 207 @raises ArcPlayMusicResourceException: Play Music app is not ready or 208 playback file is not set yet. 209 210 """ 211 if not tag_exists(arc_resource_common.PlayMusicProps.READY_TAG_FILE): 212 raise ArcPlayMusicResourceException( 213 'Play Music app is not ready yet.') 214 215 if dest_path not in self._files_pushed: 216 raise ArcPlayMusicResourceException( 217 'Playback file is not set yet') 218 219 # In case the permissions are cleared, set the permission again before 220 # each start of the app. 221 self._set_permission() 222 self._start_app(dest_path) 223 224 225 def _set_permission(self): 226 """Grants permissions to Play Music app.""" 227 for permission in self._PLAYMUSIC_PERMISSIONS: 228 arc.adb_shell('pm grant %s android.permission.%s' % ( 229 pipes.quote(self._PLAYMUSIC_PACKAGE), 230 pipes.quote(permission))) 231 232 233 def _start_app(self, dest_path): 234 """Starts Play Music app playing an audio file. 235 236 @param dest_path: Path to the file to play in container. 237 238 """ 239 ext = os.path.splitext(dest_path)[1] 240 command = ('am start -a android.intent.action.VIEW' 241 ' -d "file://%s" -t "audio/%s"' 242 ' -n "%s/%s"'% ( 243 pipes.quote(dest_path), pipes.quote(ext), 244 pipes.quote(self._PLAYMUSIC_PACKAGE), 245 pipes.quote(self._PLAYMUSIC_ACTIVITY))) 246 logging.debug(command) 247 arc.adb_shell(command) 248 249 250 def stop_playback(self): 251 """Stops Play Music app. 252 253 Stops the Play Music app by media key event. 254 255 """ 256 arc.send_keycode(self._KEYCODE_MEDIA_STOP) 257 258 259 def cleanup(self): 260 """Removes the files to play in container.""" 261 for path in self._files_pushed: 262 arc.adb_shell('rm %s' % pipes.quote(path)) 263 self._files_pushed = [] 264 265 266 class ArcPlayVideoResourceException(Exception): 267 """Exceptions in ArcPlayVideoResource.""" 268 pass 269 270 271 class ArcPlayVideoResource(object): 272 """Class to manage Play Video app in container.""" 273 _PLAYVIDEO_PACKAGE = 'org.chromium.arc.testapp.video' 274 _PLAYVIDEO_ACTIVITY = 'org.chromium.arc.testapp.video/.MainActivity' 275 _PLAYVIDEO_EXIT_TAG = "/mnt/sdcard/ArcVideoTest.tag" 276 _PLAYVIDEO_FILE_FOLDER = '/storage/emulated/0/' 277 _PLAYVIDEO_PERMISSIONS = ['WRITE_EXTERNAL_STORAGE', 'READ_EXTERNAL_STORAGE'] 278 _KEYCODE_MEDIA_PLAY = 126 279 _KEYCODE_MEDIA_PAUSE = 127 280 _KEYCODE_MEDIA_STOP = 86 281 282 def __init__(self): 283 """Initializes an ArcPlayVideoResource.""" 284 self._files_pushed = [] 285 286 287 def prepare_playback(self, file_path, fullscreen=True): 288 """Copies file into the container and starts the video player app. 289 290 @param file_path: Path to the file to play on Cros host. 291 @param fullscreen: Plays the video in fullscreen. 292 293 """ 294 if not tag_exists(arc_resource_common.PlayVideoProps.READY_TAG_FILE): 295 raise ArcPlayVideoResourceException( 296 'Play Video app is not ready yet.') 297 file_name = os.path.basename(file_path) 298 dest_path = os.path.join(self._PLAYVIDEO_FILE_FOLDER, file_name) 299 300 # pipes.quote is deprecated in 2.7 (but still available). 301 # It should be replaced by shlex.quote in python 3.3. 302 arc.adb_cmd('push %s %s' % (pipes.quote(file_path), 303 pipes.quote(dest_path))) 304 305 # In case the permissions are cleared, set the permission again before 306 # each start of the app. 307 self._set_permission() 308 self._start_app(dest_path) 309 if fullscreen: 310 self.set_fullscreen() 311 312 313 def set_fullscreen(self): 314 """Sends F4 keyevent to set fullscreen.""" 315 input_player = input_playback.InputPlayback() 316 input_player.emulate(input_type='keyboard') 317 input_player.find_connected_inputs() 318 input_player.blocking_playback_of_default_file( 319 input_type='keyboard', filename='keyboard_f4') 320 321 322 def start_playback(self, blocking_secs=None): 323 """Starts Play Video app to play a video file. 324 325 @param blocking_secs: A positive number indicates the timeout to wait 326 for the playback is finished. Set None to make 327 it non-blocking. 328 329 @raises ArcPlayVideoResourceException: Play Video app is not ready or 330 playback file is not set yet. 331 332 """ 333 arc.send_keycode(self._KEYCODE_MEDIA_PLAY) 334 335 if blocking_secs: 336 tag = lambda : arc.check_android_file_exists( 337 self._PLAYVIDEO_EXIT_TAG) 338 exception = error.TestFail('video playback timeout') 339 utils.poll_for_condition(tag, exception, blocking_secs) 340 341 342 def _set_permission(self): 343 """Grants permissions to Play Video app.""" 344 arc.grant_permissions( 345 self._PLAYVIDEO_PACKAGE, self._PLAYVIDEO_PERMISSIONS) 346 347 348 def _start_app(self, dest_path): 349 """Starts Play Video app playing a video file. 350 351 @param dest_path: Path to the file to play in container. 352 353 """ 354 arc.adb_shell('am start --activity-clear-top ' 355 '--es PATH {} {}'.format( 356 pipes.quote(dest_path), self._PLAYVIDEO_ACTIVITY)) 357 358 359 def pause_playback(self): 360 """Pauses Play Video app. 361 362 Pauses the Play Video app by media key event. 363 364 """ 365 arc.send_keycode(self._KEYCODE_MEDIA_PAUSE) 366 367 368 def stop_playback(self): 369 """Stops Play Video app. 370 371 Stops the Play Video app by media key event. 372 373 """ 374 arc.send_keycode(self._KEYCODE_MEDIA_STOP) 375 376 377 def cleanup(self): 378 """Removes the files to play in container.""" 379 for path in self._files_pushed: 380 arc.adb_shell('rm %s' % pipes.quote(path)) 381 self._files_pushed = [] 382 383 384 class ArcResource(object): 385 """Class to manage multimedia resource in container. 386 387 @properties: 388 microphone: The instance of ArcMicrophoneResource for microphone app. 389 play_music: The instance of ArcPlayMusicResource for music app. 390 play_video: The instance of ArcPlayVideoResource for video app. 391 392 """ 393 def __init__(self): 394 self.microphone = ArcMicrophoneResource() 395 self.play_music = ArcPlayMusicResource() 396 self.play_video = ArcPlayVideoResource() 397 398 399 def cleanup(self): 400 """Clean up the resources.""" 401 self.play_music.cleanup() 402 self.play_video.cleanup() 403