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