• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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