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