MediaSession2: Add a way to notify errors between session and player

This is proposed during the offline meeting

Test: Run all MediaComponents tests once
Change-Id: I2cbd980275bf88af840eb9f1933363c3ad8ff2e3
gugelfrei
Jaewan Kim 6 years ago
parent 9f303ea2df
commit 38ff0001bf

@ -38,7 +38,7 @@ import android.media.MediaItem2;
import android.media.MediaLibraryService2;
import android.media.MediaMetadata2;
import android.media.MediaPlayerInterface;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaPlayerInterface.EventCallback;
import android.media.MediaSession2;
import android.media.MediaSession2.Builder;
import android.media.MediaSession2.Command;
@ -62,6 +62,7 @@ import android.os.Process;
import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@ -84,7 +85,7 @@ public class MediaSession2Impl implements MediaSession2Provider {
private final MediaSession2Stub mSessionStub;
private final SessionToken2 mSessionToken;
private final AudioManager mAudioManager;
private final List<PlaybackListenerHolder> mListeners = new ArrayList<>();
private final ArrayMap<EventCallback, Executor> mCallbacks = new ArrayMap<>();
private final PendingIntent mSessionActivity;
// mPlayer is set to null when the session is closed, and we shouldn't throw an exception
@ -110,7 +111,7 @@ public class MediaSession2Impl implements MediaSession2Provider {
@GuardedBy("mLock")
private PlaybackInfo mPlaybackInfo;
@GuardedBy("mLock")
private MyPlaybackListener mListener;
private MyEventCallback mEventCallback;
/**
* Can be only called by the {@link Builder#build()}.
@ -223,19 +224,20 @@ public class MediaSession2Impl implements MediaSession2Provider {
}
private void setPlayer(MediaPlayerInterface player, VolumeProvider2 volumeProvider) {
PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
final PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
synchronized (mLock) {
if (mPlayer != null && mListener != null) {
if (mPlayer != null && mEventCallback != null) {
// This might not work for a poorly implemented player.
mPlayer.removePlaybackListener(mListener);
mPlayer.unregisterEventCallback(mEventCallback);
}
mPlayer = player;
mListener = new MyPlaybackListener(this, player);
player.addPlaybackListener(mCallbackExecutor, mListener);
mEventCallback = new MyEventCallback(this, player);
player.registerEventCallback(mCallbackExecutor, mEventCallback);
mVolumeProvider = volumeProvider;
mPlaybackInfo = info;
}
mSessionStub.notifyPlaybackInfoChanged(info);
notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
}
private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@ -291,7 +293,7 @@ public class MediaSession2Impl implements MediaSession2Provider {
synchronized (mLock) {
if (mPlayer != null) {
// close can be called multiple times
mPlayer.removePlaybackListener(mListener);
mPlayer.unregisterEventCallback(mEventCallback);
mPlayer = null;
}
}
@ -520,32 +522,31 @@ public class MediaSession2Impl implements MediaSession2Provider {
}
@Override
public void addPlaybackListener_impl(Executor executor, PlaybackListener listener) {
public void registerPlayerEventCallback_impl(Executor executor, EventCallback callback) {
if (executor == null) {
throw new IllegalArgumentException("executor shouldn't be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener shouldn't be null");
if (callback == null) {
throw new IllegalArgumentException("callback shouldn't be null");
}
ensureCallingThread();
if (PlaybackListenerHolder.contains(mListeners, listener)) {
Log.w(TAG, "listener is already added. Ignoring.");
if (mCallbacks.get(callback) != null) {
Log.w(TAG, "callback is already added. Ignoring.");
return;
}
mListeners.add(new PlaybackListenerHolder(executor, listener));
executor.execute(() -> listener.onPlaybackChanged(getInstance().getPlaybackState()));
mCallbacks.put(callback, executor);
// TODO(jaewan): Double check if we need this.
final PlaybackState2 state = getInstance().getPlaybackState();
executor.execute(() -> callback.onPlaybackStateChanged(state));
}
@Override
public void removePlaybackListener_impl(PlaybackListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener shouldn't be null");
public void unregisterPlayerEventCallback_impl(EventCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback shouldn't be null");
}
ensureCallingThread();
int idx = PlaybackListenerHolder.indexOf(mListeners, listener);
if (idx >= 0) {
mListeners.remove(idx);
}
mCallbacks.remove(callback);
}
@Override
@ -586,19 +587,35 @@ public class MediaSession2Impl implements MediaSession2Provider {
}*/
}
private void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
List<PlaybackListenerHolder> listeners = new ArrayList<>();
private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
ArrayMap<EventCallback, Executor> callbacks = new ArrayMap<>();
synchronized (mLock) {
listeners.addAll(mListeners);
callbacks.putAll(mCallbacks);
}
// Notify to listeners added directly to this session
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).postPlaybackChange(state);
// Notify to callbacks added directly to this session
for (int i = 0; i < callbacks.size(); i++) {
final EventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
executor.execute(() -> callback.onPlaybackStateChanged(state));
}
// Notify to controllers as well.
mSessionStub.notifyPlaybackStateChangedNotLocked(state);
}
private void notifyErrorNotLocked(String mediaId, int what, int extra) {
ArrayMap<EventCallback, Executor> callbacks = new ArrayMap<>();
synchronized (mLock) {
callbacks.putAll(mCallbacks);
}
// Notify to callbacks added directly to this session
for (int i = 0; i < callbacks.size(); i++) {
final EventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
executor.execute(() -> callback.onError(mediaId, what, extra));
}
// TODO(jaewan): Notify to controllers as well.
}
Context getContext() {
return mContext;
}
@ -637,25 +654,43 @@ public class MediaSession2Impl implements MediaSession2Provider {
return mSessionActivity;
}
private static class MyPlaybackListener implements MediaPlayerInterface.PlaybackListener {
private static class MyEventCallback implements EventCallback {
private final WeakReference<MediaSession2Impl> mSession;
private final MediaPlayerInterface mPlayer;
private MyPlaybackListener(MediaSession2Impl session, MediaPlayerInterface player) {
private MyEventCallback(MediaSession2Impl session, MediaPlayerInterface player) {
mSession = new WeakReference<>(session);
mPlayer = player;
}
@Override
public void onPlaybackChanged(PlaybackState2 state) {
public void onPlaybackStateChanged(PlaybackState2 state) {
MediaSession2Impl session = mSession.get();
if (mPlayer != session.mInstance.getPlayer()) {
Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
new IllegalStateException());
return;
}
if (DEBUG) {
Log.d(TAG, "onPlaybackStateChanged from player, state=" + state);
}
session.notifyPlaybackStateChangedNotLocked(state);
}
@Override
public void onError(String mediaId, int what, int extra) {
MediaSession2Impl session = mSession.get();
if (mPlayer != session.mInstance.getPlayer()) {
Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
new IllegalStateException());
return;
}
if (DEBUG) {
Log.d(TAG, "onError from player, mediaId=" + mediaId + ", what=" + what
+ ", extra=" + extra);
}
session.notifyErrorNotLocked(mediaId, what, extra);
}
}
public static final class CommandImpl implements CommandProvider {

@ -22,7 +22,7 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaPlayerInterface.EventCallback;
import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
@ -42,7 +42,7 @@ public class MediaSessionService2Impl implements MediaSessionService2Provider {
private static final boolean DEBUG = true; // TODO(jaewan): Change this.
private final MediaSessionService2 mInstance;
private final PlaybackListener mListener = new SessionServicePlaybackListener();
private final EventCallback mCallback = new SessionServiceEventCallback();
private final Object mLock = new Object();
@GuardedBy("mLock")
@ -94,7 +94,7 @@ public class MediaSessionService2Impl implements MediaSessionService2Provider {
+ ", but got " + mSession);
}
// TODO(jaewan): Uncomment here.
// mSession.addPlaybackListener(mListener, mSession.getExecutor());
// mSession.registerPlayerEventCallback(mCallback, mSession.getExecutor());
}
@TokenType int getSessionType() {
@ -135,9 +135,9 @@ public class MediaSessionService2Impl implements MediaSessionService2Provider {
mediaNotification.getNotification());
}
private class SessionServicePlaybackListener implements PlaybackListener {
private class SessionServiceEventCallback implements EventCallback {
@Override
public void onPlaybackChanged(PlaybackState2 state) {
public void onPlaybackStateChanged(PlaybackState2 state) {
if (state == null) {
Log.w(TAG, "Ignoring null playback state");
return;

@ -1,72 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.media;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.PlaybackState2;
import android.os.Handler;
import android.support.annotation.NonNull;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Holds {@link PlaybackListener} with the {@link Handler}.
*/
public class PlaybackListenerHolder {
public final Executor executor;
public final PlaybackListener listener;
public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
this.executor = executor;
this.listener = listener;
}
public void postPlaybackChange(final PlaybackState2 state) {
executor.execute(() -> listener.onPlaybackChanged(state));
}
/**
* Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
* the given listener.
*
* @param list list to check
* @param listener listener to check
* @return {@code true} if the given list contains listener. {@code false} otherwise.
*/
public static <Holder extends PlaybackListenerHolder> boolean contains(
@NonNull List<Holder> list, PlaybackListener listener) {
return indexOf(list, listener) >= 0;
}
/**
* Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
*
* @param list list to check
* @param listener listener to check
* @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
*/
public static <Holder extends PlaybackListenerHolder> int indexOf(
@NonNull List<Holder> list, PlaybackListener listener) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).listener == listener) {
return i;
}
}
return -1;
}
}

@ -30,7 +30,6 @@ public final class PlaybackState2Impl implements PlaybackState2Provider {
private static final String KEY_BUFFERED_POSITION =
"android.media.playbackstate2.buffered_position";
private static final String KEY_SPEED = "android.media.playbackstate2.speed";
private static final String KEY_ERROR_MESSAGE = "android.media.playbackstate2.error_message";
private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time";
private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id";
@ -42,11 +41,9 @@ public final class PlaybackState2Impl implements PlaybackState2Provider {
private final float mSpeed;
private final long mBufferedPosition;
private final long mActiveItemId;
private final CharSequence mErrorMessage;
public PlaybackState2Impl(Context context, PlaybackState2 instance, int state, long position,
long updateTime, float speed, long bufferedPosition, long activeItemId,
CharSequence error) {
long updateTime, float speed, long bufferedPosition, long activeItemId) {
mContext = context;
mInstance = instance;
mState = state;
@ -55,7 +52,6 @@ public final class PlaybackState2Impl implements PlaybackState2Provider {
mUpdateTime = updateTime;
mBufferedPosition = bufferedPosition;
mActiveItemId = activeItemId;
mErrorMessage = error;
}
@Override
@ -67,7 +63,6 @@ public final class PlaybackState2Impl implements PlaybackState2Provider {
bob.append(", speed=").append(mSpeed);
bob.append(", updated=").append(mUpdateTime);
bob.append(", active item id=").append(mActiveItemId);
bob.append(", error=").append(mErrorMessage);
bob.append("}");
return bob.toString();
}
@ -92,11 +87,6 @@ public final class PlaybackState2Impl implements PlaybackState2Provider {
return mSpeed;
}
@Override
public CharSequence getErrorMessage_impl() {
return mErrorMessage;
}
@Override
public long getLastPositionUpdateTime_impl() {
return mUpdateTime;
@ -116,7 +106,6 @@ public final class PlaybackState2Impl implements PlaybackState2Provider {
bundle.putFloat(KEY_SPEED, mSpeed);
bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition);
bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId);
bundle.putCharSequence(KEY_ERROR_MESSAGE, mErrorMessage);
return bundle;
}
@ -129,18 +118,15 @@ public final class PlaybackState2Impl implements PlaybackState2Provider {
|| !bundle.containsKey(KEY_UPDATE_TIME)
|| !bundle.containsKey(KEY_SPEED)
|| !bundle.containsKey(KEY_BUFFERED_POSITION)
|| !bundle.containsKey(KEY_ACTIVE_ITEM_ID)
|| !bundle.containsKey(KEY_ERROR_MESSAGE)) {
|| !bundle.containsKey(KEY_ACTIVE_ITEM_ID)) {
return null;
}
return new PlaybackState2(context,
bundle.getInt(KEY_STATE),
bundle.getLong(KEY_POSITION),
bundle.getLong(KEY_UPDATE_TIME),
bundle.getFloat(KEY_SPEED),
bundle.getLong(KEY_BUFFERED_POSITION),
bundle.getLong(KEY_ACTIVE_ITEM_ID),
bundle.getCharSequence(KEY_ERROR_MESSAGE));
bundle.getLong(KEY_ACTIVE_ITEM_ID));
}
}

