• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.platform.helpers.media;
18 
19 import android.app.Instrumentation;
20 import android.graphics.Rect;
21 import android.media.MediaMetadata;
22 import android.media.session.PlaybackState;
23 import android.platform.test.scenario.tapl_common.Gestures;
24 
25 import androidx.test.platform.app.InstrumentationRegistry;
26 import androidx.test.uiautomator.By;
27 import androidx.test.uiautomator.BySelector;
28 import androidx.test.uiautomator.Direction;
29 import androidx.test.uiautomator.UiDevice;
30 import androidx.test.uiautomator.UiObject2;
31 import androidx.test.uiautomator.Until;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.TimeUnit;
37 
38 public class MediaController {
39 
40     private static final String PKG = "com.android.systemui";
41     private static final String HIDE_BTN_RES = "dismiss";
42     private static final BySelector PLAY_BTN_SELECTOR =
43         By.res(PKG, "actionPlayPause").descContains("Play");
44     private static final BySelector PAUSE_BTN_SELECTOR =
45         By.res(PKG, "actionPlayPause").descContains("Pause");
46     private static final BySelector SKIP_NEXT_BTN_SELECTOR =
47         By.res(PKG, "actionNext").descContains("Next");
48     private static final BySelector SKIP_PREV_BTN_SELECTOR =
49         By.res(PKG, "actionPrev").descContains("Previous");
50     private static final int WAIT_TIME_MILLIS = 10_000;
51     private static final int LONG_PRESS_TIME_MILLIS = 1_000;
52     private static final long UI_WAIT_TIMEOUT = 3_000;
53 
54     private final UiObject2 mUiObject;
55     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
56     private final UiDevice mDevice = UiDevice.getInstance(mInstrumentation);
57     private final List<Integer> mStateChanges;
58     private Runnable mStateListener;
59 
MediaController(MediaInstrumentation media, UiObject2 uiObject)60     MediaController(MediaInstrumentation media, UiObject2 uiObject) {
61         media.addMediaSessionStateChangedListeners(this::onMediaSessionStageChanged);
62         mUiObject = uiObject;
63         mStateChanges = new ArrayList<>();
64     }
65 
play()66     public void play() {
67         runToNextState(
68             () -> mUiObject
69                 .wait(Until.findObject(PLAY_BTN_SELECTOR), WAIT_TIME_MILLIS)
70                 .click(),
71             PlaybackState.STATE_PLAYING);
72     }
73 
pause()74     public void pause() {
75         runToNextState(
76                 () -> Gestures.click(
77                         mUiObject.wait(Until.findObject(PAUSE_BTN_SELECTOR), WAIT_TIME_MILLIS),
78                         "Pause button"),
79                 PlaybackState.STATE_PAUSED);
80     }
81 
skipToNext()82     public void skipToNext() {
83         runToNextState(
84                 () -> Gestures.click(
85                         mUiObject.wait(Until.findObject(SKIP_NEXT_BTN_SELECTOR), WAIT_TIME_MILLIS),
86                         "Next button"),
87                 PlaybackState.STATE_SKIPPING_TO_NEXT);
88     }
89 
skipToPrev()90     public void skipToPrev() {
91         runToNextState(
92                 () -> Gestures.click(
93                         mUiObject.wait(Until.findObject(SKIP_PREV_BTN_SELECTOR), WAIT_TIME_MILLIS),
94                         "Previous button"),
95                 PlaybackState.STATE_SKIPPING_TO_PREVIOUS);
96     }
97 
runToNextState(Runnable runnable, int state)98     private void runToNextState(Runnable runnable, int state) {
99         mStateChanges.clear();
100         CountDownLatch latch = new CountDownLatch(1);
101         mStateListener = latch::countDown;
102         runnable.run();
103         try {
104             if (!latch.await(WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS)) {
105                 throw new RuntimeException("PlaybackState didn't change and timeout.");
106             }
107         } catch (InterruptedException e) {
108             throw new RuntimeException();
109         }
110         if (!mStateChanges.contains(state)) {
111             throw new RuntimeException(String.format("Fail to run to next state(%d).", state));
112         }
113     }
114 
onMediaSessionStageChanged(int state)115     private void onMediaSessionStageChanged(int state) {
116         mStateChanges.add(state);
117         if (mStateListener != null) {
118             mStateListener.run();
119             mStateListener = null;
120         }
121     }
122 
title()123     public String title() {
124         UiObject2 header =
125             mUiObject.wait(Until.findObject(By.res(PKG, "header_title")), WAIT_TIME_MILLIS);
126         if (header == null) {
127             return "";
128         }
129         return header.getText();
130     }
131 
132     /**
133      * Long press for {@link #LONG_PRESS_TIME_MILLIS} ms on UMO then clik the hide button.
134      */
longPressAndHide()135     public void longPressAndHide() {
136         if (mUiObject == null) {
137             throw new RuntimeException("UMO should exist to do long press.");
138         }
139 
140         mUiObject.click(LONG_PRESS_TIME_MILLIS);
141         UiObject2 hideBtn = mUiObject.wait(
142             Until.findObject(By.res(PKG, HIDE_BTN_RES)), WAIT_TIME_MILLIS);
143         if (hideBtn == null) {
144             throw new RuntimeException("Hide button should exist after long press on UMO.");
145         }
146         hideBtn.clickAndWait(Until.newWindow(), UI_WAIT_TIMEOUT);
147     }
148 
149     /**
150      * Checks if the current media session is using the given MediaMetadata.
151      *
152      * @param meta MediaMetadata to get media title and artist.
153      * @return boolean
154      */
hasMetadata(MediaMetadata meta)155     public boolean hasMetadata(MediaMetadata meta) {
156         final BySelector mediaTitleSelector =
157             By.res(PKG, "header_title").text(meta.getString(MediaMetadata.METADATA_KEY_TITLE));
158         final BySelector mediaArtistSelector =
159             By.res(PKG, "header_artist")
160                 .text(meta.getString(MediaMetadata.METADATA_KEY_ARTIST));
161         return mUiObject.hasObject(mediaTitleSelector) && mUiObject.hasObject(mediaArtistSelector);
162     }
163 
swipe(Direction direction)164     public boolean swipe(Direction direction) {
165         Rect bound = mUiObject.getVisibleBounds();
166         final int startX;
167         final int endX;
168         switch (direction) {
169             case LEFT:
170                 startX = (bound.right + bound.centerX()) / 2;
171                 endX = bound.left;
172                 break;
173             case RIGHT:
174                 startX = (bound.left + bound.centerX()) / 2;
175                 endX = bound.right;
176                 break;
177             default:
178                 throw new RuntimeException(
179                     String.format("swipe to %s on UMO isn't supported.", direction));
180         }
181         return mDevice.swipe(startX, bound.centerY(), endX, bound.centerY(), 10);
182     }
183 
getVisibleBound()184     public Rect getVisibleBound() {
185         return mUiObject.getVisibleBounds();
186     }
187 }
188