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 package androidx.media;
     17 
     18 import static android.content.Context.KEYGUARD_SERVICE;
     19 
     20 import static org.junit.Assert.assertEquals;
     21 import static org.junit.Assert.assertFalse;
     22 import static org.junit.Assert.assertTrue;
     23 import static org.junit.Assert.fail;
     24 
     25 import android.app.Instrumentation;
     26 import android.app.KeyguardManager;
     27 import android.content.Context;
     28 import android.content.res.AssetFileDescriptor;
     29 import android.content.res.Resources;
     30 import android.media.AudioManager;
     31 import android.media.MediaTimestamp;
     32 import android.media.SubtitleData;
     33 import android.media.TimedMetaData;
     34 import android.net.Uri;
     35 import android.os.PersistableBundle;
     36 import android.os.PowerManager;
     37 import android.support.test.InstrumentationRegistry;
     38 import android.support.test.rule.ActivityTestRule;
     39 import android.view.SurfaceHolder;
     40 import android.view.WindowManager;
     41 
     42 import androidx.annotation.CallSuper;
     43 
     44 import org.junit.After;
     45 import org.junit.Before;
     46 import org.junit.Rule;
     47 
     48 import java.io.IOException;
     49 import java.net.HttpCookie;
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 import java.util.Map;
     53 import java.util.Set;
     54 import java.util.concurrent.ExecutorService;
     55 import java.util.concurrent.Executors;
     56 import java.util.logging.Logger;
     57 
     58 /**
     59  * Base class for tests which use MediaPlayer2 to play audio or video.
     60  */
     61 public class MediaPlayer2TestBase {
     62     private static final Logger LOG = Logger.getLogger(MediaPlayer2TestBase.class.getName());
     63 
     64     protected static final int SLEEP_TIME = 1000;
     65     protected static final int LONG_SLEEP_TIME = 6000;
     66     protected static final int STREAM_RETRIES = 20;
     67 
     68     protected Monitor mOnVideoSizeChangedCalled = new Monitor();
     69     protected Monitor mOnVideoRenderingStartCalled = new Monitor();
     70     protected Monitor mOnBufferingUpdateCalled = new Monitor();
     71     protected Monitor mOnPrepareCalled = new Monitor();
     72     protected Monitor mOnPlayCalled = new Monitor();
     73     protected Monitor mOnDeselectTrackCalled = new Monitor();
     74     protected Monitor mOnSeekCompleteCalled = new Monitor();
     75     protected Monitor mOnCompletionCalled = new Monitor();
     76     protected Monitor mOnInfoCalled = new Monitor();
     77     protected Monitor mOnErrorCalled = new Monitor();
     78     protected Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
     79     protected int mCallStatus;
     80 
     81     protected Context mContext;
     82     protected Resources mResources;
     83 
     84     protected ExecutorService mExecutor;
     85 
     86     protected MediaPlayer2 mPlayer = null;
     87     protected MediaPlayer2 mPlayer2 = null;
     88     protected MediaStubActivity mActivity;
     89     protected Instrumentation mInstrumentation;
     90 
     91     protected final Object mEventCbLock = new Object();
     92     protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks = new ArrayList<>();
     93     protected final Object mEventCbLock2 = new Object();
     94     protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks2 = new ArrayList<>();
     95 
     96     @Rule
     97     public ActivityTestRule<MediaStubActivity> mActivityRule =
     98             new ActivityTestRule<>(MediaStubActivity.class);
     99     public PowerManager.WakeLock mScreenLock;
    100     private KeyguardManager mKeyguardManager;
    101 
    102     // convenience functions to create MediaPlayer2
    103     protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri) {
    104         return createMediaPlayer2(context, uri, null);
    105     }
    106 
    107     protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri,
    108             SurfaceHolder holder) {
    109         AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    110         int s = am.generateAudioSessionId();
    111         return createMediaPlayer2(context, uri, holder, null, s > 0 ? s : 0);
    112     }
    113 
    114     protected MediaPlayer2 createMediaPlayer2(Context context, Uri uri, SurfaceHolder holder,
    115             AudioAttributesCompat audioAttributes, int audioSessionId) {
    116         try {
    117             MediaPlayer2 mp = createMediaPlayer2OnUiThread();
    118             final AudioAttributesCompat aa = audioAttributes != null ? audioAttributes :
    119                     new AudioAttributesCompat.Builder().build();
    120             mp.setAudioAttributes(aa);
    121             mp.setAudioSessionId(audioSessionId);
    122             mp.setDataSource(new DataSourceDesc.Builder()
    123                     .setDataSource(context, uri)
    124                     .build());
    125             if (holder != null) {
    126                 mp.setSurface(holder.getSurface());
    127             }
    128             final Monitor onPrepareCalled = new Monitor();
    129             ExecutorService executor = Executors.newFixedThreadPool(1);
    130             MediaPlayer2.MediaPlayer2EventCallback ecb =
    131                     new MediaPlayer2.MediaPlayer2EventCallback() {
    132                         @Override
    133                         public void onInfo(
    134                                 MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
    135                             if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
    136                                 onPrepareCalled.signal();
    137                             }
    138                         }
    139                     };
    140             mp.setMediaPlayer2EventCallback(executor, ecb);
    141             mp.prepare();
    142             onPrepareCalled.waitForSignal();
    143             mp.clearMediaPlayer2EventCallback();
    144             executor.shutdown();
    145             return mp;
    146         } catch (IllegalArgumentException ex) {
    147             LOG.warning("create failed:" + ex);
    148             // fall through
    149         } catch (SecurityException ex) {
    150             LOG.warning("create failed:" + ex);
    151             // fall through
    152         } catch (InterruptedException ex) {
    153             LOG.warning("create failed:" + ex);
    154             // fall through
    155         }
    156         return null;
    157     }
    158 
    159     protected MediaPlayer2 createMediaPlayer2(Context context, int resid) {
    160         AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    161         int s = am.generateAudioSessionId();
    162         return createMediaPlayer2(context, resid, null, s > 0 ? s : 0);
    163     }
    164 
    165     protected MediaPlayer2 createMediaPlayer2(Context context, int resid,
    166             AudioAttributesCompat audioAttributes, int audioSessionId) {
    167         try {
    168             AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
    169             if (afd == null) {
    170                 return null;
    171             }
    172 
    173             MediaPlayer2 mp = createMediaPlayer2OnUiThread();
    174 
    175             final AudioAttributesCompat aa = audioAttributes != null ? audioAttributes :
    176                     new AudioAttributesCompat.Builder().build();
    177             mp.setAudioAttributes(aa);
    178             mp.setAudioSessionId(audioSessionId);
    179 
    180             mp.setDataSource(new DataSourceDesc.Builder()
    181                     .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
    182                     .build());
    183 
    184             final Monitor onPrepareCalled = new Monitor();
    185             ExecutorService executor = Executors.newFixedThreadPool(1);
    186             MediaPlayer2.MediaPlayer2EventCallback ecb =
    187                     new MediaPlayer2.MediaPlayer2EventCallback() {
    188                         @Override
    189                         public void onInfo(
    190                                 MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
    191                             if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
    192                                 onPrepareCalled.signal();
    193                             }
    194                         }
    195                     };
    196             mp.setMediaPlayer2EventCallback(executor, ecb);
    197             mp.prepare();
    198             onPrepareCalled.waitForSignal();
    199             mp.clearMediaPlayer2EventCallback();
    200             afd.close();
    201             executor.shutdown();
    202             return mp;
    203         } catch (IOException ex) {
    204             LOG.warning("create failed:" + ex);
    205             // fall through
    206         } catch (IllegalArgumentException ex) {
    207             LOG.warning("create failed:" + ex);
    208             // fall through
    209         } catch (SecurityException ex) {
    210             LOG.warning("create failed:" + ex);
    211             // fall through
    212         } catch (InterruptedException ex) {
    213             LOG.warning("create failed:" + ex);
    214             // fall through
    215         }
    216         return null;
    217     }
    218 
    219     private MediaPlayer2 createMediaPlayer2OnUiThread() {
    220         final MediaPlayer2[] mp = new MediaPlayer2[1];
    221         try {
    222             mActivityRule.runOnUiThread(new Runnable() {
    223                 public void run() {
    224                     mp[0] = MediaPlayer2.create();
    225                 }
    226             });
    227         } catch (Throwable throwable) {
    228             fail("Failed to create MediaPlayer2 instance on UI thread.");
    229         }
    230         return mp[0];
    231     }
    232 
    233     public static class Monitor {
    234         private int mNumSignal;
    235 
    236         public synchronized void reset() {
    237             mNumSignal = 0;
    238         }
    239 
    240         public synchronized void signal() {
    241             mNumSignal++;
    242             notifyAll();
    243         }
    244 
    245         public synchronized boolean waitForSignal() throws InterruptedException {
    246             return waitForCountedSignals(1) > 0;
    247         }
    248 
    249         public synchronized int waitForCountedSignals(int targetCount) throws InterruptedException {
    250             while (mNumSignal < targetCount) {
    251                 wait();
    252             }
    253             return mNumSignal;
    254         }
    255 
    256         public synchronized boolean waitForSignal(long timeoutMs) throws InterruptedException {
    257             return waitForCountedSignals(1, timeoutMs) > 0;
    258         }
    259 
    260         public synchronized int waitForCountedSignals(int targetCount, long timeoutMs)
    261                 throws InterruptedException {
    262             if (timeoutMs == 0) {
    263                 return waitForCountedSignals(targetCount);
    264             }
    265             long deadline = System.currentTimeMillis() + timeoutMs;
    266             while (mNumSignal < targetCount) {
    267                 long delay = deadline - System.currentTimeMillis();
    268                 if (delay <= 0) {
    269                     break;
    270                 }
    271                 wait(delay);
    272             }
    273             return mNumSignal;
    274         }
    275 
    276         public synchronized boolean isSignalled() {
    277             return mNumSignal >= 1;
    278         }
    279 
    280         public synchronized int getNumSignal() {
    281             return mNumSignal;
    282         }
    283     }
    284 
    285     @Before
    286     @CallSuper
    287     public void setUp() throws Throwable {
    288         mInstrumentation = InstrumentationRegistry.getInstrumentation();
    289         mKeyguardManager = (KeyguardManager)
    290                 mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
    291         mActivity = mActivityRule.getActivity();
    292         mActivityRule.runOnUiThread(new Runnable() {
    293             @Override
    294             public void run() {
    295                 // Keep screen on while testing.
    296                 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    297                 mActivity.setTurnScreenOn(true);
    298                 mActivity.setShowWhenLocked(true);
    299                 mKeyguardManager.requestDismissKeyguard(mActivity, null);
    300             }
    301         });
    302         mInstrumentation.waitForIdleSync();
    303 
    304         try {
    305             mActivityRule.runOnUiThread(new Runnable() {
    306                 public void run() {
    307                     mPlayer = MediaPlayer2.create();
    308                     mPlayer2 = MediaPlayer2.create();
    309                 }
    310             });
    311         } catch (Throwable e) {
    312             e.printStackTrace();
    313             fail();
    314         }
    315         mContext = mActivityRule.getActivity();
    316         mResources = mContext.getResources();
    317         mExecutor = Executors.newFixedThreadPool(1);
    318 
    319         setUpMP2ECb(mPlayer, mEventCbLock, mEventCallbacks);
    320         setUpMP2ECb(mPlayer2, mEventCbLock2, mEventCallbacks2);
    321     }
    322 
    323     @After
    324     @CallSuper
    325     public void tearDown() throws Exception {
    326         if (mPlayer != null) {
    327             mPlayer.close();
    328             mPlayer = null;
    329         }
    330         if (mPlayer2 != null) {
    331             mPlayer2.close();
    332             mPlayer2 = null;
    333         }
    334         mExecutor.shutdown();
    335         mActivity = null;
    336     }
    337 
    338     protected void setUpMP2ECb(MediaPlayer2 mp, final Object cbLock,
    339             final List<MediaPlayer2.MediaPlayer2EventCallback> ecbs) {
    340         mp.setMediaPlayer2EventCallback(mExecutor, new MediaPlayer2.MediaPlayer2EventCallback() {
    341             @Override
    342             public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) {
    343                 synchronized (cbLock) {
    344                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    345                         ecb.onVideoSizeChanged(mp, dsd, w, h);
    346                     }
    347                 }
    348             }
    349 
    350             @Override
    351             public void onTimedMetaDataAvailable(MediaPlayer2 mp, DataSourceDesc dsd,
    352                     TimedMetaData data) {
    353                 synchronized (cbLock) {
    354                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    355                         ecb.onTimedMetaDataAvailable(mp, dsd, data);
    356                     }
    357                 }
    358             }
    359 
    360             @Override
    361             public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
    362                 synchronized (cbLock) {
    363                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    364                         ecb.onError(mp, dsd, what, extra);
    365                     }
    366                 }
    367             }
    368 
    369             @Override
    370             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
    371                 synchronized (cbLock) {
    372                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    373                         ecb.onInfo(mp, dsd, what, extra);
    374                     }
    375                 }
    376             }
    377 
    378             @Override
    379             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
    380                 synchronized (cbLock) {
    381                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    382                         ecb.onCallCompleted(mp, dsd, what, status);
    383                     }
    384                 }
    385             }
    386 
    387             @Override
    388             public void onMediaTimeDiscontinuity(MediaPlayer2 mp, DataSourceDesc dsd,
    389                     MediaTimestamp timestamp) {
    390                 synchronized (cbLock) {
    391                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    392                         ecb.onMediaTimeDiscontinuity(mp, dsd, timestamp);
    393                     }
    394                 }
    395             }
    396 
    397             @Override
    398             public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
    399                 synchronized (cbLock) {
    400                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    401                         ecb.onCommandLabelReached(mp, label);
    402                     }
    403                 }
    404             }
    405             @Override
    406             public  void onSubtitleData(MediaPlayer2 mp, DataSourceDesc dsd,
    407                     final SubtitleData data) {
    408                 synchronized (cbLock) {
    409                     for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
    410                         ecb.onSubtitleData(mp, dsd, data);
    411                     }
    412                 }
    413             }
    414         });
    415     }
    416 
    417     // returns true on success
    418     protected boolean loadResource(int resid) throws Exception {
    419         /* FIXME: ensure device has capability.
    420         if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
    421             return false;
    422         }
    423         */
    424 
    425         AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
    426         try {
    427             mPlayer.setDataSource(new DataSourceDesc.Builder()
    428                     .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
    429                     .build());
    430         } finally {
    431             // TODO: close afd only after setDataSource is confirmed.
    432             // afd.close();
    433         }
    434         return true;
    435     }
    436 
    437     protected DataSourceDesc createDataSourceDesc(int resid) throws Exception {
    438         /* FIXME: ensure device has capability.
    439         if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
    440             return null;
    441         }
    442         */
    443 
    444         AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
    445         return new DataSourceDesc.Builder()
    446                 .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
    447                 .build();
    448     }
    449 
    450     protected boolean checkLoadResource(int resid) throws Exception {
    451         return loadResource(resid);
    452 
    453         /* FIXME: ensure device has capability.
    454         return MediaUtils.check(loadResource(resid), "no decoder found");
    455         */
    456     }
    457 
    458     protected void playLiveVideoTest(String path, int playTime) throws Exception {
    459         playVideoWithRetries(path, null, null, playTime);
    460     }
    461 
    462     protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception {
    463         playVideoWithRetries(path, -1, -1, playTime);
    464     }
    465 
    466     protected void playVideoTest(String path, int width, int height) throws Exception {
    467         playVideoWithRetries(path, width, height, 0);
    468     }
    469 
    470     protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime)
    471             throws Exception {
    472         boolean playedSuccessfully = false;
    473         final Uri uri = Uri.parse(path);
    474         for (int i = 0; i < STREAM_RETRIES; i++) {
    475             try {
    476                 mPlayer.setDataSource(new DataSourceDesc.Builder()
    477                         .setDataSource(mContext, uri)
    478                         .build());
    479                 playLoadedVideo(width, height, playTime);
    480                 playedSuccessfully = true;
    481                 break;
    482             } catch (PrepareFailedException e) {
    483                 // prepare() can fail because of network issues, so try again
    484                 LOG.warning("prepare() failed on try " + i + ", trying playback again");
    485             }
    486         }
    487         assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
    488     }
    489 
    490     protected void playVideoTest(int resid, int width, int height) throws Exception {
    491         if (!checkLoadResource(resid)) {
    492             return; // skip
    493         }
    494 
    495         playLoadedVideo(width, height, 0);
    496     }
    497 
    498     protected void playLiveVideoTest(
    499             Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
    500             int playTime) throws Exception {
    501         playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime);
    502     }
    503 
    504     protected void playVideoWithRetries(
    505             Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
    506             Integer width, Integer height, int playTime) throws Exception {
    507         boolean playedSuccessfully = false;
    508         for (int i = 0; i < STREAM_RETRIES; i++) {
    509             try {
    510                 mPlayer.setDataSource(new DataSourceDesc.Builder()
    511                         .setDataSource(mContext,
    512                             uri, headers, cookies)
    513                         .build());
    514                 playLoadedVideo(width, height, playTime);
    515                 playedSuccessfully = true;
    516                 break;
    517             } catch (PrepareFailedException e) {
    518                 // prepare() can fail because of network issues, so try again
    519                 // playLoadedVideo already has reset the player so we can try again safely.
    520                 LOG.warning("prepare() failed on try " + i + ", trying playback again");
    521             }
    522         }
    523         assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
    524     }
    525 
    526     /**
    527      * Play a video which has already been loaded with setDataSource().
    528      *
    529      * @param width width of the video to verify, or null to skip verification
    530      * @param height height of the video to verify, or null to skip verification
    531      * @param playTime length of time to play video, or 0 to play entire video.
    532      * with a non-negative value, this method stops the playback after the length of
    533      * time or the duration the video is elapsed. With a value of -1,
    534      * this method simply starts the video and returns immediately without
    535      * stoping the video playback.
    536      */
    537     protected void playLoadedVideo(final Integer width, final Integer height, int playTime)
    538             throws Exception {
    539         final float volume = 0.5f;
    540 
    541         boolean audioOnly = (width != null && width.intValue() == -1)
    542                 || (height != null && height.intValue() == -1);
    543         mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
    544 
    545         synchronized (mEventCbLock) {
    546             mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() {
    547                 @Override
    548                 public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) {
    549                     if (w == 0 && h == 0) {
    550                         // A size of 0x0 can be sent initially one time when using NuPlayer.
    551                         assertFalse(mOnVideoSizeChangedCalled.isSignalled());
    552                         return;
    553                     }
    554                     mOnVideoSizeChangedCalled.signal();
    555                     if (width != null) {
    556                         assertEquals(width.intValue(), w);
    557                     }
    558                     if (height != null) {
    559                         assertEquals(height.intValue(), h);
    560                     }
    561                 }
    562 
    563                 @Override
    564                 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
    565                     fail("Media player had error " + what + " playing video");
    566                 }
    567 
    568                 @Override
    569                 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
    570                     if (what == MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START) {
    571                         mOnVideoRenderingStartCalled.signal();
    572                     } else if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
    573                         mOnPrepareCalled.signal();
    574                     }
    575                 }
    576 
    577                 @Override
    578                 public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
    579                         int what, int status) {
    580                     if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
    581                         mOnPlayCalled.signal();
    582                     }
    583                 }
    584             });
    585         }
    586         try {
    587             mOnPrepareCalled.reset();
    588             mPlayer.prepare();
    589             mOnPrepareCalled.waitForSignal();
    590         } catch (Exception e) {
    591             mPlayer.reset();
    592             throw new PrepareFailedException();
    593         }
    594 
    595         mOnPlayCalled.reset();
    596         mPlayer.play();
    597         mOnPlayCalled.waitForSignal();
    598         if (!audioOnly) {
    599             mOnVideoSizeChangedCalled.waitForSignal();
    600             mOnVideoRenderingStartCalled.waitForSignal();
    601         }
    602         mPlayer.setPlayerVolume(volume);
    603 
    604         // waiting to complete
    605         if (playTime == -1) {
    606             return;
    607         } else if (playTime == 0) {
    608             while (mPlayer.getMediaPlayer2State() == MediaPlayer2.MEDIAPLAYER2_STATE_PLAYING) {
    609                 Thread.sleep(SLEEP_TIME);
    610             }
    611         } else {
    612             Thread.sleep(playTime);
    613         }
    614 
    615         // validate a few MediaMetrics.
    616         PersistableBundle metrics = mPlayer.getMetrics();
    617         if (metrics == null) {
    618             fail("MediaPlayer.getMetrics() returned null metrics");
    619         } else if (metrics.isEmpty()) {
    620             fail("MediaPlayer.getMetrics() returned empty metrics");
    621         } else {
    622 
    623             int size = metrics.size();
    624             Set<String> keys = metrics.keySet();
    625 
    626             if (keys == null) {
    627                 fail("MediaMetricsSet returned no keys");
    628             } else if (keys.size() != size) {
    629                 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
    630             }
    631 
    632             // we played something; so one of these should be non-null
    633             String vmime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_VIDEO, null);
    634             String amime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_AUDIO, null);
    635             if (vmime == null && amime == null) {
    636                 fail("getMetrics() returned neither video nor audio mime value");
    637             }
    638 
    639             long duration = metrics.getLong(MediaPlayer2.MetricsConstants.DURATION, -2);
    640             if (duration == -2) {
    641                 fail("getMetrics() didn't return a duration");
    642             }
    643             long playing = metrics.getLong(MediaPlayer2.MetricsConstants.PLAYING, -2);
    644             if (playing == -2) {
    645                 fail("getMetrics() didn't return a playing time");
    646             }
    647             if (!keys.contains(MediaPlayer2.MetricsConstants.PLAYING)) {
    648                 fail("MediaMetricsSet.keys() missing: " + MediaPlayer2.MetricsConstants.PLAYING);
    649             }
    650         }
    651         mPlayer.reset();
    652     }
    653 
    654     private static class PrepareFailedException extends Exception {}
    655 
    656     protected void setOnErrorListener() {
    657         synchronized (mEventCbLock) {
    658             mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() {
    659                 @Override
    660                 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
    661                     mOnErrorCalled.signal();
    662                 }
    663             });
    664         }
    665     }
    666 }
    667