@ -294,9 +294,9 @@ public class ApiFactory implements StaticProvider {
@Override
public PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance,
int state, long position, long updateTime, float speed, long bufferedPosition,
long activeItemId, CharSequence error) {
long activeItemId) {
return new PlaybackState2Impl(context, instance, state, position, updateTime, speed,
bufferedPosition, activeItemId, error);
bufferedPosition, activeItemId);
}
@Override

@ -19,7 +19,7 @@ package android.media;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaPlayerInterface.EventCallback;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
@ -675,7 +675,7 @@ public class MediaController2Test extends MediaSession2TestBase {
// TODO(jaewan): Add equivalent tests again
/*
final CountDownLatch latch = new CountDownLatch(1);
mController.addPlaybackListener((state) -> {
mController.registerPlayerEventCallback((state) -> {
assertNotNull(state);
assertEquals(PlaybackState.STATE_REWINDING, state.getState());
latch.countDown();
@ -774,16 +774,19 @@ public class MediaController2Test extends MediaSession2TestBase {
private void testNoInteraction() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final PlaybackListener playbackListener = (state) -> {
fail("Controller shouldn't be notified about change in session after the close.");
latch.countDown();
final EventCallback callback = new EventCallback() {
@Override
public void onPlaybackStateChanged(PlaybackState2 state) {
fail("Controller shouldn't be notified about change in session after the close.");
latch.countDown();
}
};
// TODO(jaewan): Add equivalent tests again
/*
mController.addPlaybackListener(playbackListener, sHandler);
mController.registerPlayerEventCallback(playbackListener, sHandler);
mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
mController.removePlaybackListener(playbackListener);
mController.unregisterPlayerEventCallback(playbackListener);
*/
}

