• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.StringDef;
24 import android.annotation.TestApi;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.res.AssetFileDescriptor;
28 import android.graphics.Rect;
29 import android.graphics.SurfaceTexture;
30 import android.media.MediaDrm.KeyRequest;
31 import android.media.MediaPlayer2.DrmInfo;
32 import android.media.MediaPlayer2Proto.PlayerMessage;
33 import android.media.MediaPlayer2Proto.Value;
34 import android.media.protobuf.InvalidProtocolBufferException;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.ParcelFileDescriptor;
41 import android.os.PersistableBundle;
42 import android.os.PowerManager;
43 import android.util.Log;
44 import android.util.Pair;
45 import android.util.Size;
46 import android.view.Surface;
47 import android.view.SurfaceHolder;
48 
49 import com.android.internal.annotations.GuardedBy;
50 
51 import java.io.ByteArrayOutputStream;
52 import java.io.File;
53 import java.io.FileDescriptor;
54 import java.io.FileInputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.lang.annotation.Retention;
58 import java.lang.annotation.RetentionPolicy;
59 import java.lang.ref.WeakReference;
60 import java.net.HttpCookie;
61 import java.net.HttpURLConnection;
62 import java.net.URL;
63 import java.nio.ByteOrder;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.Collection;
67 import java.util.Collections;
68 import java.util.HashMap;
69 import java.util.Iterator;
70 import java.util.LinkedList;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.Queue;
74 import java.util.UUID;
75 import java.util.concurrent.CompletableFuture;
76 import java.util.concurrent.ConcurrentLinkedQueue;
77 import java.util.concurrent.ExecutionException;
78 import java.util.concurrent.Executor;
79 import java.util.concurrent.ExecutorService;
80 import java.util.concurrent.Executors;
81 import java.util.concurrent.Future;
82 import java.util.concurrent.RejectedExecutionException;
83 import java.util.concurrent.TimeUnit;
84 import java.util.concurrent.TimeoutException;
85 import java.util.concurrent.atomic.AtomicInteger;
86 import java.util.concurrent.atomic.AtomicLong;
87 
88 /**
89  * MediaPlayer2 class can be used to control playback of audio/video files and streams.
90  *
91  * <p>
92  * This API is not generally intended for third party application developers.
93  * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
94  * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
95  * for consistent behavior across all devices.
96  *
97  * <p>Topics covered here are:
98  * <ol>
99  * <li><a href="#PlayerStates">Player states</a>
100  * <li><a href="#InvalidStates">Invalid method calls</a>
101  * <li><a href="#Permissions">Permissions</a>
102  * <li><a href="#Callbacks">Callbacks</a>
103  * </ol>
104  *
105  *
106  * <h3 id="PlayerStates">Player states</h3>
107  *
108  * <p>The playback control of audio/video files is managed as a state machine.</p>
109  * <p><div style="text-align:center;"><img src="../../../images/mediaplayer2_state_diagram.png"
110  *         alt="MediaPlayer2 State diagram"
111  *         border="0" /></div></p>
112  * <p>The MediaPlayer2 object has five states:</p>
113  * <ol>
114  *     <li><p>{@link #PLAYER_STATE_IDLE}: MediaPlayer2 is in the <strong>Idle</strong>
115  *         state after it's created, or after calling {@link #reset()}.</p>
116  *
117  *         <p>While in this state, you should call
118  *         {@link #setDataSource setDataSource}. It is a good
119  *         programming practice to register an {@link EventCallback#onCallCompleted onCallCompleted}
120  *         <a href="#Callbacks">callback</a> and watch for {@link #CALL_STATUS_BAD_VALUE} and
121  *         {@link #CALL_STATUS_ERROR_IO}, which might be caused by <code>setDataSource</code>.
122  *         </p>
123  *
124  *         <p>Calling {@link #prepare()} transfers a MediaPlayer2 object to
125  *         the <strong>Prepared</strong> state. Note
126  *         that {@link #prepare()} is asynchronous. When the preparation completes,
127  *         if you register an {@link EventCallback#onInfo onInfo} <a href="#Callbacks">callback</a>,
128  *         the player executes the callback
129  *         with {@link #MEDIA_INFO_PREPARED} and transitions to the
130  *         <strong>Prepared</strong> state.</p>
131  *         </li>
132  *
133  *     <li>{@link #PLAYER_STATE_PREPARED}: A MediaPlayer object must be in the
134  *         <strong>Prepared</strong> state before playback can be started for the first time.
135  *         While in this state, you can set player properties
136  *         such as audio/sound volume and looping by invoking the corresponding set methods.
137  *         Calling {@link #play()} transfers a MediaPlayer2 object to
138  *         the <strong>Playing</strong> state.
139  *      </li>
140  *
141  *     <li>{@link #PLAYER_STATE_PLAYING}:
142  *         <p>The player plays the data source while in this state.
143  *         If you register an {@link EventCallback#onInfo onInfo} <a href="#Callbacks">callback</a>,
144  *         the player regularly executes the callback with
145  *         {@link #MEDIA_INFO_BUFFERING_UPDATE}.
146  *         This allows applications to keep track of the buffering status
147  *         while streaming audio/video.</p>
148  *
149  *         <p> When the playback reaches the end of stream, the behavior depends on whether or
150  *         not you've enabled looping by calling {@link #loopCurrent}:</p>
151  *         <ul>
152  *         <li>If the looping mode was set to <code>false</code>, the player will transfer
153  *         to the <strong>Paused</strong> state. If you registered an {@link EventCallback#onInfo
154  *         onInfo} <a href="#Callbacks">callback</a>
155  *         the player calls the callback with {@link #MEDIA_INFO_DATA_SOURCE_END} and enters
156  *         the <strong>Paused</strong> state.
157  *         </li>
158  *         <li>If the looping mode was set to <code>true</code>,
159  *         the MediaPlayer2 object remains in the <strong>Playing</strong> state and replays its
160  *         data source from the beginning.</li>
161  *         </ul>
162  *         </li>
163  *
164  *     <li>{@link #PLAYER_STATE_PAUSED}: Audio/video playback pauses while in this state.
165  *         Call {@link #play()} to resume playback from the position where it paused.</li>
166  *
167  *     <li>{@link #PLAYER_STATE_ERROR}: <p>In general, playback might fail due to various
168  *          reasons such as unsupported audio/video format, poorly interleaved
169  *          audio/video, resolution too high, streaming timeout, and others.
170  *          In addition, due to programming errors, a playback
171  *          control operation might be performed from an <a href="#InvalidStates">invalid state</a>.
172  *          In these cases the player transitions to the <strong>Error</strong> state.</p>
173  *
174  *          <p>If you register an {@link EventCallback#onError onError}}
175  *          <a href="#Callbacks">callback</a>,
176  *          the callback will be performed when entering the state. When programming errors happen,
177  *          such as calling {@link #prepare()} and
178  *          {@link #setDataSource} methods
179  *          from an <a href="#InvalidStates">invalid state</a>, the callback is called with
180  *          {@link #CALL_STATUS_INVALID_OPERATION}. The MediaPlayer2 object enters the
181  *          <strong>Error</strong> state whether or not a callback exists. </p>
182  *
183  *          <p>To recover from an error and reuse a MediaPlayer2 object that is in the <strong>
184  *          Error</strong> state,
185  *          call {@link #reset()}. The object will return to the <strong>Idle</strong>
186  *          state and all state information will be lost.</p>
187  *          </li>
188  * </ol>
189  *
190  * <p>You should follow these best practices when coding an app that uses MediaPlayer2:</p>
191  *
192  * <ul>
193  *
194  * <li>Use <a href="#Callbacks">callbacks</a> to respond to state changes and errors.</li>
195  *
196  * <li>When  a MediaPlayer2 object is no longer being used, call {@link #close()} as soon as
197  * possible to release the resources used by the internal player engine associated with the
198  * MediaPlayer2. Failure to call {@link #close()} may cause subsequent instances of
199  * MediaPlayer2 objects to fallback to software implementations or fail altogether.
200  * You cannot use MediaPlayer2
201  * after you call {@link #close()}. There is no way to bring it back to any other state.</li>
202  *
203  * <li>The current playback position can be retrieved with a call to
204  * {@link #getCurrentPosition()},
205  * which is helpful for applications such as a Music player that need to keep track of the playback
206  * progress.</li>
207  *
208  * <li>The playback position can be adjusted with a call to {@link #seekTo}. Although the
209  * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
210  * while to finish, especially for audio/video being streamed. If you register an
211  * {@link EventCallback#onCallCompleted onCallCompleted} <a href="#Callbacks">callback</a>,
212  * the callback is
213  * called When the seek operation completes with {@link #CALL_COMPLETED_SEEK_TO}.</li>
214  *
215  * <li>You can call {@link #seekTo} from the <strong>Paused</strong> state.
216  * In this case, if you are playing a video stream and
217  * the requested position is valid  one video frame is displayed.</li>
218  *
219  * </ul>
220  *
221  * <h3 id="InvalidStates">Invalid method calls</h3>
222  *
223  * <p>The only methods you safely call from the <strong>Error</strong> state are
224  * {@link #close},
225  * {@link #reset},
226  * {@link #notifyWhenCommandLabelReached},
227  * {@link #clearPendingCommands},
228  * {@link #registerEventCallback},
229  * {@link #unregisterEventCallback}
230  * and {@link #getState}.
231  * Any other methods might throw an exception, return meaningless data, or invoke a
232  * {@link EventCallback#onCallCompleted onCallCompleted} with an error code.</p>
233  *
234  * <p>Most methods can be called from any non-Error state. They will either perform their work or
235  * silently have no effect. The following table lists the methods that will invoke a
236  * {@link EventCallback#onCallCompleted onCallCompleted} with an error code
237  * or throw an exception when they are called from the associated invalid states.</p>
238  *
239  * <table border="0" cellspacing="0" cellpadding="0">
240  * <tr><th>Method Name</th>
241  * <th>Invalid States</th></tr>
242  *
243  * <tr><td>setDataSource</td> <td>{Prepared, Paused, Playing}</td></tr>
244  * <tr><td>prepare</td> <td>{Prepared, Paused, Playing}</td></tr>
245  * <tr><td>play</td> <td>{Idle}</td></tr>
246  * <tr><td>pause</td> <td>{Idle}</td></tr>
247  * <tr><td>seekTo</td> <td>{Idle}</td></tr>
248  * <tr><td>getCurrentPosition</td> <td>{Idle}</td></tr>
249  * <tr><td>getDuration</td> <td>{Idle}</td></tr>
250  * <tr><td>getBufferedPosition</td> <td>{Idle}</td></tr>
251  * <tr><td>getTrackInfo</td> <td>{Idle}</td></tr>
252  * <tr><td>getSelectedTrack</td> <td>{Idle}</td></tr>
253  * <tr><td>selectTrack</td> <td>{Idle}</td></tr>
254  * <tr><td>deselectTrack</td> <td>{Idle}</td></tr>
255  * </table>
256  *
257  * <h3 id="Permissions">Permissions</h3>
258  * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
259  * when used with network-based content.
260  *
261  * <h3 id="Callbacks">Callbacks</h3>
262  * <p>Many errors do not result in a transition to the  <strong>Error</strong> state.
263  * It is good programming practice to register callback listeners using
264  * {@link #registerEventCallback}.
265  * You can receive a callback at any time and from any state.</p>
266  *
267  * <p>If it's important for your app to respond to state changes (for instance, to update the
268  * controls on a transport UI), you should register an
269  * {@link EventCallback#onCallCompleted onCallCompleted} and
270  * detect state change commands by testing the <code>what</code> parameter for a callback from one
271  * of the state transition methods: {@link #CALL_COMPLETED_PREPARE}, {@link #CALL_COMPLETED_PLAY},
272  * and {@link #CALL_COMPLETED_PAUSE}.
273  * Then check the <code>status</code> parameter. The value {@link #CALL_STATUS_NO_ERROR} indicates a
274  * successful transition. Any other value will be an error. Call {@link #getState()} to
275  * determine the current state. </p>
276  *
277  * @hide
278  */
279 public class MediaPlayer2 implements AutoCloseable, AudioRouting {
280     static {
281         System.loadLibrary("media2_jni");
native_init()282         native_init();
283     }
284 
native_init()285     private static native void native_init();
286 
287     private static final int NEXT_SOURCE_STATE_ERROR = -1;
288     private static final int NEXT_SOURCE_STATE_INIT = 0;
289     private static final int NEXT_SOURCE_STATE_PREPARING = 1;
290     private static final int NEXT_SOURCE_STATE_PREPARED = 2;
291 
292     private static final String TAG = "MediaPlayer2";
293 
294     private Context mContext;
295 
296     private long mNativeContext;  // accessed by native methods
297     private long mNativeSurfaceTexture;  // accessed by native methods
298     private int mListenerContext;  // accessed by native methods
299     private SurfaceHolder mSurfaceHolder;
300     private PowerManager.WakeLock mWakeLock = null;
301     private boolean mScreenOnWhilePlaying;
302     private boolean mStayAwake;
303 
304     private final Object mSrcLock = new Object();
305     //--- guarded by |mSrcLock| start
306     private SourceInfo mCurrentSourceInfo;
307     private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>();
308     //--- guarded by |mSrcLock| end
309     private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
310 
311     private volatile float mVolume = 1.0f;
312     private Size mVideoSize = new Size(0, 0);
313 
314     private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool();
315 
316     // Creating a dummy audio track, used for keeping session id alive
317     private final Object mSessionIdLock = new Object();
318     @GuardedBy("mSessionIdLock")
319     private AudioTrack mDummyAudioTrack;
320 
321     private HandlerThread mHandlerThread;
322     private final TaskHandler mTaskHandler;
323     private final Object mTaskLock = new Object();
324     @GuardedBy("mTaskLock")
325     private final List<Task> mPendingTasks = new LinkedList<>();
326     @GuardedBy("mTaskLock")
327     private Task mCurrentTask;
328     private final AtomicLong mTaskIdGenerator = new AtomicLong(0);
329 
330     @GuardedBy("mTaskLock")
331     boolean mIsPreviousCommandSeekTo = false;
332     // |mPreviousSeekPos| and |mPreviousSeekMode| are valid only when |mIsPreviousCommandSeekTo|
333     // is true, and they are accessed on |mHandlerThread| only.
334     long mPreviousSeekPos = -1;
335     int mPreviousSeekMode = SEEK_PREVIOUS_SYNC;
336 
337     @GuardedBy("this")
338     private boolean mReleased;
339 
340     private final CloseGuard mGuard = CloseGuard.get();
341 
342     /**
343      * Default constructor.
344      * <p>When done with the MediaPlayer2, you should call {@link #close()},
345      * to free the resources. If not released, too many MediaPlayer2 instances may
346      * result in an exception.</p>
347      */
MediaPlayer2(@onNull Context context)348     public MediaPlayer2(@NonNull Context context) {
349         mGuard.open("close");
350 
351         mContext = context;
352         mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
353         mHandlerThread.start();
354         Looper looper = mHandlerThread.getLooper();
355         mTaskHandler = new TaskHandler(this, looper);
356         AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
357         int sessionId = am.generateAudioSessionId();
358         keepAudioSessionIdAlive(sessionId);
359 
360         /* Native setup requires a weak reference to our object.
361          * It's easier to create it here than in C++.
362          */
363         native_setup(sessionId, new WeakReference<MediaPlayer2>(this));
364     }
365 
native_setup(int sessionId, Object mediaplayer2This)366     private native void native_setup(int sessionId, Object mediaplayer2This);
367 
368     /**
369      * Releases the resources held by this {@code MediaPlayer2} object.
370      *
371      * It is considered good practice to call this method when you're
372      * done using the MediaPlayer2. In particular, whenever an Activity
373      * of an application is paused (its onPause() method is called),
374      * or stopped (its onStop() method is called), this method should be
375      * invoked to release the MediaPlayer2 object, unless the application
376      * has a special need to keep the object around. In addition to
377      * unnecessary resources (such as memory and instances of codecs)
378      * being held, failure to call this method immediately if a
379      * MediaPlayer2 object is no longer needed may also lead to
380      * continuous battery consumption for mobile devices, and playback
381      * failure for other applications if no multiple instances of the
382      * same codec are supported on a device. Even if multiple instances
383      * of the same codec are supported, some performance degradation
384      * may be expected when unnecessary multiple instances are used
385      * at the same time.
386      *
387      * {@code close()} may be safely called after a prior {@code close()}.
388      * This class implements the Java {@code AutoCloseable} interface and
389      * may be used with try-with-resources.
390      */
391     // This is a synchronous call.
392     @Override
close()393     public void close() {
394         synchronized (mGuard) {
395             mGuard.close();
396         }
397         release();
398     }
399 
release()400     private synchronized void release() {
401         if (mReleased) {
402             return;
403         }
404         stayAwake(false);
405         updateSurfaceScreenOn();
406         synchronized (mEventCbLock) {
407             mEventCallbackRecords.clear();
408         }
409         if (mHandlerThread != null) {
410             mHandlerThread.quitSafely();
411             mHandlerThread = null;
412         }
413 
414         clearSourceInfos();
415 
416         // Modular DRM clean up
417         synchronized (mDrmEventCallbackLock) {
418             mDrmEventCallback = null;
419         }
420         clearMediaDrmObjects();
421 
422         native_release();
423 
424         synchronized (mSessionIdLock) {
425             mDummyAudioTrack.release();
426         }
427 
428         mReleased = true;
429     }
430 
clearMediaDrmObjects()431     void clearMediaDrmObjects() {
432         Collection<MediaDrm> drmObjs = mDrmObjs.values();
433         synchronized (mDrmObjs) {
434             for (MediaDrm drmObj : drmObjs) {
435                 drmObj.close();
436             }
437             mDrmObjs.clear();
438         }
439     }
440 
native_release()441     private native void native_release();
442 
443     // Have to declare protected for finalize() since it is protected
444     // in the base class Object.
445     @Override
finalize()446     protected void finalize() throws Throwable {
447         if (mGuard != null) {
448             mGuard.warnIfOpen();
449         }
450 
451         close();
452         native_finalize();
453     }
454 
native_finalize()455     private native void native_finalize();
456 
457     /**
458      * Resets the MediaPlayer2 to its uninitialized state. After calling
459      * this method, you will have to initialize it again by setting the
460      * data source and calling prepare().
461      */
462     // This is a synchronous call.
reset()463     public void reset() {
464         clearSourceInfos();
465         clearMediaDrmObjects();
466 
467         stayAwake(false);
468         native_reset();
469 
470         AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
471         int sessionId = am.generateAudioSessionId();
472         keepAudioSessionIdAlive(sessionId);
473 
474         // make sure none of the listeners get called anymore
475         if (mTaskHandler != null) {
476             mTaskHandler.removeCallbacksAndMessages(null);
477         }
478 
479     }
480 
native_reset()481     private native void native_reset();
482 
483     /**
484      * Starts or resumes playback. If playback had previously been paused,
485      * playback will continue from where it was paused. If playback had
486      * reached end of stream and been paused, or never started before,
487      * playback will start at the beginning.
488      *
489      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
490      */
491     // This is an asynchronous call.
play()492     public @NonNull Object play() {
493         return addTask(new Task(CALL_COMPLETED_PLAY, false) {
494             @Override
495             void process() {
496                 stayAwake(true);
497                 native_start();
498             }
499         });
500     }
501 
502     private native void native_start() throws IllegalStateException;
503 
504     /**
505      * Prepares the player for playback, asynchronously.
506      *
507      * After setting the datasource and the display surface, you need to call prepare().
508      *
509      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
510      */
511     // This is an asynchronous call.
512     public @NonNull Object prepare() {
513         return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
514             @Override
515             void process() {
516                 native_prepare();
517             }
518         });
519     }
520 
521     private native void native_prepare();
522 
523     /**
524      * Pauses playback. Call play() to resume.
525      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
526      */
527     // This is an asynchronous call.
528     public @NonNull Object pause() {
529         return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
530             @Override
531             void process() {
532                 stayAwake(false);
533 
534                 native_pause();
535             }
536         });
537     }
538 
539     private native void native_pause() throws IllegalStateException;
540 
541     /**
542      * Tries to play next data source if applicable.
543      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
544      */
545     // This is an asynchronous call.
546     public @NonNull Object skipToNext() {
547         return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
548             @Override
549             void process() {
550                 if (getState() == PLAYER_STATE_PLAYING) {
551                     native_pause();
552                 }
553                 playNextDataSource();
554             }
555         });
556     }
557 
558     /**
559      * Gets the current playback position.
560      *
561      * @return the current position in milliseconds
562      */
563     public native long getCurrentPosition();
564 
565     /**
566      * Gets the duration of the current data source.
567      * Same as {@link #getDuration(DataSourceDesc)} with
568      * {@code dsd = getCurrentDataSource()}.
569      *
570      * @return the duration in milliseconds, if no duration is available
571      *         (for example, if streaming live content), -1 is returned.
572      * @throws NullPointerException if current data source is null
573      */
574     public long getDuration() {
575         return getDuration(getCurrentDataSource());
576     }
577 
578     /**
579      * Gets the duration of the dsd.
580      *
581      * @param dsd the descriptor of data source of which you want to get duration
582      * @return the duration in milliseconds, if no duration is available
583      *         (for example, if streaming live content), -1 is returned.
584      * @throws NullPointerException if dsd is null
585      */
586     public long getDuration(@NonNull DataSourceDesc dsd) {
587         if (dsd == null) {
588             throw new NullPointerException("non-null dsd is expected");
589         }
590         SourceInfo sourceInfo = getSourceInfo(dsd);
591         if (sourceInfo == null) {
592             return -1;
593         }
594 
595         return native_getDuration(sourceInfo.mId);
596     }
597 
598     private native long native_getDuration(long srcId);
599 
600     /**
601      * Gets the buffered media source position of current data source.
602      * Same as {@link #getBufferedPosition(DataSourceDesc)} with
603      * {@code dsd = getCurrentDataSource()}.
604      *
605      * @return the current buffered media source position in milliseconds
606      * @throws NullPointerException if current data source is null
607      */
608     public long getBufferedPosition() {
609         return getBufferedPosition(getCurrentDataSource());
610     }
611 
612     /**
613      * Gets the buffered media source position of given dsd.
614      * For example a buffering update of 8000 milliseconds when 5000 milliseconds of the content
615      * has already been played indicates that the next 3000 milliseconds of the
616      * content to play has been buffered.
617      *
618      * @param dsd the descriptor of data source of which you want to get buffered position
619      * @return the current buffered media source position in milliseconds
620      * @throws NullPointerException if dsd is null
621      */
622     public long getBufferedPosition(@NonNull DataSourceDesc dsd) {
623         if (dsd == null) {
624             throw new NullPointerException("non-null dsd is expected");
625         }
626         SourceInfo sourceInfo = getSourceInfo(dsd);
627         if (sourceInfo == null) {
628             return 0;
629         }
630 
631         // Use cached buffered percent for now.
632         int bufferedPercentage = sourceInfo.mBufferedPercentage.get();
633 
634         long duration = getDuration(dsd);
635         if (duration < 0) {
636             duration = 0;
637         }
638 
639         return duration * bufferedPercentage / 100;
640     }
641 
642     /**
643      * MediaPlayer2 has not been prepared or just has been reset.
644      * In this state, MediaPlayer2 doesn't fetch data.
645      */
646     public static final int PLAYER_STATE_IDLE = 1001;
647 
648     /**
649      * MediaPlayer2 has been just prepared.
650      * In this state, MediaPlayer2 just fetches data from media source,
651      * but doesn't actively render data.
652      */
653     public static final int PLAYER_STATE_PREPARED = 1002;
654 
655     /**
656      * MediaPlayer2 is paused.
657      * In this state, MediaPlayer2 has allocated resources to construct playback
658      * pipeline, but it doesn't actively render data.
659      */
660     public static final int PLAYER_STATE_PAUSED = 1003;
661 
662     /**
663      * MediaPlayer2 is actively playing back data.
664      */
665     public static final int PLAYER_STATE_PLAYING = 1004;
666 
667     /**
668      * MediaPlayer2 has hit some fatal error and cannot continue playback.
669      */
670     public static final int PLAYER_STATE_ERROR = 1005;
671 
672     /**
673      * @hide
674      */
675     @IntDef(flag = false, prefix = "MEDIAPLAYER2_STATE", value = {
676         PLAYER_STATE_IDLE,
677         PLAYER_STATE_PREPARED,
678         PLAYER_STATE_PAUSED,
679         PLAYER_STATE_PLAYING,
680         PLAYER_STATE_ERROR })
681     @Retention(RetentionPolicy.SOURCE)
682     public @interface MediaPlayer2State {}
683 
684     /**
685      * Gets the current player state.
686      *
687      * @return the current player state.
688      */
689     public @MediaPlayer2State int getState() {
690         return native_getState();
691     }
692 
693     private native int native_getState();
694 
695     /**
696      * Sets the audio attributes for this MediaPlayer2.
697      * See {@link AudioAttributes} for how to build and configure an instance of this class.
698      * You must call this method before {@link #play()} and {@link #pause()} in order
699      * for the audio attributes to become effective thereafter.
700      * @param attributes a non-null set of audio attributes
701      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
702      */
703     // This is an asynchronous call.
704     public @NonNull Object setAudioAttributes(@NonNull AudioAttributes attributes) {
705         return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
706             @Override
707             void process() {
708                 if (attributes == null) {
709                     final String msg = "Cannot set AudioAttributes to null";
710                     throw new IllegalArgumentException(msg);
711                 }
712                 native_setAudioAttributes(attributes);
713             }
714         });
715     }
716 
717     // return true if the parameter is set successfully, false otherwise
718     private native boolean native_setAudioAttributes(AudioAttributes audioAttributes);
719 
720     /**
721      * Gets the audio attributes for this MediaPlayer2.
722      * @return attributes a set of audio attributes
723      */
724     public @NonNull AudioAttributes getAudioAttributes() {
725         return native_getAudioAttributes();
726     }
727 
728     private native AudioAttributes native_getAudioAttributes();
729 
730     /**
731      * Sets the data source as described by a DataSourceDesc.
732      * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor}
733      * in the {@link FileDataSourceDesc} will be closed by the player.
734      *
735      * @param dsd the descriptor of data source you want to play
736      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
737      */
738     // This is an asynchronous call.
739     public @NonNull Object setDataSource(@NonNull DataSourceDesc dsd) {
740         return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
741             @Override
742             void process() throws IOException {
743                 checkDataSourceDesc(dsd);
744                 int state = getState();
745                 try {
746                     if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
747                         throw new IllegalStateException("called in wrong state " + state);
748                     }
749 
750                     synchronized (mSrcLock) {
751                         setCurrentSourceInfo_l(new SourceInfo(dsd));
752                         handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
753                     }
754                 } finally {
755                     dsd.close();
756                 }
757             }
758 
759         });
760     }
761 
762     /**
763      * Sets a single data source as described by a DataSourceDesc which will be played
764      * after current data source is finished.
765      * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor}
766      * in the {@link FileDataSourceDesc} will be closed by the player.
767      *
768      * @param dsd the descriptor of data source you want to play after current one
769      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
770      */
771     // This is an asynchronous call.
772     public @NonNull Object setNextDataSource(@NonNull DataSourceDesc dsd) {
773         return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
774             @Override
775             void process() {
776                 checkDataSourceDesc(dsd);
777                 synchronized (mSrcLock) {
778                     clearNextSourceInfos_l();
779                     mNextSourceInfos.add(new SourceInfo(dsd));
780                 }
781                 prepareNextDataSource();
782             }
783         });
784     }
785 
786     /**
787      * Sets a list of data sources to be played sequentially after current data source is done.
788      * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor}
789      * in the {@link FileDataSourceDesc} will be closed by the player.
790      *
791      * @param dsds the list of data sources you want to play after current one
792      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
793      */
794     // This is an asynchronous call.
795     public @NonNull Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
796         return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
797             @Override
798             void process() {
799                 if (dsds == null || dsds.size() == 0) {
800                     throw new IllegalArgumentException("data source list cannot be null or empty.");
801                 }
802                 boolean hasError = false;
803                 for (DataSourceDesc dsd : dsds) {
804                     if (dsd == null) {
805                         hasError = true;
806                         continue;
807                     }
808                     if (dsd instanceof FileDataSourceDesc) {
809                         FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd;
810                         if (fdsd.isPFDClosed()) {
811                             hasError = true;
812                             continue;
813                         }
814 
815                         fdsd.incCount();
816                     }
817                 }
818                 if (hasError) {
819                     for (DataSourceDesc dsd : dsds) {
820                         if (dsd != null) {
821                             dsd.close();
822                         }
823                     }
824                     throw new IllegalArgumentException("invalid data source list");
825                 }
826 
827                 synchronized (mSrcLock) {
828                     clearNextSourceInfos_l();
829                     for (DataSourceDesc dsd : dsds) {
830                         mNextSourceInfos.add(new SourceInfo(dsd));
831                     }
832                 }
833                 prepareNextDataSource();
834             }
835         });
836     }
837 
838     // throws IllegalArgumentException if dsd is null or underline PFD of dsd has been closed.
839     private void checkDataSourceDesc(DataSourceDesc dsd) {
840         if (dsd == null) {
841             throw new IllegalArgumentException("dsd is expected to be non null");
842         }
843         if (dsd instanceof FileDataSourceDesc) {
844             FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd;
845             if (fdsd.isPFDClosed()) {
846                 throw new IllegalArgumentException("the underline FileDescriptor has been closed");
847             }
848             fdsd.incCount();
849         }
850     }
851 
852     /**
853      * Removes all data sources pending to be played.
854      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
855      */
856     // This is an asynchronous call.
857     public @NonNull Object clearNextDataSources() {
858         return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
859             @Override
860             void process() {
861                 synchronized (mSrcLock) {
862                     clearNextSourceInfos_l();
863                 }
864             }
865         });
866     }
867 
868     /**
869      * Gets the current data source as described by a DataSourceDesc.
870      *
871      * @return the current DataSourceDesc
872      */
873     public @Nullable DataSourceDesc getCurrentDataSource() {
874         synchronized (mSrcLock) {
875             return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD;
876         }
877     }
878 
879     private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId)
880             throws IOException {
881         Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
882 
883         if (dsd instanceof FileDataSourceDesc) {
884             FileDataSourceDesc fileDSD = (FileDataSourceDesc) dsd;
885             ParcelFileDescriptor pfd = fileDSD.getParcelFileDescriptor();
886             if (pfd.getStatSize() == -1) {
887                 // Underlying pipeline doesn't understand '-1' size. Create a wrapper for
888                 // translation.
889                 // TODO: Make native code handle '-1' size.
890                 handleDataSource(isCurrent,
891                         srcId,
892                         new ProxyDataSourceCallback(pfd),
893                         fileDSD.getStartPosition(),
894                         fileDSD.getEndPosition());
895             } else {
896                 handleDataSource(isCurrent,
897                         srcId,
898                         pfd,
899                         fileDSD.getOffset(),
900                         fileDSD.getLength(),
901                         fileDSD.getStartPosition(),
902                         fileDSD.getEndPosition());
903             }
904         } else if (dsd instanceof UriDataSourceDesc) {
905             UriDataSourceDesc uriDSD = (UriDataSourceDesc) dsd;
906             handleDataSource(isCurrent,
907                              srcId,
908                              mContext,
909                              uriDSD.getUri(),
910                              uriDSD.getHeaders(),
911                              uriDSD.getCookies(),
912                              uriDSD.getStartPosition(),
913                              uriDSD.getEndPosition());
914         } else {
915             throw new IllegalArgumentException("Unsupported DataSourceDesc. " + dsd.toString());
916         }
917     }
918 
919     /**
920      * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
921      * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
922      * this API to pass the cookies as a list of HttpCookie. If the app has not installed
923      * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
924      * the provided cookies. If the app has installed its own handler already, this API requires the
925      * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
926      *
927      * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
928      * but that can be changed with key/value pairs through the headers parameter with
929      * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
930      * disallow or allow cross domain redirection.
931      *
932      * @throws IllegalArgumentException if cookies are provided and the installed handler is not
933      *                                  a CookieManager
934      * @throws IllegalStateException    if it is called in an invalid state
935      * @throws NullPointerException     if context or uri is null
936      * @throws IOException              if uri has a file scheme and an I/O error occurs
937      */
938     private void handleDataSource(
939             boolean isCurrent, long srcId,
940             @NonNull Context context, @NonNull Uri uri,
941             @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies,
942             long startPos, long endPos)
943             throws IOException {
944         // The context and URI usually belong to the calling user. Get a resolver for that user.
945         final ContentResolver resolver = context.getContentResolver();
946         final String scheme = uri.getScheme();
947         if (ContentResolver.SCHEME_FILE.equals(scheme)) {
948             handleDataSource(isCurrent, srcId, uri.getPath(), null, null, startPos, endPos);
949             return;
950         }
951 
952         final int ringToneType = RingtoneManager.getDefaultType(uri);
953         try {
954             AssetFileDescriptor afd;
955             // Try requested Uri locally first
956             if (ContentResolver.SCHEME_CONTENT.equals(scheme) && ringToneType != -1) {
957                 afd = RingtoneManager.openDefaultRingtoneUri(context, uri);
958                 if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
959                     return;
960                 }
961                 final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(
962                         context, ringToneType);
963                 afd = resolver.openAssetFileDescriptor(actualUri, "r");
964             } else {
965                 afd = resolver.openAssetFileDescriptor(uri, "r");
966             }
967             if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
968                 return;
969             }
970         } catch (NullPointerException | SecurityException | IOException ex) {
971             Log.w(TAG, "Couldn't open " + uri == null ? "null uri" : uri.toSafeString(), ex);
972             // Fallback to media server
973         }
974         handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies, startPos, endPos);
975     }
976 
977     private boolean attemptDataSource(boolean isCurrent, long srcId, AssetFileDescriptor afd,
978             long startPos, long endPos) throws IOException {
979         try {
980             if (afd.getDeclaredLength() < 0) {
981                 handleDataSource(isCurrent,
982                         srcId,
983                         ParcelFileDescriptor.dup(afd.getFileDescriptor()),
984                         0,
985                         DataSourceDesc.LONG_MAX,
986                         startPos,
987                         endPos);
988             } else {
989                 handleDataSource(isCurrent,
990                         srcId,
991                         ParcelFileDescriptor.dup(afd.getFileDescriptor()),
992                         afd.getStartOffset(),
993                         afd.getDeclaredLength(),
994                         startPos,
995                         endPos);
996             }
997             return true;
998         } catch (NullPointerException | SecurityException | IOException ex) {
999             Log.w(TAG, "Couldn't open srcId:" + srcId + ": " + ex);
1000             return false;
1001         } finally {
1002             if (afd != null) {
1003                 afd.close();
1004             }
1005         }
1006     }
1007 
1008     private void handleDataSource(
1009             boolean isCurrent, long srcId,
1010             String path, Map<String, String> headers, List<HttpCookie> cookies,
1011             long startPos, long endPos)
1012             throws IOException {
1013         String[] keys = null;
1014         String[] values = null;
1015 
1016         if (headers != null) {
1017             keys = new String[headers.size()];
1018             values = new String[headers.size()];
1019 
1020             int i = 0;
1021             for (Map.Entry<String, String> entry: headers.entrySet()) {
1022                 keys[i] = entry.getKey();
1023                 values[i] = entry.getValue();
1024                 ++i;
1025             }
1026         }
1027         handleDataSource(isCurrent, srcId, path, keys, values, cookies, startPos, endPos);
1028     }
1029 
1030     private void handleDataSource(boolean isCurrent, long srcId,
1031             String path, String[] keys, String[] values, List<HttpCookie> cookies,
1032             long startPos, long endPos)
1033             throws IOException {
1034         final Uri uri = Uri.parse(path);
1035         final String scheme = uri.getScheme();
1036         if ("file".equals(scheme)) {
1037             path = uri.getPath();
1038         } else if (scheme != null) {
1039             // handle non-file sources
1040             Media2Utils.storeCookies(cookies);
1041             nativeHandleDataSourceUrl(
1042                     isCurrent,
1043                     srcId,
1044                     Media2HTTPService.createHTTPService(path),
1045                     path,
1046                     keys,
1047                     values,
1048                     startPos,
1049                     endPos);
1050             return;
1051         }
1052 
1053         final File file = new File(path);
1054         if (file.exists()) {
1055             FileInputStream is = new FileInputStream(file);
1056             FileDescriptor fd = is.getFD();
1057             handleDataSource(isCurrent, srcId, ParcelFileDescriptor.dup(fd),
1058                     0, DataSourceDesc.LONG_MAX, startPos, endPos);
1059             is.close();
1060         } else {
1061             throw new IOException("handleDataSource failed.");
1062         }
1063     }
1064 
1065     private native void nativeHandleDataSourceUrl(
1066             boolean isCurrent, long srcId,
1067             Media2HTTPService httpService, String path, String[] keys, String[] values,
1068             long startPos, long endPos)
1069             throws IOException;
1070 
1071     /**
1072      * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
1073      * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
1074      * to close the file descriptor. It is safe to do so as soon as this call returns.
1075      *
1076      * @throws IllegalStateException if it is called in an invalid state
1077      * @throws IllegalArgumentException if fd is not a valid FileDescriptor
1078      * @throws IOException if fd can not be read
1079      */
1080     private void handleDataSource(
1081             boolean isCurrent, long srcId,
1082             ParcelFileDescriptor pfd, long offset, long length,
1083             long startPos, long endPos) throws IOException {
1084         nativeHandleDataSourceFD(isCurrent, srcId, pfd.getFileDescriptor(), offset, length,
1085                 startPos, endPos);
1086     }
1087 
1088     private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
1089             FileDescriptor fd, long offset, long length,
1090             long startPos, long endPos) throws IOException;
1091 
1092     /**
1093      * @throws IllegalStateException if it is called in an invalid state
1094      * @throws IllegalArgumentException if dataSource is not a valid DataSourceCallback
1095      */
1096     private void handleDataSource(boolean isCurrent, long srcId, DataSourceCallback dataSource,
1097             long startPos, long endPos) {
1098         nativeHandleDataSourceCallback(isCurrent, srcId, dataSource, startPos, endPos);
1099     }
1100 
1101     private native void nativeHandleDataSourceCallback(
1102             boolean isCurrent, long srcId, DataSourceCallback dataSource,
1103             long startPos, long endPos);
1104 
1105     // return true if there is a next data source, false otherwise.
1106     // This function should be always called on |mHandlerThread|.
1107     private boolean prepareNextDataSource() {
1108         HandlerThread handlerThread = mHandlerThread;
1109         if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
1110             Log.e(TAG, "prepareNextDataSource: called on wrong looper");
1111         }
1112 
1113         boolean hasNextDSD;
1114         int state = getState();
1115         synchronized (mSrcLock) {
1116             hasNextDSD = !mNextSourceInfos.isEmpty();
1117             if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
1118                 // Current source has not been prepared yet.
1119                 return hasNextDSD;
1120             }
1121 
1122             SourceInfo nextSource = mNextSourceInfos.peek();
1123             if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) {
1124                 // There is no next source or it's in preparing or prepared state.
1125                 return hasNextDSD;
1126             }
1127 
1128             try {
1129                 nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING;
1130                 handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId);
1131             } catch (Exception e) {
1132                 Message msg = mTaskHandler.obtainMessage(
1133                         MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null);
1134                 mTaskHandler.handleMessage(msg, nextSource.mId);
1135 
1136                 SourceInfo nextSourceInfo = mNextSourceInfos.poll();
1137                 if (nextSource != null) {
1138                     nextSourceInfo.close();
1139                 }
1140                 return prepareNextDataSource();
1141             }
1142         }
1143         return hasNextDSD;
1144     }
1145 
1146     // This function should be always called on |mHandlerThread|.
1147     private void playNextDataSource() {
1148         HandlerThread handlerThread = mHandlerThread;
1149         if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
1150             Log.e(TAG, "playNextDataSource: called on wrong looper");
1151         }
1152 
1153         boolean hasNextDSD = false;
1154         synchronized (mSrcLock) {
1155             if (!mNextSourceInfos.isEmpty()) {
1156                 hasNextDSD = true;
1157                 SourceInfo nextSourceInfo = mNextSourceInfos.peek();
1158                 if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
1159                     // Switch to next source only when it has been prepared.
1160                     setCurrentSourceInfo_l(mNextSourceInfos.poll());
1161 
1162                     long srcId = mCurrentSourceInfo.mId;
1163                     try {
1164                         nativePlayNextDataSource(srcId);
1165                     } catch (Exception e) {
1166                         Message msg2 = mTaskHandler.obtainMessage(
1167                                 MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
1168                         mTaskHandler.handleMessage(msg2, srcId);
1169                         // Keep |mNextSourcePlayPending|
1170                         hasNextDSD = prepareNextDataSource();
1171                     }
1172                     if (hasNextDSD) {
1173                         stayAwake(true);
1174 
1175                         // Now a new current src is playing.
1176                         // Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source.
1177                     }
1178                 } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) {
1179                     hasNextDSD = prepareNextDataSource();
1180                 }
1181             }
1182         }
1183 
1184         if (!hasNextDSD) {
1185             sendEvent(new EventNotifier() {
1186                 @Override
1187                 public void notify(EventCallback callback) {
1188                     callback.onInfo(
1189                             MediaPlayer2.this, null, MEDIA_INFO_DATA_SOURCE_LIST_END, 0);
1190                 }
1191             });
1192         }
1193     }
1194 
1195     private native void nativePlayNextDataSource(long srcId);
1196 
1197     /**
1198      * Configures the player to loop on the current data source.
1199      * @param loop true if the current data source is meant to loop.
1200      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1201      */
1202     // This is an asynchronous call.
1203     public @NonNull Object loopCurrent(boolean loop) {
1204         return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
1205             @Override
1206             void process() {
1207                 setLooping(loop);
1208             }
1209         });
1210     }
1211 
1212     private native void setLooping(boolean looping);
1213 
1214     /**
1215      * Sets the volume of the audio of the media to play, expressed as a linear multiplier
1216      * on the audio samples.
1217      * Note that this volume is specific to the player, and is separate from stream volume
1218      * used across the platform.<br>
1219      * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified
1220      * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player.
1221      * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}.
1222      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1223      */
1224     // This is an asynchronous call.
1225     public @NonNull Object setPlayerVolume(float volume) {
1226         return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
1227             @Override
1228             void process() {
1229                 mVolume = volume;
1230                 native_setVolume(volume);
1231             }
1232         });
1233     }
1234 
1235     private native void native_setVolume(float volume);
1236 
1237     /**
1238      * Returns the current volume of this player.
1239      * Note that it does not take into account the associated stream volume.
1240      * @return the player volume.
1241      */
1242     public float getPlayerVolume() {
1243         return mVolume;
1244     }
1245 
1246     /**
1247      * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
1248      */
1249     public float getMaxPlayerVolume() {
1250         return 1.0f;
1251     }
1252 
1253     /**
1254      * Insert a task in the command queue to help the client to identify whether a batch
1255      * of commands has been finished. When this command is processed, a notification
1256      * {@link EventCallback#onCommandLabelReached onCommandLabelReached} will be fired with the
1257      * given {@code label}.
1258      *
1259      * @see EventCallback#onCommandLabelReached
1260      *
1261      * @param label An application specific Object used to help to identify the completeness
1262      * of a batch of commands.
1263      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1264      */
1265     // This is an asynchronous call.
1266     public @NonNull Object notifyWhenCommandLabelReached(@NonNull Object label) {
1267         return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
1268             @Override
1269             void process() {
1270                 sendEvent(new EventNotifier() {
1271                     @Override
1272                     public void notify(EventCallback callback) {
1273                         callback.onCommandLabelReached(
1274                                 MediaPlayer2.this, label);
1275                     }
1276                 });
1277             }
1278         });
1279     }
1280 
1281     /**
1282      * Sets the {@link SurfaceHolder} to use for displaying the video
1283      * portion of the media.
1284      *
1285      * Either a surface holder or surface must be set if a display or video sink
1286      * is needed. Not calling this method or {@link #setSurface(Surface)}
1287      * when playing back a video will result in only the audio track being played.
1288      * A null surface holder or surface will result in only the audio track being
1289      * played.
1290      *
1291      * @param sh the SurfaceHolder to use for video display
1292      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1293      */
1294     public @NonNull Object setDisplay(@Nullable SurfaceHolder sh) {
1295         return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) {
1296             @Override
1297             void process() {
1298                 mSurfaceHolder = sh;
1299                 Surface surface;
1300                 if (sh != null) {
1301                     surface = sh.getSurface();
1302                 } else {
1303                     surface = null;
1304                 }
1305                 native_setVideoSurface(surface);
1306                 updateSurfaceScreenOn();
1307             }
1308         });
1309     }
1310 
1311     /**
1312      * Sets the {@link Surface} to be used as the sink for the video portion of
1313      * the media.  Setting a
1314      * Surface will un-set any Surface or SurfaceHolder that was previously set.
1315      * A null surface will result in only the audio track being played.
1316      *
1317      * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
1318      * returned from {@link SurfaceTexture#getTimestamp()} will have an
1319      * unspecified zero point.  These timestamps cannot be directly compared
1320      * between different media sources, different instances of the same media
1321      * source, or multiple runs of the same program.  The timestamp is normally
1322      * monotonically increasing and is unaffected by time-of-day adjustments,
1323      * but it is reset when the position is set.
1324      *
1325      * @param surface The {@link Surface} to be used for the video portion of
1326      * the media.
1327      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1328      */
1329     // This is an asynchronous call.
1330     public @NonNull Object setSurface(@Nullable Surface surface) {
1331         return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
1332             @Override
1333             void process() {
1334                 if (mScreenOnWhilePlaying && surface != null) {
1335                     Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
1336                 }
1337                 mSurfaceHolder = null;
1338                 native_setVideoSurface(surface);
1339                 updateSurfaceScreenOn();
1340             }
1341         });
1342     }
1343 
1344     private native void native_setVideoSurface(Surface surface);
1345 
1346     /**
1347      * Set the low-level power management behavior for this MediaPlayer2. This
1348      * can be used when the MediaPlayer2 is not playing through a SurfaceHolder
1349      * set with {@link #setDisplay(SurfaceHolder)} and thus can use the
1350      * high-level {@link #setScreenOnWhilePlaying(boolean)} feature.
1351      *
1352      * <p>This function has the MediaPlayer2 access the low-level power manager
1353      * service to control the device's power usage while playing is occurring.
1354      * The parameter is a {@link android.os.PowerManager.WakeLock}.
1355      * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK}
1356      * permission.
1357      * By default, no attempt is made to keep the device awake during playback.
1358      *
1359      * @param wakeLock the power wake lock used during playback.
1360      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1361      * @see android.os.PowerManager
1362      */
1363     // This is an asynchronous call.
1364     public @NonNull Object setWakeLock(@NonNull PowerManager.WakeLock wakeLock) {
1365         return addTask(new Task(CALL_COMPLETED_SET_WAKE_LOCK, false) {
1366             @Override
1367             void process() {
1368                 boolean wasHeld = false;
1369 
1370                 if (mWakeLock != null) {
1371                     if (mWakeLock.isHeld()) {
1372                         wasHeld = true;
1373                         mWakeLock.release();
1374                     }
1375                 }
1376 
1377                 mWakeLock = wakeLock;
1378                 if (mWakeLock != null) {
1379                     mWakeLock.setReferenceCounted(false);
1380                     if (wasHeld) {
1381                         mWakeLock.acquire();
1382                     }
1383                 }
1384             }
1385         });
1386     }
1387 
1388     /**
1389      * Control whether we should use the attached SurfaceHolder to keep the
1390      * screen on while video playback is occurring.  This is the preferred
1391      * method over {@link #setWakeLock} where possible, since it doesn't
1392      * require that the application have permission for low-level wake lock
1393      * access.
1394      *
1395      * @param screenOn Supply true to keep the screen on, false to allow it to turn off.
1396      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1397      */
1398     // This is an asynchronous call.
1399     public @NonNull Object setScreenOnWhilePlaying(boolean screenOn) {
1400         return addTask(new Task(CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, false) {
1401             @Override
1402             void process() {
1403                 if (mScreenOnWhilePlaying != screenOn) {
1404                     if (screenOn && mSurfaceHolder == null) {
1405                         Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective"
1406                                 + " without a SurfaceHolder");
1407                     }
1408                     mScreenOnWhilePlaying = screenOn;
1409                     updateSurfaceScreenOn();
1410                 }
1411             }
1412         });
1413     }
1414 
1415     private void stayAwake(boolean awake) {
1416         if (mWakeLock != null) {
1417             if (awake && !mWakeLock.isHeld()) {
1418                 mWakeLock.acquire();
1419             } else if (!awake && mWakeLock.isHeld()) {
1420                 mWakeLock.release();
1421             }
1422         }
1423         mStayAwake = awake;
1424         updateSurfaceScreenOn();
1425     }
1426 
1427     private void updateSurfaceScreenOn() {
1428         if (mSurfaceHolder != null) {
1429             mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
1430         }
1431     }
1432 
1433     /**
1434      * Cancels a pending command.
1435      *
1436      * @param token the command to be canceled. This is the returned Object when command is issued.
1437      * @return {@code false} if the task could not be cancelled; {@code true} otherwise.
1438      * @throws IllegalArgumentException if argument token is null.
1439      */
1440     // This is a synchronous call.
1441     public boolean cancelCommand(@NonNull Object token) {
1442         if (token == null) {
1443             throw new IllegalArgumentException("command token should not be null");
1444         }
1445         synchronized (mTaskLock) {
1446             return mPendingTasks.remove(token);
1447         }
1448     }
1449 
1450     /**
1451      * Discards all pending commands.
1452      */
1453     // This is a synchronous call.
1454     public void clearPendingCommands() {
1455         synchronized (mTaskLock) {
1456             mPendingTasks.clear();
1457         }
1458     }
1459 
1460     //--------------------------------------------------------------------------
1461     // Explicit Routing
1462     //--------------------
1463     private AudioDeviceInfo mPreferredDevice = null;
1464 
1465     /**
1466      * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
1467      * the output from this MediaPlayer2.
1468      * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
1469      *  If deviceInfo is null, default routing is restored.
1470      * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
1471      * does not correspond to a valid audio device.
1472      */
1473     // This is a synchronous call.
1474     @Override
1475     public boolean setPreferredDevice(@Nullable AudioDeviceInfo deviceInfo) {
1476         boolean status = native_setPreferredDevice(deviceInfo);
1477         if (status) {
1478             synchronized (this) {
1479                 mPreferredDevice = deviceInfo;
1480             }
1481         }
1482         return status;
1483     }
1484 
1485     private native boolean native_setPreferredDevice(AudioDeviceInfo device);
1486 
1487     /**
1488      * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
1489      * is not guaranteed to correspond to the actual device being used for playback.
1490      */
1491     @Override
1492     public @Nullable AudioDeviceInfo getPreferredDevice() {
1493         synchronized (this) {
1494             return mPreferredDevice;
1495         }
1496     }
1497 
1498     /**
1499      * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2
1500      * Note: The query is only valid if the MediaPlayer2 is currently playing.
1501      * If the player is not playing, the returned device can be null or correspond to previously
1502      * selected device when the player was last active.
1503      */
1504     @Override
1505     public @Nullable native AudioDeviceInfo getRoutedDevice();
1506 
1507     /**
1508      * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
1509      * changes on this MediaPlayer2.
1510      * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
1511      * notifications of rerouting events.
1512      * @param handler  Specifies the {@link Handler} object for the thread on which to execute
1513      * the callback. If <code>null</code>, the handler on the main looper will be used.
1514      */
1515     // This is a synchronous call.
1516     @Override
1517     public void addOnRoutingChangedListener(@NonNull AudioRouting.OnRoutingChangedListener listener,
1518             @Nullable Handler handler) {
1519         if (listener == null) {
1520             throw new IllegalArgumentException("addOnRoutingChangedListener: listener is NULL");
1521         }
1522         RoutingDelegate routingDelegate = new RoutingDelegate(this, listener, handler);
1523         native_addDeviceCallback(routingDelegate);
1524     }
1525 
1526     private native void native_addDeviceCallback(RoutingDelegate rd);
1527 
1528     /**
1529      * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
1530      * to receive rerouting notifications.
1531      * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
1532      * to remove.
1533      */
1534     // This is a synchronous call.
1535     @Override
1536     public void removeOnRoutingChangedListener(
1537             @NonNull AudioRouting.OnRoutingChangedListener listener) {
1538         if (listener == null) {
1539             throw new IllegalArgumentException("removeOnRoutingChangedListener: listener is NULL");
1540         }
1541         native_removeDeviceCallback(listener);
1542     }
1543 
1544     private native void native_removeDeviceCallback(
1545             AudioRouting.OnRoutingChangedListener listener);
1546 
1547     /**
1548      * Returns the size of the video.
1549      *
1550      * @return the size of the video. The width and height of size could be 0 if there is no video,
1551      * or the size has not been determined yet.
1552      * The {@code EventCallback} can be registered via
1553      * {@link #registerEventCallback(Executor, EventCallback)} to provide a
1554      * notification {@code EventCallback.onVideoSizeChanged} when the size
1555      * is available.
1556      */
1557     public @NonNull Size getVideoSize() {
1558         return mVideoSize;
1559     }
1560 
1561     /**
1562      * Return Metrics data about the current player.
1563      *
1564      * @return a {@link PersistableBundle} containing the set of attributes and values
1565      * available for the media being handled by this instance of MediaPlayer2
1566      * The attributes are descibed in {@link MetricsConstants}.
1567      *
1568      * Additional vendor-specific fields may also be present in the return value.
1569      */
1570     public @Nullable PersistableBundle getMetrics() {
1571         PersistableBundle bundle = native_getMetrics();
1572         return bundle;
1573     }
1574 
1575     private native PersistableBundle native_getMetrics();
1576 
1577     /**
1578      * Gets the current buffering management params used by the source component.
1579      * Calling it only after {@code setDataSource} has been called.
1580      * Each type of data source might have different set of default params.
1581      *
1582      * @return the current buffering management params used by the source component.
1583      * @throws IllegalStateException if the internal player engine has not been
1584      * initialized, or {@code setDataSource} has not been called.
1585      */
1586     // TODO: make it public when ready
1587     @NonNull
1588     native BufferingParams getBufferingParams();
1589 
1590     /**
1591      * Sets buffering management params.
1592      * The object sets its internal BufferingParams to the input, except that the input is
1593      * invalid or not supported.
1594      * Call it only after {@code setDataSource} has been called.
1595      * The input is a hint to MediaPlayer2.
1596      *
1597      * @param params the buffering management params.
1598      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1599      */
1600     // TODO: make it public when ready
1601     // This is an asynchronous call.
1602     @NonNull Object setBufferingParams(@NonNull BufferingParams params) {
1603         return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
1604             @Override
1605             void process() {
1606                 Media2Utils.checkArgument(params != null, "the BufferingParams cannot be null");
1607                 native_setBufferingParams(params);
1608             }
1609         });
1610     }
1611 
1612     private native void native_setBufferingParams(@NonNull BufferingParams params);
1613 
1614     /**
1615      * Sets playback rate using {@link PlaybackParams}. The object sets its internal
1616      * PlaybackParams to the input. This allows the object to resume at previous speed
1617      * when play() is called. Speed of zero is not allowed. Calling it does not change
1618      * the object state.
1619      *
1620      * @param params the playback params.
1621      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1622      */
1623     // This is an asynchronous call.
1624     public @NonNull Object setPlaybackParams(@NonNull PlaybackParams params) {
1625         return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
1626             @Override
1627             void process() {
1628                 Media2Utils.checkArgument(params != null, "the PlaybackParams cannot be null");
1629                 native_setPlaybackParams(params);
1630             }
1631         });
1632     }
1633 
1634     private native void native_setPlaybackParams(@NonNull PlaybackParams params);
1635 
1636     /**
1637      * Gets the playback params, containing the current playback rate.
1638      *
1639      * @return the playback params.
1640      * @throws IllegalStateException if the internal player engine has not been initialized.
1641      */
1642     @NonNull
1643     public native PlaybackParams getPlaybackParams();
1644 
1645     /**
1646      * Sets A/V sync mode.
1647      *
1648      * @param params the A/V sync params to apply
1649      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1650      */
1651     // This is an asynchronous call.
1652     public @NonNull Object setSyncParams(@NonNull SyncParams params) {
1653         return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
1654             @Override
1655             void process() {
1656                 Media2Utils.checkArgument(params != null, "the SyncParams cannot be null");
1657                 native_setSyncParams(params);
1658             }
1659         });
1660     }
1661 
1662     private native void native_setSyncParams(@NonNull SyncParams params);
1663 
1664     /**
1665      * Gets the A/V sync mode.
1666      *
1667      * @return the A/V sync params
1668      * @throws IllegalStateException if the internal player engine has not been initialized.
1669      */
1670     @NonNull
1671     public native SyncParams getSyncParams();
1672 
1673     /**
1674      * Moves the media to specified time position.
1675      * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}.
1676      *
1677      * @param msec the offset in milliseconds from the start to seek to
1678      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1679      */
1680     // This is an asynchronous call.
1681     public @NonNull Object seekTo(long msec) {
1682         return seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */);
1683     }
1684 
1685     /**
1686      * Seek modes used in method seekTo(long, int) to move media position
1687      * to a specified location.
1688      *
1689      * Do not change these mode values without updating their counterparts
1690      * in include/media/IMediaSource.h!
1691      */
1692     /**
1693      * This mode is used with {@link #seekTo(long, int)} to move media position to
1694      * a sync (or key) frame associated with a data source that is located
1695      * right before or at the given time.
1696      *
1697      * @see #seekTo(long, int)
1698      */
1699     public static final int SEEK_PREVIOUS_SYNC    = 0x00;
1700     /**
1701      * This mode is used with {@link #seekTo(long, int)} to move media position to
1702      * a sync (or key) frame associated with a data source that is located
1703      * right after or at the given time.
1704      *
1705      * @see #seekTo(long, int)
1706      */
1707     public static final int SEEK_NEXT_SYNC        = 0x01;
1708     /**
1709      * This mode is used with {@link #seekTo(long, int)} to move media position to
1710      * a sync (or key) frame associated with a data source that is located
1711      * closest to (in time) or at the given time.
1712      *
1713      * @see #seekTo(long, int)
1714      */
1715     public static final int SEEK_CLOSEST_SYNC     = 0x02;
1716     /**
1717      * This mode is used with {@link #seekTo(long, int)} to move media position to
1718      * a frame (not necessarily a key frame) associated with a data source that
1719      * is located closest to or at the given time.
1720      *
1721      * @see #seekTo(long, int)
1722      */
1723     public static final int SEEK_CLOSEST          = 0x03;
1724 
1725     /** @hide */
1726     @IntDef(flag = false, prefix = "SEEK", value = {
1727             SEEK_PREVIOUS_SYNC,
1728             SEEK_NEXT_SYNC,
1729             SEEK_CLOSEST_SYNC,
1730             SEEK_CLOSEST,
1731     })
1732     @Retention(RetentionPolicy.SOURCE)
1733     public @interface SeekMode {}
1734 
1735     /**
1736      * Moves the media to specified time position by considering the given mode.
1737      * <p>
1738      * When seekTo is finished, the user will be notified via
1739      * {@link EventCallback#onCallCompleted} with {@link #CALL_COMPLETED_SEEK_TO}.
1740      * There is at most one active seekTo processed at any time. If there is a to-be-completed
1741      * seekTo, new seekTo requests will be queued in such a way that only the last request
1742      * is kept. When current seekTo is completed, the queued request will be processed if
1743      * that request is different from just-finished seekTo operation, i.e., the requested
1744      * position or mode is different.
1745      *
1746      * @param msec the offset in milliseconds from the start to seek to.
1747      * When seeking to the given time position, there is no guarantee that the data source
1748      * has a frame located at the position. When this happens, a frame nearby will be rendered.
1749      * If msec is negative, time position zero will be used.
1750      * If msec is larger than duration, duration will be used.
1751      * @param mode the mode indicating where exactly to seek to.
1752      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1753      */
1754     // This is an asynchronous call.
1755     public @NonNull Object seekTo(long msec, @SeekMode int mode) {
1756         return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
1757             @Override
1758             void process() {
1759                 if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
1760                     final String msg = "Illegal seek mode: " + mode;
1761                     throw new IllegalArgumentException(msg);
1762                 }
1763                 // TODO: pass long to native, instead of truncating here.
1764                 long posMs = msec;
1765                 if (posMs > Integer.MAX_VALUE) {
1766                     Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to "
1767                             + Integer.MAX_VALUE);
1768                     posMs = Integer.MAX_VALUE;
1769                 } else if (posMs < Integer.MIN_VALUE) {
1770                     Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to "
1771                             + Integer.MIN_VALUE);
1772                     posMs = Integer.MIN_VALUE;
1773                 }
1774 
1775                 synchronized (mTaskLock) {
1776                     if (mIsPreviousCommandSeekTo
1777                             && mPreviousSeekPos == posMs
1778                             && mPreviousSeekMode == mode) {
1779                         throw new CommandSkippedException(
1780                                 "same as previous seekTo");
1781                     }
1782                 }
1783 
1784                 native_seekTo(posMs, mode);
1785 
1786                 synchronized (mTaskLock) {
1787                     mIsPreviousCommandSeekTo = true;
1788                     mPreviousSeekPos = posMs;
1789                     mPreviousSeekMode = mode;
1790                 }
1791             }
1792         });
1793     }
1794 
1795     private native void native_seekTo(long msec, int mode);
1796 
1797     /**
1798      * Get current playback position as a {@link MediaTimestamp}.
1799      * <p>
1800      * The MediaTimestamp represents how the media time correlates to the system time in
1801      * a linear fashion using an anchor and a clock rate. During regular playback, the media
1802      * time moves fairly constantly (though the anchor frame may be rebased to a current
1803      * system time, the linear correlation stays steady). Therefore, this method does not
1804      * need to be called often.
1805      * <p>
1806      * To help users get current playback position, this method always anchors the timestamp
1807      * to the current {@link System#nanoTime system time}, so
1808      * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
1809      *
1810      * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
1811      *         is available, e.g. because the media player has not been initialized.
1812      *
1813      * @see MediaTimestamp
1814      */
1815     @Nullable
1816     public MediaTimestamp getTimestamp() {
1817         try {
1818             // TODO: get the timestamp from native side
1819             return new MediaTimestamp(
1820                     getCurrentPosition() * 1000L,
1821                     System.nanoTime(),
1822                     getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f);
1823         } catch (IllegalStateException e) {
1824             return null;
1825         }
1826     }
1827 
1828     /**
1829      * Checks whether the MediaPlayer2 is looping or non-looping.
1830      *
1831      * @return true if the MediaPlayer2 is currently looping, false otherwise
1832      */
1833     // This is a synchronous call.
1834     public native boolean isLooping();
1835 
1836     /**
1837      * Sets the audio session ID.
1838      *
1839      * @param sessionId the audio session ID.
1840      * The audio session ID is a system wide unique identifier for the audio stream played by
1841      * this MediaPlayer2 instance.
1842      * The primary use of the audio session ID  is to associate audio effects to a particular
1843      * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
1844      * this effect will be applied only to the audio content of media players within the same
1845      * audio session and not to the output mix.
1846      * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
1847      * However, it is possible to force this player to be part of an already existing audio session
1848      * by calling this method.
1849      * This method must be called when player is in {@link #PLAYER_STATE_IDLE} or
1850      * {@link #PLAYER_STATE_PREPARED} state in order to have sessionId take effect.
1851      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1852      */
1853     // This is an asynchronous call.
1854     public @NonNull Object setAudioSessionId(int sessionId) {
1855         final AudioTrack dummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
1856                     AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2,
1857                     AudioTrack.MODE_STATIC, sessionId);
1858         return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
1859             @Override
1860             void process() {
1861                 keepAudioSessionIdAlive(dummyAudioTrack);
1862                 native_setAudioSessionId(sessionId);
1863             }
1864         });
1865     }
1866 
1867     private native void native_setAudioSessionId(int sessionId);
1868 
1869     /**
1870      * Returns the audio session ID.
1871      *
1872      * @return the audio session ID. {@see #setAudioSessionId(int)}
1873      * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was
1874      * contructed.
1875      */
1876     // This is a synchronous call.
1877     public native int getAudioSessionId();
1878 
1879     /**
1880      * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
1881      * effect which can be applied on any sound source that directs a certain amount of its
1882      * energy to this effect. This amount is defined by setAuxEffectSendLevel().
1883      * See {@link #setAuxEffectSendLevel(float)}.
1884      * <p>After creating an auxiliary effect (e.g.
1885      * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
1886      * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
1887      * to attach the player to the effect.
1888      * <p>To detach the effect from the player, call this method with a null effect id.
1889      * <p>This method must be called after one of the overloaded <code> setDataSource </code>
1890      * methods.
1891      * @param effectId system wide unique id of the effect to attach
1892      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1893      */
1894     // This is an asynchronous call.
1895     public @NonNull Object attachAuxEffect(int effectId) {
1896         return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
1897             @Override
1898             void process() {
1899                 native_attachAuxEffect(effectId);
1900             }
1901         });
1902     }
1903 
1904     private native void native_attachAuxEffect(int effectId);
1905 
1906     /**
1907      * Sets the send level of the player to the attached auxiliary effect.
1908      * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
1909      * <p>By default the send level is 0, so even if an effect is attached to the player
1910      * this method must be called for the effect to be applied.
1911      * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
1912      * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
1913      * so an appropriate conversion from linear UI input x to level is:
1914      * x == 0 -> level = 0
1915      * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
1916      * @param level send level scalar
1917      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
1918      */
1919     // This is an asynchronous call.
1920     public @NonNull Object setAuxEffectSendLevel(float level) {
1921         return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
1922             @Override
1923             void process() {
1924                 native_setAuxEffectSendLevel(level);
1925             }
1926         });
1927     }
1928 
1929     private native void native_setAuxEffectSendLevel(float level);
1930 
1931     private static native void native_stream_event_onTearDown(
1932             long nativeCallbackPtr, long userDataPtr);
1933     private static native void native_stream_event_onStreamPresentationEnd(
1934             long nativeCallbackPtr, long userDataPtr);
1935     private static native void native_stream_event_onStreamDataRequest(
1936             long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr);
1937 
1938     /* Do not change these values (starting with INVOKE_ID) without updating
1939      * their counterparts in include/media/mediaplayer2.h!
1940      */
1941     private static final int INVOKE_ID_GET_TRACK_INFO = 1;
1942     private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
1943     private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
1944     private static final int INVOKE_ID_SELECT_TRACK = 4;
1945     private static final int INVOKE_ID_DESELECT_TRACK = 5;
1946     private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
1947 
1948     /**
1949      * Invoke a generic method on the native player using opaque protocol
1950      * buffer message for the request and reply. Both payloads' format is a
1951      * convention between the java caller and the native player.
1952      *
1953      * @param msg PlayerMessage for the extension.
1954      *
1955      * @return PlayerMessage with the data returned by the
1956      * native player.
1957      */
1958     private PlayerMessage invoke(PlayerMessage msg) {
1959         byte[] ret = native_invoke(msg.toByteArray());
1960         if (ret == null) {
1961             return null;
1962         }
1963         try {
1964             return PlayerMessage.parseFrom(ret);
1965         } catch (InvalidProtocolBufferException e) {
1966             return null;
1967         }
1968     }
1969 
1970     private native byte[] native_invoke(byte[] request);
1971 
1972     /**
1973      * @hide
1974      */
1975     @IntDef(flag = false, prefix = "MEDIA_TRACK_TYPE", value = {
1976             TrackInfo.MEDIA_TRACK_TYPE_VIDEO,
1977             TrackInfo.MEDIA_TRACK_TYPE_AUDIO,
1978             TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
1979     })
1980     @Retention(RetentionPolicy.SOURCE)
1981     public @interface TrackType {}
1982 
1983     /**
1984      * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
1985      *
1986      * @see MediaPlayer2#getTrackInfo
1987      */
1988     public static class TrackInfo {
1989         /**
1990          * Gets the track type.
1991          * @return TrackType which indicates if the track is video, audio, timed text.
1992          */
1993         public int getTrackType() {
1994             return mTrackType;
1995         }
1996 
1997         /**
1998          * Gets the language code of the track.
1999          * @return a language code in either way of ISO-639-1 or ISO-639-2.
2000          * When the language is unknown or could not be determined,
2001          * ISO-639-2 language code, "und", is returned.
2002          */
2003         public @NonNull String getLanguage() {
2004             String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
2005             return language == null ? "und" : language;
2006         }
2007 
2008         /**
2009          * Gets the {@link MediaFormat} of the track.  If the format is
2010          * unknown or could not be determined, null is returned.
2011          */
2012         public @Nullable MediaFormat getFormat() {
2013             if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
2014                     || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
2015                 return mFormat;
2016             }
2017             return null;
2018         }
2019 
2020         public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
2021         public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
2022         public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
2023 
2024         /** @hide */
2025         public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
2026 
2027         public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
2028         public static final int MEDIA_TRACK_TYPE_METADATA = 5;
2029 
2030         final int mId;
2031         final int mTrackType;
2032         final MediaFormat mFormat;
2033 
2034         static TrackInfo create(int idx, Iterator<Value> in) {
2035             int trackType = in.next().getInt32Value();
2036             // TODO: build the full MediaFormat; currently we are using createSubtitleFormat
2037             // even for audio/video tracks, meaning we only set the mime and language.
2038             String mime = in.next().getStringValue();
2039             String language = in.next().getStringValue();
2040             MediaFormat format = MediaFormat.createSubtitleFormat(mime, language);
2041 
2042             if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
2043                 format.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value());
2044                 format.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value());
2045                 format.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value());
2046             }
2047             return new TrackInfo(idx, trackType, format);
2048         }
2049 
2050         /** @hide */
2051         TrackInfo(int id, int type, MediaFormat format) {
2052             mId = id;
2053             mTrackType = type;
2054             mFormat = format;
2055         }
2056 
2057         @Override
2058         public String toString() {
2059             StringBuilder out = new StringBuilder(128);
2060             out.append(getClass().getName());
2061             out.append('{');
2062             switch (mTrackType) {
2063                 case MEDIA_TRACK_TYPE_VIDEO:
2064                     out.append("VIDEO");
2065                     break;
2066                 case MEDIA_TRACK_TYPE_AUDIO:
2067                     out.append("AUDIO");
2068                     break;
2069                 case MEDIA_TRACK_TYPE_TIMEDTEXT:
2070                     out.append("TIMEDTEXT");
2071                     break;
2072                 case MEDIA_TRACK_TYPE_SUBTITLE:
2073                     out.append("SUBTITLE");
2074                     break;
2075                 default:
2076                     out.append("UNKNOWN");
2077                     break;
2078             }
2079             out.append(", " + mFormat.toString());
2080             out.append("}");
2081             return out.toString();
2082         }
2083     };
2084 
2085     /**
2086      * Returns a List of track information of current data source.
2087      * Same as {@link #getTrackInfo(DataSourceDesc)} with
2088      * {@code dsd = getCurrentDataSource()}.
2089      *
2090      * @return List of track info. The total number of tracks is the array length.
2091      * Must be called again if an external timed text source has been added after
2092      * addTimedTextSource method is called.
2093      * @throws IllegalStateException if it is called in an invalid state.
2094      * @throws NullPointerException if current data source is null
2095      */
2096     public @NonNull List<TrackInfo> getTrackInfo() {
2097         return getTrackInfo(getCurrentDataSource());
2098     }
2099 
2100     /**
2101      * Returns a List of track information.
2102      *
2103      * @param dsd the descriptor of data source of which you want to get track info
2104      * @return List of track info. The total number of tracks is the array length.
2105      * Must be called again if an external timed text source has been added after
2106      * addTimedTextSource method is called.
2107      * @throws IllegalStateException if it is called in an invalid state.
2108      * @throws NullPointerException if dsd is null
2109      */
2110     public @NonNull List<TrackInfo> getTrackInfo(@NonNull DataSourceDesc dsd) {
2111         if (dsd == null) {
2112             throw new NullPointerException("non-null dsd is expected");
2113         }
2114         SourceInfo sourceInfo = getSourceInfo(dsd);
2115         if (sourceInfo == null) {
2116             return new ArrayList<TrackInfo>(0);
2117         }
2118 
2119         TrackInfo[] trackInfo = getInbandTrackInfo(sourceInfo);
2120         return (trackInfo != null ? Arrays.asList(trackInfo) : new ArrayList<TrackInfo>(0));
2121     }
2122 
2123     private TrackInfo[] getInbandTrackInfo(SourceInfo sourceInfo) throws IllegalStateException {
2124         PlayerMessage request = PlayerMessage.newBuilder()
2125                 .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO))
2126                 .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId))
2127                 .build();
2128         PlayerMessage response = invoke(request);
2129         if (response == null) {
2130             return null;
2131         }
2132         Iterator<Value> in = response.getValuesList().iterator();
2133         int size = in.next().getInt32Value();
2134         if (size == 0) {
2135             return null;
2136         }
2137         TrackInfo[] trackInfo = new TrackInfo[size];
2138         for (int i = 0; i < size; ++i) {
2139             trackInfo[i] = TrackInfo.create(i, in);
2140         }
2141         return trackInfo;
2142     }
2143 
2144     /**
2145      * Returns the index of the audio, video, or subtitle track currently selected for playback.
2146      * The return value is an index into the array returned by {@link #getTrackInfo}, and can
2147      * be used in calls to {@link #selectTrack(TrackInfo)} or {@link #deselectTrack(TrackInfo)}.
2148      * Same as {@link #getSelectedTrack(DataSourceDesc, int)} with
2149      * {@code dsd = getCurrentDataSource()}.
2150      *
2151      * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
2152      * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
2153      * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
2154      * @return metadata corresponding to the audio, video, or subtitle track currently selected for
2155      * playback; {@code null} is returned when there is no selected track for {@code trackType} or
2156      * when {@code trackType} is not one of audio, video, or subtitle.
2157      * @throws IllegalStateException if called after {@link #close()}
2158      * @throws NullPointerException if current data source is null
2159      *
2160      * @see #getTrackInfo()
2161      * @see #selectTrack(TrackInfo)
2162      * @see #deselectTrack(TrackInfo)
2163      */
2164     @Nullable
2165     public TrackInfo getSelectedTrack(@TrackType int trackType) {
2166         return getSelectedTrack(getCurrentDataSource(), trackType);
2167     }
2168 
2169     /**
2170      * Returns the index of the audio, video, or subtitle track currently selected for playback.
2171      * The return value is an index into the array returned by {@link #getTrackInfo}, and can
2172      * be used in calls to {@link #selectTrack(DataSourceDesc, TrackInfo)} or
2173      * {@link #deselectTrack(DataSourceDesc, TrackInfo)}.
2174      *
2175      * @param dsd the descriptor of data source of which you want to get selected track
2176      * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
2177      * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
2178      * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
2179      * @return metadata corresponding to the audio, video, or subtitle track currently selected for
2180      * playback; {@code null} is returned when there is no selected track for {@code trackType} or
2181      * when {@code trackType} is not one of audio, video, or subtitle.
2182      * @throws IllegalStateException if called after {@link #close()}
2183      * @throws NullPointerException if dsd is null
2184      *
2185      * @see #getTrackInfo(DataSourceDesc)
2186      * @see #selectTrack(DataSourceDesc, TrackInfo)
2187      * @see #deselectTrack(DataSourceDesc, TrackInfo)
2188      */
2189     @Nullable
2190     public TrackInfo getSelectedTrack(@NonNull DataSourceDesc dsd, @TrackType int trackType) {
2191         if (dsd == null) {
2192             throw new NullPointerException("non-null dsd is expected");
2193         }
2194         SourceInfo sourceInfo = getSourceInfo(dsd);
2195         if (sourceInfo == null) {
2196             return null;
2197         }
2198 
2199         PlayerMessage request = PlayerMessage.newBuilder()
2200                 .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK))
2201                 .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId))
2202                 .addValues(Value.newBuilder().setInt32Value(trackType))
2203                 .build();
2204         PlayerMessage response = invoke(request);
2205         if (response == null) {
2206             return null;
2207         }
2208         // TODO: return full TrackInfo data from native player instead of index
2209         final int idx = response.getValues(0).getInt32Value();
2210         final List<TrackInfo> trackInfos = getTrackInfo(dsd);
2211         return trackInfos.isEmpty() ? null : trackInfos.get(idx);
2212     }
2213 
2214     /**
2215      * Selects a track of current data source.
2216      * Same as {@link #selectTrack(DataSourceDesc, TrackInfo)} with
2217      * {@code dsd = getCurrentDataSource()}.
2218      *
2219      * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
2220      * object can be obtained from {@link #getTrackInfo()}.
2221      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
2222      *
2223      * This is an asynchronous call.
2224      *
2225      * @see MediaPlayer2#getTrackInfo()
2226      */
2227     @NonNull
2228     public Object selectTrack(@NonNull TrackInfo trackInfo) {
2229         return selectTrack(getCurrentDataSource(), trackInfo);
2230     }
2231 
2232     /**
2233      * Selects a track.
2234      * <p>
2235      * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
2236      * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately.
2237      * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
2238      * </p>
2239      * <p>
2240      * In any valid state, if it is called multiple times on the same type of track (ie. Video,
2241      * Audio, Timed Text), the most recent one will be chosen.
2242      * </p>
2243      * <p>
2244      * The first audio and video tracks are selected by default if available, even though
2245      * this method is not called. However, no timed text track will be selected until
2246      * this function is called.
2247      * </p>
2248      * <p>
2249      * Currently, only timed text tracks or audio tracks can be selected via this method.
2250      * In addition, the support for selecting an audio track at runtime is pretty limited
2251      * in that an audio track can only be selected in the <em>Prepared</em> state.
2252      * </p>
2253      * @param dsd the descriptor of data source of which you want to select track
2254      * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
2255      * object can be obtained from {@link #getTrackInfo()}.
2256      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
2257      *
2258      * This is an asynchronous call.
2259      *
2260      * @see MediaPlayer2#getTrackInfo(DataSourceDesc)
2261      */
2262     @NonNull
2263     public Object selectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) {
2264         return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
2265             @Override
2266             void process() {
2267                 selectOrDeselectTrack(dsd, trackInfo.mId, true /* select */);
2268             }
2269         });
2270     }
2271 
2272     /**
2273      * Deselect a track of current data source.
2274      * Same as {@link #deselectTrack(DataSourceDesc, TrackInfo)} with
2275      * {@code dsd = getCurrentDataSource()}.
2276      *
2277      * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
2278      * object can be obtained from {@link #getTrackInfo()}.
2279      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
2280      *
2281      * This is an asynchronous call.
2282      *
2283      * @see MediaPlayer2#getTrackInfo()
2284      */
2285     @NonNull
2286     public Object deselectTrack(@NonNull TrackInfo trackInfo) {
2287         return deselectTrack(getCurrentDataSource(), trackInfo);
2288     }
2289 
2290     /**
2291      * Deselect a track.
2292      * <p>
2293      * Currently, the track must be a timed text track and no audio or video tracks can be
2294      * deselected. If the timed text track identified by index has not been
2295      * selected before, it throws an exception.
2296      * </p>
2297      * @param dsd the descriptor of data source of which you want to deselect track
2298      * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
2299      * object can be obtained from {@link #getTrackInfo()}.
2300      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
2301      *
2302      * This is an asynchronous call.
2303      *
2304      * @see MediaPlayer2#getTrackInfo(DataSourceDesc)
2305      */
2306     @NonNull
2307     public Object deselectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) {
2308         return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
2309             @Override
2310             void process() {
2311                 selectOrDeselectTrack(dsd, trackInfo.mId, false /* select */);
2312             }
2313         });
2314     }
2315 
2316     private void selectOrDeselectTrack(@NonNull DataSourceDesc dsd, int index, boolean select) {
2317         if (dsd == null) {
2318             throw new IllegalArgumentException("non-null dsd is expected");
2319         }
2320         SourceInfo sourceInfo = getSourceInfo(dsd);
2321         if (sourceInfo == null) {
2322             return;
2323         }
2324 
2325         PlayerMessage request = PlayerMessage.newBuilder()
2326                 .addValues(Value.newBuilder().setInt32Value(
2327                             select ? INVOKE_ID_SELECT_TRACK : INVOKE_ID_DESELECT_TRACK))
2328                 .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId))
2329                 .addValues(Value.newBuilder().setInt32Value(index))
2330                 .build();
2331         invoke(request);
2332     }
2333 
2334     /* Do not change these values without updating their counterparts
2335      * in include/media/mediaplayer2.h!
2336      */
2337     private static final int MEDIA_NOP = 0; // interface test message
2338     private static final int MEDIA_PREPARED = 1;
2339     private static final int MEDIA_PLAYBACK_COMPLETE = 2;
2340     private static final int MEDIA_BUFFERING_UPDATE = 3;
2341     private static final int MEDIA_SEEK_COMPLETE = 4;
2342     private static final int MEDIA_SET_VIDEO_SIZE = 5;
2343     private static final int MEDIA_STARTED = 6;
2344     private static final int MEDIA_PAUSED = 7;
2345     private static final int MEDIA_STOPPED = 8;
2346     private static final int MEDIA_SKIPPED = 9;
2347     private static final int MEDIA_DRM_PREPARED = 10;
2348     private static final int MEDIA_NOTIFY_TIME = 98;
2349     private static final int MEDIA_TIMED_TEXT = 99;
2350     private static final int MEDIA_ERROR = 100;
2351     private static final int MEDIA_INFO = 200;
2352     private static final int MEDIA_SUBTITLE_DATA = 201;
2353     private static final int MEDIA_META_DATA = 202;
2354     private static final int MEDIA_DRM_INFO = 210;
2355 
2356     private class TaskHandler extends Handler {
2357         private MediaPlayer2 mMediaPlayer;
2358 
2359         TaskHandler(MediaPlayer2 mp, Looper looper) {
2360             super(looper);
2361             mMediaPlayer = mp;
2362         }
2363 
2364         @Override
2365         public void handleMessage(Message msg) {
2366             handleMessage(msg, 0);
2367         }
2368 
2369         public void handleMessage(Message msg, long srcId) {
2370             if (mMediaPlayer.mNativeContext == 0) {
2371                 Log.w(TAG, "mediaplayer2 went away with unhandled events");
2372                 return;
2373             }
2374             final int what = msg.arg1;
2375             final int extra = msg.arg2;
2376 
2377             final SourceInfo sourceInfo = getSourceInfo(srcId);
2378             if (sourceInfo == null) {
2379                 return;
2380             }
2381             final DataSourceDesc dsd = sourceInfo.mDSD;
2382 
2383             switch(msg.what) {
2384                 case MEDIA_PREPARED:
2385                 case MEDIA_DRM_PREPARED:
2386                 {
2387                     sourceInfo.mPrepareBarrier--;
2388                     if (sourceInfo.mPrepareBarrier > 0) {
2389                         break;
2390                     } else if (sourceInfo.mPrepareBarrier < 0) {
2391                         Log.w(TAG, "duplicated (drm) prepared events");
2392                         break;
2393                     }
2394 
2395                     if (dsd != null) {
2396                         sendEvent(new EventNotifier() {
2397                             @Override
2398                             public void notify(EventCallback callback) {
2399                                 callback.onInfo(
2400                                         mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0);
2401                             }
2402                         });
2403                     }
2404 
2405                     synchronized (mSrcLock) {
2406                         SourceInfo nextSourceInfo = mNextSourceInfos.peek();
2407                         Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
2408                                 + ", curSrc=" + mCurrentSourceInfo
2409                                 + ", nextSrc=" + nextSourceInfo);
2410 
2411                         if (isCurrentSource(srcId)) {
2412                             prepareNextDataSource();
2413                         } else if (isNextSource(srcId)) {
2414                             nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED;
2415                             if (nextSourceInfo.mPlayPendingAsNextSource) {
2416                                 playNextDataSource();
2417                             }
2418                         }
2419                     }
2420 
2421                     synchronized (mTaskLock) {
2422                         if (mCurrentTask != null
2423                                 && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
2424                                 && mCurrentTask.mDSD == dsd
2425                                 && mCurrentTask.mNeedToWaitForEventToComplete) {
2426                             mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
2427                             mCurrentTask = null;
2428                             processPendingTask_l();
2429                         }
2430                     }
2431                     return;
2432                 }
2433 
2434                 case MEDIA_DRM_INFO:
2435                 {
2436                     if (msg.obj == null) {
2437                         Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
2438                     } else if (msg.obj instanceof byte[]) {
2439                         // The PlayerMessage was parsed already in postEventFromNative
2440 
2441                         final DrmInfo drmInfo;
2442                         synchronized (sourceInfo) {
2443                             if (sourceInfo.mDrmInfo != null) {
2444                                 drmInfo = sourceInfo.mDrmInfo.makeCopy();
2445                             } else {
2446                                 drmInfo = null;
2447                             }
2448                         }
2449 
2450                         // notifying the client outside the lock
2451                         DrmPreparationInfo drmPrepareInfo = null;
2452                         if (drmInfo != null) {
2453                             try {
2454                                 drmPrepareInfo = sendDrmEventWait(
2455                                         new DrmEventNotifier<DrmPreparationInfo>() {
2456                                             @Override
2457                                             public DrmPreparationInfo notifyWait(
2458                                                     DrmEventCallback callback) {
2459                                                 return callback.onDrmInfo(mMediaPlayer, dsd,
2460                                                         drmInfo);
2461                                             }
2462                                         });
2463                             } catch (InterruptedException | ExecutionException
2464                                     | TimeoutException e) {
2465                                 Log.w(TAG, "Exception while waiting for DrmPreparationInfo", e);
2466                             }
2467                         }
2468                         if (sourceInfo.mDrmHandle.setPreparationInfo(drmPrepareInfo)) {
2469                             sourceInfo.mPrepareBarrier++;
2470                             final Task prepareDrmTask;
2471                             prepareDrmTask = newPrepareDrmTask(dsd, drmPrepareInfo.mUUID);
2472                             mTaskHandler.post(new Runnable() {
2473                                 @Override
2474                                 public void run() {
2475                                     // Run as simple Runnable, not Task
2476                                     try {
2477                                         prepareDrmTask.process();
2478                                     } catch (NoDrmSchemeException | IOException e) {
2479                                         final String errMsg;
2480                                         errMsg = "Unexpected Exception during prepareDrm";
2481                                         throw new RuntimeException(errMsg, e);
2482                                     }
2483                                 }
2484                             });
2485                         } else {
2486                             Log.w(TAG, "No valid DrmPreparationInfo set");
2487                         }
2488                     } else {
2489                         Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
2490                     }
2491                     return;
2492                 }
2493 
2494                 case MEDIA_PLAYBACK_COMPLETE:
2495                 {
2496                     if (isCurrentSource(srcId)) {
2497                         sendEvent(new EventNotifier() {
2498                             @Override
2499                             public void notify(EventCallback callback) {
2500                                 callback.onInfo(
2501                                         mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
2502                             }
2503                         });
2504                         stayAwake(false);
2505 
2506                         synchronized (mSrcLock) {
2507                             SourceInfo nextSourceInfo = mNextSourceInfos.peek();
2508                             if (nextSourceInfo != null) {
2509                                 nextSourceInfo.mPlayPendingAsNextSource = true;
2510                             }
2511                             Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
2512                                     + ", curSrc=" + mCurrentSourceInfo
2513                                     + ", nextSrc=" + nextSourceInfo);
2514                         }
2515 
2516                         playNextDataSource();
2517                     }
2518 
2519                     return;
2520                 }
2521 
2522                 case MEDIA_STOPPED:
2523                 case MEDIA_STARTED:
2524                 case MEDIA_PAUSED:
2525                 case MEDIA_SKIPPED:
2526                 case MEDIA_NOTIFY_TIME:
2527                 {
2528                     // Do nothing. The client should have enough information with
2529                     // {@link EventCallback#onMediaTimeDiscontinuity}.
2530                     break;
2531                 }
2532 
2533                 case MEDIA_BUFFERING_UPDATE:
2534                 {
2535                     final int percent = msg.arg1;
2536                     sendEvent(new EventNotifier() {
2537                         @Override
2538                         public void notify(EventCallback callback) {
2539                             callback.onInfo(
2540                                     mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, percent);
2541                         }
2542                     });
2543 
2544                     SourceInfo src = getSourceInfo(srcId);
2545                     if (src != null) {
2546                         src.mBufferedPercentage.set(percent);
2547                     }
2548 
2549                     return;
2550                 }
2551 
2552                 case MEDIA_SEEK_COMPLETE:
2553                 {
2554                     synchronized (mTaskLock) {
2555                         if (!mPendingTasks.isEmpty()
2556                                 && mPendingTasks.get(0).mMediaCallType != CALL_COMPLETED_SEEK_TO
2557                                 && getState() == PLAYER_STATE_PLAYING) {
2558                             mIsPreviousCommandSeekTo = false;
2559                         }
2560 
2561                         if (mCurrentTask != null
2562                                 && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
2563                                 && mCurrentTask.mNeedToWaitForEventToComplete) {
2564                             mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
2565                             mCurrentTask = null;
2566                             processPendingTask_l();
2567                         }
2568                     }
2569                     return;
2570                 }
2571 
2572                 case MEDIA_SET_VIDEO_SIZE:
2573                 {
2574                     final int width = msg.arg1;
2575                     final int height = msg.arg2;
2576 
2577                     mVideoSize = new Size(width, height);
2578                     sendEvent(new EventNotifier() {
2579                         @Override
2580                         public void notify(EventCallback callback) {
2581                             callback.onVideoSizeChanged(
2582                                     mMediaPlayer, dsd, mVideoSize);
2583                         }
2584                     });
2585                     return;
2586                 }
2587 
2588                 case MEDIA_ERROR:
2589                 {
2590                     Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
2591                     sendEvent(new EventNotifier() {
2592                         @Override
2593                         public void notify(EventCallback callback) {
2594                             callback.onError(
2595                                     mMediaPlayer, dsd, what, extra);
2596                         }
2597                     });
2598                     sendEvent(new EventNotifier() {
2599                         @Override
2600                         public void notify(EventCallback callback) {
2601                             callback.onInfo(
2602                                     mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
2603                         }
2604                     });
2605                     stayAwake(false);
2606                     return;
2607                 }
2608 
2609                 case MEDIA_INFO:
2610                 {
2611                     switch (msg.arg1) {
2612                         case MEDIA_INFO_VIDEO_TRACK_LAGGING:
2613                             Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
2614                             break;
2615                     }
2616 
2617                     sendEvent(new EventNotifier() {
2618                         @Override
2619                         public void notify(EventCallback callback) {
2620                             callback.onInfo(
2621                                     mMediaPlayer, dsd, what, extra);
2622                         }
2623                     });
2624 
2625                     if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
2626                         if (isCurrentSource(srcId)) {
2627                             prepareNextDataSource();
2628                         }
2629                     }
2630 
2631                     // No real default action so far.
2632                     return;
2633                 }
2634 
2635                 case MEDIA_TIMED_TEXT:
2636                 {
2637                     final TimedText text;
2638                     if (msg.obj instanceof byte[]) {
2639                         PlayerMessage playerMsg;
2640                         try {
2641                             playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
2642                         } catch (InvalidProtocolBufferException e) {
2643                             Log.w(TAG, "Failed to parse timed text.", e);
2644                             return;
2645                         }
2646                         text = TimedTextUtil.parsePlayerMessage(playerMsg);
2647                     } else {
2648                         text = null;
2649                     }
2650 
2651                     sendEvent(new EventNotifier() {
2652                         @Override
2653                         public void notify(EventCallback callback) {
2654                             callback.onTimedText(
2655                                     mMediaPlayer, dsd, text);
2656                         }
2657                     });
2658                     return;
2659                 }
2660 
2661                 case MEDIA_SUBTITLE_DATA:
2662                 {
2663                     if (msg.obj instanceof byte[]) {
2664                         PlayerMessage playerMsg;
2665                         try {
2666                             playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
2667                         } catch (InvalidProtocolBufferException e) {
2668                             Log.w(TAG, "Failed to parse subtitle data.", e);
2669                             return;
2670                         }
2671                         Iterator<Value> in = playerMsg.getValuesList().iterator();
2672                         final int trackIndex = in.next().getInt32Value();
2673                         TrackInfo trackInfo = getTrackInfo(dsd).get(trackIndex);
2674                         final long startTimeUs = in.next().getInt64Value();
2675                         final long durationTimeUs = in.next().getInt64Value();
2676                         final byte[] subData = in.next().getBytesValue().toByteArray();
2677                         SubtitleData data = new SubtitleData(trackInfo,
2678                                 startTimeUs, durationTimeUs, subData);
2679                         sendEvent(new EventNotifier() {
2680                             @Override
2681                             public void notify(EventCallback callback) {
2682                                 callback.onSubtitleData(
2683                                         mMediaPlayer, dsd, data);
2684                             }
2685                         });
2686                     }
2687                     return;
2688                 }
2689 
2690                 case MEDIA_META_DATA:
2691                 {
2692                     final TimedMetaData data;
2693                     if (msg.obj instanceof byte[]) {
2694                         PlayerMessage playerMsg;
2695                         try {
2696                             playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
2697                         } catch (InvalidProtocolBufferException e) {
2698                             Log.w(TAG, "Failed to parse timed meta data.", e);
2699                             return;
2700                         }
2701                         Iterator<Value> in = playerMsg.getValuesList().iterator();
2702                         data = new TimedMetaData(
2703                                 in.next().getInt64Value(),  // timestampUs
2704                                 in.next().getBytesValue().toByteArray());  // metaData
2705                     } else {
2706                         data = null;
2707                     }
2708 
2709                     sendEvent(new EventNotifier() {
2710                         @Override
2711                         public void notify(EventCallback callback) {
2712                             callback.onTimedMetaDataAvailable(
2713                                     mMediaPlayer, dsd, data);
2714                         }
2715                     });
2716                     return;
2717                 }
2718 
2719                 case MEDIA_NOP: // interface test message - ignore
2720                 {
2721                     break;
2722                 }
2723 
2724                 default:
2725                 {
2726                     Log.e(TAG, "Unknown message type " + msg.what);
2727                     return;
2728                 }
2729             }
2730         }
2731     }
2732 
2733     /*
2734      * Called from native code when an interesting event happens.  This method
2735      * just uses the TaskHandler system to post the event back to the main app thread.
2736      * We use a weak reference to the original MediaPlayer2 object so that the native
2737      * code is safe from the object disappearing from underneath it.  (This is
2738      * the cookie passed to native_setup().)
2739      */
2740     private static void postEventFromNative(Object mediaplayer2Ref, long srcId,
2741                                             int what, int arg1, int arg2, byte[] obj) {
2742         final MediaPlayer2 mp = (MediaPlayer2) ((WeakReference) mediaplayer2Ref).get();
2743         if (mp == null) {
2744             return;
2745         }
2746 
2747         final SourceInfo sourceInfo = mp.getSourceInfo(srcId);
2748         switch (what) {
2749             case MEDIA_DRM_INFO:
2750                 // We need to derive mDrmInfo before prepare() returns so processing it here
2751                 // before the notification is sent to TaskHandler below. TaskHandler runs in the
2752                 // notification looper so its handleMessage might process the event after prepare()
2753                 // has returned.
2754                 Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
2755                 if (obj != null && sourceInfo != null) {
2756                     PlayerMessage playerMsg;
2757                     try {
2758                         playerMsg = PlayerMessage.parseFrom(obj);
2759                     } catch (InvalidProtocolBufferException e) {
2760                         Log.w(TAG, "MEDIA_DRM_INFO failed to parse msg.obj " + obj);
2761                         break;
2762                     }
2763                     DrmInfo drmInfo = DrmInfo.create(playerMsg);
2764                     synchronized (sourceInfo) {
2765                         sourceInfo.mDrmInfo = drmInfo;
2766                     }
2767                 } else {
2768                     Log.w(TAG, "MEDIA_DRM_INFO sourceInfo " + sourceInfo
2769                             + " msg.obj of unexpected type " + obj);
2770                 }
2771                 break;
2772 
2773             case MEDIA_PREPARED:
2774                 // By this time, we've learned about DrmInfo's presence or absence. This is meant
2775                 // mainly for prepare() use case. For prepare(), this still can run to a race
2776                 // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
2777                 // so we also set mDrmInfoResolved in prepare().
2778                 if (sourceInfo != null) {
2779                     synchronized (sourceInfo) {
2780                         sourceInfo.mDrmInfoResolved = true;
2781                     }
2782                 }
2783                 break;
2784         }
2785 
2786         if (mp.mTaskHandler != null) {
2787             Message m = mp.mTaskHandler.obtainMessage(what, arg1, arg2, obj);
2788 
2789             mp.mTaskHandler.post(new Runnable() {
2790                 @Override
2791                 public void run() {
2792                     mp.mTaskHandler.handleMessage(m, srcId);
2793                 }
2794             });
2795         }
2796     }
2797 
2798     /**
2799      * Class encapsulating subtitle data, as received through the
2800      * {@link EventCallback#onSubtitleData} interface.
2801      * <p>
2802      * A {@link SubtitleData} object includes:
2803      * <ul>
2804      * <li> track metadadta in a {@link TrackInfo} object</li>
2805      * <li> the start time (in microseconds) of the data</li>
2806      * <li> the duration (in microseconds) of the data</li>
2807      * <li> the actual data.</li>
2808      * </ul>
2809      * The data is stored in a byte-array, and is encoded in one of the supported in-band
2810      * subtitle formats. The subtitle encoding is determined by the MIME type of the
2811      * {@link TrackInfo} of the subtitle track, one of
2812      * {@link MediaFormat#MIMETYPE_TEXT_CEA_608}, {@link MediaFormat#MIMETYPE_TEXT_CEA_708},
2813      * {@link MediaFormat#MIMETYPE_TEXT_VTT}.
2814      */
2815     public static final class SubtitleData {
2816 
2817         private TrackInfo mTrackInfo;
2818         private long mStartTimeUs;
2819         private long mDurationUs;
2820         private byte[] mData;
2821 
2822         private SubtitleData(TrackInfo trackInfo, long startTimeUs, long durationUs, byte[] data) {
2823             mTrackInfo = trackInfo;
2824             mStartTimeUs = startTimeUs;
2825             mDurationUs = durationUs;
2826             mData = (data != null ? data : new byte[0]);
2827         }
2828 
2829         /**
2830          * @return metadata of track which contains this subtitle data
2831          */
2832         @NonNull
2833         public TrackInfo getTrackInfo() {
2834             return mTrackInfo;
2835         }
2836 
2837         /**
2838          * @return media time at which the subtitle should start to be displayed in microseconds
2839          */
2840         public long getStartTimeUs() {
2841             return mStartTimeUs;
2842         }
2843 
2844         /**
2845          * @return the duration in microsecond during which the subtitle should be displayed
2846          */
2847         public long getDurationUs() {
2848             return mDurationUs;
2849         }
2850 
2851         /**
2852          * Returns the encoded data for the subtitle content.
2853          * Encoding format depends on the subtitle type, refer to
2854          * <a href="https://en.wikipedia.org/wiki/CEA-708">CEA 708</a>,
2855          * <a href="https://en.wikipedia.org/wiki/EIA-608">CEA/EIA 608</a> and
2856          * <a href="https://www.w3.org/TR/webvtt1/">WebVTT</a>, defined by the MIME type
2857          * of the subtitle track.
2858          * @return the encoded subtitle data
2859          */
2860         @NonNull
2861         public byte[] getData() {
2862             return mData;
2863         }
2864     }
2865 
2866     /**
2867      * Interface definition for callbacks to be invoked when the player has the corresponding
2868      * events.
2869      */
2870     public static class EventCallback {
2871         /**
2872          * Called to indicate the video size
2873          *
2874          * The video size (width and height) could be 0 if there was no video,
2875          * or the value was not determined yet.
2876          *
2877          * @param mp the MediaPlayer2 associated with this callback
2878          * @param dsd the DataSourceDesc of this data source
2879          * @param size the size of the video
2880          */
2881         public void onVideoSizeChanged(
2882                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull Size size) { }
2883 
2884         /**
2885          * Called to indicate an avaliable timed text
2886          *
2887          * @param mp the MediaPlayer2 associated with this callback
2888          * @param dsd the DataSourceDesc of this data source
2889          * @param text the timed text sample which contains the text
2890          *             needed to be displayed and the display format.
2891          * @hide
2892          */
2893         public void onTimedText(
2894                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull TimedText text) { }
2895 
2896         /**
2897          * Called to indicate avaliable timed metadata
2898          * <p>
2899          * This method will be called as timed metadata is extracted from the media,
2900          * in the same order as it occurs in the media. The timing of this event is
2901          * not controlled by the associated timestamp.
2902          * <p>
2903          * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates
2904          * {@link TimedMetaData}.
2905          *
2906          * @see MediaPlayer2#selectTrack
2907          * @see MediaPlayer2.OnTimedMetaDataAvailableListener
2908          * @see TimedMetaData
2909          *
2910          * @param mp the MediaPlayer2 associated with this callback
2911          * @param dsd the DataSourceDesc of this data source
2912          * @param data the timed metadata sample associated with this event
2913          */
2914         public void onTimedMetaDataAvailable(
2915                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
2916                 @NonNull TimedMetaData data) { }
2917 
2918         /**
2919          * Called to indicate an error.
2920          *
2921          * @param mp the MediaPlayer2 the error pertains to
2922          * @param dsd the DataSourceDesc of this data source
2923          * @param what the type of error that has occurred.
2924          * @param extra an extra code, specific to the error. Typically
2925          * implementation dependent.
2926          */
2927         public void onError(
2928                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
2929                 @MediaError int what, int extra) { }
2930 
2931         /**
2932          * Called to indicate an info or a warning.
2933          *
2934          * @param mp the MediaPlayer2 the info pertains to.
2935          * @param dsd the DataSourceDesc of this data source
2936          * @param what the type of info or warning.
2937          * @param extra an extra code, specific to the info. Typically
2938          * implementation dependent.
2939          */
2940         public void onInfo(
2941                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
2942                 @MediaInfo int what, int extra) { }
2943 
2944         /**
2945          * Called to acknowledge an API call.
2946          *
2947          * @param mp the MediaPlayer2 the call was made on.
2948          * @param dsd the DataSourceDesc of this data source
2949          * @param what the enum for the API call.
2950          * @param status the returned status code for the call.
2951          */
2952         public void onCallCompleted(
2953                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @CallCompleted int what,
2954                 @CallStatus int status) { }
2955 
2956         /**
2957          * Called to indicate media clock has changed.
2958          *
2959          * @param mp the MediaPlayer2 the media time pertains to.
2960          * @param dsd the DataSourceDesc of this data source
2961          * @param timestamp the new media clock.
2962          */
2963         public void onMediaTimeDiscontinuity(
2964                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
2965                 @NonNull MediaTimestamp timestamp) { }
2966 
2967         /**
2968          * Called to indicate {@link #notifyWhenCommandLabelReached(Object)} has been processed.
2969          *
2970          * @param mp the MediaPlayer2 {@link #notifyWhenCommandLabelReached(Object)} was called on.
2971          * @param label the application specific Object given by
2972          *        {@link #notifyWhenCommandLabelReached(Object)}.
2973          */
2974         public void onCommandLabelReached(@NonNull MediaPlayer2 mp, @NonNull Object label) { }
2975 
2976         /**
2977          * Called when when a player subtitle track has new subtitle data available.
2978          * @param mp the player that reports the new subtitle data
2979          * @param dsd the DataSourceDesc of this data source
2980          * @param data the subtitle data
2981          */
2982         public void onSubtitleData(
2983                 @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
2984                 @NonNull SubtitleData data) { }
2985     }
2986 
2987     private final Object mEventCbLock = new Object();
2988     private ArrayList<Pair<Executor, EventCallback>> mEventCallbackRecords =
2989             new ArrayList<Pair<Executor, EventCallback>>();
2990 
2991     /**
2992      * Registers the callback to be invoked for various events covered by {@link EventCallback}.
2993      *
2994      * @param executor the executor through which the callback should be invoked
2995      * @param eventCallback the callback that will be run
2996      */
2997     // This is a synchronous call.
2998     public void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
2999             @NonNull EventCallback eventCallback) {
3000         if (eventCallback == null) {
3001             throw new IllegalArgumentException("Illegal null EventCallback");
3002         }
3003         if (executor == null) {
3004             throw new IllegalArgumentException(
3005                     "Illegal null Executor for the EventCallback");
3006         }
3007         synchronized (mEventCbLock) {
3008             for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
3009                 if (cb.first == executor && cb.second == eventCallback) {
3010                     Log.w(TAG, "The callback has been registered before.");
3011                     return;
3012                 }
3013             }
3014             mEventCallbackRecords.add(new Pair(executor, eventCallback));
3015         }
3016     }
3017 
3018     /**
3019      * Unregisters the {@link EventCallback}.
3020      *
3021      * @param eventCallback the callback to be unregistered
3022      */
3023     // This is a synchronous call.
3024     public void unregisterEventCallback(@NonNull EventCallback eventCallback) {
3025         synchronized (mEventCbLock) {
3026             for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
3027                 if (cb.second == eventCallback) {
3028                     mEventCallbackRecords.remove(cb);
3029                 }
3030             }
3031         }
3032     }
3033 
3034     private void sendEvent(final EventNotifier notifier) {
3035         synchronized (mEventCbLock) {
3036             try {
3037                 for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
3038                     cb.first.execute(() -> notifier.notify(cb.second));
3039                 }
3040             } catch (RejectedExecutionException e) {
3041                 // The executor has been shut down.
3042                 Log.w(TAG, "The executor has been shut down. Ignoring event.");
3043             }
3044         }
3045     }
3046 
3047     private void sendDrmEvent(final DrmEventNotifier notifier) {
3048         synchronized (mDrmEventCallbackLock) {
3049             try {
3050                 Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
3051                 if (cb != null) {
3052                     cb.first.execute(() -> notifier.notify(cb.second));
3053                 }
3054             } catch (RejectedExecutionException e) {
3055                 // The executor has been shut down.
3056                 Log.w(TAG, "The executor has been shut down. Ignoring drm event.");
3057             }
3058         }
3059     }
3060 
3061     private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier)
3062             throws InterruptedException, ExecutionException, TimeoutException {
3063         return sendDrmEventWait(notifier, 0);
3064     }
3065 
3066     private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier, final long timeoutMs)
3067             throws InterruptedException, ExecutionException, TimeoutException {
3068         synchronized (mDrmEventCallbackLock) {
3069             Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
3070             if (cb != null) {
3071                 CompletableFuture<T> ret = new CompletableFuture<>();
3072                 cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second)));
3073                 return timeoutMs <= 0 ? ret.get() : ret.get(timeoutMs, TimeUnit.MILLISECONDS);
3074             }
3075         }
3076         return null;
3077     }
3078 
3079     private interface EventNotifier {
3080         void notify(EventCallback callback);
3081     }
3082 
3083     private interface DrmEventNotifier<T> {
3084         default void notify(DrmEventCallback callback) { }
3085         default T notifyWait(DrmEventCallback callback) {
3086             return null;
3087         }
3088     }
3089 
3090     /* Do not change these values without updating their counterparts
3091      * in include/media/MediaPlayer2Types.h!
3092      */
3093     /** Unspecified media player error.
3094      * @see EventCallback#onError
3095      */
3096     public static final int MEDIA_ERROR_UNKNOWN = 1;
3097 
3098     /**
3099      * The video is streamed and its container is not valid for progressive
3100      * playback i.e the video's index (e.g moov atom) is not at the start of the
3101      * file.
3102      * @see EventCallback#onError
3103      */
3104     public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;
3105 
3106     /** File or network related operation errors. */
3107     public static final int MEDIA_ERROR_IO = -1004;
3108     /** Bitstream is not conforming to the related coding standard or file spec. */
3109     public static final int MEDIA_ERROR_MALFORMED = -1007;
3110     /** Bitstream is conforming to the related coding standard or file spec, but
3111      * the media framework does not support the feature. */
3112     public static final int MEDIA_ERROR_UNSUPPORTED = -1010;
3113     /** Some operation takes too long to complete, usually more than 3-5 seconds. */
3114     public static final int MEDIA_ERROR_TIMED_OUT = -110;
3115 
3116     /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in
3117      * system/core/include/utils/Errors.h
3118      * @see EventCallback#onError
3119      * @hide
3120      */
3121     public static final int MEDIA_ERROR_SYSTEM = -2147483648;
3122 
3123     /**
3124      * @hide
3125      */
3126     @IntDef(flag = false, prefix = "MEDIA_ERROR", value = {
3127             MEDIA_ERROR_UNKNOWN,
3128             MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK,
3129             MEDIA_ERROR_IO,
3130             MEDIA_ERROR_MALFORMED,
3131             MEDIA_ERROR_UNSUPPORTED,
3132             MEDIA_ERROR_TIMED_OUT,
3133             MEDIA_ERROR_SYSTEM
3134     })
3135     @Retention(RetentionPolicy.SOURCE)
3136     public @interface MediaError {}
3137 
3138     /* Do not change these values without updating their counterparts
3139      * in include/media/MediaPlayer2Types.h!
3140      */
3141     /** Unspecified media player info.
3142      * @see EventCallback#onInfo
3143      */
3144     public static final int MEDIA_INFO_UNKNOWN = 1;
3145 
3146     /** The player just started the playback of this datas source.
3147      * @see EventCallback#onInfo
3148      */
3149     public static final int MEDIA_INFO_DATA_SOURCE_START = 2;
3150 
3151     /** The player just pushed the very first video frame for rendering.
3152      * @see EventCallback#onInfo
3153      */
3154     public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;
3155 
3156     /** The player just rendered the very first audio sample.
3157      * @see EventCallback#onInfo
3158      */
3159     public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4;
3160 
3161     /** The player just completed the playback of this data source.
3162      * @see EventCallback#onInfo
3163      */
3164     public static final int MEDIA_INFO_DATA_SOURCE_END = 5;
3165 
3166     /** The player just completed the playback of all data sources set by {@link #setDataSource},
3167      * {@link #setNextDataSource} and {@link #setNextDataSources}.
3168      * @see EventCallback#onInfo
3169      */
3170     public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6;
3171 
3172     /** The player just completed an iteration of playback loop. This event is sent only when
3173      *  looping is enabled by {@link #loopCurrent}.
3174      * @see EventCallback#onInfo
3175      */
3176     public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7;
3177 
3178     /** The player just prepared a data source.
3179      * @see EventCallback#onInfo
3180      */
3181     public static final int MEDIA_INFO_PREPARED = 100;
3182 
3183     /** The video is too complex for the decoder: it can't decode frames fast
3184      *  enough. Possibly only the audio plays fine at this stage.
3185      * @see EventCallback#onInfo
3186      */
3187     public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
3188 
3189     /** MediaPlayer2 is temporarily pausing playback internally in order to
3190      * buffer more data.
3191      * @see EventCallback#onInfo
3192      */
3193     public static final int MEDIA_INFO_BUFFERING_START = 701;
3194 
3195     /** MediaPlayer2 is resuming playback after filling buffers.
3196      * @see EventCallback#onInfo
3197      */
3198     public static final int MEDIA_INFO_BUFFERING_END = 702;
3199 
3200     /** Estimated network bandwidth information (kbps) is available; currently this event fires
3201      * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
3202      * when playing network files.
3203      * @see EventCallback#onInfo
3204      * @hide
3205      */
3206     public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
3207 
3208     /**
3209      * Update status in buffering a media source received through progressive downloading.
3210      * The received buffering percentage indicates how much of the content has been buffered
3211      * or played. For example a buffering update of 80 percent when half the content
3212      * has already been played indicates that the next 30 percent of the
3213      * content to play has been buffered.
3214      *
3215      * The {@code extra} parameter in {@code EventCallback.onInfo} is the
3216      * percentage (0-100) of the content that has been buffered or played thus far.
3217      * @see EventCallback#onInfo
3218      */
3219     public static final int MEDIA_INFO_BUFFERING_UPDATE = 704;
3220 
3221     /** Bad interleaving means that a media has been improperly interleaved or
3222      * not interleaved at all, e.g has all the video samples first then all the
3223      * audio ones. Video is playing but a lot of disk seeks may be happening.
3224      * @see EventCallback#onInfo
3225      */
3226     public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;
3227 
3228     /** The media cannot be seeked (e.g live stream)
3229      * @see EventCallback#onInfo
3230      */
3231     public static final int MEDIA_INFO_NOT_SEEKABLE = 801;
3232 
3233     /** A new set of metadata is available.
3234      * @see EventCallback#onInfo
3235      */
3236     public static final int MEDIA_INFO_METADATA_UPDATE = 802;
3237 
3238     /** Informs that audio is not playing. Note that playback of the video
3239      * is not interrupted.
3240      * @see EventCallback#onInfo
3241      */
3242     public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804;
3243 
3244     /** Informs that video is not playing. Note that playback of the audio
3245      * is not interrupted.
3246      * @see EventCallback#onInfo
3247      */
3248     public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805;
3249 
3250     /** Failed to handle timed text track properly.
3251      * @see EventCallback#onInfo
3252      *
3253      * {@hide}
3254      */
3255     public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
3256 
3257     /** Subtitle track was not supported by the media framework.
3258      * @see EventCallback#onInfo
3259      */
3260     public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
3261 
3262     /** Reading the subtitle track takes too long.
3263      * @see EventCallback#onInfo
3264      */
3265     public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
3266 
3267     /**
3268      * @hide
3269      */
3270     @IntDef(flag = false, prefix = "MEDIA_INFO", value = {
3271             MEDIA_INFO_UNKNOWN,
3272             MEDIA_INFO_DATA_SOURCE_START,
3273             MEDIA_INFO_VIDEO_RENDERING_START,
3274             MEDIA_INFO_AUDIO_RENDERING_START,
3275             MEDIA_INFO_DATA_SOURCE_END,
3276             MEDIA_INFO_DATA_SOURCE_LIST_END,
3277             MEDIA_INFO_PREPARED,
3278             MEDIA_INFO_VIDEO_TRACK_LAGGING,
3279             MEDIA_INFO_BUFFERING_START,
3280             MEDIA_INFO_BUFFERING_END,
3281             MEDIA_INFO_NETWORK_BANDWIDTH,
3282             MEDIA_INFO_BUFFERING_UPDATE,
3283             MEDIA_INFO_BAD_INTERLEAVING,
3284             MEDIA_INFO_NOT_SEEKABLE,
3285             MEDIA_INFO_METADATA_UPDATE,
3286             MEDIA_INFO_AUDIO_NOT_PLAYING,
3287             MEDIA_INFO_VIDEO_NOT_PLAYING,
3288             MEDIA_INFO_TIMED_TEXT_ERROR,
3289             MEDIA_INFO_UNSUPPORTED_SUBTITLE,
3290             MEDIA_INFO_SUBTITLE_TIMED_OUT
3291     })
3292     @Retention(RetentionPolicy.SOURCE)
3293     public @interface MediaInfo {}
3294 
3295     //--------------------------------------------------------------------------
3296     /** The player just completed a call {@link #attachAuxEffect}.
3297      * @see EventCallback#onCallCompleted
3298      */
3299     public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1;
3300 
3301     /** The player just completed a call {@link #deselectTrack}.
3302      * @see EventCallback#onCallCompleted
3303      */
3304     public static final int CALL_COMPLETED_DESELECT_TRACK = 2;
3305 
3306     /** The player just completed a call {@link #loopCurrent}.
3307      * @see EventCallback#onCallCompleted
3308      */
3309     public static final int CALL_COMPLETED_LOOP_CURRENT = 3;
3310 
3311     /** The player just completed a call {@link #pause}.
3312      * @see EventCallback#onCallCompleted
3313      */
3314     public static final int CALL_COMPLETED_PAUSE = 4;
3315 
3316     /** The player just completed a call {@link #play}.
3317      * @see EventCallback#onCallCompleted
3318      */
3319     public static final int CALL_COMPLETED_PLAY = 5;
3320 
3321     /** The player just completed a call {@link #prepare}.
3322      * @see EventCallback#onCallCompleted
3323      */
3324     public static final int CALL_COMPLETED_PREPARE = 6;
3325 
3326     /** The player just completed a call {@link #seekTo}.
3327      * @see EventCallback#onCallCompleted
3328      */
3329     public static final int CALL_COMPLETED_SEEK_TO = 14;
3330 
3331     /** The player just completed a call {@link #selectTrack}.
3332      * @see EventCallback#onCallCompleted
3333      */
3334     public static final int CALL_COMPLETED_SELECT_TRACK = 15;
3335 
3336     /** The player just completed a call {@link #setAudioAttributes}.
3337      * @see EventCallback#onCallCompleted
3338      */
3339     public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16;
3340 
3341     /** The player just completed a call {@link #setAudioSessionId}.
3342      * @see EventCallback#onCallCompleted
3343      */
3344     public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17;
3345 
3346     /** The player just completed a call {@link #setAuxEffectSendLevel}.
3347      * @see EventCallback#onCallCompleted
3348      */
3349     public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18;
3350 
3351     /** The player just completed a call {@link #setDataSource}.
3352      * @see EventCallback#onCallCompleted
3353      */
3354     public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19;
3355 
3356     /** The player just completed a call {@link #setNextDataSource}.
3357      * @see EventCallback#onCallCompleted
3358      */
3359     public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22;
3360 
3361     /** The player just completed a call {@link #setNextDataSources}.
3362      * @see EventCallback#onCallCompleted
3363      */
3364     public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23;
3365 
3366     /** The player just completed a call {@link #setPlaybackParams}.
3367      * @see EventCallback#onCallCompleted
3368      */
3369     public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24;
3370 
3371     /** The player just completed a call {@link #setPlayerVolume}.
3372      * @see EventCallback#onCallCompleted
3373      */
3374     public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26;
3375 
3376     /** The player just completed a call {@link #setSurface}.
3377      * @see EventCallback#onCallCompleted
3378      */
3379     public static final int CALL_COMPLETED_SET_SURFACE = 27;
3380 
3381     /** The player just completed a call {@link #setSyncParams}.
3382      * @see EventCallback#onCallCompleted
3383      */
3384     public static final int CALL_COMPLETED_SET_SYNC_PARAMS = 28;
3385 
3386     /** The player just completed a call {@link #skipToNext}.
3387      * @see EventCallback#onCallCompleted
3388      */
3389     public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29;
3390 
3391     /** The player just completed a call {@link #clearNextDataSources}.
3392      * @see EventCallback#onCallCompleted
3393      */
3394     public static final int CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES = 30;
3395 
3396     /** The player just completed a call {@link #setBufferingParams}.
3397      * @see EventCallback#onCallCompleted
3398      * @hide
3399      */
3400     public static final int CALL_COMPLETED_SET_BUFFERING_PARAMS = 31;
3401 
3402     /** The player just completed a call {@link #setDisplay}.
3403      * @see EventCallback#onCallCompleted
3404      */
3405     public static final int CALL_COMPLETED_SET_DISPLAY = 33;
3406 
3407     /** The player just completed a call {@link #setWakeLock}.
3408      * @see EventCallback#onCallCompleted
3409      */
3410     public static final int CALL_COMPLETED_SET_WAKE_LOCK = 34;
3411 
3412     /** The player just completed a call {@link #setScreenOnWhilePlaying}.
3413      * @see EventCallback#onCallCompleted
3414      */
3415     public static final int CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING = 35;
3416 
3417     /**
3418      * The start of the methods which have separate call complete callback.
3419      * @hide
3420      */
3421     public static final int SEPARATE_CALL_COMPLETED_CALLBACK_START = 1000;
3422 
3423     /** The player just completed a call {@link #notifyWhenCommandLabelReached}.
3424      * @see EventCallback#onCommandLabelReached
3425      * @hide
3426      */
3427     public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED =
3428             SEPARATE_CALL_COMPLETED_CALLBACK_START;
3429 
3430     /** The player just completed a call {@link #prepareDrm}.
3431      * @see DrmEventCallback#onDrmPrepared
3432      * @hide
3433      */
3434     public static final int CALL_COMPLETED_PREPARE_DRM =
3435             SEPARATE_CALL_COMPLETED_CALLBACK_START + 1;
3436 
3437     /**
3438      * @hide
3439      */
3440     @IntDef(flag = false, prefix = "CALL_COMPLETED", value = {
3441             CALL_COMPLETED_ATTACH_AUX_EFFECT,
3442             CALL_COMPLETED_DESELECT_TRACK,
3443             CALL_COMPLETED_LOOP_CURRENT,
3444             CALL_COMPLETED_PAUSE,
3445             CALL_COMPLETED_PLAY,
3446             CALL_COMPLETED_PREPARE,
3447             CALL_COMPLETED_SEEK_TO,
3448             CALL_COMPLETED_SELECT_TRACK,
3449             CALL_COMPLETED_SET_AUDIO_ATTRIBUTES,
3450             CALL_COMPLETED_SET_AUDIO_SESSION_ID,
3451             CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL,
3452             CALL_COMPLETED_SET_DATA_SOURCE,
3453             CALL_COMPLETED_SET_NEXT_DATA_SOURCE,
3454             CALL_COMPLETED_SET_NEXT_DATA_SOURCES,
3455             CALL_COMPLETED_SET_PLAYBACK_PARAMS,
3456             CALL_COMPLETED_SET_PLAYER_VOLUME,
3457             CALL_COMPLETED_SET_SURFACE,
3458             CALL_COMPLETED_SET_SYNC_PARAMS,
3459             CALL_COMPLETED_SKIP_TO_NEXT,
3460             CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES,
3461             CALL_COMPLETED_SET_BUFFERING_PARAMS,
3462             CALL_COMPLETED_SET_DISPLAY,
3463             CALL_COMPLETED_SET_WAKE_LOCK,
3464             CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING,
3465             CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED,
3466             CALL_COMPLETED_PREPARE_DRM,
3467     })
3468     @Retention(RetentionPolicy.SOURCE)
3469     public @interface CallCompleted {}
3470 
3471     /** Status code represents that call is completed without an error.
3472      * @see EventCallback#onCallCompleted
3473      */
3474     public static final int CALL_STATUS_NO_ERROR = 0;
3475 
3476     /** Status code represents that call is ended with an unknown error.
3477      * @see EventCallback#onCallCompleted
3478      */
3479     public static final int CALL_STATUS_ERROR_UNKNOWN = Integer.MIN_VALUE;
3480 
3481     /** Status code represents that the player is not in valid state for the operation.
3482      * @see EventCallback#onCallCompleted
3483      */
3484     public static final int CALL_STATUS_INVALID_OPERATION = 1;
3485 
3486     /** Status code represents that the argument is illegal.
3487      * @see EventCallback#onCallCompleted
3488      */
3489     public static final int CALL_STATUS_BAD_VALUE = 2;
3490 
3491     /** Status code represents that the operation is not allowed.
3492      * @see EventCallback#onCallCompleted
3493      */
3494     public static final int CALL_STATUS_PERMISSION_DENIED = 3;
3495 
3496     /** Status code represents a file or network related operation error.
3497      * @see EventCallback#onCallCompleted
3498      */
3499     public static final int CALL_STATUS_ERROR_IO = 4;
3500 
3501     /** Status code represents that the call has been skipped. For example, a {@link #seekTo}
3502      * request may be skipped if it is followed by another {@link #seekTo} request.
3503      * @see EventCallback#onCallCompleted
3504      */
3505     public static final int CALL_STATUS_SKIPPED = 5;
3506 
3507     /** Status code represents that DRM operation is called before preparing a DRM scheme through
3508      *  {@code prepareDrm}.
3509      * @see EventCallback#onCallCompleted
3510      */
3511     // TODO: change @code to @link when DRM is unhidden
3512     public static final int CALL_STATUS_NO_DRM_SCHEME = 6;
3513 
3514     /**
3515      * @hide
3516      */
3517     @IntDef(flag = false, prefix = "CALL_STATUS", value = {
3518             CALL_STATUS_NO_ERROR,
3519             CALL_STATUS_ERROR_UNKNOWN,
3520             CALL_STATUS_INVALID_OPERATION,
3521             CALL_STATUS_BAD_VALUE,
3522             CALL_STATUS_PERMISSION_DENIED,
3523             CALL_STATUS_ERROR_IO,
3524             CALL_STATUS_SKIPPED,
3525             CALL_STATUS_NO_DRM_SCHEME})
3526     @Retention(RetentionPolicy.SOURCE)
3527     public @interface CallStatus {}
3528 
3529     // Modular DRM begin
3530 
3531     /**
3532      * An immutable structure per {@link DataSourceDesc} with settings required to initiate a DRM
3533      * protected playback session.
3534      *
3535      * @see DrmPreparationInfo.Builder
3536      */
3537     public static final class DrmPreparationInfo {
3538 
3539         /**
3540          * Mutable builder to create a {@link MediaPlayer2.DrmPreparationInfo} object.
3541          *
3542          * {@link Builder#Builder(UUID) UUID} must not be null; {@link #setKeyType keyType}
3543          * must be one of {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
3544          * <p>
3545          * When {@link #setKeyType keyType} is {@link MediaDrm#KEY_TYPE_STREAMING},
3546          * {@link #setInitData(byte[]) initData} and {@link #setMimeType(String) mimeType}
3547          * must not be null; When {@link #setKeyType keyType} is {@link MediaDrm#KEY_TYPE_OFFLINE},
3548          * {@link #setKeySetId(byte[]) keySetId} must not be null.
3549          */
3550         public static final class Builder {
3551 
3552             private final UUID mUUID;
3553             private byte[] mKeySetId;
3554             private byte[] mInitData;
3555             private String mMimeType;
3556             private int mKeyType;
3557             private Map<String, String> mOptionalParameters;
3558 
3559             /**
3560              * @param uuid UUID of the crypto scheme selected to decrypt content. An UUID can be
3561              * retrieved from the source listening to {@link DrmEventCallback#onDrmInfo}.
3562              */
3563             public Builder(@NonNull UUID uuid) {
3564                 this.mUUID = uuid;
3565             }
3566 
3567             /**
3568              * Set identifier of a persisted offline key obtained from
3569              * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared}.
3570              *
3571              * A {@code keySetId} can be used to restore persisted offline keys into a new playback
3572              * session of a DRM protected data source. When {@code keySetId} is set,
3573              * {@code initData}, {@code mimeType}, {@code keyType}, {@code optionalParameters} are
3574              * ignored.
3575              *
3576              * @param keySetId identifier of a persisted offline key
3577              * @return this
3578              */
3579             public @NonNull Builder setKeySetId(@Nullable byte[] keySetId) {
3580                 this.mKeySetId = keySetId;
3581                 return this;
3582             }
3583 
3584             /**
3585              * Set container-specific DRM initialization data. Its meaning is interpreted based on
3586              * {@code mimeType}. For example, it could contain the content ID, key ID or other data
3587              * obtained from the content metadata that is required to generate a
3588              * {@link MediaDrm.KeyRequest}.
3589              *
3590              * @param initData container-specific DRM initialization data
3591              * @return this
3592              */
3593             public @NonNull Builder setInitData(@Nullable byte[] initData) {
3594                 this.mInitData = initData;
3595                 return this;
3596             }
3597 
3598             /**
3599              * Set mime type of the content
3600              *
3601              * @param mimeType mime type to the content
3602              * @return this
3603              */
3604             public @NonNull Builder setMimeType(@Nullable String mimeType) {
3605                 this.mMimeType = mimeType;
3606                 return this;
3607             }
3608 
3609             /**
3610              * Set type of the key request. The request may be to acquire keys
3611              * for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content,
3612              * {@link MediaDrm#KEY_TYPE_OFFLINE}. Releasing previously acquired keys
3613              * ({@link MediaDrm#KEY_TYPE_RELEASE}) is not allowed.
3614              *
3615              * @param keyType type of the key request
3616              * @return this
3617              */
3618             public @NonNull Builder setKeyType(@MediaPlayer2.MediaDrmKeyType int keyType) {
3619                 this.mKeyType = keyType;
3620                 return this;
3621             }
3622 
3623             /**
3624              * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent
3625              * to the license server.
3626              *
3627              * @param optionalParameters optional parameters to be included in a key request
3628              * @return this
3629              */
3630             public @NonNull Builder setOptionalParameters(
3631                     @Nullable Map<String, String> optionalParameters) {
3632                 this.mOptionalParameters = optionalParameters;
3633                 return this;
3634             }
3635 
3636             /**
3637              * @return an immutable {@link DrmPreparationInfo} based on settings of this builder
3638              */
3639             @NonNull
3640             public DrmPreparationInfo build() {
3641                 final DrmPreparationInfo info = new DrmPreparationInfo(mUUID, mKeySetId, mInitData,
3642                         mMimeType, mKeyType, mOptionalParameters);
3643                 if (!info.isValid()) {
3644                     throw new IllegalArgumentException("invalid DrmPreparationInfo");
3645                 }
3646                 return info;
3647             }
3648 
3649         }
3650 
3651         private final UUID mUUID;
3652         private final byte[] mKeySetId;
3653         private final byte[] mInitData;
3654         private final String mMimeType;
3655         private final int mKeyType;
3656         private final Map<String, String> mOptionalParameters;
3657 
3658         private DrmPreparationInfo(UUID mUUID, byte[] mKeySetId, byte[] mInitData, String mMimeType,
3659                 int mKeyType, Map<String, String> optionalParameters) {
3660             this.mUUID = mUUID;
3661             this.mKeySetId = mKeySetId;
3662             this.mInitData = mInitData;
3663             this.mMimeType = mMimeType;
3664             this.mKeyType = mKeyType;
3665             this.mOptionalParameters = optionalParameters;
3666         }
3667 
3668         boolean isValid() {
3669             if (mUUID == null) {
3670                 return false;
3671             }
3672             if (mKeySetId != null) {
3673                 // offline restore case
3674                 return true;
3675             }
3676             if (mInitData != null && mMimeType != null) {
3677                 // new streaming license case
3678                 return true;
3679             }
3680             return false;
3681         }
3682 
3683         /**
3684          * @return UUID of the crypto scheme selected to decrypt content.
3685          */
3686         @NonNull
3687         public UUID getUuid() {
3688             return mUUID;
3689         }
3690 
3691         /**
3692          * @return identifier of the persisted offline key.
3693          */
3694         @Nullable
3695         public byte[] getKeySetId() {
3696             return mKeySetId;
3697         }
3698 
3699         /**
3700          * @return container-specific DRM initialization data.
3701          */
3702         @Nullable
3703         public byte[] getInitData() {
3704             return mInitData;
3705         }
3706 
3707         /**
3708          * @return mime type of the content
3709          */
3710         @Nullable
3711         public String getMimeType() {
3712             return mMimeType;
3713         }
3714 
3715         /**
3716          * @return type of the key request.
3717          */
3718         @MediaPlayer2.MediaDrmKeyType
3719         public int getKeyType() {
3720             return mKeyType;
3721         }
3722 
3723         /**
3724          * @return optional parameters to be included in the {@link MediaDrm.KeyRequest}.
3725          */
3726         @Nullable
3727         public Map<String, String> getOptionalParameters() {
3728             return mOptionalParameters;
3729         }
3730     }
3731 
3732     /**
3733      * Interface definition for callbacks to be invoked when the player has the corresponding
3734      * DRM events.
3735      */
3736     public static abstract class DrmEventCallback {
3737 
3738         /**
3739          * Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that
3740          * bundles DRM initialization parameters.
3741          *
3742          * @param mp the {@code MediaPlayer2} associated with this callback
3743          * @param dsd the {@link DataSourceDesc} of this data source
3744          * @param drmInfo DRM info of the source including PSSH, and subset of crypto schemes
3745          *        supported by this device
3746          * @return a {@link DrmPreparationInfo} object to initialize DRM playback, or null to skip
3747          *         DRM initialization
3748          */
3749         @Nullable
3750         public abstract DrmPreparationInfo onDrmInfo(@NonNull MediaPlayer2 mp,
3751                 @NonNull DataSourceDesc dsd, @NonNull DrmInfo drmInfo);
3752 
3753         /**
3754          * Called to give the app the opportunity to configure DRM before the session is created.
3755          *
3756          * This facilitates configuration of the properties, like 'securityLevel', which
3757          * has to be set after DRM scheme creation but before the DRM session is opened.
3758          *
3759          * The only allowed DRM calls in this listener are
3760          * {@link MediaDrm#getPropertyString(String)},
3761          * {@link MediaDrm#getPropertyByteArray(String)},
3762          * {@link MediaDrm#setPropertyString(String, String)},
3763          * {@link MediaDrm#setPropertyByteArray(String, byte[])},
3764          * {@link MediaDrm#setOnExpirationUpdateListener},
3765          * and {@link MediaDrm#setOnKeyStatusChangeListener}.
3766          *
3767          * @param mp the {@code MediaPlayer2} associated with this callback
3768          * @param dsd the {@link DataSourceDesc} of this data source
3769          * @param drm handle to get/set DRM properties and listeners for this data source
3770          */
3771         public void onDrmConfig(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
3772                 @NonNull MediaDrm drm) { }
3773 
3774         /**
3775          * Called to indicate the DRM session for {@code dsd} is ready for key request/response
3776          *
3777          * @param mp the {@code MediaPlayer2} associated with this callback
3778          * @param dsd the {@link DataSourceDesc} of this data source
3779          * @param request a {@link MediaDrm.KeyRequest} prepared using the
3780          *        {@link DrmPreparationInfo} returned from
3781          *        {@link #onDrmInfo(MediaPlayer2, DataSourceDesc, DrmInfo)}
3782          * @return the response to {@code request} (from license server); returning {@code null} or
3783          *         throwing an {@link RuntimeException} from this callback would trigger an
3784          *         {@link EventCallback#onError}.
3785          */
3786         @NonNull
3787         public abstract byte[] onDrmKeyRequest(@NonNull MediaPlayer2 mp,
3788                 @NonNull DataSourceDesc dsd, @NonNull MediaDrm.KeyRequest request);
3789 
3790         /**
3791          * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
3792          * {@code dsd} or if there is an error during DRM preparation
3793          *
3794          * @param mp the {@code MediaPlayer2} associated with this callback
3795          * @param dsd the {@link DataSourceDesc} of this data source
3796          * @param status the result of DRM preparation.
3797          * @param keySetId optional identifier that can be used to restore DRM playback initiated
3798          *        with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
3799          *
3800          * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
3801          */
3802         public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
3803                 @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
3804 
3805     }
3806 
3807     private final Object mDrmEventCallbackLock = new Object();
3808     private Pair<Executor, DrmEventCallback> mDrmEventCallback;
3809 
3810     /**
3811      * Registers the callback to be invoked for various DRM events.
3812      *
3813      * This is a synchronous call.
3814      *
3815      * @param eventCallback the callback that will be run
3816      * @param executor the executor through which the callback should be invoked
3817      */
3818     public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
3819             @NonNull DrmEventCallback eventCallback) {
3820         if (eventCallback == null) {
3821             throw new IllegalArgumentException("Illegal null EventCallback");
3822         }
3823         if (executor == null) {
3824             throw new IllegalArgumentException(
3825                     "Illegal null Executor for the EventCallback");
3826         }
3827         synchronized (mDrmEventCallbackLock) {
3828             mDrmEventCallback = new Pair<Executor, DrmEventCallback>(executor, eventCallback);
3829         }
3830     }
3831 
3832     /**
3833      * Clear the {@link DrmEventCallback}.
3834      *
3835      * This is a synchronous call.
3836      */
3837     public void clearDrmEventCallback() {
3838         synchronized (mDrmEventCallbackLock) {
3839             mDrmEventCallback = null;
3840         }
3841     }
3842 
3843     /**
3844      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3845      * <p>
3846      *
3847      * DRM preparation has succeeded.
3848      */
3849     public static final int PREPARE_DRM_STATUS_SUCCESS = 0;
3850 
3851     /**
3852      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3853      * <p>
3854      *
3855      * The device required DRM provisioning but couldn't reach the provisioning server.
3856      */
3857     public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1;
3858 
3859     /**
3860      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3861      * <p>
3862      *
3863      * The device required DRM provisioning but the provisioning server denied the request.
3864      */
3865     public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2;
3866 
3867     /**
3868      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3869      * <p>
3870      *
3871      * The DRM preparation has failed .
3872      */
3873     public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3;
3874 
3875     /**
3876      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3877      * <p>
3878      *
3879      * The crypto scheme UUID is not supported by the device.
3880      */
3881     public static final int PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME = 4;
3882 
3883     /**
3884      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3885      * <p>
3886      *
3887      * The hardware resources are not available, due to being in use.
3888      */
3889     public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5;
3890 
3891     /**
3892      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3893      * <p>
3894      *
3895      * Restoring persisted offline keys failed.
3896      */
3897     public static final int PREPARE_DRM_STATUS_RESTORE_ERROR = 6;
3898 
3899     /**
3900      * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
3901      * <p>
3902      *
3903      * Error during key request/response exchange with license server.
3904      */
3905     public static final int PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR = 7;
3906 
3907     /** @hide */
3908     @IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = {
3909         PREPARE_DRM_STATUS_SUCCESS,
3910         PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR,
3911         PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR,
3912         PREPARE_DRM_STATUS_PREPARATION_ERROR,
3913         PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME,
3914         PREPARE_DRM_STATUS_RESOURCE_BUSY,
3915         PREPARE_DRM_STATUS_RESTORE_ERROR,
3916         PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR,
3917     })
3918     @Retention(RetentionPolicy.SOURCE)
3919     public @interface PrepareDrmStatusCode {}
3920 
3921     /** @hide */
3922     @IntDef({
3923         MediaDrm.KEY_TYPE_STREAMING,
3924         MediaDrm.KEY_TYPE_OFFLINE,
3925         MediaDrm.KEY_TYPE_RELEASE,
3926     })
3927     @Retention(RetentionPolicy.SOURCE)
3928     public @interface MediaDrmKeyType {}
3929 
3930     /** @hide */
3931     @StringDef({
3932         MediaDrm.PROPERTY_VENDOR,
3933         MediaDrm.PROPERTY_VERSION,
3934         MediaDrm.PROPERTY_DESCRIPTION,
3935         MediaDrm.PROPERTY_ALGORITHMS,
3936     })
3937     @Retention(RetentionPolicy.SOURCE)
3938     public @interface MediaDrmStringProperty {}
3939 
3940     /**
3941      * Retrieves the DRM Info associated with the given source
3942      *
3943      * @param dsd The DRM protected data source
3944      *
3945      * @throws IllegalStateException if called before being prepared
3946      * @hide
3947      */
3948     @TestApi
3949     public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) {
3950         final SourceInfo sourceInfo = getSourceInfo(dsd);
3951         if (sourceInfo != null) {
3952             DrmInfo drmInfo = null;
3953 
3954             // there is not much point if the app calls getDrmInfo within an OnDrmInfoListener;
3955             // regardless below returns drmInfo anyway instead of raising an exception
3956             synchronized (sourceInfo) {
3957                 if (!sourceInfo.mDrmInfoResolved && sourceInfo.mDrmInfo == null) {
3958                     final String msg = "The Player has not been prepared yet";
3959                     Log.v(TAG, msg);
3960                     throw new IllegalStateException(msg);
3961                 }
3962 
3963                 if (sourceInfo.mDrmInfo != null) {
3964                     drmInfo  = sourceInfo.mDrmInfo.makeCopy();
3965                 }
3966             }   // synchronized
3967 
3968             return drmInfo;
3969         }
3970         return null;
3971     }
3972 
3973     /**
3974      * Prepares the DRM for the given data source
3975      * <p>
3976      * If {@link DrmEventCallback} is registered, it will be called during
3977      * preparation to allow configuration of the DRM properties before opening the
3978      * DRM session. It should be used only for a series of
3979      * {@link #getDrmPropertyString(DataSourceDesc, String)} and
3980      * {@link #setDrmPropertyString(DataSourceDesc, String, String)} calls
3981      * and refrain from any lengthy operation.
3982      * <p>
3983      * If the device has not been provisioned before, this call also provisions the device
3984      * which involves accessing the provisioning server and can take a variable time to
3985      * complete depending on the network connectivity.
3986      * When needed, the provisioning will be launched  in the background.
3987      * The listener {@link DrmEventCallback#onDrmPrepared}
3988      * will be called when provisioning and preparation are finished. The application should
3989      * check the status code returned with {@link DrmEventCallback#onDrmPrepared} to proceed.
3990      * <p>
3991      * The registered {@link DrmEventCallback#onDrmPrepared} is called to indicate the DRM
3992      * session being ready. The application should not make any assumption about its call
3993      * sequence (e.g., before or after prepareDrm returns).
3994      * <p>
3995      *
3996      * @param dsd The DRM protected data source
3997      *
3998      * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
3999      * from the source listening to {@link DrmEventCallback#onDrmInfo}.
4000      *
4001      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
4002      * @hide
4003      */
4004     // This is an asynchronous call.
4005     @TestApi
4006     public @NonNull Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
4007         return addTask(newPrepareDrmTask(dsd, uuid));
4008     }
4009 
4010     private Task newPrepareDrmTask(DataSourceDesc dsd, UUID uuid) {
4011         return new Task(CALL_COMPLETED_PREPARE_DRM, true) {
4012             @Override
4013             void process() {
4014                 final SourceInfo sourceInfo = getSourceInfo(dsd);
4015                 int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
4016                 boolean finishPrepare = true;
4017 
4018                 if (sourceInfo == null) {
4019                     Log.e(TAG, "prepareDrm(): DataSource not found.");
4020                 } else if (sourceInfo.mDrmInfo == null) {
4021                     // only allowing if tied to a protected source;
4022                     // might relax for releasing offline keys
4023                     Log.e(TAG, "prepareDrm(): Wrong usage: The player must be prepared and "
4024                             + "DRM info be retrieved before this call.");
4025                 } else {
4026                     status = PREPARE_DRM_STATUS_SUCCESS;
4027                 }
4028 
4029                 try {
4030                     if (status == PREPARE_DRM_STATUS_SUCCESS) {
4031                         sourceInfo.mDrmHandle.prepare(uuid);
4032                     }
4033                 } catch (ResourceBusyException e) {
4034                     status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
4035                 } catch (UnsupportedSchemeException e) {
4036                     status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME;
4037                 } catch (NotProvisionedException e) {
4038                     Log.w(TAG, "prepareDrm: NotProvisionedException");
4039 
4040                     // handle provisioning internally; it'll reset mPrepareDrmInProgress
4041                     status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId);
4042 
4043                     if (status == PREPARE_DRM_STATUS_SUCCESS) {
4044                         // License will be setup in provisioning
4045                         finishPrepare = false;
4046                     } else {
4047                         synchronized (sourceInfo.mDrmHandle) {
4048                             sourceInfo.mDrmHandle.cleanDrmObj();
4049                         }
4050 
4051                         switch (status) {
4052                             case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
4053                                 Log.e(TAG, "prepareDrm: Provisioning was required but failed "
4054                                         + "due to a network error.");
4055                                 break;
4056 
4057                             case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
4058                                 Log.e(TAG, "prepareDrm: Provisioning was required but the request "
4059                                         + "was denied by the server.");
4060                                 break;
4061 
4062                             case PREPARE_DRM_STATUS_PREPARATION_ERROR:
4063                             default:
4064                                 Log.e(TAG, "prepareDrm: Post-provisioning preparation failed.");
4065                                 break;
4066                         }
4067                     }
4068                 } catch (Exception e) {
4069                     status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
4070                 }
4071 
4072                 if (finishPrepare) {
4073                     sourceInfo.mDrmHandle.finishPrepare(status);
4074                     synchronized (mTaskLock) {
4075                         mCurrentTask = null;
4076                         processPendingTask_l();
4077                     }
4078                 }
4079 
4080             }
4081         };
4082     }
4083 
4084     /**
4085      * Releases the DRM session for the given data source
4086      * <p>
4087      * The player has to have an active DRM session and be in stopped, or prepared
4088      * state before this call is made.
4089      * A {@link #reset()} call will release the DRM session implicitly.
4090      *
4091      * @param dsd The DRM protected data source
4092      *
4093      * @throws NoDrmSchemeException if there is no active DRM session to release
4094      * @hide
4095      */
4096     // This is a synchronous call.
4097     @TestApi
4098     public void releaseDrm(@NonNull DataSourceDesc dsd)
4099             throws NoDrmSchemeException {
4100         final SourceInfo sourceInfo = getSourceInfo(dsd);
4101         if (sourceInfo != null) {
4102             sourceInfo.mDrmHandle.release();
4103         }
4104     }
4105 
4106     private native void native_releaseDrm(long mSrcId);
4107 
4108     /**
4109      * A key request/response exchange occurs between the app and a license server
4110      * to obtain or release keys used to decrypt the given data source.
4111      * <p>
4112      * {@code getDrmKeyRequest()} is used to obtain an opaque key request byte array that is
4113      * delivered to the license server.  The opaque key request byte array is returned
4114      * in KeyRequest.data.  The recommended URL to deliver the key request to is
4115      * returned in {@code KeyRequest.defaultUrl}.
4116      * <p>
4117      * After the app has received the key request response from the server,
4118      * it should deliver to the response to the DRM engine plugin using the method
4119      * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
4120      *
4121      * @param dsd the DRM protected data source
4122      *
4123      * @param keySetId is the key-set identifier of the offline keys being released when keyType is
4124      * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
4125      * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
4126      *
4127      * @param initData is the container-specific initialization data when the keyType is
4128      * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
4129      * interpreted based on the mime type provided in the mimeType parameter.  It could
4130      * contain, for example, the content ID, key ID or other data obtained from the content
4131      * metadata that is required in generating the key request.
4132      * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
4133      *
4134      * @param mimeType identifies the mime type of the content
4135      *
4136      * @param keyType specifies the type of the request. The request may be to acquire
4137      * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
4138      * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
4139      * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
4140      *
4141      * @param optionalParameters are included in the key request message to
4142      * allow a client application to provide additional message parameters to the server.
4143      * This may be {@code null} if no additional parameters are to be sent.
4144      *
4145      * @throws NoDrmSchemeException if there is no active DRM session
4146      * @hide
4147      */
4148     @TestApi
4149     public MediaDrm.KeyRequest getDrmKeyRequest(
4150             @NonNull DataSourceDesc dsd,
4151             @Nullable byte[] keySetId, @Nullable byte[] initData,
4152             @Nullable String mimeType, @MediaDrmKeyType int keyType,
4153             @Nullable Map<String, String> optionalParameters)
4154             throws NoDrmSchemeException {
4155         Log.v(TAG, "getDrmKeyRequest: " +
4156                 " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
4157                 " keyType: " + keyType + " optionalParameters: " + optionalParameters);
4158 
4159         final SourceInfo sourceInfo = getSourceInfo(dsd);
4160         if (sourceInfo != null) {
4161             return sourceInfo.mDrmHandle.getDrmKeyRequest(
4162                     keySetId, initData, mimeType, keyType, optionalParameters);
4163         }
4164         return null;
4165     }
4166 
4167     /**
4168      * A key response is received from the license server by the app for the given DRM protected
4169      * data source, then provided to the DRM engine plugin using {@code provideDrmKeyResponse}.
4170      * <p>
4171      * When the response is for an offline key request, a key-set identifier is returned that
4172      * can be used to later restore the keys to a new session with the method
4173      * {@link #restoreDrmKeys(DataSourceDesc, byte[])}.
4174      * When the response is for a streaming or release request, null is returned.
4175      *
4176      * @param dsd the DRM protected data source
4177      *
4178      * @param keySetId When the response is for a release request, keySetId identifies the saved
4179      * key associated with the release request (i.e., the same keySetId passed to the earlier
4180      * {@link # getDrmKeyRequest(DataSourceDesc, byte[], byte[], String, int, Map)} call).
4181      * It MUST be null when the response is for either streaming or offline key requests.
4182      *
4183      * @param response the byte array response from the server
4184      *
4185      * @throws NoDrmSchemeException if there is no active DRM session
4186      * @throws DeniedByServerException if the response indicates that the
4187      * server rejected the request
4188      * @hide
4189      */
4190     // This is a synchronous call.
4191     @TestApi
4192     public byte[] provideDrmKeyResponse(
4193             @NonNull DataSourceDesc dsd,
4194             @Nullable byte[] keySetId, @NonNull byte[] response)
4195             throws NoDrmSchemeException, DeniedByServerException {
4196         Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
4197 
4198         final SourceInfo sourceInfo = getSourceInfo(dsd);
4199         if (sourceInfo != null) {
4200             return sourceInfo.mDrmHandle.provideDrmKeyResponse(keySetId, response);
4201         }
4202         return null;
4203     }
4204 
4205     /**
4206      * Restore persisted offline keys into a new session for the given DRM protected data source.
4207      * {@code keySetId} identifies the keys to load, obtained from a prior call to
4208      * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
4209      *
4210      * @param dsd the DRM protected data source
4211      *
4212      * @param keySetId identifies the saved key set to restore
4213      *
4214      * @throws NoDrmSchemeException if there is no active DRM session
4215      * @hide
4216      */
4217     // This is a synchronous call.
4218     @TestApi
4219     public void restoreDrmKeys(
4220             @NonNull DataSourceDesc dsd,
4221             @NonNull byte[] keySetId)
4222             throws NoDrmSchemeException {
4223         Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
4224 
4225         final SourceInfo sourceInfo = getSourceInfo(dsd);
4226         if (sourceInfo != null) {
4227             sourceInfo.mDrmHandle.restoreDrmKeys(keySetId);
4228         }
4229     }
4230 
4231     /**
4232      * Read a DRM engine plugin String property value, given the DRM protected data source
4233      * and property name string.
4234      *
4235      * @param dsd the DRM protected data source
4236      *
4237      * @param propertyName the property name
4238      *
4239      * Standard fields names are:
4240      * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
4241      * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
4242      *
4243      * @throws NoDrmSchemeException if there is no active DRM session
4244      * @hide
4245      */
4246     @TestApi
4247     public String getDrmPropertyString(
4248             @NonNull DataSourceDesc dsd,
4249             @NonNull @MediaDrmStringProperty String propertyName)
4250             throws NoDrmSchemeException {
4251         Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
4252 
4253         final SourceInfo sourceInfo = getSourceInfo(dsd);
4254         if (sourceInfo != null) {
4255             return sourceInfo.mDrmHandle.getDrmPropertyString(propertyName);
4256         }
4257         return null;
4258     }
4259 
4260     /**
4261      * Set a DRM engine plugin String property value for the given data source.
4262      *
4263      * @param dsd the DRM protected data source
4264      * @param propertyName the property name
4265      * @param value the property value
4266      *
4267      * Standard fields names are:
4268      * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
4269      * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
4270      *
4271      * @throws NoDrmSchemeException if there is no active DRM session
4272      * @hide
4273      */
4274     // This is a synchronous call.
4275     @TestApi
4276     public void setDrmPropertyString(
4277             @NonNull DataSourceDesc dsd,
4278             @NonNull @MediaDrmStringProperty String propertyName, @NonNull String value)
4279             throws NoDrmSchemeException {
4280         // TODO: this implementation only works when dsd is the only data source
4281         Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
4282 
4283         final SourceInfo sourceInfo = getSourceInfo(dsd);
4284         if (sourceInfo != null) {
4285             sourceInfo.mDrmHandle.setDrmPropertyString(propertyName, value);
4286         }
4287     }
4288 
4289     /**
4290      * Encapsulates the DRM properties of the source.
4291      */
4292     public static final class DrmInfo {
4293         private Map<UUID, byte[]> mMapPssh;
4294         private UUID[] mSupportedSchemes;
4295 
4296         /**
4297          * Returns the PSSH info of the data source for each supported DRM scheme.
4298          */
4299         public @NonNull Map<UUID, byte[]> getPssh() {
4300             return mMapPssh;
4301         }
4302 
4303         /**
4304          * Returns the intersection of the data source and the device DRM schemes.
4305          * It effectively identifies the subset of the source's DRM schemes which
4306          * are supported by the device too.
4307          */
4308         public @NonNull List<UUID> getSupportedSchemes() {
4309             return Arrays.asList(mSupportedSchemes);
4310         }
4311 
4312         private DrmInfo(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) {
4313             mMapPssh = pssh;
4314             mSupportedSchemes = supportedSchemes;
4315         }
4316 
4317         private static DrmInfo create(PlayerMessage msg) {
4318             Log.v(TAG, "DrmInfo.create(" + msg + ")");
4319 
4320             Iterator<Value> in = msg.getValuesList().iterator();
4321             byte[] pssh = in.next().getBytesValue().toByteArray();
4322 
4323             Log.v(TAG, "DrmInfo.create() PSSH: " + DrmInfo.arrToHex(pssh));
4324             Map<UUID, byte[]> mapPssh = DrmInfo.parsePSSH(pssh, pssh.length);
4325             Log.v(TAG, "DrmInfo.create() PSSH: " + mapPssh);
4326 
4327             int supportedDRMsCount = in.next().getInt32Value();
4328             UUID[] supportedSchemes = new UUID[supportedDRMsCount];
4329             for (int i = 0; i < supportedDRMsCount; i++) {
4330                 byte[] uuid = new byte[16];
4331                 in.next().getBytesValue().copyTo(uuid, 0);
4332 
4333                 supportedSchemes[i] = DrmInfo.bytesToUUID(uuid);
4334 
4335                 Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " + supportedSchemes[i]);
4336             }
4337 
4338             Log.v(TAG, "DrmInfo.create() psshsize: " + pssh.length
4339                     + " supportedDRMsCount: " + supportedDRMsCount);
4340             return new DrmInfo(mapPssh, supportedSchemes);
4341         }
4342 
4343         private DrmInfo makeCopy() {
4344             return new DrmInfo(this.mMapPssh, this.mSupportedSchemes);
4345         }
4346 
4347         private static String arrToHex(byte[] bytes) {
4348             String out = "0x";
4349             for (int i = 0; i < bytes.length; i++) {
4350                 out += String.format("%02x", bytes[i]);
4351             }
4352 
4353             return out;
4354         }
4355 
4356         private static UUID bytesToUUID(byte[] uuid) {
4357             long msb = 0, lsb = 0;
4358             for (int i = 0; i < 8; i++) {
4359                 msb |= (((long) uuid[i]     & 0xff) << (8 * (7 - i)));
4360                 lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i)));
4361             }
4362 
4363             return new UUID(msb, lsb);
4364         }
4365 
4366         private static Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
4367             Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
4368 
4369             final int uuidSize = 16;
4370             final int dataLenSize = 4;
4371 
4372             int len = psshsize;
4373             int numentries = 0;
4374             int i = 0;
4375 
4376             while (len > 0) {
4377                 if (len < uuidSize) {
4378                     Log.w(TAG, String.format("parsePSSH: len is too short to parse "
4379                                              + "UUID: (%d < 16) pssh: %d", len, psshsize));
4380                     return null;
4381                 }
4382 
4383                 byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize);
4384                 UUID uuid = bytesToUUID(subset);
4385                 i += uuidSize;
4386                 len -= uuidSize;
4387 
4388                 // get data length
4389                 if (len < 4) {
4390                     Log.w(TAG, String.format("parsePSSH: len is too short to parse "
4391                                              + "datalen: (%d < 4) pssh: %d", len, psshsize));
4392                     return null;
4393                 }
4394 
4395                 subset = Arrays.copyOfRange(pssh, i, i + dataLenSize);
4396                 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
4397                         ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16)
4398                         | ((subset[1] & 0xff) <<  8) |  (subset[0] & 0xff)        :
4399                         ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16)
4400                         | ((subset[2] & 0xff) <<  8) |  (subset[3] & 0xff);
4401                 i += dataLenSize;
4402                 len -= dataLenSize;
4403 
4404                 if (len < datalen) {
4405                     Log.w(TAG, String.format("parsePSSH: len is too short to parse "
4406                                              + "data: (%d < %d) pssh: %d", len, datalen, psshsize));
4407                     return null;
4408                 }
4409 
4410                 byte[] data = Arrays.copyOfRange(pssh, i, i + datalen);
4411 
4412                 // skip the data
4413                 i += datalen;
4414                 len -= datalen;
4415 
4416                 Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
4417                                          numentries, uuid, arrToHex(data), psshsize));
4418                 numentries++;
4419                 result.put(uuid, data);
4420             }
4421 
4422             return result;
4423         }
4424     };  // DrmInfo
4425 
4426     /**
4427      * Thrown when a DRM method is called when there is no active DRM session.
4428      * Extends MediaDrm.MediaDrmException
4429      */
4430     public static final class NoDrmSchemeException extends MediaDrmException {
4431         public NoDrmSchemeException(@Nullable String detailMessage) {
4432             super(detailMessage);
4433         }
4434     }
4435 
4436     private native void native_prepareDrm(
4437             long srcId, @NonNull byte[] uuid, @NonNull byte[] drmSessionId);
4438 
4439     // Instantiated from the native side
4440     @SuppressWarnings("unused")
4441     private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
4442         public long mJAudioTrackPtr;
4443         public long mNativeCallbackPtr;
4444         public long mUserDataPtr;
4445 
4446         StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) {
4447             super();
4448             mJAudioTrackPtr = jAudioTrackPtr;
4449             mNativeCallbackPtr = nativeCallbackPtr;
4450             mUserDataPtr = userDataPtr;
4451         }
4452 
4453         @Override
4454         public void onTearDown(AudioTrack track) {
4455             native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr);
4456         }
4457 
4458         @Override
4459         public void onPresentationEnded(AudioTrack track) {
4460             native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr);
4461         }
4462 
4463         @Override
4464         public void onDataRequest(AudioTrack track, int size) {
4465             native_stream_event_onStreamDataRequest(
4466                     mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr);
4467         }
4468     }
4469 
4470     /**
4471      * Returns a byte[] containing the remainder of 'in', closing it when done.
4472      */
4473     private static byte[] readInputStreamFully(InputStream in) throws IOException {
4474         try {
4475             return readInputStreamFullyNoClose(in);
4476         } finally {
4477             in.close();
4478         }
4479     }
4480 
4481     /**
4482      * Returns a byte[] containing the remainder of 'in'.
4483      */
4484     private static byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
4485         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
4486         byte[] buffer = new byte[1024];
4487         int count;
4488         while ((count = in.read(buffer)) != -1) {
4489             bytes.write(buffer, 0, count);
4490         }
4491         return bytes.toByteArray();
4492     }
4493 
4494     private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
4495         long msb = uuid.getMostSignificantBits();
4496         long lsb = uuid.getLeastSignificantBits();
4497 
4498         byte[] uuidBytes = new byte[16];
4499         for (int i = 0; i < 8; ++i) {
4500             uuidBytes[i] = (byte) (msb >>> (8 * (7 - i)));
4501             uuidBytes[8 + i] = (byte) (lsb >>> (8 * (7 - i)));
4502         }
4503 
4504         return uuidBytes;
4505     }
4506 
4507     private static class TimedTextUtil {
4508         // These keys must be in sync with the keys in TextDescription2.h
4509         private static final int KEY_START_TIME                     = 7; // int
4510         private static final int KEY_STRUCT_TEXT_POS               = 14; // TextPos
4511         private static final int KEY_STRUCT_TEXT                   = 16; // Text
4512         private static final int KEY_GLOBAL_SETTING               = 101;
4513         private static final int KEY_LOCAL_SETTING                = 102;
4514 
4515         private static TimedText parsePlayerMessage(PlayerMessage playerMsg) {
4516             if (playerMsg.getValuesCount() == 0) {
4517                 return null;
4518             }
4519 
4520             String textChars = null;
4521             Rect textBounds = null;
4522             Iterator<Value> in = playerMsg.getValuesList().iterator();
4523             int type = in.next().getInt32Value();
4524             if (type == KEY_LOCAL_SETTING) {
4525                 type = in.next().getInt32Value();
4526                 if (type != KEY_START_TIME) {
4527                     return null;
4528                 }
4529                 int startTimeMs = in.next().getInt32Value();
4530 
4531                 type = in.next().getInt32Value();
4532                 if (type != KEY_STRUCT_TEXT) {
4533                     return null;
4534                 }
4535 
4536                 byte[] text = in.next().getBytesValue().toByteArray();
4537                 if (text == null || text.length == 0) {
4538                     textChars = null;
4539                 } else {
4540                     textChars = new String(text);
4541                 }
4542 
4543             } else if (type != KEY_GLOBAL_SETTING) {
4544                 Log.w(TAG, "Invalid timed text key found: " + type);
4545                 return null;
4546             }
4547             if (in.hasNext()) {
4548                 type = in.next().getInt32Value();
4549                 if (type == KEY_STRUCT_TEXT_POS) {
4550                     int top = in.next().getInt32Value();
4551                     int left = in.next().getInt32Value();
4552                     int bottom = in.next().getInt32Value();
4553                     int right = in.next().getInt32Value();
4554                     textBounds = new Rect(left, top, right, bottom);
4555                 }
4556             }
4557             return null;
4558             /* TimedText c-tor usage is temporarily commented out.
4559              * TODO(b/117527789): use SUBTITLE path for MEDIA_MIMETYPE_TEXT_3GPP track
4560              *                    and remove TimedText path from MediaPlayer2.
4561             return new TimedText(textChars, textBounds);
4562             */
4563         }
4564     }
4565 
4566     private Object addTask(Task task) {
4567         synchronized (mTaskLock) {
4568             mPendingTasks.add(task);
4569             processPendingTask_l();
4570         }
4571         return task;
4572     }
4573 
4574     @GuardedBy("mTaskLock")
4575     private void processPendingTask_l() {
4576         if (mCurrentTask != null) {
4577             return;
4578         }
4579         if (!mPendingTasks.isEmpty()) {
4580             Task task = mPendingTasks.remove(0);
4581             mCurrentTask = task;
4582             mTaskHandler.post(task);
4583         }
4584     }
4585 
4586     private abstract class Task implements Runnable {
4587         final long mTaskId = mTaskIdGenerator.getAndIncrement();
4588         private final int mMediaCallType;
4589         private final boolean mNeedToWaitForEventToComplete;
4590         private DataSourceDesc mDSD;
4591 
4592         Task(int mediaCallType, boolean needToWaitForEventToComplete) {
4593             mMediaCallType = mediaCallType;
4594             mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
4595         }
4596 
4597         abstract void process() throws IOException, NoDrmSchemeException;
4598 
4599         @Override
4600         public void run() {
4601             int status = CALL_STATUS_NO_ERROR;
4602             try {
4603                 if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
4604                         && getState() == PLAYER_STATE_ERROR) {
4605                     status = CALL_STATUS_INVALID_OPERATION;
4606                 } else {
4607                     if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
4608                         synchronized (mTaskLock) {
4609                             if (!mPendingTasks.isEmpty()) {
4610                                 Task nextTask = mPendingTasks.get(0);
4611                                 if (nextTask.mMediaCallType == mMediaCallType) {
4612                                     throw new CommandSkippedException(
4613                                             "consecutive seekTo is skipped except last one");
4614                                 }
4615                             }
4616                         }
4617                     }
4618                     process();
4619                 }
4620             } catch (IllegalStateException e) {
4621                 status = CALL_STATUS_INVALID_OPERATION;
4622             } catch (IllegalArgumentException e) {
4623                 status = CALL_STATUS_BAD_VALUE;
4624             } catch (SecurityException e) {
4625                 status = CALL_STATUS_PERMISSION_DENIED;
4626             } catch (IOException e) {
4627                 status = CALL_STATUS_ERROR_IO;
4628             } catch (NoDrmSchemeException e) {
4629                 status = CALL_STATUS_NO_DRM_SCHEME;
4630             } catch (CommandSkippedException e) {
4631                 status = CALL_STATUS_SKIPPED;
4632             } catch (Exception e) {
4633                 status = CALL_STATUS_ERROR_UNKNOWN;
4634             }
4635             mDSD = getCurrentDataSource();
4636 
4637             if (mMediaCallType != CALL_COMPLETED_SEEK_TO) {
4638                 synchronized (mTaskLock) {
4639                     mIsPreviousCommandSeekTo = false;
4640                 }
4641             }
4642 
4643             // TODO: Make native implementations asynchronous and let them send notifications.
4644             if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) {
4645 
4646                 sendCompleteNotification(status);
4647 
4648                 synchronized (mTaskLock) {
4649                     mCurrentTask = null;
4650                     processPendingTask_l();
4651                 }
4652             }
4653         }
4654 
4655         private void sendCompleteNotification(int status) {
4656             // In {@link #notifyWhenCommandLabelReached} case, a separate callback
4657             // {@link #onCommandLabelReached} is already called in {@code process()}.
4658             // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared
4659             if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
4660                     || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) {
4661                 return;
4662             }
4663             sendEvent(new EventNotifier() {
4664                 @Override
4665                 public void notify(EventCallback callback) {
4666                     callback.onCallCompleted(
4667                             MediaPlayer2.this, mDSD, mMediaCallType, status);
4668                 }
4669             });
4670         }
4671     };
4672 
4673     private final class CommandSkippedException extends RuntimeException {
4674         CommandSkippedException(String detailMessage) {
4675             super(detailMessage);
4676         }
4677     };
4678 
4679     // Modular DRM
4680     private final Map<UUID, MediaDrm> mDrmObjs = Collections.synchronizedMap(new HashMap<>());
4681     private class DrmHandle {
4682 
4683         static final int PROVISION_TIMEOUT_MS = 60000;
4684 
4685         final DataSourceDesc mDSD;
4686         final long mSrcId;
4687 
4688         //--- guarded by |this| start
4689         MediaDrm mDrmObj;
4690         byte[] mDrmSessionId;
4691         UUID mActiveDrmUUID;
4692         boolean mDrmConfigAllowed;
4693         boolean mDrmProvisioningInProgress;
4694         boolean mPrepareDrmInProgress;
4695         Future<?> mProvisionResult;
4696         DrmPreparationInfo mPrepareInfo;
4697         //--- guarded by |this| end
4698 
4699         DrmHandle(DataSourceDesc dsd, long srcId) {
4700             mDSD = dsd;
4701             mSrcId = srcId;
4702         }
4703 
4704         void prepare(UUID uuid) throws UnsupportedSchemeException,
4705                 ResourceBusyException, NotProvisionedException, InterruptedException,
4706                 ExecutionException, TimeoutException {
4707             Log.v(TAG, "prepareDrm: uuid: " + uuid);
4708 
4709             synchronized (this) {
4710                 if (mActiveDrmUUID != null) {
4711                     final String msg = "prepareDrm(): Wrong usage: There is already "
4712                             + "an active DRM scheme with " + uuid;
4713                     Log.e(TAG, msg);
4714                     throw new IllegalStateException(msg);
4715                 }
4716 
4717                 if (mPrepareDrmInProgress) {
4718                     final String msg = "prepareDrm(): Wrong usage: There is already "
4719                             + "a pending prepareDrm call.";
4720                     Log.e(TAG, msg);
4721                     throw new IllegalStateException(msg);
4722                 }
4723 
4724                 if (mDrmProvisioningInProgress) {
4725                     final String msg = "prepareDrm(): Unexpectd: Provisioning already in progress";
4726                     Log.e(TAG, msg);
4727                     throw new IllegalStateException(msg);
4728                 }
4729 
4730                 // shouldn't need this; just for safeguard
4731                 cleanDrmObj();
4732 
4733                 mPrepareDrmInProgress = true;
4734 
4735                 try {
4736                     // only creating the DRM object to allow pre-openSession configuration
4737                     prepareDrm_createDrmStep(uuid);
4738                 } catch (Exception e) {
4739                     Log.w(TAG, "prepareDrm(): Exception ", e);
4740                     mPrepareDrmInProgress = false;
4741                     throw e;
4742                 }
4743 
4744                 mDrmConfigAllowed = true;
4745             }  // synchronized
4746 
4747             // call the callback outside the lock
4748             sendDrmEventWait(new DrmEventNotifier<Void>() {
4749                 @Override
4750                 public Void notifyWait(DrmEventCallback callback) {
4751                     callback.onDrmConfig(MediaPlayer2.this, mDSD, mDrmObj);
4752                     return null;
4753                 }
4754             });
4755 
4756             synchronized (this) {
4757                 mDrmConfigAllowed = false;
4758                 boolean earlyExit = false;
4759 
4760                 try {
4761                     prepareDrm_openSessionStep(uuid);
4762 
4763                     this.mActiveDrmUUID = uuid;
4764                     mPrepareDrmInProgress = false;
4765                 } catch (IllegalStateException e) {
4766                     final String msg = "prepareDrm(): Wrong usage: The player must be "
4767                             + "in the prepared state to call prepareDrm().";
4768                     Log.e(TAG, msg);
4769                     earlyExit = true;
4770                     mPrepareDrmInProgress = false;
4771                     throw new IllegalStateException(msg);
4772                 } catch (NotProvisionedException e) {
4773                     Log.w(TAG, "prepareDrm: NotProvisionedException", e);
4774                     throw e;
4775                 } catch (Exception e) {
4776                     Log.e(TAG, "prepareDrm: Exception " + e);
4777                     earlyExit = true;
4778                     mPrepareDrmInProgress = false;
4779                     throw e;
4780                 } finally {
4781                     if (earlyExit) {  // clean up object if didn't succeed
4782                         cleanDrmObj();
4783                     }
4784                 }  // finally
4785             }  // synchronized
4786         }
4787 
4788         void prepareDrm_createDrmStep(UUID uuid)
4789                 throws UnsupportedSchemeException {
4790             Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
4791 
4792             try {
4793                 mDrmObj = mDrmObjs.computeIfAbsent(uuid, scheme -> {
4794                     try {
4795                         return new MediaDrm(scheme);
4796                     } catch (UnsupportedSchemeException e) {
4797                         throw new IllegalArgumentException(e);
4798                     }
4799                 });
4800                 Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
4801             } catch (Exception e) { // UnsupportedSchemeException
4802                 Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
4803                 throw e;
4804             }
4805         }
4806 
4807         void prepareDrm_openSessionStep(UUID uuid)
4808                 throws NotProvisionedException, ResourceBusyException {
4809             Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
4810 
4811             // TODO:
4812             // don't need an open session for a future specialKeyReleaseDrm mode but we should do
4813             // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
4814             // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
4815             try {
4816                 mDrmSessionId = mDrmObj.openSession();
4817                 Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
4818 
4819                 // Sending it down to native/mediaserver to create the crypto object
4820                 // This call could simply fail due to bad player state, e.g., after play().
4821                 final MediaPlayer2 mp2 = MediaPlayer2.this;
4822                 mp2.native_prepareDrm(mSrcId, getByteArrayFromUUID(uuid), mDrmSessionId);
4823                 Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
4824 
4825             } catch (Exception e) { //ResourceBusyException, NotProvisionedException
4826                 Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
4827                 throw e;
4828             }
4829 
4830         }
4831 
4832         int handleProvisioninig(UUID uuid, long taskId) {
4833             synchronized (this) {
4834                 if (mDrmProvisioningInProgress) {
4835                     Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
4836                     return PREPARE_DRM_STATUS_PREPARATION_ERROR;
4837                 }
4838 
4839                 MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
4840                 if (provReq == null) {
4841                     Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
4842                     return PREPARE_DRM_STATUS_PREPARATION_ERROR;
4843                 }
4844 
4845                 Log.v(TAG, "handleProvisioninig provReq "
4846                         + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
4847 
4848                 // networking in a background thread
4849                 mDrmProvisioningInProgress = true;
4850 
4851                 mProvisionResult = sDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
4852 
4853                 return PREPARE_DRM_STATUS_SUCCESS;
4854             }
4855         }
4856 
4857         void provision(UUID uuid, long taskId) {
4858 
4859             MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
4860             String urlStr = provReq.getDefaultUrl();
4861             urlStr += "&signedRequest=" + new String(provReq.getData());
4862             Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + urlStr);
4863 
4864             byte[] response = null;
4865             boolean provisioningSucceeded = false;
4866             int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
4867             try {
4868                 URL url = new URL(urlStr);
4869                 final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
4870                 try {
4871                     connection.setRequestMethod("POST");
4872                     connection.setDoOutput(false);
4873                     connection.setDoInput(true);
4874                     connection.setConnectTimeout(PROVISION_TIMEOUT_MS);
4875                     connection.setReadTimeout(PROVISION_TIMEOUT_MS);
4876 
4877                     connection.connect();
4878                     response = readInputStreamFully(connection.getInputStream());
4879 
4880                     Log.v(TAG, "handleProvisioninig: Thread run: response " +
4881                             response.length + " " + response);
4882                 } catch (Exception e) {
4883                     status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
4884                     Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
4885                 } finally {
4886                     connection.disconnect();
4887                 }
4888             } catch (Exception e)   {
4889                 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
4890                 Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
4891             }
4892 
4893             if (response != null) {
4894                 try {
4895                     mDrmObj.provideProvisionResponse(response);
4896                     Log.v(TAG, "handleProvisioninig: Thread run: " +
4897                             "provideProvisionResponse SUCCEEDED!");
4898 
4899                     provisioningSucceeded = true;
4900                 } catch (Exception e) {
4901                     status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
4902                     Log.w(TAG, "handleProvisioninig: Thread run: " +
4903                             "provideProvisionResponse " + e);
4904                 }
4905             }
4906 
4907             boolean succeeded = false;
4908 
4909             synchronized (this) {
4910                 // continuing with prepareDrm
4911                 if (provisioningSucceeded) {
4912                     succeeded = resumePrepare(uuid);
4913                     status = (succeeded) ?
4914                             PREPARE_DRM_STATUS_SUCCESS :
4915                             PREPARE_DRM_STATUS_PREPARATION_ERROR;
4916                 }
4917                 mDrmProvisioningInProgress = false;
4918                 mPrepareDrmInProgress = false;
4919                 if (!succeeded) {
4920                     cleanDrmObj();  // cleaning up if it hasn't gone through while in the lock
4921                 }
4922             }  // synchronized
4923 
4924             // calling the callback outside the lock
4925             finishPrepare(status);
4926 
4927             synchronized (mTaskLock) {
4928                 if (mCurrentTask != null
4929                         && mCurrentTask.mTaskId == taskId
4930                         && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
4931                         && mCurrentTask.mNeedToWaitForEventToComplete) {
4932                     mCurrentTask = null;
4933                     processPendingTask_l();
4934                 }
4935             }
4936         }
4937 
4938         Runnable newProvisioningTask(UUID uuid, long taskId) {
4939             return new Runnable() {
4940                 @Override
4941                 public void run() {
4942                     provision(uuid, taskId);
4943                 }
4944             };
4945         }
4946 
4947         boolean resumePrepare(UUID uuid) {
4948             Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
4949 
4950             // mDrmLock is guaranteed to be held
4951             boolean success = false;
4952             try {
4953                 // resuming
4954                 prepareDrm_openSessionStep(uuid);
4955 
4956                 this.mActiveDrmUUID = uuid;
4957 
4958                 success = true;
4959             } catch (Exception e) {
4960                 Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed:" + e);
4961                 // mDrmObj clean up is done by the caller
4962             }
4963 
4964             return success;
4965         }
4966 
4967         synchronized boolean setPreparationInfo(DrmPreparationInfo prepareInfo) {
4968             if (prepareInfo == null || !prepareInfo.isValid() || mPrepareInfo != null) {
4969                 return false;
4970             }
4971             mPrepareInfo = prepareInfo;
4972             return true;
4973         }
4974 
4975         void finishPrepare(int status) {
4976             if (status != PREPARE_DRM_STATUS_SUCCESS) {
4977                 notifyPrepared(status, null);
4978                 return;
4979             }
4980 
4981             if (mPrepareInfo == null) {
4982                 // Deprecated: this can only happen when using MediaPlayer Version 1 APIs
4983                 notifyPrepared(status, null);
4984                 return;
4985             }
4986 
4987             final byte[] keySetId = mPrepareInfo.mKeySetId;
4988             if (keySetId != null) {
4989                 try {
4990                     mDrmObj.restoreKeys(mDrmSessionId, keySetId);
4991                     notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
4992                 } catch (Exception e) {
4993                     notifyPrepared(PREPARE_DRM_STATUS_RESTORE_ERROR, keySetId);
4994                 }
4995                 return;
4996             }
4997 
4998             sDrmThreadPool.submit(newKeyExchangeTask());
4999         }
5000 
5001         Runnable newKeyExchangeTask() {
5002             return new Runnable() {
5003                 @Override
5004                 public void run() {
5005                     final byte[] initData = mPrepareInfo.mInitData;
5006                     final String mimeType = mPrepareInfo.mMimeType;
5007                     final int keyType = mPrepareInfo.mKeyType;
5008                     final Map<String, String> optionalParams = mPrepareInfo.mOptionalParameters;
5009                     byte[] keySetId = null;
5010                     try {
5011                         KeyRequest req;
5012                         req = getDrmKeyRequest(null, initData, mimeType, keyType, optionalParams);
5013                         byte[] response = sendDrmEventWait(new DrmEventNotifier<byte[]>() {
5014                             @Override
5015                             public byte[] notifyWait(DrmEventCallback callback) {
5016                                 final MediaPlayer2 mp = MediaPlayer2.this;
5017                                 return callback.onDrmKeyRequest(mp, mDSD, req);
5018                             }
5019                         });
5020                         keySetId = provideDrmKeyResponse(null, response);
5021                     } catch (Exception e) {
5022                     }
5023                     if (keySetId == null) {
5024                         notifyPrepared(PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, null);
5025                     } else {
5026                         notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
5027                     }
5028                 }
5029             };
5030         }
5031 
5032         void notifyPrepared(final int status, byte[] keySetId) {
5033 
5034             Message msg;
5035             if (status == PREPARE_DRM_STATUS_SUCCESS) {
5036                 msg = mTaskHandler.obtainMessage(
5037                         MEDIA_DRM_PREPARED, 0, 0, null);
5038             } else {
5039                 msg = mTaskHandler.obtainMessage(
5040                         MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null);
5041             }
5042             mTaskHandler.post(new Runnable() {
5043                 @Override
5044                 public void run() {
5045                     mTaskHandler.handleMessage(msg, mSrcId);
5046                 }
5047             });
5048 
5049             sendDrmEvent(new DrmEventNotifier() {
5050                 @Override
5051                 public void notify(DrmEventCallback callback) {
5052                     callback.onDrmPrepared(MediaPlayer2.this, mDSD, status,
5053                             keySetId);
5054                 }
5055             });
5056 
5057         }
5058 
5059         void cleanDrmObj() {
5060             // the caller holds mDrmLock
5061             Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
5062 
5063             if (mDrmSessionId != null)    {
5064                 mDrmObj.closeSession(mDrmSessionId);
5065                 mDrmSessionId = null;
5066             }
5067         }
5068 
5069         void release() throws NoDrmSchemeException {
5070             synchronized (this) {
5071                 Log.v(TAG, "releaseDrm:");
5072 
5073                 if (mActiveDrmUUID == null) {
5074                     Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
5075                     throw new NoDrmSchemeException(
5076                             "releaseDrm: No active DRM scheme to release.");
5077                 }
5078 
5079                 try {
5080                     // we don't have the player's state in this layer. The below call raises
5081                     // exception if we're in a non-stopped/prepared state.
5082 
5083                     // for cleaning native/mediaserver crypto object
5084                     native_releaseDrm(mSrcId);
5085 
5086                     // for cleaning client-side MediaDrm object; only called if above has succeeded
5087                     cleanDrmObj();
5088 
5089                     this.mActiveDrmUUID = null;
5090                 } catch (IllegalStateException e) {
5091                     Log.w(TAG, "releaseDrm: Exception ", e);
5092                     throw new IllegalStateException(
5093                             "releaseDrm: The player is not in a valid state.");
5094                 } catch (Exception e) {
5095                     Log.e(TAG, "releaseDrm: Exception ", e);
5096                 }
5097             }  // synchronized
5098         }
5099 
5100         void cleanup() {
5101             synchronized (this) {
5102                 Log.v(TAG, "cleanupDrm: " +
5103                         " mProvisioningTask=" + mProvisionResult +
5104                         " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
5105                         " mActiveDrmScheme=" + mActiveDrmUUID);
5106 
5107                 if (mProvisionResult != null) {
5108                     // timeout; relying on HttpUrlConnection
5109                     try {
5110                         mProvisionResult.get();
5111                     }
5112                     catch (InterruptedException | ExecutionException e) {
5113                         Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
5114                     }
5115                 }
5116 
5117                 // set to false to avoid duplicate release calls
5118                 this.mActiveDrmUUID = null;
5119 
5120                 native_releaseDrm(mSrcId);
5121                 cleanDrmObj();
5122             }   // synchronized
5123         }
5124 
5125         Runnable newCleanupTask() {
5126             return new Runnable() {
5127                 @Override
5128                 public void run() {
5129                     cleanup();
5130                 }
5131             };
5132         }
5133 
5134         MediaDrm.KeyRequest getDrmKeyRequest(
5135                 byte[] keySetId, byte[] initData,
5136                 String mimeType, int keyType,
5137                 Map<String, String> optionalParameters)
5138                 throws NoDrmSchemeException {
5139             synchronized (this) {
5140                 if (mActiveDrmUUID == null) {
5141                     Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
5142                     throw new NoDrmSchemeException(
5143                             "getDrmKeyRequest: Has to set a DRM scheme first.");
5144                 }
5145 
5146                 try {
5147                     byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
5148                             mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
5149                             keySetId;                  // keySetId for KEY_TYPE_RELEASE
5150 
5151                     HashMap<String, String> hmapOptionalParameters =
5152                             (optionalParameters != null)
5153                             ? new HashMap<String, String>(optionalParameters)
5154                             : null;
5155 
5156                     MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(
5157                             scope, initData, mimeType, keyType, hmapOptionalParameters);
5158                     Log.v(TAG, "getDrmKeyRequest:   --> request: " + request);
5159 
5160                     return request;
5161 
5162                 } catch (NotProvisionedException e) {
5163                     Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " +
5164                             "Unexpected. Shouldn't have reached here.");
5165                     throw new IllegalStateException("getDrmKeyRequest: provisioning error.");
5166                 } catch (Exception e) {
5167                     Log.w(TAG, "getDrmKeyRequest Exception " + e);
5168                     throw e;
5169                 }
5170 
5171             }
5172         }
5173 
5174         byte[] provideDrmKeyResponse(byte[] keySetId, byte[] response)
5175                 throws NoDrmSchemeException, DeniedByServerException {
5176             synchronized (this) {
5177 
5178                 if (mActiveDrmUUID == null) {
5179                     Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
5180                     throw new NoDrmSchemeException(
5181                             "getDrmKeyRequest: Has to set a DRM scheme first.");
5182                 }
5183 
5184                 try {
5185                     byte[] scope = (keySetId == null) ?
5186                                     mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
5187                                     keySetId;                  // keySetId for KEY_TYPE_RELEASE
5188 
5189                     byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
5190 
5191                     Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId
5192                             + " response: " + response + " --> " + keySetResult);
5193 
5194 
5195                     return keySetResult;
5196 
5197                 } catch (NotProvisionedException e) {
5198                     Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " +
5199                             "Unexpected. Shouldn't have reached here.");
5200                     throw new IllegalStateException("provideDrmKeyResponse: " +
5201                             "Unexpected provisioning error.");
5202                 } catch (Exception e) {
5203                     Log.w(TAG, "provideDrmKeyResponse Exception " + e);
5204                     throw e;
5205                 }
5206             }
5207         }
5208 
5209         void restoreDrmKeys(byte[] keySetId)
5210                 throws NoDrmSchemeException {
5211             synchronized (this) {
5212                 if (mActiveDrmUUID == null) {
5213                     Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
5214                     throw new NoDrmSchemeException(
5215                             "restoreDrmKeys: Has to set a DRM scheme first.");
5216                 }
5217 
5218                 try {
5219                     mDrmObj.restoreKeys(mDrmSessionId, keySetId);
5220                 } catch (Exception e) {
5221                     Log.w(TAG, "restoreKeys Exception " + e);
5222                     throw e;
5223                 }
5224             }
5225         }
5226 
5227         String getDrmPropertyString(String propertyName)
5228                 throws NoDrmSchemeException {
5229             String v;
5230             synchronized (this) {
5231 
5232                 if (mActiveDrmUUID == null && !mDrmConfigAllowed) {
5233                     Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
5234                     throw new NoDrmSchemeException(
5235                             "getDrmPropertyString: Has to prepareDrm() first.");
5236                 }
5237 
5238                 try {
5239                     v = mDrmObj.getPropertyString(propertyName);
5240                 } catch (Exception e) {
5241                     Log.w(TAG, "getDrmPropertyString Exception " + e);
5242                     throw e;
5243                 }
5244             }   // synchronized
5245 
5246             Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + v);
5247 
5248             return v;
5249         }
5250 
5251         void setDrmPropertyString(String propertyName, String value)
5252                 throws NoDrmSchemeException {
5253             synchronized (this) {
5254 
5255                 if ( mActiveDrmUUID == null && !mDrmConfigAllowed ) {
5256                     Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
5257                     throw new NoDrmSchemeException(
5258                             "setDrmPropertyString: Has to prepareDrm() first.");
5259                 }
5260 
5261                 try {
5262                     mDrmObj.setPropertyString(propertyName, value);
5263                 } catch ( Exception e ) {
5264                     Log.w(TAG, "setDrmPropertyString Exception " + e);
5265                     throw e;
5266                 }
5267             }
5268         }
5269 
5270     }
5271 
5272     final class SourceInfo {
5273         final DataSourceDesc mDSD;
5274         final long mId = mSrcIdGenerator.getAndIncrement();
5275         AtomicInteger mBufferedPercentage = new AtomicInteger(0);
5276         boolean mClosed = false;
5277         int mPrepareBarrier = 1;
5278 
5279         // m*AsNextSource (below) only applies to pending data sources in the playlist;
5280         // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
5281         // are undefined.
5282         int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
5283         boolean mPlayPendingAsNextSource = false;
5284 
5285         // Modular DRM
5286         final DrmHandle mDrmHandle;
5287         DrmInfo mDrmInfo;
5288         boolean mDrmInfoResolved;
5289 
5290         SourceInfo(DataSourceDesc dsd) {
5291             this.mDSD = dsd;
5292             mDrmHandle = new DrmHandle(dsd, mId);
5293         }
5294 
5295         void close() {
5296             synchronized (this) {
5297                 if (!mClosed) {
5298                     if (mDSD != null) {
5299                         mDSD.close();
5300                     }
5301                     mClosed = true;
5302                 }
5303             }
5304         }
5305 
5306         @Override
5307         public String toString() {
5308             return String.format("%s(%d)", SourceInfo.class.getName(), mId);
5309         }
5310 
5311     }
5312 
5313     private SourceInfo getSourceInfo(long srcId) {
5314         synchronized (mSrcLock) {
5315             if (isCurrentSource(srcId)) {
5316                 return mCurrentSourceInfo;
5317             }
5318             if (isNextSource(srcId)) {
5319                 return mNextSourceInfos.peek();
5320             }
5321         }
5322         return null;
5323     }
5324 
5325     private SourceInfo getSourceInfo(DataSourceDesc dsd) {
5326         synchronized (mSrcLock) {
5327             if (isCurrentSource(dsd)) {
5328                 return mCurrentSourceInfo;
5329             }
5330             if (isNextSource(dsd)) {
5331                 return mNextSourceInfos.peek();
5332             }
5333         }
5334         return null;
5335     }
5336 
5337     private boolean isCurrentSource(long srcId) {
5338         synchronized (mSrcLock) {
5339             return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
5340         }
5341     }
5342 
5343     private boolean isCurrentSource(DataSourceDesc dsd) {
5344         synchronized (mSrcLock) {
5345             return mCurrentSourceInfo != null && mCurrentSourceInfo.mDSD == dsd;
5346         }
5347     }
5348 
5349     private boolean isNextSource(long srcId) {
5350         SourceInfo nextSourceInfo = mNextSourceInfos.peek();
5351         return nextSourceInfo != null && nextSourceInfo.mId == srcId;
5352     }
5353 
5354     private boolean isNextSource(DataSourceDesc dsd) {
5355         SourceInfo nextSourceInfo = mNextSourceInfos.peek();
5356         return nextSourceInfo != null && nextSourceInfo.mDSD == dsd;
5357     }
5358 
5359     @GuardedBy("mSrcLock")
5360     private void setCurrentSourceInfo_l(SourceInfo sourceInfo) {
5361         cleanupSourceInfo(mCurrentSourceInfo);
5362         mCurrentSourceInfo = sourceInfo;
5363     }
5364 
5365     @GuardedBy("mSrcLock")
5366     private void clearNextSourceInfos_l() {
5367         while (!mNextSourceInfos.isEmpty()) {
5368             cleanupSourceInfo(mNextSourceInfos.poll());
5369         }
5370     }
5371 
5372     private void cleanupSourceInfo(SourceInfo sourceInfo) {
5373         if (sourceInfo != null) {
5374             sourceInfo.close();
5375             Runnable task = sourceInfo.mDrmHandle.newCleanupTask();
5376             sDrmThreadPool.submit(task);
5377         }
5378     }
5379 
5380     private void clearSourceInfos() {
5381         synchronized (mSrcLock) {
5382             setCurrentSourceInfo_l(null);
5383             clearNextSourceInfos_l();
5384         }
5385     }
5386 
5387     public static final class MetricsConstants {
5388         private MetricsConstants() {}
5389 
5390         /**
5391          * Key to extract the MIME type of the video track
5392          * from the {@link MediaPlayer2#getMetrics} return value.
5393          * The value is a String.
5394          */
5395         public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
5396 
5397         /**
5398          * Key to extract the codec being used to decode the video track
5399          * from the {@link MediaPlayer2#getMetrics} return value.
5400          * The value is a String.
5401          */
5402         public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
5403 
5404         /**
5405          * Key to extract the width (in pixels) of the video track
5406          * from the {@link MediaPlayer2#getMetrics} return value.
5407          * The value is an integer.
5408          */
5409         public static final String WIDTH = "android.media.mediaplayer.width";
5410 
5411         /**
5412          * Key to extract the height (in pixels) of the video track
5413          * from the {@link MediaPlayer2#getMetrics} return value.
5414          * The value is an integer.
5415          */
5416         public static final String HEIGHT = "android.media.mediaplayer.height";
5417 
5418         /**
5419          * Key to extract the count of video frames played
5420          * from the {@link MediaPlayer2#getMetrics} return value.
5421          * The value is an integer.
5422          */
5423         public static final String FRAMES = "android.media.mediaplayer.frames";
5424 
5425         /**
5426          * Key to extract the count of video frames dropped
5427          * from the {@link MediaPlayer2#getMetrics} return value.
5428          * The value is an integer.
5429          */
5430         public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
5431 
5432         /**
5433          * Key to extract the MIME type of the audio track
5434          * from the {@link MediaPlayer2#getMetrics} return value.
5435          * The value is a String.
5436          */
5437         public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
5438 
5439         /**
5440          * Key to extract the codec being used to decode the audio track
5441          * from the {@link MediaPlayer2#getMetrics} return value.
5442          * The value is a String.
5443          */
5444         public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
5445 
5446         /**
5447          * Key to extract the duration (in milliseconds) of the
5448          * media being played
5449          * from the {@link MediaPlayer2#getMetrics} return value.
5450          * The value is a long.
5451          */
5452         public static final String DURATION = "android.media.mediaplayer.durationMs";
5453 
5454         /**
5455          * Key to extract the playing time (in milliseconds) of the
5456          * media being played
5457          * from the {@link MediaPlayer2#getMetrics} return value.
5458          * The value is a long.
5459          */
5460         public static final String PLAYING = "android.media.mediaplayer.playingMs";
5461 
5462         /**
5463          * Key to extract the count of errors encountered while
5464          * playing the media
5465          * from the {@link MediaPlayer2#getMetrics} return value.
5466          * The value is an integer.
5467          */
5468         public static final String ERRORS = "android.media.mediaplayer.err";
5469 
5470         /**
5471          * Key to extract an (optional) error code detected while
5472          * playing the media
5473          * from the {@link MediaPlayer2#getMetrics} return value.
5474          * The value is an integer.
5475          */
5476         public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
5477 
5478     }
5479 
5480     private void keepAudioSessionIdAlive(int sessionId) {
5481         synchronized (mSessionIdLock) {
5482             if (mDummyAudioTrack != null) {
5483                 if (mDummyAudioTrack.getAudioSessionId() == sessionId) {
5484                     return;
5485                 }
5486                 mDummyAudioTrack.release();
5487             }
5488             // TODO: parameters can be optimized
5489             mDummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
5490                     AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2,
5491                     AudioTrack.MODE_STATIC, sessionId);
5492         }
5493     }
5494 
5495     private void keepAudioSessionIdAlive(AudioTrack at) {
5496         synchronized (mSessionIdLock) {
5497             if (mDummyAudioTrack != null) {
5498                 if (mDummyAudioTrack.getAudioSessionId() == at.getAudioSessionId()) {
5499                     at.release();
5500                     return;
5501                 }
5502                 mDummyAudioTrack.release();
5503             }
5504             mDummyAudioTrack = at;
5505         }
5506     }
5507 }
5508