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