@ -28,9 +28,8 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaPlayerInterface.EventCallback;
import android.media.MediaSession2.Builder;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
@ -49,7 +48,6 @@ import android.text.TextUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -253,51 +251,67 @@ public class MediaSession2Test extends MediaSession2TestBase {
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
// TODO(jaewan): Re-enable test..
@Ignore
@Test
public void testPlaybackStateChangedListener() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(2);
public void testRegisterEventCallback() throws InterruptedException {
final int testWhat = 1001;
final MockPlayer player = new MockPlayer(0);
final PlaybackListener listener = (state) -> {
assertEquals(sHandler.getLooper(), Looper.myLooper());
assertNotNull(state);
switch ((int) latch.getCount()) {
case 2:
assertEquals(PlaybackState2.STATE_PLAYING, state.getState());
break;
case 1:
assertEquals(PlaybackState2.STATE_PAUSED, state.getState());
break;
case 0:
fail();
final CountDownLatch playbackLatch = new CountDownLatch(3);
final CountDownLatch errorLatch = new CountDownLatch(1);
final EventCallback callback = new EventCallback() {
@Override
public void onPlaybackStateChanged(PlaybackState2 state) {
assertEquals(sHandler.getLooper(), Looper.myLooper());
switch ((int) playbackLatch.getCount()) {
case 3:
assertNull(state);
break;
case 2:
assertNotNull(state);
assertEquals(PlaybackState2.STATE_PLAYING, state.getState());
break;
case 1:
assertNotNull(state);
assertEquals(PlaybackState2.STATE_PAUSED, state.getState());
break;
case 0:
fail();
}
playbackLatch.countDown();
}
@Override
public void onError(String mediaId, int what, int extra) {
assertEquals(testWhat, what);
errorLatch.countDown();
}
latch.countDown();
};
player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PLAYING));
sHandler.postAndSync(() -> {
mSession.addPlaybackListener(sHandlerExecutor, listener);
// When the player is set, listeners will be notified about the player's current state.
mSession.setPlayer(player);
});
// EventCallback will be notified with the mPlayer's playback state (null)
mSession.registerPlayerEventCallback(sHandlerExecutor, callback);
// When the player is set, EventCallback will be notified about the new player's state.
mSession.setPlayer(player);
// When the player is set, EventCallback will be notified about the new player's state.
player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(playbackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
player.notifyError(testWhat);
assertTrue(errorLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
public void testBadPlayer() throws InterruptedException {
// TODO(jaewan): Add equivalent tests again
final CountDownLatch latch = new CountDownLatch(3); // expected call + 1
final CountDownLatch latch = new CountDownLatch(4); // expected call + 1
final BadPlayer player = new BadPlayer(0);
sHandler.postAndSync(() -> {
mSession.addPlaybackListener(sHandlerExecutor, (state) -> {
mSession.registerPlayerEventCallback(sHandlerExecutor, new EventCallback() {
@Override
public void onPlaybackStateChanged(PlaybackState2 state) {
// This will be called for every setPlayer() calls, but no more.
assertNull(state);
latch.countDown();
});
mSession.setPlayer(player);
mSession.setPlayer(mPlayer);
}
});
mSession.setPlayer(player);
mSession.setPlayer(mPlayer);
player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@ -308,7 +322,7 @@ public class MediaSession2Test extends MediaSession2TestBase {
}
@Override
public void removePlaybackListener(@NonNull PlaybackListener listener) {
public void unregisterEventCallback(@NonNull EventCallback listener) {
// No-op. This bad player will keep push notification to the listener that is previously
// registered by session.setPlayer().
}

@ -118,7 +118,7 @@ abstract class MediaSession2TestBase {
*/
public PlaybackState2 createPlaybackState(int state) {
return new PlaybackState2(mContext, state, 0, 0, 1.0f,
0, 0, null);
0, 0);
}
final MediaController2 createController(SessionToken2 token) throws InterruptedException {

@ -19,6 +19,7 @@ package android.media;
import android.media.MediaSession2.PlaylistParams;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.ArrayMap;
import java.util.ArrayList;
import java.util.List;
@ -46,7 +47,7 @@ public class MockPlayer implements MediaPlayerInterface {
public boolean mSetPlaylistCalled;
public boolean mSetPlaylistParamsCalled;
public List<PlaybackListenerHolder> mListeners = new ArrayList<>();
public ArrayMap<EventCallback, Executor> mCallbacks = new ArrayMap<>();
public List<MediaItem2> mPlaylist;
public PlaylistParams mPlaylistParams;
@ -146,23 +147,30 @@ public class MockPlayer implements MediaPlayerInterface {
}
@Override
public void addPlaybackListener(@NonNull Executor executor,
@NonNull PlaybackListener listener) {
mListeners.add(new PlaybackListenerHolder(executor, listener));
public void registerEventCallback(@NonNull Executor executor,
@NonNull EventCallback callback) {
mCallbacks.put(callback, executor);
}
@Override
public void removePlaybackListener(@NonNull PlaybackListener listener) {
int index = PlaybackListenerHolder.indexOf(mListeners, listener);
if (index >= 0) {
mListeners.remove(index);
}
public void unregisterEventCallback(@NonNull EventCallback callback) {
mCallbacks.remove(callback);
}
public void notifyPlaybackState(final PlaybackState2 state) {
mLastPlaybackState = state;
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).postPlaybackChange(state);
for (int i = 0; i < mCallbacks.size(); i++) {
final EventCallback callback = mCallbacks.keyAt(i);
final Executor executor = mCallbacks.valueAt(i);
executor.execute(() -> callback.onPlaybackStateChanged(state));
}
}
public void notifyError(int what) {
for (int i = 0; i < mCallbacks.size(); i++) {
final EventCallback callback = mCallbacks.keyAt(i);
final Executor executor = mCallbacks.valueAt(i);
executor.execute(() -> callback.onError(null, what, 0));
}
}

@ -1,71 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.os.Handler;
import android.support.annotation.NonNull;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Holds {@link PlaybackListener} with the {@link Handler}.
*/
public class PlaybackListenerHolder {
public final Executor executor;
public final PlaybackListener listener;
public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
this.executor = executor;
this.listener = listener;
}
public void postPlaybackChange(final PlaybackState2 state) {
executor.execute(() -> listener.onPlaybackChanged(state));
}
/**
* Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
* the given listener.
*
* @param list list to check
* @param listener listener to check
* @return {@code true} if the given list contains listener. {@code false} otherwise.
*/
public static <Holder extends PlaybackListenerHolder> boolean contains(
@NonNull List<Holder> list, PlaybackListener listener) {
return indexOf(list, listener) >= 0;
}
/**
* Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
*
* @param list list to check
* @param listener listener to check
* @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
*/
public static <Holder extends PlaybackListenerHolder> int indexOf(
@NonNull List<Holder> list, PlaybackListener listener) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).listener == listener) {
return i;
}
}
return -1;
}
}
Loading…
Cancel
Save