Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright 2018 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.NonNull;
     21 import android.annotation.Nullable;
     22 import android.app.ActivityThread;
     23 import android.content.ContentProvider;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.res.AssetFileDescriptor;
     27 import android.graphics.SurfaceTexture;
     28 import android.media.SubtitleController.Anchor;
     29 import android.media.SubtitleTrack.RenderingWidget;
     30 import android.net.Uri;
     31 import android.os.Handler;
     32 import android.os.HandlerThread;
     33 import android.os.Looper;
     34 import android.os.Message;
     35 import android.os.Parcel;
     36 import android.os.Parcelable;
     37 import android.os.PersistableBundle;
     38 import android.os.PowerManager;
     39 import android.os.Process;
     40 import android.os.SystemProperties;
     41 import android.provider.Settings;
     42 import android.system.ErrnoException;
     43 import android.system.Os;
     44 import android.system.OsConstants;
     45 import android.util.ArrayMap;
     46 import android.util.Log;
     47 import android.util.Pair;
     48 import android.view.Surface;
     49 import android.view.SurfaceHolder;
     50 import android.widget.VideoView;
     51 
     52 import com.android.internal.annotations.GuardedBy;
     53 import com.android.internal.util.Preconditions;
     54 
     55 import dalvik.system.CloseGuard;
     56 
     57 import libcore.io.IoBridge;
     58 import libcore.io.Streams;
     59 
     60 import java.io.ByteArrayOutputStream;
     61 import java.io.File;
     62 import java.io.FileDescriptor;
     63 import java.io.FileInputStream;
     64 import java.io.IOException;
     65 import java.io.InputStream;
     66 import java.lang.ref.WeakReference;
     67 import java.net.HttpCookie;
     68 import java.net.HttpURLConnection;
     69 import java.net.URL;
     70 import java.nio.ByteOrder;
     71 import java.util.ArrayList;
     72 import java.util.Arrays;
     73 import java.util.BitSet;
     74 import java.util.HashMap;
     75 import java.util.LinkedList;
     76 import java.util.List;
     77 import java.util.Map;
     78 import java.util.Scanner;
     79 import java.util.Set;
     80 import java.util.UUID;
     81 import java.util.Vector;
     82 import java.util.concurrent.Executor;
     83 import java.util.concurrent.atomic.AtomicInteger;
     84 
     85 /**
     86  * @hide
     87  */
     88 public final class MediaPlayer2Impl extends MediaPlayer2 {
     89     static {
     90         System.loadLibrary("media2_jni");
     91         native_init();
     92     }
     93 
     94     private final static String TAG = "MediaPlayer2Impl";
     95 
     96     private long mNativeContext; // accessed by native methods
     97     private long mNativeSurfaceTexture;  // accessed by native methods
     98     private int mListenerContext; // accessed by native methods
     99     private SurfaceHolder mSurfaceHolder;
    100     private EventHandler mEventHandler;
    101     private PowerManager.WakeLock mWakeLock = null;
    102     private boolean mScreenOnWhilePlaying;
    103     private boolean mStayAwake;
    104     private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
    105     private final CloseGuard mGuard = CloseGuard.get();
    106 
    107     private final Object mSrcLock = new Object();
    108     //--- guarded by |mSrcLock| start
    109     private long mSrcIdGenerator = 0;
    110     private DataSourceDesc mCurrentDSD;
    111     private long mCurrentSrcId = mSrcIdGenerator++;
    112     private List<DataSourceDesc> mNextDSDs;
    113     private long mNextSrcId = mSrcIdGenerator++;
    114     private int mNextSourceState = NEXT_SOURCE_STATE_INIT;
    115     private boolean mNextSourcePlayPending = false;
    116     //--- guarded by |mSrcLock| end
    117 
    118     private AtomicInteger mBufferedPercentageCurrent = new AtomicInteger(0);
    119     private AtomicInteger mBufferedPercentageNext = new AtomicInteger(0);
    120     private volatile float mVolume = 1.0f;
    121 
    122     // Modular DRM
    123     private final Object mDrmLock = new Object();
    124     //--- guarded by |mDrmLock| start
    125     private UUID mDrmUUID;
    126     private DrmInfoImpl mDrmInfoImpl;
    127     private MediaDrm mDrmObj;
    128     private byte[] mDrmSessionId;
    129     private boolean mDrmInfoResolved;
    130     private boolean mActiveDrmScheme;
    131     private boolean mDrmConfigAllowed;
    132     private boolean mDrmProvisioningInProgress;
    133     private boolean mPrepareDrmInProgress;
    134     private ProvisioningThread mDrmProvisioningThread;
    135     //--- guarded by |mDrmLock| end
    136 
    137     private HandlerThread mHandlerThread;
    138     private final Handler mTaskHandler;
    139     private final Object mTaskLock = new Object();
    140     @GuardedBy("mTaskLock")
    141     private final List<Task> mPendingTasks = new LinkedList<>();
    142     @GuardedBy("mTaskLock")
    143     private Task mCurrentTask;
    144 
    145     /**
    146      * Default constructor.
    147      * <p>When done with the MediaPlayer2Impl, you should call  {@link #close()},
    148      * to free the resources. If not released, too many MediaPlayer2Impl instances may
    149      * result in an exception.</p>
    150      */
    151     public MediaPlayer2Impl() {
    152         Looper looper;
    153         if ((looper = Looper.myLooper()) != null) {
    154             mEventHandler = new EventHandler(this, looper);
    155         } else if ((looper = Looper.getMainLooper()) != null) {
    156             mEventHandler = new EventHandler(this, looper);
    157         } else {
    158             mEventHandler = null;
    159         }
    160 
    161         mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
    162         mHandlerThread.start();
    163         looper = mHandlerThread.getLooper();
    164         mTaskHandler = new Handler(looper);
    165 
    166         mTimeProvider = new TimeProvider(this);
    167         mOpenSubtitleSources = new Vector<InputStream>();
    168         mGuard.open("close");
    169 
    170         /* Native setup requires a weak reference to our object.
    171          * It's easier to create it here than in C++.
    172          */
    173         native_setup(new WeakReference<MediaPlayer2Impl>(this));
    174     }
    175 
    176     /**
    177      * Releases the resources held by this {@code MediaPlayer2} object.
    178      *
    179      * It is considered good practice to call this method when you're
    180      * done using the MediaPlayer2. In particular, whenever an Activity
    181      * of an application is paused (its onPause() method is called),
    182      * or stopped (its onStop() method is called), this method should be
    183      * invoked to release the MediaPlayer2 object, unless the application
    184      * has a special need to keep the object around. In addition to
    185      * unnecessary resources (such as memory and instances of codecs)
    186      * being held, failure to call this method immediately if a
    187      * MediaPlayer2 object is no longer needed may also lead to
    188      * continuous battery consumption for mobile devices, and playback
    189      * failure for other applications if no multiple instances of the
    190      * same codec are supported on a device. Even if multiple instances
    191      * of the same codec are supported, some performance degradation
    192      * may be expected when unnecessary multiple instances are used
    193      * at the same time.
    194      *
    195      * {@code close()} may be safely called after a prior {@code close()}.
    196      * This class implements the Java {@code AutoCloseable} interface and
    197      * may be used with try-with-resources.
    198      */
    199     @Override
    200     public void close() {
    201         synchronized (mGuard) {
    202             release();
    203         }
    204     }
    205 
    206     /**
    207      * Starts or resumes playback. If playback had previously been paused,
    208      * playback will continue from where it was paused. If playback had
    209      * been stopped, or never started before, playback will start at the
    210      * beginning.
    211      *
    212      * @throws IllegalStateException if it is called in an invalid state
    213      */
    214     @Override
    215     public void play() {
    216         addTask(new Task(CALL_COMPLETED_PLAY, false) {
    217             @Override
    218             void process() {
    219                 stayAwake(true);
    220                 _start();
    221             }
    222         });
    223     }
    224 
    225     private native void _start() throws IllegalStateException;
    226 
    227     /**
    228      * Prepares the player for playback, asynchronously.
    229      *
    230      * After setting the datasource and the display surface, you need to either
    231      * call prepare(). For streams, you should call prepare(),
    232      * which returns immediately, rather than blocking until enough data has been
    233      * buffered.
    234      *
    235      * @throws IllegalStateException if it is called in an invalid state
    236      */
    237     @Override
    238     public void prepare() {
    239         addTask(new Task(CALL_COMPLETED_PREPARE, true) {
    240             @Override
    241             void process() {
    242                 _prepare();
    243             }
    244         });
    245     }
    246 
    247     public native void _prepare();
    248 
    249     /**
    250      * Pauses playback. Call play() to resume.
    251      *
    252      * @throws IllegalStateException if the internal player engine has not been
    253      * initialized.
    254      */
    255     @Override
    256     public void pause() {
    257         addTask(new Task(CALL_COMPLETED_PAUSE, false) {
    258             @Override
    259             void process() {
    260                 stayAwake(false);
    261                 _pause();
    262             }
    263         });
    264     }
    265 
    266     private native void _pause() throws IllegalStateException;
    267 
    268     /**
    269      * Tries to play next data source if applicable.
    270      *
    271      * @throws IllegalStateException if it is called in an invalid state
    272      */
    273     @Override
    274     public void skipToNext() {
    275         addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
    276             @Override
    277             void process() {
    278                 // TODO: switch to next data source and play
    279             }
    280         });
    281     }
    282 
    283     /**
    284      * Gets the current playback position.
    285      *
    286      * @return the current position in milliseconds
    287      */
    288     @Override
    289     public native long getCurrentPosition();
    290 
    291     /**
    292      * Gets the duration of the file.
    293      *
    294      * @return the duration in milliseconds, if no duration is available
    295      *         (for example, if streaming live content), -1 is returned.
    296      */
    297     @Override
    298     public native long getDuration();
    299 
    300     /**
    301      * Gets the current buffered media source position received through progressive downloading.
    302      * The received buffering percentage indicates how much of the content has been buffered
    303      * or played. For example a buffering update of 80 percent when half the content
    304      * has already been played indicates that the next 30 percent of the
    305      * content to play has been buffered.
    306      *
    307      * @return the current buffered media source position in milliseconds
    308      */
    309     @Override
    310     public long getBufferedPosition() {
    311         // Use cached buffered percent for now.
    312         return getDuration() * mBufferedPercentageCurrent.get() / 100;
    313     }
    314 
    315     @Override
    316     public @PlayerState int getPlayerState() {
    317         int mediaplayer2State = getMediaPlayer2State();
    318         int playerState;
    319         switch (mediaplayer2State) {
    320             case MEDIAPLAYER2_STATE_IDLE:
    321                 playerState = PLAYER_STATE_IDLE;
    322                 break;
    323             case MEDIAPLAYER2_STATE_PREPARED:
    324             case MEDIAPLAYER2_STATE_PAUSED:
    325                 playerState = PLAYER_STATE_PAUSED;
    326                 break;
    327             case MEDIAPLAYER2_STATE_PLAYING:
    328                 playerState = PLAYER_STATE_PLAYING;
    329                 break;
    330             case MEDIAPLAYER2_STATE_ERROR:
    331             default:
    332                 playerState = PLAYER_STATE_ERROR;
    333                 break;
    334         }
    335 
    336         return playerState;
    337     }
    338 
    339     /**
    340      * Gets the current buffering state of the player.
    341      * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already
    342      * buffered.
    343      */
    344     @Override
    345     public @BuffState int getBufferingState() {
    346         // TODO: use cached state or call native function.
    347         return BUFFERING_STATE_UNKNOWN;
    348     }
    349 
    350     /**
    351      * Sets the audio attributes for this MediaPlayer2.
    352      * See {@link AudioAttributes} for how to build and configure an instance of this class.
    353      * You must call this method before {@link #prepare()} in order
    354      * for the audio attributes to become effective thereafter.
    355      * @param attributes a non-null set of audio attributes
    356      * @throws IllegalArgumentException if the attributes are null or invalid.
    357      */
    358     @Override
    359     public void setAudioAttributes(@NonNull AudioAttributes attributes) {
    360         addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
    361             @Override
    362             void process() {
    363                 if (attributes == null) {
    364                     final String msg = "Cannot set AudioAttributes to null";
    365                     throw new IllegalArgumentException(msg);
    366                 }
    367                 Parcel pattributes = Parcel.obtain();
    368                 attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS);
    369                 setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes);
    370                 pattributes.recycle();
    371             }
    372         });
    373     }
    374 
    375     @Override
    376     public @NonNull AudioAttributes getAudioAttributes() {
    377         Parcel pattributes = getParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES);
    378         AudioAttributes attributes = AudioAttributes.CREATOR.createFromParcel(pattributes);
    379         pattributes.recycle();
    380         return attributes;
    381     }
    382 
    383     /**
    384      * Sets the data source as described by a DataSourceDesc.
    385      *
    386      * @param dsd the descriptor of data source you want to play
    387      * @throws IllegalStateException if it is called in an invalid state
    388      * @throws NullPointerException if dsd is null
    389      */
    390     @Override
    391     public void setDataSource(@NonNull DataSourceDesc dsd) {
    392         addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
    393             @Override
    394             void process() {
    395                 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
    396                 // TODO: setDataSource could update exist data source
    397                 synchronized (mSrcLock) {
    398                     mCurrentDSD = dsd;
    399                     mCurrentSrcId = mSrcIdGenerator++;
    400                     try {
    401                         handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
    402                     } catch (IOException e) {
    403                     }
    404                 }
    405             }
    406         });
    407     }
    408 
    409     /**
    410      * Sets a single data source as described by a DataSourceDesc which will be played
    411      * after current data source is finished.
    412      *
    413      * @param dsd the descriptor of data source you want to play after current one
    414      * @throws IllegalStateException if it is called in an invalid state
    415      * @throws NullPointerException if dsd is null
    416      */
    417     @Override
    418     public void setNextDataSource(@NonNull DataSourceDesc dsd) {
    419         addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
    420             @Override
    421             void process() {
    422                 Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
    423                 synchronized (mSrcLock) {
    424                     mNextDSDs = new ArrayList<DataSourceDesc>(1);
    425                     mNextDSDs.add(dsd);
    426                     mNextSrcId = mSrcIdGenerator++;
    427                     mNextSourceState = NEXT_SOURCE_STATE_INIT;
    428                     mNextSourcePlayPending = false;
    429                 }
    430                 int state = getMediaPlayer2State();
    431                 if (state != MEDIAPLAYER2_STATE_IDLE) {
    432                     synchronized (mSrcLock) {
    433                         prepareNextDataSource_l();
    434                     }
    435                 }
    436             }
    437         });
    438     }
    439 
    440     /**
    441      * Sets a list of data sources to be played sequentially after current data source is done.
    442      *
    443      * @param dsds the list of data sources you want to play after current one
    444      * @throws IllegalStateException if it is called in an invalid state
    445      * @throws IllegalArgumentException if dsds is null or empty, or contains null DataSourceDesc
    446      */
    447     @Override
    448     public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
    449         addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
    450             @Override
    451             void process() {
    452                 if (dsds == null || dsds.size() == 0) {
    453                     throw new IllegalArgumentException("data source list cannot be null or empty.");
    454                 }
    455                 for (DataSourceDesc dsd : dsds) {
    456                     if (dsd == null) {
    457                         throw new IllegalArgumentException(
    458                                 "DataSourceDesc in the source list cannot be null.");
    459                     }
    460                 }
    461 
    462                 synchronized (mSrcLock) {
    463                     mNextDSDs = new ArrayList(dsds);
    464                     mNextSrcId = mSrcIdGenerator++;
    465                     mNextSourceState = NEXT_SOURCE_STATE_INIT;
    466                     mNextSourcePlayPending = false;
    467                 }
    468                 int state = getMediaPlayer2State();
    469                 if (state != MEDIAPLAYER2_STATE_IDLE) {
    470                     synchronized (mSrcLock) {
    471                         prepareNextDataSource_l();
    472                     }
    473                 }
    474             }
    475         });
    476     }
    477 
    478     @Override
    479     public @NonNull DataSourceDesc getCurrentDataSource() {
    480         synchronized (mSrcLock) {
    481             return mCurrentDSD;
    482         }
    483     }
    484 
    485     /**
    486      * Configures the player to loop on the current data source.
    487      * @param loop true if the current data source is meant to loop.
    488      */
    489     @Override
    490     public void loopCurrent(boolean loop) {
    491         addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
    492             @Override
    493             void process() {
    494                 // TODO: set the looping mode, send notification
    495                 setLooping(loop);
    496             }
    497         });
    498     }
    499 
    500     private native void setLooping(boolean looping);
    501 
    502     /**
    503      * Sets the playback speed.
    504      * A value of 1.0f is the default playback value.
    505      * A negative value indicates reverse playback, check {@link #isReversePlaybackSupported()}
    506      * before using negative values.<br>
    507      * After changing the playback speed, it is recommended to query the actual speed supported
    508      * by the player, see {@link #getPlaybackSpeed()}.
    509      * @param speed the desired playback speed
    510      */
    511     @Override
    512     public void setPlaybackSpeed(float speed) {
    513         addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_SPEED, false) {
    514             @Override
    515             void process() {
    516                 _setPlaybackParams(getPlaybackParams().setSpeed(speed));
    517             }
    518         });
    519     }
    520 
    521     /**
    522      * Returns the actual playback speed to be used by the player when playing.
    523      * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
    524      * @return the actual playback speed
    525      */
    526     @Override
    527     public float getPlaybackSpeed() {
    528         return getPlaybackParams().getSpeed();
    529     }
    530 
    531     /**
    532      * Indicates whether reverse playback is supported.
    533      * Reverse playback is indicated by negative playback speeds, see
    534      * {@link #setPlaybackSpeed(float)}.
    535      * @return true if reverse playback is supported.
    536      */
    537     @Override
    538     public boolean isReversePlaybackSupported() {
    539         return false;
    540     }
    541 
    542     /**
    543      * Sets the volume of the audio of the media to play, expressed as a linear multiplier
    544      * on the audio samples.
    545      * Note that this volume is specific to the player, and is separate from stream volume
    546      * used across the platform.<br>
    547      * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified
    548      * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player.
    549      * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}.
    550      */
    551     @Override
    552     public void setPlayerVolume(float volume) {
    553         addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
    554             @Override
    555             void process() {
    556                 mVolume = volume;
    557                 _setVolume(volume, volume);
    558             }
    559         });
    560     }
    561 
    562     private native void _setVolume(float leftVolume, float rightVolume);
    563 
    564     /**
    565      * Returns the current volume of this player to this player.
    566      * Note that it does not take into account the associated stream volume.
    567      * @return the player volume.
    568      */
    569     @Override
    570     public float getPlayerVolume() {
    571         return mVolume;
    572     }
    573 
    574     /**
    575      * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
    576      */
    577     @Override
    578     public float getMaxPlayerVolume() {
    579         return 1.0f;
    580     }
    581 
    582     /**
    583      * Adds a callback to be notified of events for this player.
    584      * @param e the {@link Executor} to be used for the events.
    585      * @param cb the callback to receive the events.
    586      */
    587     @Override
    588     public void registerPlayerEventCallback(@NonNull Executor e,
    589             @NonNull PlayerEventCallback cb) {
    590     }
    591 
    592     /**
    593      * Removes a previously registered callback for player events
    594      * @param cb the callback to remove
    595      */
    596     @Override
    597     public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback cb) {
    598     }
    599 
    600 
    601     private static final int NEXT_SOURCE_STATE_ERROR = -1;
    602     private static final int NEXT_SOURCE_STATE_INIT = 0;
    603     private static final int NEXT_SOURCE_STATE_PREPARING = 1;
    604     private static final int NEXT_SOURCE_STATE_PREPARED = 2;
    605 
    606     /*
    607      * Update the MediaPlayer2Impl SurfaceTexture.
    608      * Call after setting a new display surface.
    609      */
    610     private native void _setVideoSurface(Surface surface);
    611 
    612     /* Do not change these values (starting with INVOKE_ID) without updating
    613      * their counterparts in include/media/mediaplayer2.h!
    614      */
    615     private static final int INVOKE_ID_GET_TRACK_INFO = 1;
    616     private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
    617     private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
    618     private static final int INVOKE_ID_SELECT_TRACK = 4;
    619     private static final int INVOKE_ID_DESELECT_TRACK = 5;
    620     private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6;
    621     private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
    622 
    623     /**
    624      * Create a request parcel which can be routed to the native media
    625      * player using {@link #invoke(Parcel, Parcel)}. The Parcel
    626      * returned has the proper InterfaceToken set. The caller should
    627      * not overwrite that token, i.e it can only append data to the
    628      * Parcel.
    629      *
    630      * @return A parcel suitable to hold a request for the native
    631      * player.
    632      * {@hide}
    633      */
    634     @Override
    635     public Parcel newRequest() {
    636         Parcel parcel = Parcel.obtain();
    637         return parcel;
    638     }
    639 
    640     /**
    641      * Invoke a generic method on the native player using opaque
    642      * parcels for the request and reply. Both payloads' format is a
    643      * convention between the java caller and the native player.
    644      * Must be called after setDataSource or setPlaylist to make sure a native player
    645      * exists. On failure, a RuntimeException is thrown.
    646      *
    647      * @param request Parcel with the data for the extension. The
    648      * caller must use {@link #newRequest()} to get one.
    649      *
    650      * @param reply Output parcel with the data returned by the
    651      * native player.
    652      * {@hide}
    653      */
    654     @Override
    655     public void invoke(Parcel request, Parcel reply) {
    656         int retcode = native_invoke(request, reply);
    657         reply.setDataPosition(0);
    658         if (retcode != 0) {
    659             throw new RuntimeException("failure code: " + retcode);
    660         }
    661     }
    662 
    663     @Override
    664     public void notifyWhenCommandLabelReached(Object label) {
    665         addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
    666             @Override
    667             void process() {
    668                 synchronized (mEventCbLock) {
    669                     for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
    670                         cb.first.execute(() -> cb.second.onCommandLabelReached(
    671                                 MediaPlayer2Impl.this, label));
    672                     }
    673                 }
    674             }
    675         });
    676     }
    677 
    678     /**
    679      * Sets the {@link SurfaceHolder} to use for displaying the video
    680      * portion of the media.
    681      *
    682      * Either a surface holder or surface must be set if a display or video sink
    683      * is needed.  Not calling this method or {@link #setSurface(Surface)}
    684      * when playing back a video will result in only the audio track being played.
    685      * A null surface holder or surface will result in only the audio track being
    686      * played.
    687      *
    688      * @param sh the SurfaceHolder to use for video display
    689      * @throws IllegalStateException if the internal player engine has not been
    690      * initialized or has been released.
    691      * @hide
    692      */
    693     @Override
    694     public void setDisplay(SurfaceHolder sh) {
    695         mSurfaceHolder = sh;
    696         Surface surface;
    697         if (sh != null) {
    698             surface = sh.getSurface();
    699         } else {
    700             surface = null;
    701         }
    702         _setVideoSurface(surface);
    703         updateSurfaceScreenOn();
    704     }
    705 
    706     /**
    707      * Sets the {@link Surface} to be used as the sink for the video portion of
    708      * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but
    709      * does not support {@link #setScreenOnWhilePlaying(boolean)}.  Setting a
    710      * Surface will un-set any Surface or SurfaceHolder that was previously set.
    711      * A null surface will result in only the audio track being played.
    712      *
    713      * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
    714      * returned from {@link SurfaceTexture#getTimestamp()} will have an
    715      * unspecified zero point.  These timestamps cannot be directly compared
    716      * between different media sources, different instances of the same media
    717      * source, or multiple runs of the same program.  The timestamp is normally
    718      * monotonically increasing and is unaffected by time-of-day adjustments,
    719      * but it is reset when the position is set.
    720      *
    721      * @param surface The {@link Surface} to be used for the video portion of
    722      * the media.
    723      * @throws IllegalStateException if the internal player engine has not been
    724      * initialized or has been released.
    725      */
    726     @Override
    727     public void setSurface(Surface surface) {
    728         addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
    729             @Override
    730             void process() {
    731                 if (mScreenOnWhilePlaying && surface != null) {
    732                     Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
    733                 }
    734                 mSurfaceHolder = null;
    735                 _setVideoSurface(surface);
    736                 updateSurfaceScreenOn();
    737             }
    738         });
    739     }
    740 
    741     /**
    742      * Sets video scaling mode. To make the target video scaling mode
    743      * effective during playback, this method must be called after
    744      * data source is set. If not called, the default video
    745      * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}.
    746      *
    747      * <p> The supported video scaling modes are:
    748      * <ul>
    749      * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}
    750      * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}
    751      * </ul>
    752      *
    753      * @param mode target video scaling mode. Must be one of the supported
    754      * video scaling modes; otherwise, IllegalArgumentException will be thrown.
    755      *
    756      * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT
    757      * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
    758      * @hide
    759      */
    760     @Override
    761     public void setVideoScalingMode(int mode) {
    762         addTask(new Task(CALL_COMPLETED_SET_VIDEO_SCALING_MODE, false) {
    763             @Override
    764             void process() {
    765                 if (!isVideoScalingModeSupported(mode)) {
    766                     final String msg = "Scaling mode " + mode + " is not supported";
    767                     throw new IllegalArgumentException(msg);
    768                 }
    769                 Parcel request = Parcel.obtain();
    770                 Parcel reply = Parcel.obtain();
    771                 try {
    772                     request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE);
    773                     request.writeInt(mode);
    774                     invoke(request, reply);
    775                 } finally {
    776                     request.recycle();
    777                     reply.recycle();
    778                 }
    779             }
    780         });
    781     }
    782 
    783     /**
    784      * Discards all pending commands.
    785      */
    786     @Override
    787     public void clearPendingCommands() {
    788     }
    789 
    790     private void addTask(Task task) {
    791         synchronized (mTaskLock) {
    792             mPendingTasks.add(task);
    793             processPendingTask_l();
    794         }
    795     }
    796 
    797     @GuardedBy("mTaskLock")
    798     private void processPendingTask_l() {
    799         if (mCurrentTask != null) {
    800             return;
    801         }
    802         if (!mPendingTasks.isEmpty()) {
    803             Task task = mPendingTasks.remove(0);
    804             mCurrentTask = task;
    805             mTaskHandler.post(task);
    806         }
    807     }
    808 
    809     private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId)
    810             throws IOException {
    811         Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
    812 
    813         switch (dsd.getType()) {
    814             case DataSourceDesc.TYPE_CALLBACK:
    815                 handleDataSource(isCurrent,
    816                                  srcId,
    817                                  dsd.getMedia2DataSource());
    818                 break;
    819 
    820             case DataSourceDesc.TYPE_FD:
    821                 handleDataSource(isCurrent,
    822                                  srcId,
    823                                  dsd.getFileDescriptor(),
    824                                  dsd.getFileDescriptorOffset(),
    825                                  dsd.getFileDescriptorLength());
    826                 break;
    827 
    828             case DataSourceDesc.TYPE_URI:
    829                 handleDataSource(isCurrent,
    830                                  srcId,
    831                                  dsd.getUriContext(),
    832                                  dsd.getUri(),
    833                                  dsd.getUriHeaders(),
    834                                  dsd.getUriCookies());
    835                 break;
    836 
    837             default:
    838                 break;
    839         }
    840     }
    841 
    842     /**
    843      * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
    844      * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
    845      * this API to pass the cookies as a list of HttpCookie. If the app has not installed
    846      * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
    847      * the provided cookies. If the app has installed its own handler already, this API requires the
    848      * handler to be of CookieManager type such that the API can update the managers CookieStore.
    849      *
    850      * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
    851      * but that can be changed with key/value pairs through the headers parameter with
    852      * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
    853      * disallow or allow cross domain redirection.
    854      *
    855      * @throws IllegalArgumentException if cookies are provided and the installed handler is not
    856      *                                  a CookieManager
    857      * @throws IllegalStateException    if it is called in an invalid state
    858      * @throws NullPointerException     if context or uri is null
    859      * @throws IOException              if uri has a file scheme and an I/O error occurs
    860      */
    861     private void handleDataSource(
    862             boolean isCurrent, long srcId,
    863             @NonNull Context context, @NonNull Uri uri,
    864             @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
    865             throws IOException {
    866         // The context and URI usually belong to the calling user. Get a resolver for that user
    867         // and strip out the userId from the URI if present.
    868         final ContentResolver resolver = context.getContentResolver();
    869         final String scheme = uri.getScheme();
    870         final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority());
    871         if (ContentResolver.SCHEME_FILE.equals(scheme)) {
    872             handleDataSource(isCurrent, srcId, uri.getPath(), null, null);
    873             return;
    874         }
    875 
    876         if (ContentResolver.SCHEME_CONTENT.equals(scheme)
    877                 && Settings.AUTHORITY.equals(authority)) {
    878             // Try cached ringtone first since the actual provider may not be
    879             // encryption aware, or it may be stored on CE media storage
    880             final int type = RingtoneManager.getDefaultType(uri);
    881             final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId());
    882             final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
    883             if (attemptDataSource(isCurrent, srcId, resolver, cacheUri)) {
    884                 return;
    885             }
    886             if (attemptDataSource(isCurrent, srcId, resolver, actualUri)) {
    887                 return;
    888             }
    889             handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies);
    890         } else {
    891             // Try requested Uri locally first, or fallback to media server
    892             if (attemptDataSource(isCurrent, srcId, resolver, uri)) {
    893                 return;
    894             }
    895             handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies);
    896         }
    897     }
    898 
    899     private boolean attemptDataSource(
    900             boolean isCurrent, long srcId, ContentResolver resolver, Uri uri) {
    901         try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
    902             if (afd.getDeclaredLength() < 0) {
    903                 handleDataSource(isCurrent,
    904                                  srcId,
    905                                  afd.getFileDescriptor(),
    906                                  0,
    907                                  DataSourceDesc.LONG_MAX);
    908             } else {
    909                 handleDataSource(isCurrent,
    910                                  srcId,
    911                                  afd.getFileDescriptor(),
    912                                  afd.getStartOffset(),
    913                                  afd.getDeclaredLength());
    914             }
    915             return true;
    916         } catch (NullPointerException | SecurityException | IOException ex) {
    917             Log.w(TAG, "Couldn't open " + uri + ": " + ex);
    918             return false;
    919         }
    920     }
    921 
    922     private void handleDataSource(
    923             boolean isCurrent, long srcId,
    924             String path, Map<String, String> headers, List<HttpCookie> cookies)
    925             throws IOException {
    926         String[] keys = null;
    927         String[] values = null;
    928 
    929         if (headers != null) {
    930             keys = new String[headers.size()];
    931             values = new String[headers.size()];
    932 
    933             int i = 0;
    934             for (Map.Entry<String, String> entry: headers.entrySet()) {
    935                 keys[i] = entry.getKey();
    936                 values[i] = entry.getValue();
    937                 ++i;
    938             }
    939         }
    940         handleDataSource(isCurrent, srcId, path, keys, values, cookies);
    941     }
    942 
    943     private void handleDataSource(boolean isCurrent, long srcId,
    944             String path, String[] keys, String[] values, List<HttpCookie> cookies)
    945             throws IOException {
    946         final Uri uri = Uri.parse(path);
    947         final String scheme = uri.getScheme();
    948         if ("file".equals(scheme)) {
    949             path = uri.getPath();
    950         } else if (scheme != null) {
    951             // handle non-file sources
    952             nativeHandleDataSourceUrl(
    953                 isCurrent,
    954                 srcId,
    955                 Media2HTTPService.createHTTPService(path, cookies),
    956                 path,
    957                 keys,
    958                 values);
    959             return;
    960         }
    961 
    962         final File file = new File(path);
    963         if (file.exists()) {
    964             FileInputStream is = new FileInputStream(file);
    965             FileDescriptor fd = is.getFD();
    966             handleDataSource(isCurrent, srcId, fd, 0, DataSourceDesc.LONG_MAX);
    967             is.close();
    968         } else {
    969             throw new IOException("handleDataSource failed.");
    970         }
    971     }
    972 
    973     private native void nativeHandleDataSourceUrl(
    974             boolean isCurrent, long srcId,
    975             Media2HTTPService httpService, String path, String[] keys, String[] values)
    976             throws IOException;
    977 
    978     /**
    979      * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
    980      * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
    981      * to close the file descriptor. It is safe to do so as soon as this call returns.
    982      *
    983      * @throws IllegalStateException if it is called in an invalid state
    984      * @throws IllegalArgumentException if fd is not a valid FileDescriptor
    985      * @throws IOException if fd can not be read
    986      */
    987     private void handleDataSource(
    988             boolean isCurrent, long srcId,
    989             FileDescriptor fd, long offset, long length) throws IOException {
    990         nativeHandleDataSourceFD(isCurrent, srcId, fd, offset, length);
    991     }
    992 
    993     private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
    994             FileDescriptor fd, long offset, long length) throws IOException;
    995 
    996     /**
    997      * @throws IllegalStateException if it is called in an invalid state
    998      * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource
    999      */
   1000     private void handleDataSource(boolean isCurrent, long srcId, Media2DataSource dataSource) {
   1001         nativeHandleDataSourceCallback(isCurrent, srcId, dataSource);
   1002     }
   1003 
   1004     private native void nativeHandleDataSourceCallback(
   1005             boolean isCurrent, long srcId, Media2DataSource dataSource);
   1006 
   1007     // This function shall be called with |mSrcLock| acquired.
   1008     private void prepareNextDataSource_l() {
   1009         if (mNextDSDs == null || mNextDSDs.isEmpty()
   1010                 || mNextSourceState != NEXT_SOURCE_STATE_INIT) {
   1011             // There is no next source or it's in preparing or prepared state.
   1012             return;
   1013         }
   1014 
   1015         try {
   1016             mNextSourceState = NEXT_SOURCE_STATE_PREPARING;
   1017             handleDataSource(false /* isCurrent */, mNextDSDs.get(0), mNextSrcId);
   1018         } catch (Exception e) {
   1019             Message msg2 = mEventHandler.obtainMessage(
   1020                     MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
   1021             final long nextSrcId = mNextSrcId;
   1022             mEventHandler.post(new Runnable() {
   1023                 @Override
   1024                 public void run() {
   1025                     mEventHandler.handleMessage(msg2, nextSrcId);
   1026                 }
   1027             });
   1028         }
   1029     }
   1030 
   1031     // This function shall be called with |mSrcLock| acquired.
   1032     private void playNextDataSource_l() {
   1033         if (mNextDSDs == null || mNextDSDs.isEmpty()) {
   1034             return;
   1035         }
   1036 
   1037         if (mNextSourceState == NEXT_SOURCE_STATE_PREPARED) {
   1038             // Switch to next source only when it's in prepared state.
   1039             mCurrentDSD = mNextDSDs.get(0);
   1040             mCurrentSrcId = mNextSrcId;
   1041             mBufferedPercentageCurrent.set(mBufferedPercentageNext.get());
   1042             mNextDSDs.remove(0);
   1043             mNextSrcId = mSrcIdGenerator++;  // make it different from mCurrentSrcId
   1044             mBufferedPercentageNext.set(0);
   1045             mNextSourceState = NEXT_SOURCE_STATE_INIT;
   1046             mNextSourcePlayPending = false;
   1047 
   1048             long srcId = mCurrentSrcId;
   1049             try {
   1050                 nativePlayNextDataSource(srcId);
   1051             } catch (Exception e) {
   1052                 Message msg2 = mEventHandler.obtainMessage(
   1053                         MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
   1054                 mEventHandler.post(new Runnable() {
   1055                     @Override
   1056                     public void run() {
   1057                         mEventHandler.handleMessage(msg2, srcId);
   1058                     }
   1059                 });
   1060             }
   1061 
   1062             // Wait for MEDIA2_INFO_STARTED_AS_NEXT to prepare next source.
   1063         } else {
   1064             if (mNextSourceState == NEXT_SOURCE_STATE_INIT) {
   1065                 prepareNextDataSource_l();
   1066             }
   1067             mNextSourcePlayPending = true;
   1068         }
   1069     }
   1070 
   1071     private native void nativePlayNextDataSource(long srcId);
   1072 
   1073 
   1074     private int getAudioStreamType() {
   1075         if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
   1076             mStreamType = _getAudioStreamType();
   1077         }
   1078         return mStreamType;
   1079     }
   1080 
   1081     private native int _getAudioStreamType() throws IllegalStateException;
   1082 
   1083     /**
   1084      * Stops playback after playback has been started or paused.
   1085      *
   1086      * @throws IllegalStateException if the internal player engine has not been
   1087      * initialized.
   1088      * #hide
   1089      */
   1090     @Override
   1091     public void stop() {
   1092         stayAwake(false);
   1093         _stop();
   1094     }
   1095 
   1096     private native void _stop() throws IllegalStateException;
   1097 
   1098     //--------------------------------------------------------------------------
   1099     // Explicit Routing
   1100     //--------------------
   1101     private AudioDeviceInfo mPreferredDevice = null;
   1102 
   1103     /**
   1104      * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
   1105      * the output from this MediaPlayer2.
   1106      * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
   1107      *  If deviceInfo is null, default routing is restored.
   1108      * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
   1109      * does not correspond to a valid audio device.
   1110      */
   1111     @Override
   1112     public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
   1113         if (deviceInfo != null && !deviceInfo.isSink()) {
   1114             return false;
   1115         }
   1116         int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
   1117         boolean status = native_setOutputDevice(preferredDeviceId);
   1118         if (status == true) {
   1119             synchronized (this) {
   1120                 mPreferredDevice = deviceInfo;
   1121             }
   1122         }
   1123         return status;
   1124     }
   1125 
   1126     /**
   1127      * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
   1128      * is not guaranteed to correspond to the actual device being used for playback.
   1129      */
   1130     @Override
   1131     public AudioDeviceInfo getPreferredDevice() {
   1132         synchronized (this) {
   1133             return mPreferredDevice;
   1134         }
   1135     }
   1136 
   1137     /**
   1138      * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2
   1139      * Note: The query is only valid if the MediaPlayer2 is currently playing.
   1140      * If the player is not playing, the returned device can be null or correspond to previously
   1141      * selected device when the player was last active.
   1142      */
   1143     @Override
   1144     public AudioDeviceInfo getRoutedDevice() {
   1145         int deviceId = native_getRoutedDeviceId();
   1146         if (deviceId == 0) {
   1147             return null;
   1148         }
   1149         AudioDeviceInfo[] devices =
   1150                 AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
   1151         for (int i = 0; i < devices.length; i++) {
   1152             if (devices[i].getId() == deviceId) {
   1153                 return devices[i];
   1154             }
   1155         }
   1156         return null;
   1157     }
   1158 
   1159     /*
   1160      * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
   1161      */
   1162     @GuardedBy("mRoutingChangeListeners")
   1163     private void enableNativeRoutingCallbacksLocked(boolean enabled) {
   1164         if (mRoutingChangeListeners.size() == 0) {
   1165             native_enableDeviceCallback(enabled);
   1166         }
   1167     }
   1168 
   1169     /**
   1170      * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
   1171      * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
   1172      * by an app to receive (re)routing notifications.
   1173      */
   1174     @GuardedBy("mRoutingChangeListeners")
   1175     private ArrayMap<AudioRouting.OnRoutingChangedListener,
   1176             NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
   1177 
   1178     /**
   1179      * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
   1180      * changes on this MediaPlayer2.
   1181      * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
   1182      * notifications of rerouting events.
   1183      * @param handler  Specifies the {@link Handler} object for the thread on which to execute
   1184      * the callback. If <code>null</code>, the handler on the main looper will be used.
   1185      */
   1186     @Override
   1187     public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
   1188             Handler handler) {
   1189         synchronized (mRoutingChangeListeners) {
   1190             if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
   1191                 enableNativeRoutingCallbacksLocked(true);
   1192                 mRoutingChangeListeners.put(
   1193                         listener, new NativeRoutingEventHandlerDelegate(this, listener,
   1194                                 handler != null ? handler : mEventHandler));
   1195             }
   1196         }
   1197     }
   1198 
   1199     /**
   1200      * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
   1201      * to receive rerouting notifications.
   1202      * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
   1203      * to remove.
   1204      */
   1205     @Override
   1206     public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
   1207         synchronized (mRoutingChangeListeners) {
   1208             if (mRoutingChangeListeners.containsKey(listener)) {
   1209                 mRoutingChangeListeners.remove(listener);
   1210                 enableNativeRoutingCallbacksLocked(false);
   1211             }
   1212         }
   1213     }
   1214 
   1215     private native final boolean native_setOutputDevice(int deviceId);
   1216     private native final int native_getRoutedDeviceId();
   1217     private native final void native_enableDeviceCallback(boolean enabled);
   1218 
   1219     /**
   1220      * Set the low-level power management behavior for this MediaPlayer2.  This
   1221      * can be used when the MediaPlayer2 is not playing through a SurfaceHolder
   1222      * set with {@link #setDisplay(SurfaceHolder)} and thus can use the
   1223      * high-level {@link #setScreenOnWhilePlaying(boolean)} feature.
   1224      *
   1225      * <p>This function has the MediaPlayer2 access the low-level power manager
   1226      * service to control the device's power usage while playing is occurring.
   1227      * The parameter is a combination of {@link android.os.PowerManager} wake flags.
   1228      * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK}
   1229      * permission.
   1230      * By default, no attempt is made to keep the device awake during playback.
   1231      *
   1232      * @param context the Context to use
   1233      * @param mode    the power/wake mode to set
   1234      * @see android.os.PowerManager
   1235      * @hide
   1236      */
   1237     @Override
   1238     public void setWakeMode(Context context, int mode) {
   1239         boolean washeld = false;
   1240 
   1241         /* Disable persistant wakelocks in media player based on property */
   1242         if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) {
   1243             Log.w(TAG, "IGNORING setWakeMode " + mode);
   1244             return;
   1245         }
   1246 
   1247         if (mWakeLock != null) {
   1248             if (mWakeLock.isHeld()) {
   1249                 washeld = true;
   1250                 mWakeLock.release();
   1251             }
   1252             mWakeLock = null;
   1253         }
   1254 
   1255         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
   1256         mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer2Impl.class.getName());
   1257         mWakeLock.setReferenceCounted(false);
   1258         if (washeld) {
   1259             mWakeLock.acquire();
   1260         }
   1261     }
   1262 
   1263     /**
   1264      * Control whether we should use the attached SurfaceHolder to keep the
   1265      * screen on while video playback is occurring.  This is the preferred
   1266      * method over {@link #setWakeMode} where possible, since it doesn't
   1267      * require that the application have permission for low-level wake lock
   1268      * access.
   1269      *
   1270      * @param screenOn Supply true to keep the screen on, false to allow it
   1271      * to turn off.
   1272      * @hide
   1273      */
   1274     @Override
   1275     public void setScreenOnWhilePlaying(boolean screenOn) {
   1276         if (mScreenOnWhilePlaying != screenOn) {
   1277             if (screenOn && mSurfaceHolder == null) {
   1278                 Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder");
   1279             }
   1280             mScreenOnWhilePlaying = screenOn;
   1281             updateSurfaceScreenOn();
   1282         }
   1283     }
   1284 
   1285     private void stayAwake(boolean awake) {
   1286         if (mWakeLock != null) {
   1287             if (awake && !mWakeLock.isHeld()) {
   1288                 mWakeLock.acquire();
   1289             } else if (!awake && mWakeLock.isHeld()) {
   1290                 mWakeLock.release();
   1291             }
   1292         }
   1293         mStayAwake = awake;
   1294         updateSurfaceScreenOn();
   1295     }
   1296 
   1297     private void updateSurfaceScreenOn() {
   1298         if (mSurfaceHolder != null) {
   1299             mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
   1300         }
   1301     }
   1302 
   1303     /**
   1304      * Returns the width of the video.
   1305      *
   1306      * @return the width of the video, or 0 if there is no video,
   1307      * no display surface was set, or the width has not been determined
   1308      * yet. The {@code MediaPlayer2EventCallback} can be registered via
   1309      * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a
   1310      * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the width
   1311      * is available.
   1312      */
   1313     @Override
   1314     public native int getVideoWidth();
   1315 
   1316     /**
   1317      * Returns the height of the video.
   1318      *
   1319      * @return the height of the video, or 0 if there is no video,
   1320      * no display surface was set, or the height has not been determined
   1321      * yet. The {@code MediaPlayer2EventCallback} can be registered via
   1322      * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a
   1323      * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the height
   1324      * is available.
   1325      */
   1326     @Override
   1327     public native int getVideoHeight();
   1328 
   1329     /**
   1330      * Return Metrics data about the current player.
   1331      *
   1332      * @return a {@link PersistableBundle} containing the set of attributes and values
   1333      * available for the media being handled by this instance of MediaPlayer2
   1334      * The attributes are descibed in {@link MetricsConstants}.
   1335      *
   1336      *  Additional vendor-specific fields may also be present in
   1337      *  the return value.
   1338      */
   1339     @Override
   1340     public PersistableBundle getMetrics() {
   1341         PersistableBundle bundle = native_getMetrics();
   1342         return bundle;
   1343     }
   1344 
   1345     private native PersistableBundle native_getMetrics();
   1346 
   1347     /**
   1348      * Checks whether the MediaPlayer2 is playing.
   1349      *
   1350      * @return true if currently playing, false otherwise
   1351      * @throws IllegalStateException if the internal player engine has not been
   1352      * initialized or has been released.
   1353      * @hide
   1354      */
   1355     @Override
   1356     public native boolean isPlaying();
   1357 
   1358     @Override
   1359     public @MediaPlayer2State int getMediaPlayer2State() {
   1360         return native_getMediaPlayer2State();
   1361     }
   1362 
   1363     private native int native_getMediaPlayer2State();
   1364 
   1365     /**
   1366      * Gets the current buffering management params used by the source component.
   1367      * Calling it only after {@code setDataSource} has been called.
   1368      * Each type of data source might have different set of default params.
   1369      *
   1370      * @return the current buffering management params used by the source component.
   1371      * @throws IllegalStateException if the internal player engine has not been
   1372      * initialized, or {@code setDataSource} has not been called.
   1373      * @hide
   1374      */
   1375     @Override
   1376     @NonNull
   1377     public native BufferingParams getBufferingParams();
   1378 
   1379     /**
   1380      * Sets buffering management params.
   1381      * The object sets its internal BufferingParams to the input, except that the input is
   1382      * invalid or not supported.
   1383      * Call it only after {@code setDataSource} has been called.
   1384      * The input is a hint to MediaPlayer2.
   1385      *
   1386      * @param params the buffering management params.
   1387      *
   1388      * @throws IllegalStateException if the internal player engine has not been
   1389      * initialized or has been released, or {@code setDataSource} has not been called.
   1390      * @throws IllegalArgumentException if params is invalid or not supported.
   1391      * @hide
   1392      */
   1393     @Override
   1394     public void setBufferingParams(@NonNull BufferingParams params) {
   1395         addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
   1396             @Override
   1397             void process() {
   1398                 Preconditions.checkNotNull(params, "the BufferingParams cannot be null");
   1399                 _setBufferingParams(params);
   1400             }
   1401         });
   1402     }
   1403 
   1404     private native void _setBufferingParams(@NonNull BufferingParams params);
   1405 
   1406     /**
   1407      * Sets playback rate and audio mode.
   1408      *
   1409      * @param rate the ratio between desired playback rate and normal one.
   1410      * @param audioMode audio playback mode. Must be one of the supported
   1411      * audio modes.
   1412      *
   1413      * @throws IllegalStateException if the internal player engine has not been
   1414      * initialized.
   1415      * @throws IllegalArgumentException if audioMode is not supported.
   1416      *
   1417      * @hide
   1418      */
   1419     @Override
   1420     @NonNull
   1421     public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) {
   1422         PlaybackParams params = new PlaybackParams();
   1423         params.allowDefaults();
   1424         switch (audioMode) {
   1425         case PLAYBACK_RATE_AUDIO_MODE_DEFAULT:
   1426             params.setSpeed(rate).setPitch(1.0f);
   1427             break;
   1428         case PLAYBACK_RATE_AUDIO_MODE_STRETCH:
   1429             params.setSpeed(rate).setPitch(1.0f)
   1430                     .setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL);
   1431             break;
   1432         case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE:
   1433             params.setSpeed(rate).setPitch(rate);
   1434             break;
   1435         default:
   1436             final String msg = "Audio playback mode " + audioMode + " is not supported";
   1437             throw new IllegalArgumentException(msg);
   1438         }
   1439         return params;
   1440     }
   1441 
   1442     /**
   1443      * Sets playback rate using {@link PlaybackParams}. The object sets its internal
   1444      * PlaybackParams to the input, except that the object remembers previous speed
   1445      * when input speed is zero. This allows the object to resume at previous speed
   1446      * when play() is called. Calling it before the object is prepared does not change
   1447      * the object state. After the object is prepared, calling it with zero speed is
   1448      * equivalent to calling pause(). After the object is prepared, calling it with
   1449      * non-zero speed is equivalent to calling play().
   1450      *
   1451      * @param params the playback params.
   1452      *
   1453      * @throws IllegalStateException if the internal player engine has not been
   1454      * initialized or has been released.
   1455      * @throws IllegalArgumentException if params is not supported.
   1456      */
   1457     @Override
   1458     public void setPlaybackParams(@NonNull PlaybackParams params) {
   1459         addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
   1460             @Override
   1461             void process() {
   1462                 Preconditions.checkNotNull(params, "the PlaybackParams cannot be null");
   1463                 _setPlaybackParams(params);
   1464             }
   1465         });
   1466     }
   1467 
   1468     private native void _setPlaybackParams(@NonNull PlaybackParams params);
   1469 
   1470     /**
   1471      * Gets the playback params, containing the current playback rate.
   1472      *
   1473      * @return the playback params.
   1474      * @throws IllegalStateException if the internal player engine has not been
   1475      * initialized.
   1476      */
   1477     @Override
   1478     @NonNull
   1479     public native PlaybackParams getPlaybackParams();
   1480 
   1481     /**
   1482      * Sets A/V sync mode.
   1483      *
   1484      * @param params the A/V sync params to apply
   1485      *
   1486      * @throws IllegalStateException if the internal player engine has not been
   1487      * initialized.
   1488      * @throws IllegalArgumentException if params are not supported.
   1489      */
   1490     @Override
   1491     public void setSyncParams(@NonNull SyncParams params) {
   1492         addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
   1493             @Override
   1494             void process() {
   1495                 Preconditions.checkNotNull(params, "the SyncParams cannot be null");
   1496                 _setSyncParams(params);
   1497             }
   1498         });
   1499     }
   1500 
   1501     private native void _setSyncParams(@NonNull SyncParams params);
   1502 
   1503     /**
   1504      * Gets the A/V sync mode.
   1505      *
   1506      * @return the A/V sync params
   1507      *
   1508      * @throws IllegalStateException if the internal player engine has not been
   1509      * initialized.
   1510      */
   1511     @Override
   1512     @NonNull
   1513     public native SyncParams getSyncParams();
   1514 
   1515     /**
   1516      * Moves the media to specified time position by considering the given mode.
   1517      * <p>
   1518      * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user.
   1519      * There is at most one active seekTo processed at any time. If there is a to-be-completed
   1520      * seekTo, new seekTo requests will be queued in such a way that only the last request
   1521      * is kept. When current seekTo is completed, the queued request will be processed if
   1522      * that request is different from just-finished seekTo operation, i.e., the requested
   1523      * position or mode is different.
   1524      *
   1525      * @param msec the offset in milliseconds from the start to seek to.
   1526      * When seeking to the given time position, there is no guarantee that the data source
   1527      * has a frame located at the position. When this happens, a frame nearby will be rendered.
   1528      * If msec is negative, time position zero will be used.
   1529      * If msec is larger than duration, duration will be used.
   1530      * @param mode the mode indicating where exactly to seek to.
   1531      * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame
   1532      * that has a timestamp earlier than or the same as msec. Use
   1533      * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame
   1534      * that has a timestamp later than or the same as msec. Use
   1535      * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame
   1536      * that has a timestamp closest to or the same as msec. Use
   1537      * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may
   1538      * or may not be a sync frame but is closest to or the same as msec.
   1539      * {@link #SEEK_CLOSEST} often has larger performance overhead compared
   1540      * to the other options if there is no sync frame located at msec.
   1541      * @throws IllegalStateException if the internal player engine has not been
   1542      * initialized
   1543      * @throws IllegalArgumentException if the mode is invalid.
   1544      */
   1545     @Override
   1546     public void seekTo(final long msec, @SeekMode int mode) {
   1547         addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
   1548             @Override
   1549             void process() {
   1550                 if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
   1551                     final String msg = "Illegal seek mode: " + mode;
   1552                     throw new IllegalArgumentException(msg);
   1553                 }
   1554                 // TODO: pass long to native, instead of truncating here.
   1555                 long posMs = msec;
   1556                 if (posMs > Integer.MAX_VALUE) {
   1557                     Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to "
   1558                             + Integer.MAX_VALUE);
   1559                     posMs = Integer.MAX_VALUE;
   1560                 } else if (posMs < Integer.MIN_VALUE) {
   1561                     Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to "
   1562                             + Integer.MIN_VALUE);
   1563                     posMs = Integer.MIN_VALUE;
   1564                 }
   1565                 _seekTo(posMs, mode);
   1566             }
   1567         });
   1568     }
   1569 
   1570     private native final void _seekTo(long msec, int mode);
   1571 
   1572     /**
   1573      * Get current playback position as a {@link MediaTimestamp}.
   1574      * <p>
   1575      * The MediaTimestamp represents how the media time correlates to the system time in
   1576      * a linear fashion using an anchor and a clock rate. During regular playback, the media
   1577      * time moves fairly constantly (though the anchor frame may be rebased to a current
   1578      * system time, the linear correlation stays steady). Therefore, this method does not
   1579      * need to be called often.
   1580      * <p>
   1581      * To help users get current playback position, this method always anchors the timestamp
   1582      * to the current {@link System#nanoTime system time}, so
   1583      * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
   1584      *
   1585      * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
   1586      *         is available, e.g. because the media player has not been initialized.
   1587      *
   1588      * @see MediaTimestamp
   1589      */
   1590     @Override
   1591     @Nullable
   1592     public MediaTimestamp getTimestamp()
   1593     {
   1594         try {
   1595             // TODO: get the timestamp from native side
   1596             return new MediaTimestamp(
   1597                     getCurrentPosition() * 1000L,
   1598                     System.nanoTime(),
   1599                     isPlaying() ? getPlaybackParams().getSpeed() : 0.f);
   1600         } catch (IllegalStateException e) {
   1601             return null;
   1602         }
   1603     }
   1604 
   1605     /**
   1606      * Gets the media metadata.
   1607      *
   1608      * @param update_only controls whether the full set of available
   1609      * metadata is returned or just the set that changed since the
   1610      * last call. See {@see #METADATA_UPDATE_ONLY} and {@see
   1611      * #METADATA_ALL}.
   1612      *
   1613      * @param apply_filter if true only metadata that matches the
   1614      * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see
   1615      * #BYPASS_METADATA_FILTER}.
   1616      *
   1617      * @return The metadata, possibly empty. null if an error occured.
   1618      // FIXME: unhide.
   1619      * {@hide}
   1620      */
   1621     @Override
   1622     public Metadata getMetadata(final boolean update_only,
   1623                                 final boolean apply_filter) {
   1624         Parcel reply = Parcel.obtain();
   1625         Metadata data = new Metadata();
   1626 
   1627         if (!native_getMetadata(update_only, apply_filter, reply)) {
   1628             reply.recycle();
   1629             return null;
   1630         }
   1631 
   1632         // Metadata takes over the parcel, don't recycle it unless
   1633         // there is an error.
   1634         if (!data.parse(reply)) {
   1635             reply.recycle();
   1636             return null;
   1637         }
   1638         return data;
   1639     }
   1640 
   1641     /**
   1642      * Set a filter for the metadata update notification and update
   1643      * retrieval. The caller provides 2 set of metadata keys, allowed
   1644      * and blocked. The blocked set always takes precedence over the
   1645      * allowed one.
   1646      * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as
   1647      * shorthands to allow/block all or no metadata.
   1648      *
   1649      * By default, there is no filter set.
   1650      *
   1651      * @param allow Is the set of metadata the client is interested
   1652      *              in receiving new notifications for.
   1653      * @param block Is the set of metadata the client is not interested
   1654      *              in receiving new notifications for.
   1655      * @return The call status code.
   1656      *
   1657      // FIXME: unhide.
   1658      * {@hide}
   1659      */
   1660     @Override
   1661     public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) {
   1662         // Do our serialization manually instead of calling
   1663         // Parcel.writeArray since the sets are made of the same type
   1664         // we avoid paying the price of calling writeValue (used by
   1665         // writeArray) which burns an extra int per element to encode
   1666         // the type.
   1667         Parcel request =  newRequest();
   1668 
   1669         // The parcel starts already with an interface token. There
   1670         // are 2 filters. Each one starts with a 4bytes number to
   1671         // store the len followed by a number of int (4 bytes as well)
   1672         // representing the metadata type.
   1673         int capacity = request.dataSize() + 4 * (1 + allow.size() + 1 + block.size());
   1674 
   1675         if (request.dataCapacity() < capacity) {
   1676             request.setDataCapacity(capacity);
   1677         }
   1678 
   1679         request.writeInt(allow.size());
   1680         for(Integer t: allow) {
   1681             request.writeInt(t);
   1682         }
   1683         request.writeInt(block.size());
   1684         for(Integer t: block) {
   1685             request.writeInt(t);
   1686         }
   1687         return native_setMetadataFilter(request);
   1688     }
   1689 
   1690     /**
   1691      * Resets the MediaPlayer2 to its uninitialized state. After calling
   1692      * this method, you will have to initialize it again by setting the
   1693      * data source and calling prepare().
   1694      */
   1695     @Override
   1696     public void reset() {
   1697         mSelectedSubtitleTrackIndex = -1;
   1698         synchronized(mOpenSubtitleSources) {
   1699             for (final InputStream is: mOpenSubtitleSources) {
   1700                 try {
   1701                     is.close();
   1702                 } catch (IOException e) {
   1703                 }
   1704             }
   1705             mOpenSubtitleSources.clear();
   1706         }
   1707         if (mSubtitleController != null) {
   1708             mSubtitleController.reset();
   1709         }
   1710         if (mTimeProvider != null) {
   1711             mTimeProvider.close();
   1712             mTimeProvider = null;
   1713         }
   1714 
   1715         synchronized (mEventCbLock) {
   1716             mEventCallbackRecords.clear();
   1717         }
   1718         synchronized (mDrmEventCbLock) {
   1719             mDrmEventCallbackRecords.clear();
   1720         }
   1721 
   1722         stayAwake(false);
   1723         _reset();
   1724         // make sure none of the listeners get called anymore
   1725         if (mEventHandler != null) {
   1726             mEventHandler.removeCallbacksAndMessages(null);
   1727         }
   1728 
   1729         synchronized (mIndexTrackPairs) {
   1730             mIndexTrackPairs.clear();
   1731             mInbandTrackIndices.clear();
   1732         };
   1733 
   1734         resetDrmState();
   1735     }
   1736 
   1737     private native void _reset();
   1738 
   1739     /**
   1740      * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be
   1741      * notified when the presentation time reaches (becomes greater than or equal to)
   1742      * the value specified.
   1743      *
   1744      * @param mediaTimeUs presentation time to get timed event callback at
   1745      * @hide
   1746      */
   1747     @Override
   1748     public void notifyAt(long mediaTimeUs) {
   1749         _notifyAt(mediaTimeUs);
   1750     }
   1751 
   1752     private native void _notifyAt(long mediaTimeUs);
   1753 
   1754     // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer2.h
   1755     private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400;
   1756     /**
   1757      * Sets the parameter indicated by key.
   1758      * @param key key indicates the parameter to be set.
   1759      * @param value value of the parameter to be set.
   1760      * @return true if the parameter is set successfully, false otherwise
   1761      */
   1762     private native boolean setParameter(int key, Parcel value);
   1763 
   1764     private native Parcel getParameter(int key);
   1765 
   1766 
   1767     /**
   1768      * Checks whether the MediaPlayer2 is looping or non-looping.
   1769      *
   1770      * @return true if the MediaPlayer2 is currently looping, false otherwise
   1771      * @hide
   1772      */
   1773     @Override
   1774     public native boolean isLooping();
   1775 
   1776     /**
   1777      * Sets the audio session ID.
   1778      *
   1779      * @param sessionId the audio session ID.
   1780      * The audio session ID is a system wide unique identifier for the audio stream played by
   1781      * this MediaPlayer2 instance.
   1782      * The primary use of the audio session ID  is to associate audio effects to a particular
   1783      * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
   1784      * this effect will be applied only to the audio content of media players within the same
   1785      * audio session and not to the output mix.
   1786      * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
   1787      * However, it is possible to force this player to be part of an already existing audio session
   1788      * by calling this method.
   1789      * This method must be called before one of the overloaded <code> setDataSource </code> methods.
   1790      * @throws IllegalStateException if it is called in an invalid state
   1791      * @throws IllegalArgumentException if the sessionId is invalid.
   1792      */
   1793     @Override
   1794     public void setAudioSessionId(int sessionId) {
   1795         addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
   1796             @Override
   1797             void process() {
   1798                 _setAudioSessionId(sessionId);
   1799             }
   1800         });
   1801     }
   1802 
   1803     private native void _setAudioSessionId(int sessionId);
   1804 
   1805     /**
   1806      * Returns the audio session ID.
   1807      *
   1808      * @return the audio session ID. {@see #setAudioSessionId(int)}
   1809      * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.
   1810      */
   1811     @Override
   1812     public native int getAudioSessionId();
   1813 
   1814     /**
   1815      * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
   1816      * effect which can be applied on any sound source that directs a certain amount of its
   1817      * energy to this effect. This amount is defined by setAuxEffectSendLevel().
   1818      * See {@link #setAuxEffectSendLevel(float)}.
   1819      * <p>After creating an auxiliary effect (e.g.
   1820      * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
   1821      * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
   1822      * to attach the player to the effect.
   1823      * <p>To detach the effect from the player, call this method with a null effect id.
   1824      * <p>This method must be called after one of the overloaded <code> setDataSource </code>
   1825      * methods.
   1826      * @param effectId system wide unique id of the effect to attach
   1827      */
   1828     @Override
   1829     public void attachAuxEffect(int effectId) {
   1830         addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
   1831             @Override
   1832             void process() {
   1833                 _attachAuxEffect(effectId);
   1834             }
   1835         });
   1836     }
   1837 
   1838     private native void _attachAuxEffect(int effectId);
   1839 
   1840     /**
   1841      * Sets the send level of the player to the attached auxiliary effect.
   1842      * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
   1843      * <p>By default the send level is 0, so even if an effect is attached to the player
   1844      * this method must be called for the effect to be applied.
   1845      * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
   1846      * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
   1847      * so an appropriate conversion from linear UI input x to level is:
   1848      * x == 0 -> level = 0
   1849      * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
   1850      * @param level send level scalar
   1851      */
   1852     @Override
   1853     public void setAuxEffectSendLevel(float level) {
   1854         addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
   1855             @Override
   1856             void process() {
   1857                 _setAuxEffectSendLevel(level);
   1858             }
   1859         });
   1860     }
   1861 
   1862     private native void _setAuxEffectSendLevel(float level);
   1863 
   1864     /*
   1865      * @param request Parcel destinated to the media player.
   1866      * @param reply[out] Parcel that will contain the reply.
   1867      * @return The status code.
   1868      */
   1869     private native final int native_invoke(Parcel request, Parcel reply);
   1870 
   1871 
   1872     /*
   1873      * @param update_only If true fetch only the set of metadata that have
   1874      *                    changed since the last invocation of getMetadata.
   1875      *                    The set is built using the unfiltered
   1876      *                    notifications the native player sent to the
   1877      *                    MediaPlayer2Manager during that period of
   1878      *                    time. If false, all the metadatas are considered.
   1879      * @param apply_filter  If true, once the metadata set has been built based on
   1880      *                     the value update_only, the current filter is applied.
   1881      * @param reply[out] On return contains the serialized
   1882      *                   metadata. Valid only if the call was successful.
   1883      * @return The status code.
   1884      */
   1885     private native final boolean native_getMetadata(boolean update_only,
   1886                                                     boolean apply_filter,
   1887                                                     Parcel reply);
   1888 
   1889     /*
   1890      * @param request Parcel with the 2 serialized lists of allowed
   1891      *                metadata types followed by the one to be
   1892      *                dropped. Each list starts with an integer
   1893      *                indicating the number of metadata type elements.
   1894      * @return The status code.
   1895      */
   1896     private native final int native_setMetadataFilter(Parcel request);
   1897 
   1898     private static native final void native_init();
   1899     private native final void native_setup(Object mediaplayer2_this);
   1900     private native final void native_finalize();
   1901 
   1902     private static native final void native_stream_event_onTearDown(
   1903             long nativeCallbackPtr, long userDataPtr);
   1904     private static native final void native_stream_event_onStreamPresentationEnd(
   1905             long nativeCallbackPtr, long userDataPtr);
   1906     private static native final void native_stream_event_onStreamDataRequest(
   1907             long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr);
   1908 
   1909     /**
   1910      * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
   1911      *
   1912      * @see android.media.MediaPlayer2#getTrackInfo
   1913      */
   1914     public static final class TrackInfoImpl extends TrackInfo {
   1915         /**
   1916          * Gets the track type.
   1917          * @return TrackType which indicates if the track is video, audio, timed text.
   1918          */
   1919         @Override
   1920         public int getTrackType() {
   1921             return mTrackType;
   1922         }
   1923 
   1924         /**
   1925          * Gets the language code of the track.
   1926          * @return a language code in either way of ISO-639-1 or ISO-639-2.
   1927          * When the language is unknown or could not be determined,
   1928          * ISO-639-2 language code, "und", is returned.
   1929          */
   1930         @Override
   1931         public String getLanguage() {
   1932             String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
   1933             return language == null ? "und" : language;
   1934         }
   1935 
   1936         /**
   1937          * Gets the {@link MediaFormat} of the track.  If the format is
   1938          * unknown or could not be determined, null is returned.
   1939          */
   1940         @Override
   1941         public MediaFormat getFormat() {
   1942             if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
   1943                     || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
   1944                 return mFormat;
   1945             }
   1946             return null;
   1947         }
   1948 
   1949         final int mTrackType;
   1950         final MediaFormat mFormat;
   1951 
   1952         TrackInfoImpl(Parcel in) {
   1953             mTrackType = in.readInt();
   1954             // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat
   1955             // even for audio/video tracks, meaning we only set the mime and language.
   1956             String mime = in.readString();
   1957             String language = in.readString();
   1958             mFormat = MediaFormat.createSubtitleFormat(mime, language);
   1959 
   1960             if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
   1961                 mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt());
   1962                 mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt());
   1963                 mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt());
   1964             }
   1965         }
   1966 
   1967         /** @hide */
   1968         TrackInfoImpl(int type, MediaFormat format) {
   1969             mTrackType = type;
   1970             mFormat = format;
   1971         }
   1972 
   1973         /**
   1974          * Flatten this object in to a Parcel.
   1975          *
   1976          * @param dest The Parcel in which the object should be written.
   1977          * @param flags Additional flags about how the object should be written.
   1978          * May be 0 or {@link android.os.Parcelable#PARCELABLE_WRITE_RETURN_VALUE}.
   1979          */
   1980         /* package private */ void writeToParcel(Parcel dest, int flags) {
   1981             dest.writeInt(mTrackType);
   1982             dest.writeString(getLanguage());
   1983 
   1984             if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
   1985                 dest.writeString(mFormat.getString(MediaFormat.KEY_MIME));
   1986                 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT));
   1987                 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT));
   1988                 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE));
   1989             }
   1990         }
   1991 
   1992         @Override
   1993         public String toString() {
   1994             StringBuilder out = new StringBuilder(128);
   1995             out.append(getClass().getName());
   1996             out.append('{');
   1997             switch (mTrackType) {
   1998             case MEDIA_TRACK_TYPE_VIDEO:
   1999                 out.append("VIDEO");
   2000                 break;
   2001             case MEDIA_TRACK_TYPE_AUDIO:
   2002                 out.append("AUDIO");
   2003                 break;
   2004             case MEDIA_TRACK_TYPE_TIMEDTEXT:
   2005                 out.append("TIMEDTEXT");
   2006                 break;
   2007             case MEDIA_TRACK_TYPE_SUBTITLE:
   2008                 out.append("SUBTITLE");
   2009                 break;
   2010             default:
   2011                 out.append("UNKNOWN");
   2012                 break;
   2013             }
   2014             out.append(", " + mFormat.toString());
   2015             out.append("}");
   2016             return out.toString();
   2017         }
   2018 
   2019         /**
   2020          * Used to read a TrackInfoImpl from a Parcel.
   2021          */
   2022         /* package private */ static final Parcelable.Creator<TrackInfoImpl> CREATOR
   2023                 = new Parcelable.Creator<TrackInfoImpl>() {
   2024                     @Override
   2025                     public TrackInfoImpl createFromParcel(Parcel in) {
   2026                         return new TrackInfoImpl(in);
   2027                     }
   2028 
   2029                     @Override
   2030                     public TrackInfoImpl[] newArray(int size) {
   2031                         return new TrackInfoImpl[size];
   2032                     }
   2033                 };
   2034 
   2035     };
   2036 
   2037     // We would like domain specific classes with more informative names than the `first` and `second`
   2038     // in generic Pair, but we would also like to avoid creating new/trivial classes. As a compromise
   2039     // we document the meanings of `first` and `second` here:
   2040     //
   2041     // Pair.first - inband track index; non-null iff representing an inband track.
   2042     // Pair.second - a SubtitleTrack registered with mSubtitleController; non-null iff representing
   2043     //               an inband subtitle track or any out-of-band track (subtitle or timedtext).
   2044     private Vector<Pair<Integer, SubtitleTrack>> mIndexTrackPairs = new Vector<>();
   2045     private BitSet mInbandTrackIndices = new BitSet();
   2046 
   2047     /**
   2048      * Returns a List of track information.
   2049      *
   2050      * @return List of track info. The total number of tracks is the array length.
   2051      * Must be called again if an external timed text source has been added after
   2052      * addTimedTextSource method is called.
   2053      * @throws IllegalStateException if it is called in an invalid state.
   2054      */
   2055     @Override
   2056     public List<TrackInfo> getTrackInfo() {
   2057         TrackInfoImpl trackInfo[] = getInbandTrackInfoImpl();
   2058         // add out-of-band tracks
   2059         synchronized (mIndexTrackPairs) {
   2060             TrackInfoImpl allTrackInfo[] = new TrackInfoImpl[mIndexTrackPairs.size()];
   2061             for (int i = 0; i < allTrackInfo.length; i++) {
   2062                 Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
   2063                 if (p.first != null) {
   2064                     // inband track
   2065                     allTrackInfo[i] = trackInfo[p.first];
   2066                 } else {
   2067                     SubtitleTrack track = p.second;
   2068                     allTrackInfo[i] = new TrackInfoImpl(track.getTrackType(), track.getFormat());
   2069                 }
   2070             }
   2071             return Arrays.asList(allTrackInfo);
   2072         }
   2073     }
   2074 
   2075     private TrackInfoImpl[] getInbandTrackInfoImpl() throws IllegalStateException {
   2076         Parcel request = Parcel.obtain();
   2077         Parcel reply = Parcel.obtain();
   2078         try {
   2079             request.writeInt(INVOKE_ID_GET_TRACK_INFO);
   2080             invoke(request, reply);
   2081             TrackInfoImpl trackInfo[] = reply.createTypedArray(TrackInfoImpl.CREATOR);
   2082             return trackInfo;
   2083         } finally {
   2084             request.recycle();
   2085             reply.recycle();
   2086         }
   2087     }
   2088 
   2089     /*
   2090      * A helper function to check if the mime type is supported by media framework.
   2091      */
   2092     private static boolean availableMimeTypeForExternalSource(String mimeType) {
   2093         if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) {
   2094             return true;
   2095         }
   2096         return false;
   2097     }
   2098 
   2099     private SubtitleController mSubtitleController;
   2100 
   2101     /** @hide */
   2102     @Override
   2103     public void setSubtitleAnchor(
   2104             SubtitleController controller,
   2105             SubtitleController.Anchor anchor) {
   2106         // TODO: create SubtitleController in MediaPlayer2
   2107         mSubtitleController = controller;
   2108         mSubtitleController.setAnchor(anchor);
   2109     }
   2110 
   2111     /**
   2112      * The private version of setSubtitleAnchor is used internally to set mSubtitleController if
   2113      * necessary when clients don't provide their own SubtitleControllers using the public version
   2114      * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one).
   2115      */
   2116     private synchronized void setSubtitleAnchor() {
   2117         if ((mSubtitleController == null) && (ActivityThread.currentApplication() != null)) {
   2118             final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread");
   2119             thread.start();
   2120             Handler handler = new Handler(thread.getLooper());
   2121             handler.post(new Runnable() {
   2122                 @Override
   2123                 public void run() {
   2124                     Context context = ActivityThread.currentApplication();
   2125                     mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer2Impl.this);
   2126                     mSubtitleController.setAnchor(new Anchor() {
   2127                         @Override
   2128                         public void setSubtitleWidget(RenderingWidget subtitleWidget) {
   2129                         }
   2130 
   2131                         @Override
   2132                         public Looper getSubtitleLooper() {
   2133                             return Looper.getMainLooper();
   2134                         }
   2135                     });
   2136                     thread.getLooper().quitSafely();
   2137                 }
   2138             });
   2139             try {
   2140                 thread.join();
   2141             } catch (InterruptedException e) {
   2142                 Thread.currentThread().interrupt();
   2143                 Log.w(TAG, "failed to join SetSubtitleAnchorThread");
   2144             }
   2145         }
   2146     }
   2147 
   2148     private int mSelectedSubtitleTrackIndex = -1;
   2149     private Vector<InputStream> mOpenSubtitleSources;
   2150 
   2151     private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() {
   2152         @Override
   2153         public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) {
   2154             int index = data.getTrackIndex();
   2155             synchronized (mIndexTrackPairs) {
   2156                 for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) {
   2157                     if (p.first != null && p.first == index && p.second != null) {
   2158                         // inband subtitle track that owns data
   2159                         SubtitleTrack track = p.second;
   2160                         track.onData(data);
   2161                     }
   2162                 }
   2163             }
   2164         }
   2165     };
   2166 
   2167     /** @hide */
   2168     @Override
   2169     public void onSubtitleTrackSelected(SubtitleTrack track) {
   2170         if (mSelectedSubtitleTrackIndex >= 0) {
   2171             try {
   2172                 selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false);
   2173             } catch (IllegalStateException e) {
   2174             }
   2175             mSelectedSubtitleTrackIndex = -1;
   2176         }
   2177         setOnSubtitleDataListener(null);
   2178         if (track == null) {
   2179             return;
   2180         }
   2181 
   2182         synchronized (mIndexTrackPairs) {
   2183             for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) {
   2184                 if (p.first != null && p.second == track) {
   2185                     // inband subtitle track that is selected
   2186                     mSelectedSubtitleTrackIndex = p.first;
   2187                     break;
   2188                 }
   2189             }
   2190         }
   2191 
   2192         if (mSelectedSubtitleTrackIndex >= 0) {
   2193             try {
   2194                 selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true);
   2195             } catch (IllegalStateException e) {
   2196             }
   2197             setOnSubtitleDataListener(mSubtitleDataListener);
   2198         }
   2199         // no need to select out-of-band tracks
   2200     }
   2201 
   2202     /** @hide */
   2203     @Override
   2204     public void addSubtitleSource(InputStream is, MediaFormat format)
   2205             throws IllegalStateException
   2206     {
   2207         final InputStream fIs = is;
   2208         final MediaFormat fFormat = format;
   2209 
   2210         if (is != null) {
   2211             // Ensure all input streams are closed.  It is also a handy
   2212             // way to implement timeouts in the future.
   2213             synchronized(mOpenSubtitleSources) {
   2214                 mOpenSubtitleSources.add(is);
   2215             }
   2216         } else {
   2217             Log.w(TAG, "addSubtitleSource called with null InputStream");
   2218         }
   2219 
   2220         getMediaTimeProvider();
   2221 
   2222         // process each subtitle in its own thread
   2223         final HandlerThread thread = new HandlerThread("SubtitleReadThread",
   2224               Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE);
   2225         thread.start();
   2226         Handler handler = new Handler(thread.getLooper());
   2227         handler.post(new Runnable() {
   2228             private int addTrack() {
   2229                 if (fIs == null || mSubtitleController == null) {
   2230                     return MEDIA_INFO_UNSUPPORTED_SUBTITLE;
   2231                 }
   2232 
   2233                 SubtitleTrack track = mSubtitleController.addTrack(fFormat);
   2234                 if (track == null) {
   2235                     return MEDIA_INFO_UNSUPPORTED_SUBTITLE;
   2236                 }
   2237 
   2238                 // TODO: do the conversion in the subtitle track
   2239                 Scanner scanner = new Scanner(fIs, "UTF-8");
   2240                 String contents = scanner.useDelimiter("\\A").next();
   2241                 synchronized(mOpenSubtitleSources) {
   2242                     mOpenSubtitleSources.remove(fIs);
   2243                 }
   2244                 scanner.close();
   2245                 synchronized (mIndexTrackPairs) {
   2246                     mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track));
   2247                 }
   2248                 Handler h = mTimeProvider.mEventHandler;
   2249                 int what = TimeProvider.NOTIFY;
   2250                 int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
   2251                 Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, contents.getBytes());
   2252                 Message m = h.obtainMessage(what, arg1, 0, trackData);
   2253                 h.sendMessage(m);
   2254                 return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
   2255             }
   2256 
   2257             public void run() {
   2258                 int res = addTrack();
   2259                 if (mEventHandler != null) {
   2260                     Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null);
   2261                     mEventHandler.sendMessage(m);
   2262                 }
   2263                 thread.getLooper().quitSafely();
   2264             }
   2265         });
   2266     }
   2267 
   2268     private void scanInternalSubtitleTracks() {
   2269         setSubtitleAnchor();
   2270 
   2271         populateInbandTracks();
   2272 
   2273         if (mSubtitleController != null) {
   2274             mSubtitleController.selectDefaultTrack();
   2275         }
   2276     }
   2277 
   2278     private void populateInbandTracks() {
   2279         TrackInfoImpl[] tracks = getInbandTrackInfoImpl();
   2280         synchronized (mIndexTrackPairs) {
   2281             for (int i = 0; i < tracks.length; i++) {
   2282                 if (mInbandTrackIndices.get(i)) {
   2283                     continue;
   2284                 } else {
   2285                     mInbandTrackIndices.set(i);
   2286                 }
   2287 
   2288                 // newly appeared inband track
   2289                 if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
   2290                     SubtitleTrack track = mSubtitleController.addTrack(
   2291                             tracks[i].getFormat());
   2292                     mIndexTrackPairs.add(Pair.create(i, track));
   2293                 } else {
   2294                     mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(i, null));
   2295                 }
   2296             }
   2297         }
   2298     }
   2299 
   2300     /* TODO: Limit the total number of external timed text source to a reasonable number.
   2301      */
   2302     /**
   2303      * Adds an external timed text source file.
   2304      *
   2305      * Currently supported format is SubRip with the file extension .srt, case insensitive.
   2306      * Note that a single external timed text source may contain multiple tracks in it.
   2307      * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
   2308      * additional tracks become available after this method call.
   2309      *
   2310      * @param path The file path of external timed text source file.
   2311      * @param mimeType The mime type of the file. Must be one of the mime types listed above.
   2312      * @throws IOException if the file cannot be accessed or is corrupted.
   2313      * @throws IllegalArgumentException if the mimeType is not supported.
   2314      * @throws IllegalStateException if called in an invalid state.
   2315      * @hide
   2316      */
   2317     @Override
   2318     public void addTimedTextSource(String path, String mimeType)
   2319             throws IOException {
   2320         if (!availableMimeTypeForExternalSource(mimeType)) {
   2321             final String msg = "Illegal mimeType for timed text source: " + mimeType;
   2322             throw new IllegalArgumentException(msg);
   2323         }
   2324 
   2325         File file = new File(path);
   2326         if (file.exists()) {
   2327             FileInputStream is = new FileInputStream(file);
   2328             FileDescriptor fd = is.getFD();
   2329             addTimedTextSource(fd, mimeType);
   2330             is.close();
   2331         } else {
   2332             // We do not support the case where the path is not a file.
   2333             throw new IOException(path);
   2334         }
   2335     }
   2336 
   2337 
   2338     /**
   2339      * Adds an external timed text source file (Uri).
   2340      *
   2341      * Currently supported format is SubRip with the file extension .srt, case insensitive.
   2342      * Note that a single external timed text source may contain multiple tracks in it.
   2343      * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
   2344      * additional tracks become available after this method call.
   2345      *
   2346      * @param context the Context to use when resolving the Uri
   2347      * @param uri the Content URI of the data you want to play
   2348      * @param mimeType The mime type of the file. Must be one of the mime types listed above.
   2349      * @throws IOException if the file cannot be accessed or is corrupted.
   2350      * @throws IllegalArgumentException if the mimeType is not supported.
   2351      * @throws IllegalStateException if called in an invalid state.
   2352      * @hide
   2353      */
   2354     @Override
   2355     public void addTimedTextSource(Context context, Uri uri, String mimeType)
   2356             throws IOException {
   2357         String scheme = uri.getScheme();
   2358         if(scheme == null || scheme.equals("file")) {
   2359             addTimedTextSource(uri.getPath(), mimeType);
   2360             return;
   2361         }
   2362 
   2363         AssetFileDescriptor fd = null;
   2364         try {
   2365             ContentResolver resolver = context.getContentResolver();
   2366             fd = resolver.openAssetFileDescriptor(uri, "r");
   2367             if (fd == null) {
   2368                 return;
   2369             }
   2370             addTimedTextSource(fd.getFileDescriptor(), mimeType);
   2371             return;
   2372         } catch (SecurityException ex) {
   2373         } catch (IOException ex) {
   2374         } finally {
   2375             if (fd != null) {
   2376                 fd.close();
   2377             }
   2378         }
   2379     }
   2380 
   2381     /**
   2382      * Adds an external timed text source file (FileDescriptor).
   2383      *
   2384      * It is the caller's responsibility to close the file descriptor.
   2385      * It is safe to do so as soon as this call returns.
   2386      *
   2387      * Currently supported format is SubRip. Note that a single external timed text source may
   2388      * contain multiple tracks in it. One can find the total number of available tracks
   2389      * using {@link #getTrackInfo()} to see what additional tracks become available
   2390      * after this method call.
   2391      *
   2392      * @param fd the FileDescriptor for the file you want to play
   2393      * @param mimeType The mime type of the file. Must be one of the mime types listed above.
   2394      * @throws IllegalArgumentException if the mimeType is not supported.
   2395      * @throws IllegalStateException if called in an invalid state.
   2396      * @hide
   2397      */
   2398     @Override
   2399     public void addTimedTextSource(FileDescriptor fd, String mimeType) {
   2400         // intentionally less than LONG_MAX
   2401         addTimedTextSource(fd, 0, 0x7ffffffffffffffL, mimeType);
   2402     }
   2403 
   2404     /**
   2405      * Adds an external timed text file (FileDescriptor).
   2406      *
   2407      * It is the caller's responsibility to close the file descriptor.
   2408      * It is safe to do so as soon as this call returns.
   2409      *
   2410      * Currently supported format is SubRip. Note that a single external timed text source may
   2411      * contain multiple tracks in it. One can find the total number of available tracks
   2412      * using {@link #getTrackInfo()} to see what additional tracks become available
   2413      * after this method call.
   2414      *
   2415      * @param fd the FileDescriptor for the file you want to play
   2416      * @param offset the offset into the file where the data to be played starts, in bytes
   2417      * @param length the length in bytes of the data to be played
   2418      * @param mime The mime type of the file. Must be one of the mime types listed above.
   2419      * @throws IllegalArgumentException if the mimeType is not supported.
   2420      * @throws IllegalStateException if called in an invalid state.
   2421      * @hide
   2422      */
   2423     @Override
   2424     public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) {
   2425         if (!availableMimeTypeForExternalSource(mime)) {
   2426             throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime);
   2427         }
   2428 
   2429         final FileDescriptor dupedFd;
   2430         try {
   2431             dupedFd = Os.dup(fd);
   2432         } catch (ErrnoException ex) {
   2433             Log.e(TAG, ex.getMessage(), ex);
   2434             throw new RuntimeException(ex);
   2435         }
   2436 
   2437         final MediaFormat fFormat = new MediaFormat();
   2438         fFormat.setString(MediaFormat.KEY_MIME, mime);
   2439         fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1);
   2440 
   2441         // A MediaPlayer2 created by a VideoView should already have its mSubtitleController set.
   2442         if (mSubtitleController == null) {
   2443             setSubtitleAnchor();
   2444         }
   2445 
   2446         if (!mSubtitleController.hasRendererFor(fFormat)) {
   2447             // test and add not atomic
   2448             Context context = ActivityThread.currentApplication();
   2449             mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler));
   2450         }
   2451         final SubtitleTrack track = mSubtitleController.addTrack(fFormat);
   2452         synchronized (mIndexTrackPairs) {
   2453             mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track));
   2454         }
   2455 
   2456         getMediaTimeProvider();
   2457 
   2458         final long offset2 = offset;
   2459         final long length2 = length;
   2460         final HandlerThread thread = new HandlerThread(
   2461                 "TimedTextReadThread",
   2462                 Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE);
   2463         thread.start();
   2464         Handler handler = new Handler(thread.getLooper());
   2465         handler.post(new Runnable() {
   2466             private int addTrack() {
   2467                 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
   2468                 try {
   2469                     Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
   2470                     byte[] buffer = new byte[4096];
   2471                     for (long total = 0; total < length2;) {
   2472                         int bytesToRead = (int) Math.min(buffer.length, length2 - total);
   2473                         int bytes = IoBridge.read(dupedFd, buffer, 0, bytesToRead);
   2474                         if (bytes < 0) {
   2475                             break;
   2476                         } else {
   2477                             bos.write(buffer, 0, bytes);
   2478                             total += bytes;
   2479                         }
   2480                     }
   2481                     Handler h = mTimeProvider.mEventHandler;
   2482                     int what = TimeProvider.NOTIFY;
   2483                     int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
   2484                     Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, bos.toByteArray());
   2485                     Message m = h.obtainMessage(what, arg1, 0, trackData);
   2486                     h.sendMessage(m);
   2487                     return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
   2488                 } catch (Exception e) {
   2489                     Log.e(TAG, e.getMessage(), e);
   2490                     return MEDIA_INFO_TIMED_TEXT_ERROR;
   2491                 } finally {
   2492                     try {
   2493                         Os.close(dupedFd);
   2494                     } catch (ErrnoException e) {
   2495                         Log.e(TAG, e.getMessage(), e);
   2496                     }
   2497                 }
   2498             }
   2499 
   2500             public void run() {
   2501                 int res = addTrack();
   2502                 if (mEventHandler != null) {
   2503                     Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null);
   2504                     mEventHandler.sendMessage(m);
   2505                 }
   2506                 thread.getLooper().quitSafely();
   2507             }
   2508         });
   2509     }
   2510 
   2511     /**
   2512      * Returns the index of the audio, video, or subtitle track currently selected for playback,
   2513      * The return value is an index into the array returned by {@link #getTrackInfo()}, and can
   2514      * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}.
   2515      *
   2516      * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
   2517      * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
   2518      * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
   2519      * @return index of the audio, video, or subtitle track currently selected for playback;
   2520      * a negative integer is returned when there is no selected track for {@code trackType} or
   2521      * when {@code trackType} is not one of audio, video, or subtitle.
   2522      * @throws IllegalStateException if called after {@link #close()}
   2523      *
   2524      * @see #getTrackInfo()
   2525      * @see #selectTrack(int)
   2526      * @see #deselectTrack(int)
   2527      */
   2528     @Override
   2529     public int getSelectedTrack(int trackType) {
   2530         if (mSubtitleController != null
   2531                 && (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
   2532                 || trackType == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT)) {
   2533             SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack();
   2534             if (subtitleTrack != null) {
   2535                 synchronized (mIndexTrackPairs) {
   2536                     for (int i = 0; i < mIndexTrackPairs.size(); i++) {
   2537                         Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
   2538                         if (p.second == subtitleTrack && subtitleTrack.getTrackType() == trackType) {
   2539                             return i;
   2540                         }
   2541                     }
   2542                 }
   2543             }
   2544         }
   2545 
   2546         Parcel request = Parcel.obtain();
   2547         Parcel reply = Parcel.obtain();
   2548         try {
   2549             request.writeInt(INVOKE_ID_GET_SELECTED_TRACK);
   2550             request.writeInt(trackType);
   2551             invoke(request, reply);
   2552             int inbandTrackIndex = reply.readInt();
   2553             synchronized (mIndexTrackPairs) {
   2554                 for (int i = 0; i < mIndexTrackPairs.size(); i++) {
   2555                     Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
   2556                     if (p.first != null && p.first == inbandTrackIndex) {
   2557                         return i;
   2558                     }
   2559                 }
   2560             }
   2561             return -1;
   2562         } finally {
   2563             request.recycle();
   2564             reply.recycle();
   2565         }
   2566     }
   2567 
   2568     /**
   2569      * Selects a track.
   2570      * <p>
   2571      * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
   2572      * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately.
   2573      * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
   2574      * </p>
   2575      * <p>
   2576      * In any valid state, if it is called multiple times on the same type of track (ie. Video,
   2577      * Audio, Timed Text), the most recent one will be chosen.
   2578      * </p>
   2579      * <p>
   2580      * The first audio and video tracks are selected by default if available, even though
   2581      * this method is not called. However, no timed text track will be selected until
   2582      * this function is called.
   2583      * </p>
   2584      * <p>
   2585      * Currently, only timed text tracks or audio tracks can be selected via this method.
   2586      * In addition, the support for selecting an audio track at runtime is pretty limited
   2587      * in that an audio track can only be selected in the <em>Prepared</em> state.
   2588      * </p>
   2589      * @param index the index of the track to be selected. The valid range of the index
   2590      * is 0..total number of track - 1. The total number of tracks as well as the type of
   2591      * each individual track can be found by calling {@link #getTrackInfo()} method.
   2592      * @throws IllegalStateException if called in an invalid state.
   2593      *
   2594      * @see android.media.MediaPlayer2#getTrackInfo
   2595      */
   2596     @Override
   2597     public void selectTrack(int index) {
   2598         addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
   2599             @Override
   2600             void process() {
   2601                 selectOrDeselectTrack(index, true /* select */);
   2602             }
   2603         });
   2604     }
   2605 
   2606     /**
   2607      * Deselect a track.
   2608      * <p>
   2609      * Currently, the track must be a timed text track and no audio or video tracks can be
   2610      * deselected. If the timed text track identified by index has not been
   2611      * selected before, it throws an exception.
   2612      * </p>
   2613      * @param index the index of the track to be deselected. The valid range of the index
   2614      * is 0..total number of tracks - 1. The total number of tracks as well as the type of
   2615      * each individual track can be found by calling {@link #getTrackInfo()} method.
   2616      * @throws IllegalStateException if called in an invalid state.
   2617      *
   2618      * @see android.media.MediaPlayer2#getTrackInfo
   2619      */
   2620     @Override
   2621     public void deselectTrack(int index) {
   2622         addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
   2623             @Override
   2624             void process() {
   2625                 selectOrDeselectTrack(index, false /* select */);
   2626             }
   2627         });
   2628     }
   2629 
   2630     private void selectOrDeselectTrack(int index, boolean select)
   2631             throws IllegalStateException {
   2632         // handle subtitle track through subtitle controller
   2633         populateInbandTracks();
   2634 
   2635         Pair<Integer,SubtitleTrack> p = null;
   2636         try {
   2637             p = mIndexTrackPairs.get(index);
   2638         } catch (ArrayIndexOutOfBoundsException e) {
   2639             // ignore bad index
   2640             return;
   2641         }
   2642 
   2643         SubtitleTrack track = p.second;
   2644         if (track == null) {
   2645             // inband (de)select
   2646             selectOrDeselectInbandTrack(p.first, select);
   2647             return;
   2648         }
   2649 
   2650         if (mSubtitleController == null) {
   2651             return;
   2652         }
   2653 
   2654         if (!select) {
   2655             // out-of-band deselect
   2656             if (mSubtitleController.getSelectedTrack() == track) {
   2657                 mSubtitleController.selectTrack(null);
   2658             } else {
   2659                 Log.w(TAG, "trying to deselect track that was not selected");
   2660             }
   2661             return;
   2662         }
   2663 
   2664         // out-of-band select
   2665         if (track.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
   2666             int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
   2667             synchronized (mIndexTrackPairs) {
   2668                 if (ttIndex >= 0 && ttIndex < mIndexTrackPairs.size()) {
   2669                     Pair<Integer,SubtitleTrack> p2 = mIndexTrackPairs.get(ttIndex);
   2670                     if (p2.first != null && p2.second == null) {
   2671                         // deselect inband counterpart
   2672                         selectOrDeselectInbandTrack(p2.first, false);
   2673                     }
   2674                 }
   2675             }
   2676         }
   2677         mSubtitleController.selectTrack(track);
   2678     }
   2679 
   2680     private void selectOrDeselectInbandTrack(int index, boolean select)
   2681             throws IllegalStateException {
   2682         Parcel request = Parcel.obtain();
   2683         Parcel reply = Parcel.obtain();
   2684         try {
   2685             request.writeInt(select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK);
   2686             request.writeInt(index);
   2687             invoke(request, reply);
   2688         } finally {
   2689             request.recycle();
   2690             reply.recycle();
   2691         }
   2692     }
   2693 
   2694     // Have to declare protected for finalize() since it is protected
   2695     // in the base class Object.
   2696     @Override
   2697     protected void finalize() throws Throwable {
   2698         if (mGuard != null) {
   2699             mGuard.warnIfOpen();
   2700         }
   2701 
   2702         close();
   2703         native_finalize();
   2704     }
   2705 
   2706     private void release() {
   2707         stayAwake(false);
   2708         updateSurfaceScreenOn();
   2709         synchronized (mEventCbLock) {
   2710             mEventCallbackRecords.clear();
   2711         }
   2712         if (mHandlerThread != null) {
   2713             mHandlerThread.quitSafely();
   2714             mHandlerThread = null;
   2715         }
   2716         if (mTimeProvider != null) {
   2717             mTimeProvider.close();
   2718             mTimeProvider = null;
   2719         }
   2720         mOnSubtitleDataListener = null;
   2721 
   2722         // Modular DRM clean up
   2723         mOnDrmConfigHelper = null;
   2724         synchronized (mDrmEventCbLock) {
   2725             mDrmEventCallbackRecords.clear();
   2726         }
   2727         resetDrmState();
   2728 
   2729         _release();
   2730     }
   2731 
   2732     private native void _release();
   2733 
   2734     /* Do not change these values without updating their counterparts
   2735      * in include/media/mediaplayer2.h!
   2736      */
   2737     private static final int MEDIA_NOP = 0; // interface test message
   2738     private static final int MEDIA_PREPARED = 1;
   2739     private static final int MEDIA_PLAYBACK_COMPLETE = 2;
   2740     private static final int MEDIA_BUFFERING_UPDATE = 3;
   2741     private static final int MEDIA_SEEK_COMPLETE = 4;
   2742     private static final int MEDIA_SET_VIDEO_SIZE = 5;
   2743     private static final int MEDIA_STARTED = 6;
   2744     private static final int MEDIA_PAUSED = 7;
   2745     private static final int MEDIA_STOPPED = 8;
   2746     private static final int MEDIA_SKIPPED = 9;
   2747     private static final int MEDIA_NOTIFY_TIME = 98;
   2748     private static final int MEDIA_TIMED_TEXT = 99;
   2749     private static final int MEDIA_ERROR = 100;
   2750     private static final int MEDIA_INFO = 200;
   2751     private static final int MEDIA_SUBTITLE_DATA = 201;
   2752     private static final int MEDIA_META_DATA = 202;
   2753     private static final int MEDIA_DRM_INFO = 210;
   2754     private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000;
   2755 
   2756     private TimeProvider mTimeProvider;
   2757 
   2758     /** @hide */
   2759     @Override
   2760     public MediaTimeProvider getMediaTimeProvider() {
   2761         if (mTimeProvider == null) {
   2762             mTimeProvider = new TimeProvider(this);
   2763         }
   2764         return mTimeProvider;
   2765     }
   2766 
   2767     private class EventHandler extends Handler {
   2768         private MediaPlayer2Impl mMediaPlayer;
   2769 
   2770         public EventHandler(MediaPlayer2Impl mp, Looper looper) {
   2771             super(looper);
   2772             mMediaPlayer = mp;
   2773         }
   2774 
   2775         @Override
   2776         public void handleMessage(Message msg) {
   2777             handleMessage(msg, 0);
   2778         }
   2779 
   2780         public void handleMessage(Message msg, long srcId) {
   2781             if (mMediaPlayer.mNativeContext == 0) {
   2782                 Log.w(TAG, "mediaplayer2 went away with unhandled events");
   2783                 return;
   2784             }
   2785             final int what = msg.arg1;
   2786             final int extra = msg.arg2;
   2787 
   2788             switch(msg.what) {
   2789             case MEDIA_PREPARED:
   2790             {
   2791                 try {
   2792                     scanInternalSubtitleTracks();
   2793                 } catch (RuntimeException e) {
   2794                     // send error message instead of crashing;
   2795                     // send error message instead of inlining a call to onError
   2796                     // to avoid code duplication.
   2797                     Message msg2 = obtainMessage(
   2798                             MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
   2799                     sendMessage(msg2);
   2800                 }
   2801 
   2802                 final DataSourceDesc dsd;
   2803                 synchronized (mSrcLock) {
   2804                     Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
   2805                             + ", currentSrcId=" + mCurrentSrcId + ", nextSrcId=" + mNextSrcId);
   2806                     if (srcId == mCurrentSrcId) {
   2807                         dsd = mCurrentDSD;
   2808                         prepareNextDataSource_l();
   2809                     } else if (mNextDSDs != null && !mNextDSDs.isEmpty()
   2810                             && srcId == mNextSrcId) {
   2811                         dsd = mNextDSDs.get(0);
   2812                         mNextSourceState = NEXT_SOURCE_STATE_PREPARED;
   2813                         if (mNextSourcePlayPending) {
   2814                             playNextDataSource_l();
   2815                         }
   2816                     } else {
   2817                         dsd = null;
   2818                     }
   2819                 }
   2820 
   2821                 if (dsd != null) {
   2822                     synchronized (mEventCbLock) {
   2823                         for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   2824                             cb.first.execute(() -> cb.second.onInfo(
   2825                                     mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0));
   2826                         }
   2827                     }
   2828                 }
   2829                 synchronized (mTaskLock) {
   2830                     if (mCurrentTask != null
   2831                             && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
   2832                             && mCurrentTask.mDSD == dsd
   2833                             && mCurrentTask.mNeedToWaitForEventToComplete) {
   2834                         mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
   2835                         mCurrentTask = null;
   2836                         processPendingTask_l();
   2837                     }
   2838                 }
   2839                 return;
   2840             }
   2841 
   2842             case MEDIA_DRM_INFO:
   2843             {
   2844                 if (msg.obj == null) {
   2845                     Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
   2846                 } else if (msg.obj instanceof Parcel) {
   2847                     // The parcel was parsed already in postEventFromNative
   2848                     final DrmInfoImpl drmInfo;
   2849 
   2850                     synchronized (mDrmLock) {
   2851                         if (mDrmInfoImpl != null) {
   2852                             drmInfo = mDrmInfoImpl.makeCopy();
   2853                         } else {
   2854                             drmInfo = null;
   2855                         }
   2856                     }
   2857 
   2858                     // notifying the client outside the lock
   2859                     if (drmInfo != null) {
   2860                         synchronized (mEventCbLock) {
   2861                             for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
   2862                                 cb.first.execute(() -> cb.second.onDrmInfo(
   2863                                         mMediaPlayer, mCurrentDSD, drmInfo));
   2864                             }
   2865                         }
   2866                     }
   2867                 } else {
   2868                     Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
   2869                 }
   2870                 return;
   2871             }
   2872 
   2873             case MEDIA_PLAYBACK_COMPLETE:
   2874             {
   2875                 final DataSourceDesc dsd = mCurrentDSD;
   2876                 synchronized (mSrcLock) {
   2877                     if (srcId == mCurrentSrcId) {
   2878                         Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
   2879                                 + ", currentSrcId=" + mCurrentSrcId + ", nextSrcId=" + mNextSrcId);
   2880                         playNextDataSource_l();
   2881                     }
   2882                 }
   2883 
   2884                 synchronized (mEventCbLock) {
   2885                     for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   2886                         cb.first.execute(() -> cb.second.onInfo(
   2887                                 mMediaPlayer, dsd, MEDIA_INFO_PLAYBACK_COMPLETE, 0));
   2888                     }
   2889                 }
   2890                 stayAwake(false);
   2891                 return;
   2892             }
   2893 
   2894             case MEDIA_STOPPED:
   2895             {
   2896                 TimeProvider timeProvider = mTimeProvider;
   2897                 if (timeProvider != null) {
   2898                     timeProvider.onStopped();
   2899                 }
   2900                 break;
   2901             }
   2902 
   2903             case MEDIA_STARTED:
   2904             case MEDIA_PAUSED:
   2905             {
   2906                 TimeProvider timeProvider = mTimeProvider;
   2907                 if (timeProvider != null) {
   2908                     timeProvider.onPaused(msg.what == MEDIA_PAUSED);
   2909                 }
   2910                 break;
   2911             }
   2912 
   2913             case MEDIA_BUFFERING_UPDATE:
   2914             {
   2915                 final int percent = msg.arg1;
   2916                 synchronized (mEventCbLock) {
   2917                     if (srcId == mCurrentSrcId) {
   2918                         mBufferedPercentageCurrent.set(percent);
   2919                         for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   2920                             cb.first.execute(() -> cb.second.onInfo(
   2921                                     mMediaPlayer, mCurrentDSD, MEDIA_INFO_BUFFERING_UPDATE,
   2922                                     percent));
   2923                         }
   2924                     } else if (srcId == mNextSrcId && !mNextDSDs.isEmpty()) {
   2925                         mBufferedPercentageNext.set(percent);
   2926                         DataSourceDesc nextDSD = mNextDSDs.get(0);
   2927                         for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   2928                             cb.first.execute(() -> cb.second.onInfo(
   2929                                     mMediaPlayer, nextDSD, MEDIA_INFO_BUFFERING_UPDATE,
   2930                                     percent));
   2931                         }
   2932                     }
   2933                 }
   2934                 return;
   2935             }
   2936 
   2937             case MEDIA_SEEK_COMPLETE:
   2938             {
   2939                 synchronized (mTaskLock) {
   2940                     if (mCurrentTask != null
   2941                             && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
   2942                             && mCurrentTask.mNeedToWaitForEventToComplete) {
   2943                         mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
   2944                         mCurrentTask = null;
   2945                         processPendingTask_l();
   2946                     }
   2947                 }
   2948             }
   2949                 // fall through
   2950 
   2951             case MEDIA_SKIPPED:
   2952             {
   2953                 TimeProvider timeProvider = mTimeProvider;
   2954                 if (timeProvider != null) {
   2955                     timeProvider.onSeekComplete(mMediaPlayer);
   2956                 }
   2957                 return;
   2958             }
   2959 
   2960             case MEDIA_SET_VIDEO_SIZE:
   2961             {
   2962                 final int width = msg.arg1;
   2963                 final int height = msg.arg2;
   2964                 synchronized (mEventCbLock) {
   2965                     for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   2966                         cb.first.execute(() -> cb.second.onVideoSizeChanged(
   2967                                 mMediaPlayer, mCurrentDSD, width, height));
   2968                     }
   2969                 }
   2970                 return;
   2971             }
   2972 
   2973             case MEDIA_ERROR:
   2974             {
   2975                 Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
   2976                 synchronized (mEventCbLock) {
   2977                     for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   2978                         cb.first.execute(() -> cb.second.onError(
   2979                                 mMediaPlayer, mCurrentDSD, what, extra));
   2980                         cb.first.execute(() -> cb.second.onInfo(
   2981                                 mMediaPlayer, mCurrentDSD, MEDIA_INFO_PLAYBACK_COMPLETE, 0));
   2982                     }
   2983                 }
   2984                 stayAwake(false);
   2985                 return;
   2986             }
   2987 
   2988             case MEDIA_INFO:
   2989             {
   2990                 switch (msg.arg1) {
   2991                     case MEDIA_INFO_STARTED_AS_NEXT:
   2992                         if (srcId == mCurrentSrcId) {
   2993                             prepareNextDataSource_l();
   2994                         }
   2995                         break;
   2996 
   2997                     case MEDIA_INFO_VIDEO_TRACK_LAGGING:
   2998                         Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
   2999                         break;
   3000 
   3001                     case MEDIA_INFO_METADATA_UPDATE:
   3002                         try {
   3003                             scanInternalSubtitleTracks();
   3004                         } catch (RuntimeException e) {
   3005                             Message msg2 = obtainMessage(
   3006                                     MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED,
   3007                                     null);
   3008                             sendMessage(msg2);
   3009                         }
   3010                         // fall through
   3011 
   3012                     case MEDIA_INFO_EXTERNAL_METADATA_UPDATE:
   3013                         msg.arg1 = MEDIA_INFO_METADATA_UPDATE;
   3014                         // update default track selection
   3015                         if (mSubtitleController != null) {
   3016                             mSubtitleController.selectDefaultTrack();
   3017                         }
   3018                         break;
   3019 
   3020                     case MEDIA_INFO_BUFFERING_START:
   3021                     case MEDIA_INFO_BUFFERING_END:
   3022                         TimeProvider timeProvider = mTimeProvider;
   3023                         if (timeProvider != null) {
   3024                             timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START);
   3025                         }
   3026                         break;
   3027                 }
   3028 
   3029                 synchronized (mEventCbLock) {
   3030                     for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   3031                         cb.first.execute(() -> cb.second.onInfo(
   3032                                 mMediaPlayer, mCurrentDSD, what, extra));
   3033                     }
   3034                 }
   3035                 // No real default action so far.
   3036                 return;
   3037             }
   3038 
   3039             case MEDIA_NOTIFY_TIME:
   3040             {
   3041                 TimeProvider timeProvider = mTimeProvider;
   3042                 if (timeProvider != null) {
   3043                     timeProvider.onNotifyTime();
   3044                 }
   3045                 return;
   3046             }
   3047 
   3048             case MEDIA_TIMED_TEXT:
   3049             {
   3050                 final TimedText text;
   3051                 if (msg.obj instanceof Parcel) {
   3052                     Parcel parcel = (Parcel)msg.obj;
   3053                     text = new TimedText(parcel);
   3054                     parcel.recycle();
   3055                 } else {
   3056                     text = null;
   3057                 }
   3058 
   3059                 synchronized (mEventCbLock) {
   3060                     for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   3061                         cb.first.execute(() -> cb.second.onTimedText(mMediaPlayer, mCurrentDSD, text));
   3062                     }
   3063                 }
   3064                 return;
   3065             }
   3066 
   3067             case MEDIA_SUBTITLE_DATA:
   3068             {
   3069                 OnSubtitleDataListener onSubtitleDataListener = mOnSubtitleDataListener;
   3070                 if (onSubtitleDataListener == null) {
   3071                     return;
   3072                 }
   3073                 if (msg.obj instanceof Parcel) {
   3074                     Parcel parcel = (Parcel) msg.obj;
   3075                     SubtitleData data = new SubtitleData(parcel);
   3076                     parcel.recycle();
   3077                     onSubtitleDataListener.onSubtitleData(mMediaPlayer, data);
   3078                 }
   3079                 return;
   3080             }
   3081 
   3082             case MEDIA_META_DATA:
   3083             {
   3084                 final TimedMetaData data;
   3085                 if (msg.obj instanceof Parcel) {
   3086                     Parcel parcel = (Parcel) msg.obj;
   3087                     data = TimedMetaData.createTimedMetaDataFromParcel(parcel);
   3088                     parcel.recycle();
   3089                 } else {
   3090                     data = null;
   3091                 }
   3092 
   3093                 synchronized (mEventCbLock) {
   3094                     for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   3095                         cb.first.execute(() -> cb.second.onTimedMetaDataAvailable(
   3096                                 mMediaPlayer, mCurrentDSD, data));
   3097                     }
   3098                 }
   3099                 return;
   3100             }
   3101 
   3102             case MEDIA_NOP: // interface test message - ignore
   3103             {
   3104                 break;
   3105             }
   3106 
   3107             case MEDIA_AUDIO_ROUTING_CHANGED:
   3108             {
   3109                 AudioManager.resetAudioPortGeneration();
   3110                 synchronized (mRoutingChangeListeners) {
   3111                     for (NativeRoutingEventHandlerDelegate delegate
   3112                             : mRoutingChangeListeners.values()) {
   3113                         delegate.notifyClient();
   3114                     }
   3115                 }
   3116                 return;
   3117             }
   3118 
   3119             default:
   3120             {
   3121                 Log.e(TAG, "Unknown message type " + msg.what);
   3122                 return;
   3123             }
   3124             }
   3125         }
   3126     }
   3127 
   3128     /*
   3129      * Called from native code when an interesting event happens.  This method
   3130      * just uses the EventHandler system to post the event back to the main app thread.
   3131      * We use a weak reference to the original MediaPlayer2 object so that the native
   3132      * code is safe from the object disappearing from underneath it.  (This is
   3133      * the cookie passed to native_setup().)
   3134      */
   3135     private static void postEventFromNative(Object mediaplayer2_ref, long srcId,
   3136                                             int what, int arg1, int arg2, Object obj)
   3137     {
   3138         final MediaPlayer2Impl mp = (MediaPlayer2Impl)((WeakReference)mediaplayer2_ref).get();
   3139         if (mp == null) {
   3140             return;
   3141         }
   3142 
   3143         switch (what) {
   3144         case MEDIA_INFO:
   3145             if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
   3146                 new Thread(new Runnable() {
   3147                     @Override
   3148                     public void run() {
   3149                         // this acquires the wakelock if needed, and sets the client side state
   3150                         mp.play();
   3151                     }
   3152                 }).start();
   3153                 Thread.yield();
   3154             }
   3155             break;
   3156 
   3157         case MEDIA_DRM_INFO:
   3158             // We need to derive mDrmInfoImpl before prepare() returns so processing it here
   3159             // before the notification is sent to EventHandler below. EventHandler runs in the
   3160             // notification looper so its handleMessage might process the event after prepare()
   3161             // has returned.
   3162             Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
   3163             if (obj instanceof Parcel) {
   3164                 Parcel parcel = (Parcel)obj;
   3165                 DrmInfoImpl drmInfo = new DrmInfoImpl(parcel);
   3166                 synchronized (mp.mDrmLock) {
   3167                     mp.mDrmInfoImpl = drmInfo;
   3168                 }
   3169             } else {
   3170                 Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
   3171             }
   3172             break;
   3173 
   3174         case MEDIA_PREPARED:
   3175             // By this time, we've learned about DrmInfo's presence or absence. This is meant
   3176             // mainly for prepare() use case. For prepare(), this still can run to a race
   3177             // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
   3178             // so we also set mDrmInfoResolved in prepare().
   3179             synchronized (mp.mDrmLock) {
   3180                 mp.mDrmInfoResolved = true;
   3181             }
   3182             break;
   3183 
   3184         }
   3185 
   3186         if (mp.mEventHandler != null) {
   3187             Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
   3188 
   3189             mp.mEventHandler.post(new Runnable() {
   3190                 @Override
   3191                 public void run() {
   3192                     mp.mEventHandler.handleMessage(m, srcId);
   3193                 }
   3194             });
   3195         }
   3196     }
   3197 
   3198     private final Object mEventCbLock = new Object();
   3199     private ArrayList<Pair<Executor, MediaPlayer2EventCallback> > mEventCallbackRecords
   3200         = new ArrayList<Pair<Executor, MediaPlayer2EventCallback> >();
   3201 
   3202     /**
   3203      * Register a callback to be invoked when the media source is ready
   3204      * for playback.
   3205      *
   3206      * @param eventCallback the callback that will be run
   3207      * @param executor the executor through which the callback should be invoked
   3208      */
   3209     @Override
   3210     public void setMediaPlayer2EventCallback(@NonNull @CallbackExecutor Executor executor,
   3211             @NonNull MediaPlayer2EventCallback eventCallback) {
   3212         if (eventCallback == null) {
   3213             throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback");
   3214         }
   3215         if (executor == null) {
   3216             throw new IllegalArgumentException(
   3217                     "Illegal null Executor for the MediaPlayer2EventCallback");
   3218         }
   3219         synchronized (mEventCbLock) {
   3220             mEventCallbackRecords.add(new Pair(executor, eventCallback));
   3221         }
   3222     }
   3223 
   3224     /**
   3225      * Clears the {@link MediaPlayer2EventCallback}.
   3226      */
   3227     @Override
   3228     public void clearMediaPlayer2EventCallback() {
   3229         synchronized (mEventCbLock) {
   3230             mEventCallbackRecords.clear();
   3231         }
   3232     }
   3233 
   3234     /**
   3235      * Register a callback to be invoked when a track has data available.
   3236      *
   3237      * @param listener the callback that will be run
   3238      *
   3239      * @hide
   3240      */
   3241     @Override
   3242     public void setOnSubtitleDataListener(OnSubtitleDataListener listener) {
   3243         mOnSubtitleDataListener = listener;
   3244     }
   3245 
   3246     private OnSubtitleDataListener mOnSubtitleDataListener;
   3247 
   3248 
   3249     // Modular DRM begin
   3250 
   3251     /**
   3252      * Register a callback to be invoked for configuration of the DRM object before
   3253      * the session is created.
   3254      * The callback will be invoked synchronously during the execution
   3255      * of {@link #prepareDrm(UUID uuid)}.
   3256      *
   3257      * @param listener the callback that will be run
   3258      */
   3259     @Override
   3260     public void setOnDrmConfigHelper(OnDrmConfigHelper listener)
   3261     {
   3262         synchronized (mDrmLock) {
   3263             mOnDrmConfigHelper = listener;
   3264         } // synchronized
   3265     }
   3266 
   3267     private OnDrmConfigHelper mOnDrmConfigHelper;
   3268 
   3269     private final Object mDrmEventCbLock = new Object();
   3270     private ArrayList<Pair<Executor, DrmEventCallback> > mDrmEventCallbackRecords
   3271         = new ArrayList<Pair<Executor, DrmEventCallback> >();
   3272 
   3273     /**
   3274      * Register a callback to be invoked when the media source is ready
   3275      * for playback.
   3276      *
   3277      * @param eventCallback the callback that will be run
   3278      * @param executor the executor through which the callback should be invoked
   3279      */
   3280     @Override
   3281     public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
   3282             @NonNull DrmEventCallback eventCallback) {
   3283         if (eventCallback == null) {
   3284             throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback");
   3285         }
   3286         if (executor == null) {
   3287             throw new IllegalArgumentException(
   3288                     "Illegal null Executor for the MediaPlayer2EventCallback");
   3289         }
   3290         synchronized (mDrmEventCbLock) {
   3291             mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
   3292         }
   3293     }
   3294 
   3295     /**
   3296      * Clears the {@link DrmEventCallback}.
   3297      */
   3298     @Override
   3299     public void clearDrmEventCallback() {
   3300         synchronized (mDrmEventCbLock) {
   3301             mDrmEventCallbackRecords.clear();
   3302         }
   3303     }
   3304 
   3305 
   3306     /**
   3307      * Retrieves the DRM Info associated with the current source
   3308      *
   3309      * @throws IllegalStateException if called before prepare()
   3310      */
   3311     @Override
   3312     public DrmInfo getDrmInfo() {
   3313         DrmInfoImpl drmInfo = null;
   3314 
   3315         // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
   3316         // regardless below returns drmInfo anyway instead of raising an exception
   3317         synchronized (mDrmLock) {
   3318             if (!mDrmInfoResolved && mDrmInfoImpl == null) {
   3319                 final String msg = "The Player has not been prepared yet";
   3320                 Log.v(TAG, msg);
   3321                 throw new IllegalStateException(msg);
   3322             }
   3323 
   3324             if (mDrmInfoImpl != null) {
   3325                 drmInfo = mDrmInfoImpl.makeCopy();
   3326             }
   3327         }   // synchronized
   3328 
   3329         return drmInfo;
   3330     }
   3331 
   3332 
   3333     /**
   3334      * Prepares the DRM for the current source
   3335      * <p>
   3336      * If {@code OnDrmConfigHelper} is registered, it will be called during
   3337      * preparation to allow configuration of the DRM properties before opening the
   3338      * DRM session. Note that the callback is called synchronously in the thread that called
   3339      * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
   3340      * and {@code setDrmPropertyString} calls and refrain from any lengthy operation.
   3341      * <p>
   3342      * If the device has not been provisioned before, this call also provisions the device
   3343      * which involves accessing the provisioning server and can take a variable time to
   3344      * complete depending on the network connectivity.
   3345      * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking
   3346      * mode by launching the provisioning in the background and returning. The listener
   3347      * will be called when provisioning and preparation has finished. If a
   3348      * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning
   3349      * and preparation has finished, i.e., runs in blocking mode.
   3350      * <p>
   3351      * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM
   3352      * session being ready. The application should not make any assumption about its call
   3353      * sequence (e.g., before or after prepareDrm returns), or the thread context that will
   3354      * execute the listener (unless the listener is registered with a handler thread).
   3355      * <p>
   3356      *
   3357      * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
   3358      * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}.
   3359      *
   3360      * @throws IllegalStateException              if called before prepare(), or the DRM was
   3361      *                                            prepared already
   3362      * @throws UnsupportedSchemeException         if the crypto scheme is not supported
   3363      * @throws ResourceBusyException              if required DRM resources are in use
   3364      * @throws ProvisioningNetworkErrorException  if provisioning is required but failed due to a
   3365      *                                            network error
   3366      * @throws ProvisioningServerErrorException   if provisioning is required but failed due to
   3367      *                                            the request denied by the provisioning server
   3368      */
   3369     @Override
   3370     public void prepareDrm(@NonNull UUID uuid)
   3371             throws UnsupportedSchemeException, ResourceBusyException,
   3372                    ProvisioningNetworkErrorException, ProvisioningServerErrorException
   3373     {
   3374         Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
   3375 
   3376         boolean allDoneWithoutProvisioning = false;
   3377 
   3378         synchronized (mDrmLock) {
   3379 
   3380             // only allowing if tied to a protected source; might relax for releasing offline keys
   3381             if (mDrmInfoImpl == null) {
   3382                 final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " +
   3383                         "DRM info be retrieved before this call.";
   3384                 Log.e(TAG, msg);
   3385                 throw new IllegalStateException(msg);
   3386             }
   3387 
   3388             if (mActiveDrmScheme) {
   3389                 final String msg = "prepareDrm(): Wrong usage: There is already " +
   3390                         "an active DRM scheme with " + mDrmUUID;
   3391                 Log.e(TAG, msg);
   3392                 throw new IllegalStateException(msg);
   3393             }
   3394 
   3395             if (mPrepareDrmInProgress) {
   3396                 final String msg = "prepareDrm(): Wrong usage: There is already " +
   3397                         "a pending prepareDrm call.";
   3398                 Log.e(TAG, msg);
   3399                 throw new IllegalStateException(msg);
   3400             }
   3401 
   3402             if (mDrmProvisioningInProgress) {
   3403                 final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
   3404                 Log.e(TAG, msg);
   3405                 throw new IllegalStateException(msg);
   3406             }
   3407 
   3408             // shouldn't need this; just for safeguard
   3409             cleanDrmObj();
   3410 
   3411             mPrepareDrmInProgress = true;
   3412 
   3413             try {
   3414                 // only creating the DRM object to allow pre-openSession configuration
   3415                 prepareDrm_createDrmStep(uuid);
   3416             } catch (Exception e) {
   3417                 Log.w(TAG, "prepareDrm(): Exception ", e);
   3418                 mPrepareDrmInProgress = false;
   3419                 throw e;
   3420             }
   3421 
   3422             mDrmConfigAllowed = true;
   3423         }   // synchronized
   3424 
   3425 
   3426         // call the callback outside the lock
   3427         if (mOnDrmConfigHelper != null)  {
   3428             mOnDrmConfigHelper.onDrmConfig(this, mCurrentDSD);
   3429         }
   3430 
   3431         synchronized (mDrmLock) {
   3432             mDrmConfigAllowed = false;
   3433             boolean earlyExit = false;
   3434 
   3435             try {
   3436                 prepareDrm_openSessionStep(uuid);
   3437 
   3438                 mDrmUUID = uuid;
   3439                 mActiveDrmScheme = true;
   3440 
   3441                 allDoneWithoutProvisioning = true;
   3442             } catch (IllegalStateException e) {
   3443                 final String msg = "prepareDrm(): Wrong usage: The player must be " +
   3444                         "in the prepared state to call prepareDrm().";
   3445                 Log.e(TAG, msg);
   3446                 earlyExit = true;
   3447                 throw new IllegalStateException(msg);
   3448             } catch (NotProvisionedException e) {
   3449                 Log.w(TAG, "prepareDrm: NotProvisionedException");
   3450 
   3451                 // handle provisioning internally; it'll reset mPrepareDrmInProgress
   3452                 int result = HandleProvisioninig(uuid);
   3453 
   3454                 // if blocking mode, we're already done;
   3455                 // if non-blocking mode, we attempted to launch background provisioning
   3456                 if (result != PREPARE_DRM_STATUS_SUCCESS) {
   3457                     earlyExit = true;
   3458                     String msg;
   3459 
   3460                     switch (result) {
   3461                     case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
   3462                         msg = "prepareDrm: Provisioning was required but failed " +
   3463                                 "due to a network error.";
   3464                         Log.e(TAG, msg);
   3465                         throw new ProvisioningNetworkErrorExceptionImpl(msg);
   3466 
   3467                     case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
   3468                         msg = "prepareDrm: Provisioning was required but the request " +
   3469                                 "was denied by the server.";
   3470                         Log.e(TAG, msg);
   3471                         throw new ProvisioningServerErrorExceptionImpl(msg);
   3472 
   3473                     case PREPARE_DRM_STATUS_PREPARATION_ERROR:
   3474                     default: // default for safeguard
   3475                         msg = "prepareDrm: Post-provisioning preparation failed.";
   3476                         Log.e(TAG, msg);
   3477                         throw new IllegalStateException(msg);
   3478                     }
   3479                 }
   3480                 // nothing else to do;
   3481                 // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
   3482             } catch (Exception e) {
   3483                 Log.e(TAG, "prepareDrm: Exception " + e);
   3484                 earlyExit = true;
   3485                 throw e;
   3486             } finally {
   3487                 if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception
   3488                     mPrepareDrmInProgress = false;
   3489                 }
   3490                 if (earlyExit) {    // cleaning up object if didn't succeed
   3491                     cleanDrmObj();
   3492                 }
   3493             } // finally
   3494         }   // synchronized
   3495 
   3496 
   3497         // if finished successfully without provisioning, call the callback outside the lock
   3498         if (allDoneWithoutProvisioning) {
   3499             synchronized (mDrmEventCbLock) {
   3500                 for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
   3501                     cb.first.execute(() -> cb.second.onDrmPrepared(
   3502                             this, mCurrentDSD, PREPARE_DRM_STATUS_SUCCESS));
   3503                 }
   3504             }
   3505         }
   3506 
   3507     }
   3508 
   3509 
   3510     private native void _releaseDrm();
   3511 
   3512     /**
   3513      * Releases the DRM session
   3514      * <p>
   3515      * The player has to have an active DRM session and be in stopped, or prepared
   3516      * state before this call is made.
   3517      * A {@code reset()} call will release the DRM session implicitly.
   3518      *
   3519      * @throws NoDrmSchemeException if there is no active DRM session to release
   3520      */
   3521     @Override
   3522     public void releaseDrm()
   3523             throws NoDrmSchemeException
   3524     {
   3525         addTask(new Task(CALL_COMPLETED_RELEASE_DRM, false) {
   3526             @Override
   3527             void process() throws NoDrmSchemeException {
   3528                 synchronized (mDrmLock) {
   3529                     Log.v(TAG, "releaseDrm:");
   3530 
   3531                     if (!mActiveDrmScheme) {
   3532                         Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
   3533                         throw new NoDrmSchemeExceptionImpl(
   3534                                 "releaseDrm: No active DRM scheme to release.");
   3535                     }
   3536 
   3537                     try {
   3538                         // we don't have the player's state in this layer. The below call raises
   3539                         // exception if we're in a non-stopped/prepared state.
   3540 
   3541                         // for cleaning native/mediaserver crypto object
   3542                         _releaseDrm();
   3543 
   3544                         // for cleaning client-side MediaDrm object; only called if above has succeeded
   3545                         cleanDrmObj();
   3546 
   3547                         mActiveDrmScheme = false;
   3548                     } catch (IllegalStateException e) {
   3549                         Log.w(TAG, "releaseDrm: Exception ", e);
   3550                         throw new IllegalStateException(
   3551                                 "releaseDrm: The player is not in a valid state.");
   3552                     } catch (Exception e) {
   3553                         Log.e(TAG, "releaseDrm: Exception ", e);
   3554                     }
   3555                 }   // synchronized
   3556             }
   3557         });
   3558     }
   3559 
   3560 
   3561     /**
   3562      * A key request/response exchange occurs between the app and a license server
   3563      * to obtain or release keys used to decrypt encrypted content.
   3564      * <p>
   3565      * getDrmKeyRequest() is used to obtain an opaque key request byte array that is
   3566      * delivered to the license server.  The opaque key request byte array is returned
   3567      * in KeyRequest.data.  The recommended URL to deliver the key request to is
   3568      * returned in KeyRequest.defaultUrl.
   3569      * <p>
   3570      * After the app has received the key request response from the server,
   3571      * it should deliver to the response to the DRM engine plugin using the method
   3572      * {@link #provideDrmKeyResponse}.
   3573      *
   3574      * @param keySetId is the key-set identifier of the offline keys being released when keyType is
   3575      * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
   3576      * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
   3577      *
   3578      * @param initData is the container-specific initialization data when the keyType is
   3579      * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
   3580      * interpreted based on the mime type provided in the mimeType parameter.  It could
   3581      * contain, for example, the content ID, key ID or other data obtained from the content
   3582      * metadata that is required in generating the key request.
   3583      * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
   3584      *
   3585      * @param mimeType identifies the mime type of the content
   3586      *
   3587      * @param keyType specifies the type of the request. The request may be to acquire
   3588      * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
   3589      * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
   3590      * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
   3591      *
   3592      * @param optionalParameters are included in the key request message to
   3593      * allow a client application to provide additional message parameters to the server.
   3594      * This may be {@code null} if no additional parameters are to be sent.
   3595      *
   3596      * @throws NoDrmSchemeException if there is no active DRM session
   3597      */
   3598     @Override
   3599     @NonNull
   3600     public MediaDrm.KeyRequest getDrmKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData,
   3601             @Nullable String mimeType, @MediaDrm.KeyType int keyType,
   3602             @Nullable Map<String, String> optionalParameters)
   3603             throws NoDrmSchemeException
   3604     {
   3605         Log.v(TAG, "getDrmKeyRequest: " +
   3606                 " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
   3607                 " keyType: " + keyType + " optionalParameters: " + optionalParameters);
   3608 
   3609         synchronized (mDrmLock) {
   3610             if (!mActiveDrmScheme) {
   3611                 Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
   3612                 throw new NoDrmSchemeExceptionImpl(
   3613                         "getDrmKeyRequest: Has to set a DRM scheme first.");
   3614             }
   3615 
   3616             try {
   3617                 byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
   3618                         mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
   3619                         keySetId;       // keySetId for KEY_TYPE_RELEASE
   3620 
   3621                 HashMap<String, String> hmapOptionalParameters =
   3622                                                 (optionalParameters != null) ?
   3623                                                 new HashMap<String, String>(optionalParameters) :
   3624                                                 null;
   3625 
   3626                 MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
   3627                                                               keyType, hmapOptionalParameters);
   3628                 Log.v(TAG, "getDrmKeyRequest:   --> request: " + request);
   3629 
   3630                 return request;
   3631 
   3632             } catch (NotProvisionedException e) {
   3633                 Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " +
   3634                         "Unexpected. Shouldn't have reached here.");
   3635                 throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error.");
   3636             } catch (Exception e) {
   3637                 Log.w(TAG, "getDrmKeyRequest Exception " + e);
   3638                 throw e;
   3639             }
   3640 
   3641         }   // synchronized
   3642     }
   3643 
   3644 
   3645     /**
   3646      * A key response is received from the license server by the app, then it is
   3647      * provided to the DRM engine plugin using provideDrmKeyResponse. When the
   3648      * response is for an offline key request, a key-set identifier is returned that
   3649      * can be used to later restore the keys to a new session with the method
   3650      * {@ link # restoreDrmKeys}.
   3651      * When the response is for a streaming or release request, null is returned.
   3652      *
   3653      * @param keySetId When the response is for a release request, keySetId identifies
   3654      * the saved key associated with the release request (i.e., the same keySetId
   3655      * passed to the earlier {@ link #getDrmKeyRequest} call. It MUST be null when the
   3656      * response is for either streaming or offline key requests.
   3657      *
   3658      * @param response the byte array response from the server
   3659      *
   3660      * @throws NoDrmSchemeException if there is no active DRM session
   3661      * @throws DeniedByServerException if the response indicates that the
   3662      * server rejected the request
   3663      */
   3664     @Override
   3665     public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
   3666             throws NoDrmSchemeException, DeniedByServerException
   3667     {
   3668         Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
   3669 
   3670         synchronized (mDrmLock) {
   3671 
   3672             if (!mActiveDrmScheme) {
   3673                 Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
   3674                 throw new NoDrmSchemeExceptionImpl(
   3675                         "getDrmKeyRequest: Has to set a DRM scheme first.");
   3676             }
   3677 
   3678             try {
   3679                 byte[] scope = (keySetId == null) ?
   3680                                 mDrmSessionId :     // sessionId for KEY_TYPE_STREAMING/OFFLINE
   3681                                 keySetId;           // keySetId for KEY_TYPE_RELEASE
   3682 
   3683                 byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
   3684 
   3685                 Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response
   3686                         + " --> " + keySetResult);
   3687 
   3688 
   3689                 return keySetResult;
   3690 
   3691             } catch (NotProvisionedException e) {
   3692                 Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " +
   3693                         "Unexpected. Shouldn't have reached here.");
   3694                 throw new IllegalStateException("provideDrmKeyResponse: " +
   3695                         "Unexpected provisioning error.");
   3696             } catch (Exception e) {
   3697                 Log.w(TAG, "provideDrmKeyResponse Exception " + e);
   3698                 throw e;
   3699             }
   3700         }   // synchronized
   3701     }
   3702 
   3703 
   3704     /**
   3705      * Restore persisted offline keys into a new session.  keySetId identifies the
   3706      * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}.
   3707      *
   3708      * @param keySetId identifies the saved key set to restore
   3709      */
   3710     @Override
   3711     public void restoreDrmKeys(@NonNull byte[] keySetId)
   3712             throws NoDrmSchemeException
   3713     {
   3714         addTask(new Task(CALL_COMPLETED_RESTORE_DRM_KEYS, false) {
   3715             @Override
   3716             void process() throws NoDrmSchemeException {
   3717                 Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
   3718 
   3719                 synchronized (mDrmLock) {
   3720 
   3721                     if (!mActiveDrmScheme) {
   3722                         Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
   3723                         throw new NoDrmSchemeExceptionImpl(
   3724                                 "restoreDrmKeys: Has to set a DRM scheme first.");
   3725                     }
   3726 
   3727                     try {
   3728                         mDrmObj.restoreKeys(mDrmSessionId, keySetId);
   3729                     } catch (Exception e) {
   3730                         Log.w(TAG, "restoreKeys Exception " + e);
   3731                         throw e;
   3732                     }
   3733 
   3734                 }   // synchronized
   3735             }
   3736         });
   3737     }
   3738 
   3739 
   3740     /**
   3741      * Read a DRM engine plugin String property value, given the property name string.
   3742      * <p>
   3743      * @param propertyName the property name
   3744      *
   3745      * Standard fields names are:
   3746      * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
   3747      * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
   3748      */
   3749     @Override
   3750     @NonNull
   3751     public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
   3752             throws NoDrmSchemeException
   3753     {
   3754         Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
   3755 
   3756         String value;
   3757         synchronized (mDrmLock) {
   3758 
   3759             if (!mActiveDrmScheme && !mDrmConfigAllowed) {
   3760                 Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
   3761                 throw new NoDrmSchemeExceptionImpl(
   3762                         "getDrmPropertyString: Has to prepareDrm() first.");
   3763             }
   3764 
   3765             try {
   3766                 value = mDrmObj.getPropertyString(propertyName);
   3767             } catch (Exception e) {
   3768                 Log.w(TAG, "getDrmPropertyString Exception " + e);
   3769                 throw e;
   3770             }
   3771         }   // synchronized
   3772 
   3773         Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
   3774 
   3775         return value;
   3776     }
   3777 
   3778 
   3779     /**
   3780      * Set a DRM engine plugin String property value.
   3781      * <p>
   3782      * @param propertyName the property name
   3783      * @param value the property value
   3784      *
   3785      * Standard fields names are:
   3786      * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
   3787      * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
   3788      */
   3789     @Override
   3790     public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
   3791                                      @NonNull String value)
   3792             throws NoDrmSchemeException
   3793     {
   3794         Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
   3795 
   3796         synchronized (mDrmLock) {
   3797 
   3798             if ( !mActiveDrmScheme && !mDrmConfigAllowed ) {
   3799                 Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
   3800                 throw new NoDrmSchemeExceptionImpl(
   3801                         "setDrmPropertyString: Has to prepareDrm() first.");
   3802             }
   3803 
   3804             try {
   3805                 mDrmObj.setPropertyString(propertyName, value);
   3806             } catch ( Exception e ) {
   3807                 Log.w(TAG, "setDrmPropertyString Exception " + e);
   3808                 throw e;
   3809             }
   3810         }   // synchronized
   3811     }
   3812 
   3813     /**
   3814      * Encapsulates the DRM properties of the source.
   3815      */
   3816     public static final class DrmInfoImpl extends DrmInfo {
   3817         private Map<UUID, byte[]> mapPssh;
   3818         private UUID[] supportedSchemes;
   3819 
   3820         /**
   3821          * Returns the PSSH info of the data source for each supported DRM scheme.
   3822          */
   3823         @Override
   3824         public Map<UUID, byte[]> getPssh() {
   3825             return mapPssh;
   3826         }
   3827 
   3828         /**
   3829          * Returns the intersection of the data source and the device DRM schemes.
   3830          * It effectively identifies the subset of the source's DRM schemes which
   3831          * are supported by the device too.
   3832          */
   3833         @Override
   3834         public List<UUID> getSupportedSchemes() {
   3835             return Arrays.asList(supportedSchemes);
   3836         }
   3837 
   3838         private DrmInfoImpl(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) {
   3839             mapPssh = Pssh;
   3840             supportedSchemes = SupportedSchemes;
   3841         }
   3842 
   3843         private DrmInfoImpl(Parcel parcel) {
   3844             Log.v(TAG, "DrmInfoImpl(" + parcel + ") size " + parcel.dataSize());
   3845 
   3846             int psshsize = parcel.readInt();
   3847             byte[] pssh = new byte[psshsize];
   3848             parcel.readByteArray(pssh);
   3849 
   3850             Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh));
   3851             mapPssh = parsePSSH(pssh, psshsize);
   3852             Log.v(TAG, "DrmInfoImpl() PSSH: " + mapPssh);
   3853 
   3854             int supportedDRMsCount = parcel.readInt();
   3855             supportedSchemes = new UUID[supportedDRMsCount];
   3856             for (int i = 0; i < supportedDRMsCount; i++) {
   3857                 byte[] uuid = new byte[16];
   3858                 parcel.readByteArray(uuid);
   3859 
   3860                 supportedSchemes[i] = bytesToUUID(uuid);
   3861 
   3862                 Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " +
   3863                       supportedSchemes[i]);
   3864             }
   3865 
   3866             Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + psshsize +
   3867                   " supportedDRMsCount: " + supportedDRMsCount);
   3868         }
   3869 
   3870         private DrmInfoImpl makeCopy() {
   3871             return new DrmInfoImpl(this.mapPssh, this.supportedSchemes);
   3872         }
   3873 
   3874         private String arrToHex(byte[] bytes) {
   3875             String out = "0x";
   3876             for (int i = 0; i < bytes.length; i++) {
   3877                 out += String.format("%02x", bytes[i]);
   3878             }
   3879 
   3880             return out;
   3881         }
   3882 
   3883         private UUID bytesToUUID(byte[] uuid) {
   3884             long msb = 0, lsb = 0;
   3885             for (int i = 0; i < 8; i++) {
   3886                 msb |= ( ((long)uuid[i]   & 0xff) << (8 * (7 - i)) );
   3887                 lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) );
   3888             }
   3889 
   3890             return new UUID(msb, lsb);
   3891         }
   3892 
   3893         private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
   3894             Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
   3895 
   3896             final int UUID_SIZE = 16;
   3897             final int DATALEN_SIZE = 4;
   3898 
   3899             int len = psshsize;
   3900             int numentries = 0;
   3901             int i = 0;
   3902 
   3903             while (len > 0) {
   3904                 if (len < UUID_SIZE) {
   3905                     Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
   3906                                              "UUID: (%d < 16) pssh: %d", len, psshsize));
   3907                     return null;
   3908                 }
   3909 
   3910                 byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE);
   3911                 UUID uuid = bytesToUUID(subset);
   3912                 i += UUID_SIZE;
   3913                 len -= UUID_SIZE;
   3914 
   3915                 // get data length
   3916                 if (len < 4) {
   3917                     Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
   3918                                              "datalen: (%d < 4) pssh: %d", len, psshsize));
   3919                     return null;
   3920                 }
   3921 
   3922                 subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE);
   3923                 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ?
   3924                     ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) |
   3925                     ((subset[1] & 0xff) <<  8) |  (subset[0] & 0xff)          :
   3926                     ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) |
   3927                     ((subset[2] & 0xff) <<  8) |  (subset[3] & 0xff) ;
   3928                 i += DATALEN_SIZE;
   3929                 len -= DATALEN_SIZE;
   3930 
   3931                 if (len < datalen) {
   3932                     Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
   3933                                              "data: (%d < %d) pssh: %d", len, datalen, psshsize));
   3934                     return null;
   3935                 }
   3936 
   3937                 byte[] data = Arrays.copyOfRange(pssh, i, i+datalen);
   3938 
   3939                 // skip the data
   3940                 i += datalen;
   3941                 len -= datalen;
   3942 
   3943                 Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
   3944                                          numentries, uuid, arrToHex(data), psshsize));
   3945                 numentries++;
   3946                 result.put(uuid, data);
   3947             }
   3948 
   3949             return result;
   3950         }
   3951 
   3952     };  // DrmInfoImpl
   3953 
   3954     /**
   3955      * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
   3956      * Extends MediaDrm.MediaDrmException
   3957      */
   3958     public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException {
   3959         public NoDrmSchemeExceptionImpl(String detailMessage) {
   3960             super(detailMessage);
   3961         }
   3962     }
   3963 
   3964     /**
   3965      * Thrown when the device requires DRM provisioning but the provisioning attempt has
   3966      * failed due to a network error (Internet reachability, timeout, etc.).
   3967      * Extends MediaDrm.MediaDrmException
   3968      */
   3969     public static final class ProvisioningNetworkErrorExceptionImpl
   3970             extends ProvisioningNetworkErrorException {
   3971         public ProvisioningNetworkErrorExceptionImpl(String detailMessage) {
   3972             super(detailMessage);
   3973         }
   3974     }
   3975 
   3976     /**
   3977      * Thrown when the device requires DRM provisioning but the provisioning attempt has
   3978      * failed due to the provisioning server denying the request.
   3979      * Extends MediaDrm.MediaDrmException
   3980      */
   3981     public static final class ProvisioningServerErrorExceptionImpl
   3982             extends ProvisioningServerErrorException {
   3983         public ProvisioningServerErrorExceptionImpl(String detailMessage) {
   3984             super(detailMessage);
   3985         }
   3986     }
   3987 
   3988 
   3989     private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
   3990 
   3991         // Modular DRM helpers
   3992 
   3993     private void prepareDrm_createDrmStep(@NonNull UUID uuid)
   3994             throws UnsupportedSchemeException {
   3995         Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
   3996 
   3997         try {
   3998             mDrmObj = new MediaDrm(uuid);
   3999             Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
   4000         } catch (Exception e) { // UnsupportedSchemeException
   4001             Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
   4002             throw e;
   4003         }
   4004     }
   4005 
   4006     private void prepareDrm_openSessionStep(@NonNull UUID uuid)
   4007             throws NotProvisionedException, ResourceBusyException {
   4008         Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
   4009 
   4010         // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
   4011         // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
   4012         // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
   4013         try {
   4014             mDrmSessionId = mDrmObj.openSession();
   4015             Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
   4016 
   4017             // Sending it down to native/mediaserver to create the crypto object
   4018             // This call could simply fail due to bad player state, e.g., after play().
   4019             _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
   4020             Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded");
   4021 
   4022         } catch (Exception e) { //ResourceBusyException, NotProvisionedException
   4023             Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
   4024             throw e;
   4025         }
   4026 
   4027     }
   4028 
   4029     // Called from the native side
   4030     @SuppressWarnings("unused")
   4031     private static boolean setAudioOutputDeviceById(AudioTrack track, int deviceId) {
   4032         if (track == null) {
   4033             return false;
   4034         }
   4035 
   4036         if (deviceId == 0) {
   4037             // Use default routing.
   4038             track.setPreferredDevice(null);
   4039             return true;
   4040         }
   4041 
   4042         // TODO: Unhide AudioManager.getDevicesStatic.
   4043         AudioDeviceInfo[] outputDevices =
   4044                 AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
   4045 
   4046         boolean success = false;
   4047         for (AudioDeviceInfo device : outputDevices) {
   4048             if (device.getId() == deviceId) {
   4049                 track.setPreferredDevice(device);
   4050                 success = true;
   4051                 break;
   4052             }
   4053         }
   4054         return success;
   4055     }
   4056 
   4057     // Instantiated from the native side
   4058     @SuppressWarnings("unused")
   4059     private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
   4060         public long mJAudioTrackPtr;
   4061         public long mNativeCallbackPtr;
   4062         public long mUserDataPtr;
   4063 
   4064         public StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) {
   4065             super();
   4066             mJAudioTrackPtr = jAudioTrackPtr;
   4067             mNativeCallbackPtr = nativeCallbackPtr;
   4068             mUserDataPtr = userDataPtr;
   4069         }
   4070 
   4071         @Override
   4072         public void onTearDown(AudioTrack track) {
   4073             native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr);
   4074         }
   4075 
   4076         @Override
   4077         public void onStreamPresentationEnd(AudioTrack track) {
   4078             native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr);
   4079         }
   4080 
   4081         @Override
   4082         public void onStreamDataRequest(AudioTrack track) {
   4083             native_stream_event_onStreamDataRequest(
   4084                     mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr);
   4085         }
   4086     }
   4087 
   4088     private class ProvisioningThread extends Thread {
   4089         public static final int TIMEOUT_MS = 60000;
   4090 
   4091         private UUID uuid;
   4092         private String urlStr;
   4093         private Object drmLock;
   4094         private MediaPlayer2Impl mediaPlayer;
   4095         private int status;
   4096         private boolean finished;
   4097         public  int status() {
   4098             return status;
   4099         }
   4100 
   4101         public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
   4102                                           UUID uuid, MediaPlayer2Impl mediaPlayer) {
   4103             // lock is held by the caller
   4104             drmLock = mediaPlayer.mDrmLock;
   4105             this.mediaPlayer = mediaPlayer;
   4106 
   4107             urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
   4108             this.uuid = uuid;
   4109 
   4110             status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
   4111 
   4112             Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr);
   4113             return this;
   4114         }
   4115 
   4116         public void run() {
   4117 
   4118             byte[] response = null;
   4119             boolean provisioningSucceeded = false;
   4120             try {
   4121                 URL url = new URL(urlStr);
   4122                 final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
   4123                 try {
   4124                     connection.setRequestMethod("POST");
   4125                     connection.setDoOutput(false);
   4126                     connection.setDoInput(true);
   4127                     connection.setConnectTimeout(TIMEOUT_MS);
   4128                     connection.setReadTimeout(TIMEOUT_MS);
   4129 
   4130                     connection.connect();
   4131                     response = Streams.readFully(connection.getInputStream());
   4132 
   4133                     Log.v(TAG, "HandleProvisioninig: Thread run: response " +
   4134                             response.length + " " + response);
   4135                 } catch (Exception e) {
   4136                     status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
   4137                     Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url);
   4138                 } finally {
   4139                     connection.disconnect();
   4140                 }
   4141             } catch (Exception e)   {
   4142                 status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
   4143                 Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e);
   4144             }
   4145 
   4146             if (response != null) {
   4147                 try {
   4148                     mDrmObj.provideProvisionResponse(response);
   4149                     Log.v(TAG, "HandleProvisioninig: Thread run: " +
   4150                             "provideProvisionResponse SUCCEEDED!");
   4151 
   4152                     provisioningSucceeded = true;
   4153                 } catch (Exception e) {
   4154                     status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
   4155                     Log.w(TAG, "HandleProvisioninig: Thread run: " +
   4156                             "provideProvisionResponse " + e);
   4157                 }
   4158             }
   4159 
   4160             boolean succeeded = false;
   4161 
   4162             boolean hasCallback = false;
   4163             synchronized (mDrmEventCbLock) {
   4164                 hasCallback = !mDrmEventCallbackRecords.isEmpty();
   4165             }
   4166             // non-blocking mode needs the lock
   4167             if (hasCallback) {
   4168 
   4169                 synchronized (drmLock) {
   4170                     // continuing with prepareDrm
   4171                     if (provisioningSucceeded) {
   4172                         succeeded = mediaPlayer.resumePrepareDrm(uuid);
   4173                         status = (succeeded) ?
   4174                                 PREPARE_DRM_STATUS_SUCCESS :
   4175                                 PREPARE_DRM_STATUS_PREPARATION_ERROR;
   4176                     }
   4177                     mediaPlayer.mDrmProvisioningInProgress = false;
   4178                     mediaPlayer.mPrepareDrmInProgress = false;
   4179                     if (!succeeded) {
   4180                         cleanDrmObj();  // cleaning up if it hasn't gone through while in the lock
   4181                     }
   4182                 } // synchronized
   4183 
   4184                 // calling the callback outside the lock
   4185                 synchronized (mDrmEventCbLock) {
   4186                     for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
   4187                         cb.first.execute(() -> cb.second.onDrmPrepared(
   4188                                 mediaPlayer, mCurrentDSD, status));
   4189                     }
   4190                 }
   4191             } else {   // blocking mode already has the lock
   4192 
   4193                 // continuing with prepareDrm
   4194                 if (provisioningSucceeded) {
   4195                     succeeded = mediaPlayer.resumePrepareDrm(uuid);
   4196                     status = (succeeded) ?
   4197                             PREPARE_DRM_STATUS_SUCCESS :
   4198                             PREPARE_DRM_STATUS_PREPARATION_ERROR;
   4199                 }
   4200                 mediaPlayer.mDrmProvisioningInProgress = false;
   4201                 mediaPlayer.mPrepareDrmInProgress = false;
   4202                 if (!succeeded) {
   4203                     cleanDrmObj();  // cleaning up if it hasn't gone through
   4204                 }
   4205             }
   4206 
   4207             finished = true;
   4208         }   // run()
   4209 
   4210     }   // ProvisioningThread
   4211 
   4212     private int HandleProvisioninig(UUID uuid) {
   4213         // the lock is already held by the caller
   4214 
   4215         if (mDrmProvisioningInProgress) {
   4216             Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress");
   4217             return PREPARE_DRM_STATUS_PREPARATION_ERROR;
   4218         }
   4219 
   4220         MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
   4221         if (provReq == null) {
   4222             Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null.");
   4223             return PREPARE_DRM_STATUS_PREPARATION_ERROR;
   4224         }
   4225 
   4226         Log.v(TAG, "HandleProvisioninig provReq " +
   4227                 " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
   4228 
   4229         // networking in a background thread
   4230         mDrmProvisioningInProgress = true;
   4231 
   4232         mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
   4233         mDrmProvisioningThread.start();
   4234 
   4235         int result;
   4236 
   4237         // non-blocking: this is not the final result
   4238         boolean hasCallback = false;
   4239         synchronized (mDrmEventCbLock) {
   4240             hasCallback = !mDrmEventCallbackRecords.isEmpty();
   4241         }
   4242         if (hasCallback) {
   4243             result = PREPARE_DRM_STATUS_SUCCESS;
   4244         } else {
   4245             // if blocking mode, wait till provisioning is done
   4246             try {
   4247                 mDrmProvisioningThread.join();
   4248             } catch (Exception e) {
   4249                 Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e);
   4250             }
   4251             result = mDrmProvisioningThread.status();
   4252             // no longer need the thread
   4253             mDrmProvisioningThread = null;
   4254         }
   4255 
   4256         return result;
   4257     }
   4258 
   4259     private boolean resumePrepareDrm(UUID uuid) {
   4260         Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
   4261 
   4262         // mDrmLock is guaranteed to be held
   4263         boolean success = false;
   4264         try {
   4265             // resuming
   4266             prepareDrm_openSessionStep(uuid);
   4267 
   4268             mDrmUUID = uuid;
   4269             mActiveDrmScheme = true;
   4270 
   4271             success = true;
   4272         } catch (Exception e) {
   4273             Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e);
   4274             // mDrmObj clean up is done by the caller
   4275         }
   4276 
   4277         return success;
   4278     }
   4279 
   4280     private void resetDrmState() {
   4281         synchronized (mDrmLock) {
   4282             Log.v(TAG, "resetDrmState: " +
   4283                     " mDrmInfoImpl=" + mDrmInfoImpl +
   4284                     " mDrmProvisioningThread=" + mDrmProvisioningThread +
   4285                     " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
   4286                     " mActiveDrmScheme=" + mActiveDrmScheme);
   4287 
   4288             mDrmInfoResolved = false;
   4289             mDrmInfoImpl = null;
   4290 
   4291             if (mDrmProvisioningThread != null) {
   4292                 // timeout; relying on HttpUrlConnection
   4293                 try {
   4294                     mDrmProvisioningThread.join();
   4295                 }
   4296                 catch (InterruptedException e) {
   4297                     Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
   4298                 }
   4299                 mDrmProvisioningThread = null;
   4300             }
   4301 
   4302             mPrepareDrmInProgress = false;
   4303             mActiveDrmScheme = false;
   4304 
   4305             cleanDrmObj();
   4306         }   // synchronized
   4307     }
   4308 
   4309     private void cleanDrmObj() {
   4310         // the caller holds mDrmLock
   4311         Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
   4312 
   4313         if (mDrmSessionId != null)    {
   4314             mDrmObj.closeSession(mDrmSessionId);
   4315             mDrmSessionId = null;
   4316         }
   4317         if (mDrmObj != null) {
   4318             mDrmObj.release();
   4319             mDrmObj = null;
   4320         }
   4321     }
   4322 
   4323     private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
   4324         long msb = uuid.getMostSignificantBits();
   4325         long lsb = uuid.getLeastSignificantBits();
   4326 
   4327         byte[] uuidBytes = new byte[16];
   4328         for (int i = 0; i < 8; ++i) {
   4329             uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
   4330             uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
   4331         }
   4332 
   4333         return uuidBytes;
   4334     }
   4335 
   4336     // Modular DRM end
   4337 
   4338     /*
   4339      * Test whether a given video scaling mode is supported.
   4340      */
   4341     private boolean isVideoScalingModeSupported(int mode) {
   4342         return (mode == VIDEO_SCALING_MODE_SCALE_TO_FIT ||
   4343                 mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
   4344     }
   4345 
   4346     /** @hide */
   4347     static class TimeProvider implements MediaTimeProvider {
   4348         private static final String TAG = "MTP";
   4349         private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L;
   4350         private static final long MAX_EARLY_CALLBACK_US = 1000;
   4351         private static final long TIME_ADJUSTMENT_RATE = 2;  /* meaning 1/2 */
   4352         private long mLastTimeUs = 0;
   4353         private MediaPlayer2Impl mPlayer;
   4354         private boolean mPaused = true;
   4355         private boolean mStopped = true;
   4356         private boolean mBuffering;
   4357         private long mLastReportedTime;
   4358         // since we are expecting only a handful listeners per stream, there is
   4359         // no need for log(N) search performance
   4360         private MediaTimeProvider.OnMediaTimeListener mListeners[];
   4361         private long mTimes[];
   4362         private EventHandler mEventHandler;
   4363         private boolean mRefresh = false;
   4364         private boolean mPausing = false;
   4365         private boolean mSeeking = false;
   4366         private static final int NOTIFY = 1;
   4367         private static final int NOTIFY_TIME = 0;
   4368         private static final int NOTIFY_STOP = 2;
   4369         private static final int NOTIFY_SEEK = 3;
   4370         private static final int NOTIFY_TRACK_DATA = 4;
   4371         private HandlerThread mHandlerThread;
   4372 
   4373         /** @hide */
   4374         public boolean DEBUG = false;
   4375 
   4376         public TimeProvider(MediaPlayer2Impl mp) {
   4377             mPlayer = mp;
   4378             try {
   4379                 getCurrentTimeUs(true, false);
   4380             } catch (IllegalStateException e) {
   4381                 // we assume starting position
   4382                 mRefresh = true;
   4383             }
   4384 
   4385             Looper looper;
   4386             if ((looper = Looper.myLooper()) == null &&
   4387                 (looper = Looper.getMainLooper()) == null) {
   4388                 // Create our own looper here in case MP was created without one
   4389                 mHandlerThread = new HandlerThread("MediaPlayer2MTPEventThread",
   4390                       Process.THREAD_PRIORITY_FOREGROUND);
   4391                 mHandlerThread.start();
   4392                 looper = mHandlerThread.getLooper();
   4393             }
   4394             mEventHandler = new EventHandler(looper);
   4395 
   4396             mListeners = new MediaTimeProvider.OnMediaTimeListener[0];
   4397             mTimes = new long[0];
   4398             mLastTimeUs = 0;
   4399         }
   4400 
   4401         private void scheduleNotification(int type, long delayUs) {
   4402             // ignore time notifications until seek is handled
   4403             if (mSeeking && type == NOTIFY_TIME) {
   4404                 return;
   4405             }
   4406 
   4407             if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs);
   4408             mEventHandler.removeMessages(NOTIFY);
   4409             Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0);
   4410             mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000));
   4411         }
   4412 
   4413         /** @hide */
   4414         public void close() {
   4415             mEventHandler.removeMessages(NOTIFY);
   4416             if (mHandlerThread != null) {
   4417                 mHandlerThread.quitSafely();
   4418                 mHandlerThread = null;
   4419             }
   4420         }
   4421 
   4422         /** @hide */
   4423         protected void finalize() {
   4424             if (mHandlerThread != null) {
   4425                 mHandlerThread.quitSafely();
   4426             }
   4427         }
   4428 
   4429         /** @hide */
   4430         public void onNotifyTime() {
   4431             synchronized (this) {
   4432                 if (DEBUG) Log.d(TAG, "onNotifyTime: ");
   4433                 scheduleNotification(NOTIFY_TIME, 0 /* delay */);
   4434             }
   4435         }
   4436 
   4437         /** @hide */
   4438         public void onPaused(boolean paused) {
   4439             synchronized(this) {
   4440                 if (DEBUG) Log.d(TAG, "onPaused: " + paused);
   4441                 if (mStopped) { // handle as seek if we were stopped
   4442                     mStopped = false;
   4443                     mSeeking = true;
   4444                     scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
   4445                 } else {
   4446                     mPausing = paused;  // special handling if player disappeared
   4447                     mSeeking = false;
   4448                     scheduleNotification(NOTIFY_TIME, 0 /* delay */);
   4449                 }
   4450             }
   4451         }
   4452 
   4453         /** @hide */
   4454         public void onBuffering(boolean buffering) {
   4455             synchronized (this) {
   4456                 if (DEBUG) Log.d(TAG, "onBuffering: " + buffering);
   4457                 mBuffering = buffering;
   4458                 scheduleNotification(NOTIFY_TIME, 0 /* delay */);
   4459             }
   4460         }
   4461 
   4462         /** @hide */
   4463         public void onStopped() {
   4464             synchronized(this) {
   4465                 if (DEBUG) Log.d(TAG, "onStopped");
   4466                 mPaused = true;
   4467                 mStopped = true;
   4468                 mSeeking = false;
   4469                 mBuffering = false;
   4470                 scheduleNotification(NOTIFY_STOP, 0 /* delay */);
   4471             }
   4472         }
   4473 
   4474         /** @hide */
   4475         public void onSeekComplete(MediaPlayer2Impl mp) {
   4476             synchronized(this) {
   4477                 mStopped = false;
   4478                 mSeeking = true;
   4479                 scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
   4480             }
   4481         }
   4482 
   4483         /** @hide */
   4484         public void onNewPlayer() {
   4485             if (mRefresh) {
   4486                 synchronized(this) {
   4487                     mStopped = false;
   4488                     mSeeking = true;
   4489                     mBuffering = false;
   4490                     scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
   4491                 }
   4492             }
   4493         }
   4494 
   4495         private synchronized void notifySeek() {
   4496             mSeeking = false;
   4497             try {
   4498                 long timeUs = getCurrentTimeUs(true, false);
   4499                 if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs);
   4500 
   4501                 for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) {
   4502                     if (listener == null) {
   4503                         break;
   4504                     }
   4505                     listener.onSeek(timeUs);
   4506                 }
   4507             } catch (IllegalStateException e) {
   4508                 // we should not be there, but at least signal pause
   4509                 if (DEBUG) Log.d(TAG, "onSeekComplete but no player");
   4510                 mPausing = true;  // special handling if player disappeared
   4511                 notifyTimedEvent(false /* refreshTime */);
   4512             }
   4513         }
   4514 
   4515         private synchronized void notifyTrackData(Pair<SubtitleTrack, byte[]> trackData) {
   4516             SubtitleTrack track = trackData.first;
   4517             byte[] data = trackData.second;
   4518             track.onData(data, true /* eos */, ~0 /* runID: keep forever */);
   4519         }
   4520 
   4521         private synchronized void notifyStop() {
   4522             for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) {
   4523                 if (listener == null) {
   4524                     break;
   4525                 }
   4526                 listener.onStop();
   4527             }
   4528         }
   4529 
   4530         private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) {
   4531             int i = 0;
   4532             for (; i < mListeners.length; i++) {
   4533                 if (mListeners[i] == listener || mListeners[i] == null) {
   4534                     break;
   4535                 }
   4536             }
   4537 
   4538             // new listener
   4539             if (i >= mListeners.length) {
   4540                 MediaTimeProvider.OnMediaTimeListener[] newListeners =
   4541                     new MediaTimeProvider.OnMediaTimeListener[i + 1];
   4542                 long[] newTimes = new long[i + 1];
   4543                 System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length);
   4544                 System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length);
   4545                 mListeners = newListeners;
   4546                 mTimes = newTimes;
   4547             }
   4548 
   4549             if (mListeners[i] == null) {
   4550                 mListeners[i] = listener;
   4551                 mTimes[i] = MediaTimeProvider.NO_TIME;
   4552             }
   4553             return i;
   4554         }
   4555 
   4556         public void notifyAt(
   4557                 long timeUs, MediaTimeProvider.OnMediaTimeListener listener) {
   4558             synchronized(this) {
   4559                 if (DEBUG) Log.d(TAG, "notifyAt " + timeUs);
   4560                 mTimes[registerListener(listener)] = timeUs;
   4561                 scheduleNotification(NOTIFY_TIME, 0 /* delay */);
   4562             }
   4563         }
   4564 
   4565         public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) {
   4566             synchronized(this) {
   4567                 if (DEBUG) Log.d(TAG, "scheduleUpdate");
   4568                 int i = registerListener(listener);
   4569 
   4570                 if (!mStopped) {
   4571                     mTimes[i] = 0;
   4572                     scheduleNotification(NOTIFY_TIME, 0 /* delay */);
   4573                 }
   4574             }
   4575         }
   4576 
   4577         public void cancelNotifications(
   4578                 MediaTimeProvider.OnMediaTimeListener listener) {
   4579             synchronized(this) {
   4580                 int i = 0;
   4581                 for (; i < mListeners.length; i++) {
   4582                     if (mListeners[i] == listener) {
   4583                         System.arraycopy(mListeners, i + 1,
   4584                                 mListeners, i, mListeners.length - i - 1);
   4585                         System.arraycopy(mTimes, i + 1,
   4586                                 mTimes, i, mTimes.length - i - 1);
   4587                         mListeners[mListeners.length - 1] = null;
   4588                         mTimes[mTimes.length - 1] = NO_TIME;
   4589                         break;
   4590                     } else if (mListeners[i] == null) {
   4591                         break;
   4592                     }
   4593                 }
   4594 
   4595                 scheduleNotification(NOTIFY_TIME, 0 /* delay */);
   4596             }
   4597         }
   4598 
   4599         private synchronized void notifyTimedEvent(boolean refreshTime) {
   4600             // figure out next callback
   4601             long nowUs;
   4602             try {
   4603                 nowUs = getCurrentTimeUs(refreshTime, true);
   4604             } catch (IllegalStateException e) {
   4605                 // assume we paused until new player arrives
   4606                 mRefresh = true;
   4607                 mPausing = true; // this ensures that call succeeds
   4608                 nowUs = getCurrentTimeUs(refreshTime, true);
   4609             }
   4610             long nextTimeUs = nowUs;
   4611 
   4612             if (mSeeking) {
   4613                 // skip timed-event notifications until seek is complete
   4614                 return;
   4615             }
   4616 
   4617             if (DEBUG) {
   4618                 StringBuilder sb = new StringBuilder();
   4619                 sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ")
   4620                         .append(nowUs).append(") from {");
   4621                 boolean first = true;
   4622                 for (long time: mTimes) {
   4623                     if (time == NO_TIME) {
   4624                         continue;
   4625                     }
   4626                     if (!first) sb.append(", ");
   4627                     sb.append(time);
   4628                     first = false;
   4629                 }
   4630                 sb.append("}");
   4631                 Log.d(TAG, sb.toString());
   4632             }
   4633 
   4634             Vector<MediaTimeProvider.OnMediaTimeListener> activatedListeners =
   4635                 new Vector<MediaTimeProvider.OnMediaTimeListener>();
   4636             for (int ix = 0; ix < mTimes.length; ix++) {
   4637                 if (mListeners[ix] == null) {
   4638                     break;
   4639                 }
   4640                 if (mTimes[ix] <= NO_TIME) {
   4641                     // ignore, unless we were stopped
   4642                 } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) {
   4643                     activatedListeners.add(mListeners[ix]);
   4644                     if (DEBUG) Log.d(TAG, "removed");
   4645                     mTimes[ix] = NO_TIME;
   4646                 } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) {
   4647                     nextTimeUs = mTimes[ix];
   4648                 }
   4649             }
   4650 
   4651             if (nextTimeUs > nowUs && !mPaused) {
   4652                 // schedule callback at nextTimeUs
   4653                 if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs);
   4654                 mPlayer.notifyAt(nextTimeUs);
   4655             } else {
   4656                 mEventHandler.removeMessages(NOTIFY);
   4657                 // no more callbacks
   4658             }
   4659 
   4660             for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) {
   4661                 listener.onTimedEvent(nowUs);
   4662             }
   4663         }
   4664 
   4665         public long getCurrentTimeUs(boolean refreshTime, boolean monotonic)
   4666                 throws IllegalStateException {
   4667             synchronized (this) {
   4668                 // we always refresh the time when the paused-state changes, because
   4669                 // we expect to have received the pause-change event delayed.
   4670                 if (mPaused && !refreshTime) {
   4671                     return mLastReportedTime;
   4672                 }
   4673 
   4674                 try {
   4675                     mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
   4676                     mPaused = !mPlayer.isPlaying() || mBuffering;
   4677                     if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
   4678                 } catch (IllegalStateException e) {
   4679                     if (mPausing) {
   4680                         // if we were pausing, get last estimated timestamp
   4681                         mPausing = false;
   4682                         if (!monotonic || mLastReportedTime < mLastTimeUs) {
   4683                             mLastReportedTime = mLastTimeUs;
   4684                         }
   4685                         mPaused = true;
   4686                         if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime);
   4687                         return mLastReportedTime;
   4688                     }
   4689                     // TODO get time when prepared
   4690                     throw e;
   4691                 }
   4692                 if (monotonic && mLastTimeUs < mLastReportedTime) {
   4693                     /* have to adjust time */
   4694                     if (mLastReportedTime - mLastTimeUs > 1000000) {
   4695                         // schedule seeked event if time jumped significantly
   4696                         // TODO: do this properly by introducing an exception
   4697                         mStopped = false;
   4698                         mSeeking = true;
   4699                         scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
   4700                     }
   4701                 } else {
   4702                     mLastReportedTime = mLastTimeUs;
   4703                 }
   4704 
   4705                 return mLastReportedTime;
   4706             }
   4707         }
   4708 
   4709         private class EventHandler extends Handler {
   4710             public EventHandler(Looper looper) {
   4711                 super(looper);
   4712             }
   4713 
   4714             @Override
   4715             public void handleMessage(Message msg) {
   4716                 if (msg.what == NOTIFY) {
   4717                     switch (msg.arg1) {
   4718                     case NOTIFY_TIME:
   4719                         notifyTimedEvent(true /* refreshTime */);
   4720                         break;
   4721                     case NOTIFY_STOP:
   4722                         notifyStop();
   4723                         break;
   4724                     case NOTIFY_SEEK:
   4725                         notifySeek();
   4726                         break;
   4727                     case NOTIFY_TRACK_DATA:
   4728                         notifyTrackData((Pair<SubtitleTrack, byte[]>)msg.obj);
   4729                         break;
   4730                     }
   4731                 }
   4732             }
   4733         }
   4734     }
   4735 
   4736     private abstract class Task implements Runnable {
   4737         private final int mMediaCallType;
   4738         private final boolean mNeedToWaitForEventToComplete;
   4739         private DataSourceDesc mDSD;
   4740 
   4741         public Task (int mediaCallType, boolean needToWaitForEventToComplete) {
   4742             mMediaCallType = mediaCallType;
   4743             mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
   4744         }
   4745 
   4746         abstract void process() throws IOException, NoDrmSchemeException;
   4747 
   4748         @Override
   4749         public void run() {
   4750             int status = CALL_STATUS_NO_ERROR;
   4751             try {
   4752                 process();
   4753             } catch (IllegalStateException e) {
   4754                 status = CALL_STATUS_INVALID_OPERATION;
   4755             } catch (IllegalArgumentException e) {
   4756                 status = CALL_STATUS_BAD_VALUE;
   4757             } catch (SecurityException e) {
   4758                 status = CALL_STATUS_PERMISSION_DENIED;
   4759             } catch (IOException e) {
   4760                 status = CALL_STATUS_ERROR_IO;
   4761             } catch (NoDrmSchemeException e) {
   4762                 status = CALL_STATUS_NO_DRM_SCHEME;
   4763             } catch (Exception e) {
   4764                 status = CALL_STATUS_ERROR_UNKNOWN;
   4765             }
   4766             synchronized (mSrcLock) {
   4767                 mDSD = mCurrentDSD;
   4768             }
   4769 
   4770             // TODO: Make native implementations asynchronous and let them send notifications.
   4771             if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) {
   4772 
   4773                 sendCompleteNotification(status);
   4774 
   4775                 synchronized (mTaskLock) {
   4776                     mCurrentTask = null;
   4777                     processPendingTask_l();
   4778                 }
   4779             }
   4780         }
   4781 
   4782         private void sendCompleteNotification(int status) {
   4783             // In {@link #notifyWhenCommandLabelReached} case, a separate callback
   4784             // {#link #onCommandLabelReached} is already called in {@code process()}.
   4785             if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED) {
   4786                 return;
   4787             }
   4788             synchronized (mEventCbLock) {
   4789                 for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
   4790                     cb.first.execute(() -> cb.second.onCallCompleted(
   4791                             MediaPlayer2Impl.this, mDSD, mMediaCallType, status));
   4792                 }
   4793             }
   4794         }
   4795     };
   4796 }
   4797