1 /* 2 * Copyright 2023 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.media.cujcommon.cts; 18 19 import static android.media.cujcommon.cts.CujTestBase.ORIENTATIONS; 20 21 import static com.google.common.truth.Truth.assertWithMessage; 22 23 import static org.junit.Assert.assertTrue; 24 25 import android.app.Activity; 26 import android.content.pm.ActivityInfo; 27 import android.media.AudioManager; 28 import android.os.Looper; 29 import android.util.DisplayMetrics; 30 31 import androidx.annotation.NonNull; 32 import androidx.media3.common.C; 33 import androidx.media3.common.Format; 34 import androidx.media3.common.Player; 35 import androidx.media3.common.Player.Events; 36 import androidx.media3.common.TrackSelectionOverride; 37 import androidx.media3.common.TrackSelectionParameters; 38 import androidx.media3.common.Tracks; 39 40 import java.time.Duration; 41 import java.util.ArrayList; 42 import java.util.List; 43 44 public abstract class PlayerListener implements Player.Listener { 45 46 public static final Duration NOTIFICATIONTEST_PLAYBACK_DELTA = Duration.ofSeconds(6); 47 public static final Object LISTENER_LOCK = new Object(); 48 public static int CURRENT_MEDIA_INDEX = 0; 49 50 // Enum Declared for Test Type 51 public enum TestType { 52 PLAYBACK_TEST, 53 SEEK_TEST, 54 ORIENTATION_TEST, 55 ADAPTIVE_PLAYBACK_TEST, 56 SCROLL_TEST, 57 SWITCH_AUDIO_TRACK_TEST, 58 SWITCH_SUBTITLE_TRACK_TEST, 59 CALL_NOTIFICATION_TEST, 60 MESSAGE_NOTIFICATION_TEST, 61 PINCH_TO_ZOOM_TEST, 62 SPEED_CHANGE_TEST, 63 PIP_MODE_TEST, 64 SPLIT_SCREEN_TEST, 65 DEVICE_LOCK_TEST, 66 LOCK_PLAYBACK_CONTROLLER_TEST, 67 AUDIO_OFFLOAD_TEST 68 } 69 70 public static boolean mPlaybackEnded; 71 protected long mExpectedTotalTime; 72 protected MainActivity mActivity; 73 protected ScrollTestActivity mScrollActivity; 74 protected AudioOffloadTestActivity mAudioOffloadActivity; 75 protected Duration mSendMessagePosition; 76 protected int mPreviousOrientation; 77 protected int mOrientationIndex; 78 protected boolean mScrollRequested; 79 protected boolean mTrackChangeRequested; 80 protected List<Tracks.Group> mTrackGroups; 81 protected Format mStartTrackFormat; 82 protected Format mCurrentTrackFormat; 83 protected Format mConfiguredTrackFormat; 84 protected long mStartTime; 85 protected AudioManager mAudioManager; 86 protected boolean mRingVolumeUpdated; 87 protected Duration mTotalSeekOverhead; 88 PlayerListener()89 public PlayerListener() { 90 this.mSendMessagePosition = Duration.ofSeconds(0); 91 this.mTotalSeekOverhead = Duration.ofSeconds(0); 92 } 93 94 /** 95 * Returns the type of test. 96 */ getTestType()97 public abstract TestType getTestType(); 98 99 /** 100 * Returns the aggregated seek overhead for Seek test. 101 */ getTotalSeekOverhead()102 public Duration getTotalSeekOverhead() { 103 return mTotalSeekOverhead; 104 } 105 106 /** 107 * Returns True for Orientation test. 108 */ isOrientationTest()109 public final boolean isOrientationTest() { 110 return getTestType().equals(TestType.ORIENTATION_TEST); 111 } 112 113 /** 114 * Returns True for Scroll test. 115 */ isScrollTest()116 public final boolean isScrollTest() { 117 return getTestType().equals(TestType.SCROLL_TEST); 118 } 119 120 /** 121 * Returns True for Call Notification test. 122 */ isCallNotificationTest()123 public final boolean isCallNotificationTest() { 124 return getTestType().equals(TestType.CALL_NOTIFICATION_TEST); 125 } 126 127 /** 128 * Returns True for PinchToZoom test. 129 */ isPinchToZoomTest()130 public final boolean isPinchToZoomTest() { 131 return getTestType().equals(TestType.PINCH_TO_ZOOM_TEST); 132 } 133 134 /** 135 * Returns True for PIP Minimized Playback Mode test. 136 */ isPipTest()137 public final boolean isPipTest() { 138 return getTestType().equals(TestType.PIP_MODE_TEST); 139 } 140 141 /** 142 * Returns True for Split Screen test. 143 */ isSplitScreenTest()144 public final boolean isSplitScreenTest() { 145 return getTestType().equals(TestType.SPLIT_SCREEN_TEST); 146 } 147 148 /** 149 * Returns True for Audio Offload test. 150 */ isAudioOffloadTest()151 public final boolean isAudioOffloadTest() { 152 return getTestType().equals(TestType.AUDIO_OFFLOAD_TEST); 153 } 154 155 /** 156 * Returns expected playback time for the playlist. 157 */ getExpectedTotalTime()158 public final long getExpectedTotalTime() { 159 return mExpectedTotalTime; 160 } 161 162 /** 163 * Sets activity for test. 164 */ setActivity(MainActivity activity)165 public final void setActivity(MainActivity activity) { 166 this.mActivity = activity; 167 if (isOrientationTest()) { 168 mOrientationIndex = 0; 169 mActivity.setRequestedOrientation( 170 ORIENTATIONS[mOrientationIndex] /* SCREEN_ORIENTATION_PORTRAIT */); 171 } 172 } 173 174 /** 175 * Get Orientation of the device. 176 */ getDeviceOrientation(final Activity activity)177 protected static int getDeviceOrientation(final Activity activity) { 178 final DisplayMetrics displayMetrics = new DisplayMetrics(); 179 activity.getDisplay().getRealMetrics(displayMetrics); 180 if (displayMetrics.widthPixels < displayMetrics.heightPixels) { 181 return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 182 } else { 183 return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 184 } 185 } 186 187 /** 188 * Sets activity for scroll test. 189 */ setScrollActivity(ScrollTestActivity activity)190 public final void setScrollActivity(ScrollTestActivity activity) { 191 this.mScrollActivity = activity; 192 } 193 194 /** 195 * Sets activity for audio offload test. 196 */ setAudioOffloadActivity(AudioOffloadTestActivity activity)197 public final void setAudioOffloadActivity(AudioOffloadTestActivity activity) { 198 this.mAudioOffloadActivity = activity; 199 } 200 201 /** 202 * Check if two formats are similar. 203 * 204 * @param refFormat Reference format 205 * @param testFormat Test format 206 * @return True, if two formats are similar, false otherwise 207 */ isFormatSimilar(Format refFormat, Format testFormat)208 protected final boolean isFormatSimilar(Format refFormat, Format testFormat) { 209 String refMediaType = refFormat.sampleMimeType; 210 String testMediaType = testFormat.sampleMimeType; 211 if (getTestType().equals(TestType.SWITCH_AUDIO_TRACK_TEST)) { 212 assertTrue(refMediaType.startsWith("audio/") && testMediaType.startsWith("audio/")); 213 if ((refFormat.channelCount != testFormat.channelCount) || (refFormat.sampleRate 214 != testFormat.sampleRate)) { 215 return false; 216 } 217 } else if (getTestType().equals(TestType.SWITCH_SUBTITLE_TRACK_TEST)) { 218 assertTrue((refMediaType.startsWith("text/") && testMediaType.startsWith("text/")) || ( 219 refMediaType.startsWith("application/") && testMediaType.startsWith("application/"))); 220 } 221 if (!refMediaType.equals(testMediaType)) { 222 return false; 223 } 224 if (!refFormat.id.equals(testFormat.id)) { 225 return false; 226 } 227 return true; 228 } 229 230 /** 231 * Called when player states changed. 232 * 233 * @param player The {@link Player} whose state changed. Use the getters to obtain the latest 234 * states. 235 * @param events The {@link Events} that happened in this iteration, indicating which player 236 * states changed. 237 */ onEvents(@onNull Player player, Events events)238 public final void onEvents(@NonNull Player player, Events events) { 239 if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) { 240 onEventsPlaybackStateChanged(player); 241 synchronized (LISTENER_LOCK) { 242 if (player.getPlaybackState() == Player.STATE_ENDED) { 243 if (mPlaybackEnded) { 244 throw new RuntimeException("mPlaybackEnded already set, player could be ended"); 245 } 246 if (isScrollTest()) { 247 assertTrue(mScrollRequested); 248 mScrollActivity.removePlayerListener(); 249 } else if (isAudioOffloadTest()) { 250 mAudioOffloadActivity.removePlayerListener(); 251 } else { 252 mActivity.removePlayerListener(); 253 } 254 // Verify the total time taken by the notification test 255 if (getTestType().equals(TestType.CALL_NOTIFICATION_TEST) || getTestType().equals( 256 TestType.MESSAGE_NOTIFICATION_TEST)) { 257 // Restore the ring volume in case it was updated 258 if (mRingVolumeUpdated) { 259 mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 260 mAudioManager.getStreamMinVolume(AudioManager.STREAM_RING), 0 /*no flag used*/); 261 } 262 long actualTime = System.currentTimeMillis() - mStartTime; 263 assertWithMessage("Test did not complete within expected time").that(actualTime) 264 .isWithin(NOTIFICATIONTEST_PLAYBACK_DELTA.toMillis()).of(mExpectedTotalTime); 265 } 266 if (isAudioOffloadTest()) { 267 assertTrue("Player did not sleep for audio offload", 268 mAudioOffloadActivity.mIsSleepingForAudioOffloadEnabled); 269 assertTrue("Audio offload was not enabled", 270 mAudioOffloadActivity.mIsAudioOffloadEnabled); 271 } 272 mPlaybackEnded = true; 273 LISTENER_LOCK.notify(); 274 } 275 } 276 } 277 if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) { 278 onEventsMediaItemTransition(player); 279 // Add duration on media transition. 280 long duration = player.getDuration(); 281 if (duration != C.TIME_UNSET) { 282 mExpectedTotalTime += duration; 283 } 284 } 285 } 286 287 /** 288 * Called when the value returned from {@link Player#getPlaybackState()} changes. 289 * 290 * @param player The {@link Player} whose state changed. Use the getters to obtain the latest 291 * states. 292 */ onEventsPlaybackStateChanged(@onNull Player player)293 public abstract void onEventsPlaybackStateChanged(@NonNull Player player); 294 295 /** 296 * Called when the value returned from {@link Player#getCurrentMediaItem()} changes or the player 297 * starts repeating the current item. 298 * 299 * @param player The {@link Player} whose state changed. Use the getters to obtain the latest 300 * states. 301 */ onEventsMediaItemTransition(@onNull Player player)302 public abstract void onEventsMediaItemTransition(@NonNull Player player); 303 304 /** 305 * Create a message at given position to change the audio or the subtitle track 306 * 307 * @param sendMessagePosition Position at which message needs to be executed 308 * @param trackGroupIndex Index of the current track group 309 * @param trackIndex Index of the current track 310 */ createSwitchTrackMessage(Duration sendMessagePosition, int trackGroupIndex, int trackIndex)311 protected final void createSwitchTrackMessage(Duration sendMessagePosition, int trackGroupIndex, 312 int trackIndex) { 313 mActivity.mPlayer.createMessage((messageType, payload) -> { 314 TrackSelectionParameters currentParameters = 315 mActivity.mPlayer.getTrackSelectionParameters(); 316 TrackSelectionParameters newParameters = currentParameters 317 .buildUpon() 318 .setOverrideForType( 319 new TrackSelectionOverride( 320 mTrackGroups.get(trackGroupIndex).getMediaTrackGroup(), 321 trackIndex)) 322 .build(); 323 mActivity.mPlayer.setTrackSelectionParameters(newParameters); 324 mConfiguredTrackFormat = mTrackGroups.get(trackGroupIndex) 325 .getTrackFormat(trackIndex); 326 mTrackChangeRequested = true; 327 }).setLooper(Looper.getMainLooper()).setPosition(sendMessagePosition.toMillis()) 328 .setDeleteAfterDelivery(true).send(); 329 } 330 331 /** 332 * Called when the value of getCurrentTracks() changes. onEvents(Player, Player.Events) will also 333 * be called to report this event along with other events that happen in the same Looper message 334 * queue iteration. 335 * 336 * @param tracks The available tracks information. Never null, but may be of length zero. 337 */ 338 @Override onTracksChanged(Tracks tracks)339 public final void onTracksChanged(Tracks tracks) { 340 for (Tracks.Group currentTrackGroup : tracks.getGroups()) { 341 if (currentTrackGroup.isSelected() && ( 342 (getTestType().equals(TestType.SWITCH_AUDIO_TRACK_TEST) && (currentTrackGroup.getType() 343 == C.TRACK_TYPE_AUDIO)) || (getTestType().equals(TestType.SWITCH_SUBTITLE_TRACK_TEST) 344 && (currentTrackGroup.getType() == C.TRACK_TYPE_TEXT)))) { 345 for (int trackIndex = 0; trackIndex < currentTrackGroup.length; trackIndex++) { 346 if (currentTrackGroup.isTrackSelected(trackIndex)) { 347 if (!mTrackChangeRequested) { 348 mStartTrackFormat = currentTrackGroup.getTrackFormat(trackIndex); 349 } else { 350 mCurrentTrackFormat = currentTrackGroup.getTrackFormat(trackIndex); 351 } 352 } 353 } 354 } 355 } 356 } 357 358 /** 359 * This method is triggered after the test completes, that is there are no more 360 * actions left to be performed in the test. This can be used to free resources, assert 361 * conditions and or anything else that is required to be done post completion of the test. 362 */ onTestCompletion()363 public void onTestCompletion() { /* Default empty/Noop implementation. */ } 364 365 /** 366 * Get all audio/subtitle tracks group from the player's Tracks. 367 */ getTrackGroups()368 protected final List<Tracks.Group> getTrackGroups() { 369 List<Tracks.Group> trackGroups = new ArrayList<>(); 370 Tracks currentTracks = mActivity.mPlayer.getCurrentTracks(); 371 for (Tracks.Group currentTrackGroup : currentTracks.getGroups()) { 372 if ((currentTrackGroup.getType() == C.TRACK_TYPE_AUDIO) || (currentTrackGroup.getType() 373 == C.TRACK_TYPE_TEXT)) { 374 trackGroups.add(currentTrackGroup); 375 } 376 } 377 return trackGroups; 378 } 379 } 380