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
7import logging
8import os
9import pipes
10import time
11
12from autotest_lib.client.bin import utils
13from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib.cros import arc
15from autotest_lib.client.cros.multimedia import arc_resource_common
16from autotest_lib.client.cros.input_playback import input_playback
17
18
19def 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
28def 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
37class ArcMicrophoneResourceException(Exception):
38    """Exceptions in ArcResource."""
39    pass
40
41
42class 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
163class ArcPlayMusicResourceException(Exception):
164    """Exceptions in ArcPlayMusicResource."""
165    pass
166
167
168class 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
266class ArcPlayVideoResourceException(Exception):
267    """Exceptions in ArcPlayVideoResource."""
268    pass
269
270
271class 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
384class 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