1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.gecko.tests;
6 
7 import org.mozilla.gecko.R;
8 import org.mozilla.gecko.Tab;
9 import org.mozilla.gecko.Tabs;
10 import org.mozilla.gecko.media.AudioFocusAgent;
11 import org.mozilla.gecko.media.AudioFocusAgent.State;
12 import org.mozilla.gecko.media.MediaControlService;
13 import org.mozilla.gecko.tests.helpers.JavascriptBridge;
14 
15 import android.content.Intent;
16 import android.content.Context;
17 
18 import android.app.Notification;
19 import android.app.NotificationManager;
20 import android.os.Build;
21 import android.service.notification.StatusBarNotification;
22 
23 import com.robotium.solo.Condition;
24 
25 abstract class MediaPlaybackTest extends OldBaseTest {
26     private Context mContext;
27     private int mPrevIcon = 0;
28     protected String mPrevURL = "";
29     private JavascriptBridge mJs;
30 
31     private static final int UI_CHANGED_WAIT_MS = 6000;
32     private static final int MEDIA_PLAYBACK_CHANGED_WAIT_MS = 30000;
33 
info(String msg)34     protected final void info(String msg) {
35         mAsserter.dumpLog(msg);
36     }
37 
getContext()38     protected final Context getContext() {
39         if (mContext == null) {
40             mContext = getInstrumentation().getTargetContext();
41         }
42         return mContext;
43     }
44 
45     /**
46      * Get the system active notification and check whether its UI icon has
47      * been changed.
48      */
waitUntilNotificationUIChanged()49     protected final void waitUntilNotificationUIChanged() {
50         if (!isAvailableToCheckNotification()) {
51             return;
52         }
53         waitForCondition(new Condition() {
54             @Override
55             public boolean isSatisfied() {
56                 NotificationManager notificationManager = (NotificationManager)
57                     getContext().getSystemService(Context.NOTIFICATION_SERVICE);
58                 StatusBarNotification[] sbns = notificationManager.getActiveNotifications();
59                 /**
60                  * Make sure the notification content changed.
61                  * (1) icon changed :
62                  * same website, but playback state changed. eg. play -> pause
63                  * (2) title changed :
64                  * play new media from different tab, and change notification
65                  * content for the new tab.
66                  */
67                 boolean findCorrectNotification = false;
68                 for (int idx = 0; idx < sbns.length; idx++) {
69                     if (sbns[idx].getId() != R.id.mediaControlNotification) {
70                        continue;
71                     }
72                     findCorrectNotification = true;
73                     final Notification notification = sbns[idx].getNotification();
74                     if ((notification.actions.length == 1 &&
75                          notification.actions[0].icon != mPrevIcon) ||
76                          notification.extras.getString(Notification.EXTRA_TEXT) != mPrevURL) {
77                         mPrevIcon = notification.actions[0].icon;
78                         mPrevURL = notification.extras.getString(Notification.EXTRA_TEXT);
79                         return true;
80                     }
81                 }
82 
83                 // The notification was cleared.
84                 if (!findCorrectNotification && mPrevIcon != 0) {
85                     mPrevIcon = 0;
86                     mPrevURL = "";
87                     return true;
88                 }
89                 return false;
90             }
91         }, UI_CHANGED_WAIT_MS);
92     }
93 
94     /**
95      * Use these methods to wait the tab playing related states changed.
96      */
waitUntilTabAudioPlayingStateChanged(final Tab tab, final boolean isTabPlaying)97     private final void waitUntilTabAudioPlayingStateChanged(final Tab tab,
98                                                             final boolean isTabPlaying) {
99         if (tab.isAudioPlaying() == isTabPlaying) {
100             return;
101         }
102         waitForCondition(new Condition() {
103             @Override
104             public boolean isSatisfied() {
105                 return tab.isAudioPlaying() == isTabPlaying;
106             }
107         }, MEDIA_PLAYBACK_CHANGED_WAIT_MS);
108     }
109 
waitUntilTabMediaPlaybackChanged(final Tab tab, final boolean isTabPlaying)110     private final void waitUntilTabMediaPlaybackChanged(final Tab tab,
111                                                         final boolean isTabPlaying) {
112         if (tab.isMediaPlaying() == isTabPlaying) {
113             return;
114         }
115         waitForCondition(new Condition() {
116             @Override
117             public boolean isSatisfied() {
118                 return tab.isMediaPlaying() == isTabPlaying;
119             }
120         }, MEDIA_PLAYBACK_CHANGED_WAIT_MS);
121     }
122 
123     /**
124      * These methods are used to check Tab's playing related attributes.
125      * isMediaPlaying : is any media playing (might be audible or non-audbile)
126      * isAudioPlaying : is any audible media playing
127      */
checkTabMediaPlayingState(final Tab tab, final boolean isTabPlaying)128     protected final void checkTabMediaPlayingState(final Tab tab,
129                                                    final boolean isTabPlaying) {
130         waitUntilTabMediaPlaybackChanged(tab, isTabPlaying);
131         mAsserter.ok(isTabPlaying == tab.isMediaPlaying(),
132                      "Checking the media playing state of tab, isTabPlaying = " + isTabPlaying,
133                      "Tab's media playing state is correct.");
134     }
135 
checkTabAudioPlayingState(final Tab tab, final boolean isTabPlaying)136     protected final void checkTabAudioPlayingState(final Tab tab,
137                                                    final boolean isTabPlaying) {
138         waitUntilTabAudioPlayingStateChanged(tab, isTabPlaying);
139         mAsserter.ok(isTabPlaying == tab.isAudioPlaying(),
140                      "Checking the audio playing state of tab, isTabPlaying = " + isTabPlaying,
141                      "Tab's audio playing state is correct.");
142     }
143 
144     /**
145      * Since we can't testing media control via clicking the media control, we
146      * directly send intent to service to simulate the behavior.
147      */
notifyMediaControlService(String action)148     protected final void notifyMediaControlService(String action) {
149         Intent intent = new Intent(getContext(), MediaControlService.class);
150         intent.setAction(action);
151         getContext().startService(intent);
152     }
153 
154     /**
155      * Use these methods when both media control and audio focus state should
156      * be changed and you want to check whether the changing are correct or not.
157      * Checking selected tab is default option.
158      */
checkIfMediaPlayingSuccess(boolean isTabPlaying)159     protected final void checkIfMediaPlayingSuccess(boolean isTabPlaying) {
160         checkIfMediaPlayingSuccess(isTabPlaying, false);
161     }
162 
checkIfMediaPlayingSuccess(boolean isTabPlaying, boolean clearNotification)163     protected final void checkIfMediaPlayingSuccess(boolean isTabPlaying,
164                                                     boolean clearNotification) {
165         final Tab tab = Tabs.getInstance().getSelectedTab();
166         checkTabMediaPlayingState(tab, isTabPlaying);
167         checkMediaNotificationStatesAfterChanged(tab, isTabPlaying, clearNotification);
168 
169         checkTabAudioPlayingState(tab, isTabPlaying);
170         checkAudioFocusStateAfterChanged(isTabPlaying);
171     }
172 
173     /**
174      * This method is used to check whether notification states are correct or
175      * not after notification UI changed.
176      */
checkMediaNotificationStatesAfterChanged(final Tab tab, final boolean isTabPlaying)177     protected final void checkMediaNotificationStatesAfterChanged(final Tab tab,
178                                                                   final boolean isTabPlaying) {
179         checkMediaNotificationStatesAfterChanged(tab, isTabPlaying, false);
180     }
181 
checkMediaNotificationStatesAfterChanged(final Tab tab, final boolean isTabPlaying, final boolean clearNotification)182     protected final void checkMediaNotificationStatesAfterChanged(final Tab tab,
183                                                                   final boolean isTabPlaying,
184                                                                   final boolean clearNotification) {
185         waitUntilNotificationUIChanged();
186 
187         if (clearNotification) {
188             checkIfMediaNotificationBeCleared();
189         } else {
190             checkMediaNotificationStates(tab, isTabPlaying);
191         }
192     }
193 
checkMediaNotificationStates(final Tab tab, final boolean isTabPlaying)194     protected final void checkMediaNotificationStates(final Tab tab,
195                                                       final boolean isTabPlaying) {
196         if (!isAvailableToCheckNotification()) {
197             return;
198         }
199         NotificationManager notificationManager = (NotificationManager)
200             getContext().getSystemService(Context.NOTIFICATION_SERVICE);
201 
202         StatusBarNotification[] sbns = notificationManager.getActiveNotifications();
203         boolean findCorrectNotification = false;
204         for (int idx = 0; idx < sbns.length; idx++) {
205             if (sbns[idx].getId() == R.id.mediaControlNotification) {
206                 findCorrectNotification = true;
207                 break;
208             }
209         }
210         mAsserter.ok(findCorrectNotification,
211                      "Showing correct notification in system's status bar.",
212                      "Check system notification");
213 
214         Notification notification = sbns[0].getNotification();
215         mAsserter.is(notification.icon,
216                      R.drawable.ic_status_logo,
217                      "Notification shows correct small icon.");
218         mAsserter.is(notification.extras.get(Notification.EXTRA_TITLE),
219                      tab.getTitle(),
220                      "Notification shows correct title.");
221         mAsserter.is(notification.extras.get(Notification.EXTRA_TEXT),
222                      tab.getURL(),
223                      "Notification shows correct text.");
224         mAsserter.is(notification.actions.length, 1,
225                      "Only has one action in notification.");
226         mAsserter.is(notification.actions[0].title,
227                      getContext().getString(isTabPlaying ? R.string.media_pause : R.string.media_play),
228                      "Action has correct title.");
229         mAsserter.is(notification.actions[0].icon,
230                      isTabPlaying ? R.drawable.ic_media_pause : R.drawable.ic_media_play,
231                      "Action has correct icon.");
232     }
233 
checkIfMediaNotificationBeCleared()234     protected final void checkIfMediaNotificationBeCleared() {
235         if (!isAvailableToCheckNotification()) {
236             return;
237         }
238         NotificationManager notificationManager = (NotificationManager)
239             getContext().getSystemService(Context.NOTIFICATION_SERVICE);
240         StatusBarNotification[] sbns = notificationManager.getActiveNotifications();
241 
242         boolean findCorrectNotification = false;
243         for (int idx = 0; idx < sbns.length; idx++) {
244             if (sbns[idx].getId() == R.id.mediaControlNotification) {
245                 findCorrectNotification = true;
246                 break;
247             }
248         }
249         mAsserter.ok(!findCorrectNotification,
250                      "Should not have notification in system's status bar.",
251                      "Check system notification.");
252     }
253 
254     /**
255      * This method is used to check whether audio focus state are correct or
256      * not after tab's audio playing state changed.
257      */
checkAudioFocusStateAfterChanged(boolean isTabPlaying)258     protected final void checkAudioFocusStateAfterChanged(boolean isTabPlaying) {
259         if (isTabPlaying) {
260             mAsserter.is(AudioFocusAgent.getInstance().getAudioFocusState(),
261                          State.OWN_FOCUS,
262                          "Audio focus state is correct.");
263         } else {
264             boolean isLostFocus =
265                 AudioFocusAgent.getInstance().getAudioFocusState().equals(State.LOST_FOCUS) ||
266                 AudioFocusAgent.getInstance().getAudioFocusState().equals(State.LOST_FOCUS_TRANSIENT);
267             mAsserter.ok(isLostFocus,
268                          "Checking the audio focus when the tab is not playing",
269                          "Audio focus state is correct.");
270         }
271     }
272 
getAudioFocusAgent()273     protected final AudioFocusAgent getAudioFocusAgent() {
274         return AudioFocusAgent.getInstance();
275     }
276 
requestAudioFocus()277     protected final void requestAudioFocus() {
278         getAudioFocusAgent().notifyStartedPlaying();
279         if (getAudioFocusAgent().getAudioFocusState() == State.OWN_FOCUS) {
280             return;
281         }
282 
283         // Request audio focus might fail, depend on the andriod's audio mode.
284         waitForCondition(new Condition() {
285             @Override
286             public boolean isSatisfied() {
287                 getAudioFocusAgent().notifyStartedPlaying();
288                 return getAudioFocusAgent().getAudioFocusState() == State.OWN_FOCUS;
289             }
290         }, MAX_WAIT_MS);
291     }
292 
293     /**
294      * The method NotificationManager.getActiveNotifications() is only avaiable
295      * after version 23, so we need to check version ensure running the test on
296      * the correct version.
297      */
checkAndroidVersionForMediaControlTest()298     protected final void checkAndroidVersionForMediaControlTest() {
299         mAsserter.ok(isAvailableToCheckNotification(),
300                      "Checking the android version for media control testing",
301                      "The API to check system notification is only available after version 23.");
302     }
303 
isAvailableToCheckNotification()304     protected final boolean isAvailableToCheckNotification() {
305         return Build.VERSION.SDK_INT >= 23;
306     }
307 
308      /**
309      * Can communicte with JS bey getJS(), but caller should create and destroy
310      * JSBridge manually.
311      */
getJS()312     protected JavascriptBridge getJS() {
313         mAsserter.ok(mJs != null,
314                      "JSBridege existence check",
315                      "Should connect JSBridge before using JS!");
316         return mJs;
317     }
318 
createJSBridge()319     protected void createJSBridge() {
320         mAsserter.ok(mJs == null,
321                      "JSBridege existence check",
322                      "Should not recreate the JSBridge!");
323         mJs = new JavascriptBridge(this, mActions, mAsserter);
324     }
325 
destroyJSBridge()326     protected void destroyJSBridge() {
327         mAsserter.ok(mJs != null,
328                      "JSBridege existence check",
329                      "Should create JSBridge before destroy it!");
330         mJs.disconnect();
331         mJs = null;
332     }
333 }
334