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 org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.content.res.Configuration;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.view.MotionEvent;
26 import android.view.ScaleGestureDetector;
27 import android.view.View;
28 import android.view.WindowManager;
29 
30 import androidx.annotation.NonNull;
31 import androidx.appcompat.app.AppCompatActivity;
32 import androidx.media3.common.C;
33 import androidx.media3.common.MediaItem;
34 import androidx.media3.common.Tracks.Group;
35 import androidx.media3.exoplayer.ExoPlayer;
36 import androidx.media3.ui.PlayerView;
37 
38 import com.google.common.collect.ImmutableList;
39 
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.List;
43 
44 public class MainActivity extends AppCompatActivity {
45 
46   protected PlayerView mExoplayerView;
47   protected ExoPlayer mPlayer;
48   protected static List<String> sVideoUrls = new ArrayList<>();
49   protected PlayerListener mPlayerListener;
50   protected ScaleGestureDetector mScaleGestureDetector = null;
51   protected boolean mConfiguredPipMode;
52   protected boolean mIsInPipMode;
53   protected boolean mConfiguredSplitScreenMode;
54   protected boolean mIsInMultiWindowMode;
55   protected View mExoRewindButton;
56   protected View mLockControllerButton;
57 
58   @Override
onCreate(Bundle savedInstanceState)59   protected void onCreate(Bundle savedInstanceState) {
60     super.onCreate(savedInstanceState);
61     setContentView(R.layout.activity_main);
62     getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
63     buildPlayer();
64   }
65 
66   /**
67    * Build the player
68    */
buildPlayer()69   protected void buildPlayer() {
70     mPlayer = new ExoPlayer.Builder(this).build();
71     mExoplayerView = findViewById(R.id.exoplayer);
72     mLockControllerButton = findViewById(R.id.lock_controller);
73     mLockControllerButton.setVisibility(View.INVISIBLE);
74     mExoRewindButton = findViewById(androidx.media3.ui.R.id.exo_rew_with_amount);
75     mExoplayerView.setPlayer(mPlayer);
76   }
77 
78   /**
79    * Prepare input list and add it to player's playlist.
80    */
prepareMediaItems(List<String> urls)81   public void prepareMediaItems(List<String> urls) {
82     sVideoUrls = urls != null ? Collections.unmodifiableList(urls) : null;
83     if (sVideoUrls == null) {
84       return;
85     }
86     for (String videoUrl : sVideoUrls) {
87       MediaItem mediaItem = MediaItem.fromUri(Uri.parse(videoUrl));
88       mPlayer.addMediaItem(mediaItem);
89     }
90   }
91 
92   /**
93    * Prepare and play the player.
94    */
95   @Override
onStart()96   protected void onStart() {
97     super.onStart();
98     mPlayer.prepare();
99     mPlayer.play();
100   }
101 
102   /**
103    * Stop the player.
104    */
105   @Override
onStop()106   protected void onStop() {
107     // When activity is stopped, don't pause the playback if it is an audio only clip
108     ImmutableList<Group> currentTrackGroups = mPlayer.getCurrentTracks().getGroups();
109     if ((currentTrackGroups.size() == 1) && (currentTrackGroups.get(0).getType()
110         == C.TRACK_TYPE_AUDIO)) {
111       super.onStop();
112     } else {
113       mPlayer.stop();
114       super.onStop();
115     }
116   }
117 
118   /**
119    * Release the player and destroy the activity
120    */
121   @Override
onDestroy()122   protected void onDestroy() {
123     super.onDestroy();
124     mPlayer.release();
125     getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
126   }
127 
128   /**
129    * Called to process touch screen events. You can override this to intercept all touch screen
130    * events before they are dispatched to the window. Be sure to call this implementation for touch
131    * screen events that should be handled normally.
132    *
133    * @param event The touch screen event.
134    * @return Return true if this event was consumed.
135    */
136   @Override
dispatchTouchEvent(MotionEvent event)137   public boolean dispatchTouchEvent(MotionEvent event) {
138     if (mScaleGestureDetector != null) {
139       mScaleGestureDetector.onTouchEvent(event);
140     }
141     return super.dispatchTouchEvent(event);
142   }
143 
144   /**
145    * Called by the system when the activity changes to and from picture-in-picture mode. This
146    * method provides the same configuration that will be sent in the following
147    * {@link #onConfigurationChanged(Configuration)} call after the activity enters this mode.
148    *
149    * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
150    * @param newConfig The new configuration of the activity with the state
151    *                  {@code isInPictureInPictureMode}.
152    */
153   @Override
onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig)154   public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
155       Configuration newConfig) {
156     super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
157     if (mPlayerListener.isPipTest()) {
158       mIsInPipMode = isInPictureInPictureMode;
159       assertEquals(mConfiguredPipMode, isInPictureInPictureMode);
160       // Verify that the player is playing in PIP mode
161       if (isInPictureInPictureMode) {
162         assertTrue(mPlayer.isPlaying());
163       }
164     }
165   }
166 
167   /**
168    * {@inheritDoc}
169    */
170   @Override
onMultiWindowModeChanged(boolean isInMultiWindowMode, @NonNull Configuration newConfig)171   public void onMultiWindowModeChanged(boolean isInMultiWindowMode,
172       @NonNull Configuration newConfig) {
173     super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
174     if (mPlayerListener.isSplitScreenTest()) {
175       mIsInMultiWindowMode = isInMultiWindowMode;
176       assertEquals(mConfiguredSplitScreenMode, isInMultiWindowMode);
177       // Verify that the player is playing in Split screen mode
178       if (isInMultiWindowMode) {
179         assertTrue(mPlayer.isPlaying());
180       }
181     }
182   }
183 
184   /**
185    * Register a listener to receive events from the player.
186    *
187    * <p>This method can be called from any thread.
188    *
189    * @param listener The listener to register.
190    */
addPlayerListener(PlayerListener listener)191   public void addPlayerListener(PlayerListener listener) {
192     mPlayer.addListener(listener);
193     this.mPlayerListener = listener;
194   }
195 
196   /**
197    * Unregister a listener registered through addPlayerListener(Listener). The listener will no
198    * longer receive events.
199    */
removePlayerListener()200   public void removePlayerListener() {
201     mPlayer.removeListener(this.mPlayerListener);
202   }
203 }
204