mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Flatten Library and Session service impls
PiperOrigin-RevId: 414396999
This commit is contained in:
parent
f1ab3cf195
commit
e59e15d29d
@ -684,14 +684,15 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
/* package */ MediaSessionServiceImpl createImpl() {
|
|
||||||
return new MediaLibraryServiceImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public IBinder onBind(@Nullable Intent intent) {
|
public IBinder onBind(@Nullable Intent intent) {
|
||||||
|
if (intent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (MediaLibraryService.SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||||
|
return getServiceBinder();
|
||||||
|
}
|
||||||
return super.onBind(intent);
|
return super.onBind(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 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 androidx.media3.session;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
/** Implementation of {@link MediaLibraryService}. */
|
|
||||||
/* package */ class MediaLibraryServiceImpl extends MediaSessionServiceImpl {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public IBinder onBind(@Nullable Intent intent) {
|
|
||||||
if (intent == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (MediaLibraryService.SERVICE_INTERFACE.equals(intent.getAction())) {
|
|
||||||
return getServiceBinder();
|
|
||||||
}
|
|
||||||
return super.onBind(intent);
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,19 +17,35 @@ package androidx.media3.session;
|
|||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static androidx.media3.common.util.Util.postOrRun;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.collection.ArrayMap;
|
||||||
import androidx.media.MediaBrowserServiceCompat;
|
import androidx.media.MediaBrowserServiceCompat;
|
||||||
|
import androidx.media.MediaSessionManager;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Superclass to be extended by services hosting {@link MediaSession media sessions}.
|
* Superclass to be extended by services hosting {@link MediaSession media sessions}.
|
||||||
@ -111,18 +127,25 @@ public abstract class MediaSessionService extends Service {
|
|||||||
/** The action for {@link Intent} filter that must be declared by the service. */
|
/** The action for {@link Intent} filter that must be declared by the service. */
|
||||||
public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
|
public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
|
||||||
|
|
||||||
private final MediaSessionServiceImpl impl;
|
private static final String TAG = "MSSImpl";
|
||||||
|
|
||||||
|
private final Object lock;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final Map<String, MediaSession> sessions;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private MediaSessionServiceStub stub;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private MediaNotificationHandler notificationHandler;
|
||||||
|
|
||||||
/** Creates a service. */
|
/** Creates a service. */
|
||||||
@SuppressWarnings("nullness:method.invocation") // createImpl() under initialization
|
|
||||||
public MediaSessionService() {
|
public MediaSessionService() {
|
||||||
super();
|
lock = new Object();
|
||||||
// Note: This service doesn't have valid context at this moment.
|
sessions = new ArrayMap<>();
|
||||||
impl = createImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ MediaSessionServiceImpl createImpl() {
|
|
||||||
return new androidx.media3.session.MediaSessionServiceImpl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +157,10 @@ public abstract class MediaSessionService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
impl.onCreate(this);
|
synchronized (lock) {
|
||||||
|
stub = new MediaSessionServiceStub(this);
|
||||||
|
notificationHandler = new MediaNotificationHandler(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,7 +212,32 @@ public abstract class MediaSessionService extends Service {
|
|||||||
public final void addSession(MediaSession session) {
|
public final void addSession(MediaSession session) {
|
||||||
checkNotNull(session, "session must not be null");
|
checkNotNull(session, "session must not be null");
|
||||||
checkArgument(!session.isReleased(), "session is already released");
|
checkArgument(!session.isReleased(), "session is already released");
|
||||||
impl.addSession(session);
|
@Nullable MediaSession old;
|
||||||
|
synchronized (lock) {
|
||||||
|
old = sessions.get(session.getId());
|
||||||
|
checkArgument(old == null || old == session, "Session ID should be unique");
|
||||||
|
sessions.put(session.getId(), session);
|
||||||
|
}
|
||||||
|
if (old == null) {
|
||||||
|
// Session has returned for the first time. Register callbacks.
|
||||||
|
// TODO(b/191644474): Check whether the session is registered to multiple services.
|
||||||
|
MediaNotificationHandler handler;
|
||||||
|
synchronized (lock) {
|
||||||
|
handler = checkStateNotNull(notificationHandler);
|
||||||
|
}
|
||||||
|
postOrRun(
|
||||||
|
session.getImpl().getApplicationHandler(),
|
||||||
|
() -> {
|
||||||
|
handler.onPlayerInfoChanged(
|
||||||
|
session,
|
||||||
|
/* oldPlayerInfo= */ PlayerInfo.DEFAULT,
|
||||||
|
/* newPlayerInfo= */ session
|
||||||
|
.getImpl()
|
||||||
|
.getPlayerWrapper()
|
||||||
|
.createPlayerInfoForBundling());
|
||||||
|
session.setForegroundServiceEventCallback(handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,7 +250,11 @@ public abstract class MediaSessionService extends Service {
|
|||||||
*/
|
*/
|
||||||
public final void removeSession(MediaSession session) {
|
public final void removeSession(MediaSession session) {
|
||||||
checkNotNull(session, "session must not be null");
|
checkNotNull(session, "session must not be null");
|
||||||
impl.removeSession(session);
|
synchronized (lock) {
|
||||||
|
sessions.remove(session.getId());
|
||||||
|
}
|
||||||
|
postOrRun(
|
||||||
|
session.getImpl().getApplicationHandler(), session::clearForegroundServiceEventCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,7 +277,11 @@ public abstract class MediaSessionService extends Service {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public MediaNotification onUpdateNotification(MediaSession session) {
|
public MediaNotification onUpdateNotification(MediaSession session) {
|
||||||
checkNotNull(session, "session must not be null");
|
checkNotNull(session, "session must not be null");
|
||||||
return impl.onUpdateNotification(session);
|
MediaNotificationHandler handler;
|
||||||
|
synchronized (lock) {
|
||||||
|
handler = checkStateNotNull(notificationHandler, "Service hasn't created");
|
||||||
|
}
|
||||||
|
return handler.onUpdateNotification(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,7 +289,9 @@ public abstract class MediaSessionService extends Service {
|
|||||||
* #addSession} or {@link #onGetSession(ControllerInfo)}.
|
* #addSession} or {@link #onGetSession(ControllerInfo)}.
|
||||||
*/
|
*/
|
||||||
public final List<MediaSession> getSessions() {
|
public final List<MediaSession> getSessions() {
|
||||||
return impl.getSessions();
|
synchronized (lock) {
|
||||||
|
return new ArrayList<>(sessions.values());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -245,7 +306,36 @@ public abstract class MediaSessionService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public IBinder onBind(@Nullable Intent intent) {
|
public IBinder onBind(@Nullable Intent intent) {
|
||||||
return impl.onBind(intent);
|
if (intent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Nullable String action = intent.getAction();
|
||||||
|
if (action == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case MediaSessionService.SERVICE_INTERFACE:
|
||||||
|
return getServiceBinder();
|
||||||
|
case MediaBrowserServiceCompat.SERVICE_INTERFACE:
|
||||||
|
{
|
||||||
|
ControllerInfo controllerInfo = ControllerInfo.createLegacyControllerInfo();
|
||||||
|
@Nullable MediaSession session = onGetSession(controllerInfo);
|
||||||
|
if (session == null) {
|
||||||
|
// Legacy MediaBrowser(Compat) cannot connect to this service.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
addSession(session);
|
||||||
|
// Return a specific session's legacy binder although the Android framework caches
|
||||||
|
// the returned binder here and next binding request may reuse cached binder even
|
||||||
|
// after the session is closed.
|
||||||
|
// Disclaimer: Although MediaBrowserCompat can only get the session that initially
|
||||||
|
// set, it doesn't make things bad. Such limitation had been there between
|
||||||
|
// MediaBrowserCompat and MediaBrowserServiceCompat.
|
||||||
|
return session.getLegacyBrowserServiceBinder();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -258,7 +348,25 @@ public abstract class MediaSessionService extends Service {
|
|||||||
@CallSuper
|
@CallSuper
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
|
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
|
||||||
return impl.onStartCommand(intent, flags, startId);
|
if (intent == null) {
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
|
||||||
|
@Nullable Uri uri = intent.getData();
|
||||||
|
@Nullable MediaSession session = uri != null ? MediaSession.getSession(uri) : null;
|
||||||
|
if (session == null) {
|
||||||
|
ControllerInfo controllerInfo = ControllerInfo.createLegacyControllerInfo();
|
||||||
|
session = onGetSession(controllerInfo);
|
||||||
|
}
|
||||||
|
if (session == null) {
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
||||||
|
if (keyEvent != null) {
|
||||||
|
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -270,7 +378,18 @@ public abstract class MediaSessionService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
impl.onDestroy();
|
synchronized (lock) {
|
||||||
|
if (stub != null) {
|
||||||
|
stub.release();
|
||||||
|
stub = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ IBinder getServiceBinder() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return checkStateNotNull(stub).asBinder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A notification for media playback returned by {@link #onUpdateNotification(MediaSession)}. */
|
/** A notification for media playback returned by {@link #onUpdateNotification(MediaSession)}. */
|
||||||
@ -298,4 +417,105 @@ public abstract class MediaSessionService extends Service {
|
|||||||
this.notification = checkNotNull(notification);
|
this.notification = checkNotNull(notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class MediaSessionServiceStub extends IMediaSessionService.Stub {
|
||||||
|
|
||||||
|
private final WeakReference<MediaSessionService> serviceReference;
|
||||||
|
private final Handler handler;
|
||||||
|
private final MediaSessionManager mediaSessionManager;
|
||||||
|
|
||||||
|
public MediaSessionServiceStub(MediaSessionService serviceReference) {
|
||||||
|
this.serviceReference = new WeakReference<>(serviceReference);
|
||||||
|
Context context = serviceReference.getApplicationContext();
|
||||||
|
handler = new Handler(context.getMainLooper());
|
||||||
|
mediaSessionManager = MediaSessionManager.getSessionManager(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(
|
||||||
|
@Nullable IMediaController caller, @Nullable Bundle connectionRequestBundle) {
|
||||||
|
if (caller == null || connectionRequestBundle == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (serviceReference.get() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ConnectionRequest request;
|
||||||
|
try {
|
||||||
|
request = ConnectionRequest.CREATOR.fromBundle(connectionRequestBundle);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Log.w(TAG, "Ignoring malformed Bundle for ConnectionRequest", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int callingPid = Binder.getCallingPid();
|
||||||
|
int uid = Binder.getCallingUid();
|
||||||
|
long token = Binder.clearCallingIdentity();
|
||||||
|
int pid = (callingPid != 0) ? callingPid : request.pid;
|
||||||
|
MediaSessionManager.RemoteUserInfo remoteUserInfo =
|
||||||
|
new MediaSessionManager.RemoteUserInfo(request.packageName, pid, uid);
|
||||||
|
boolean isTrusted = mediaSessionManager.isTrustedForMediaControl(remoteUserInfo);
|
||||||
|
try {
|
||||||
|
handler.post(
|
||||||
|
() -> {
|
||||||
|
boolean shouldNotifyDisconnected = true;
|
||||||
|
try {
|
||||||
|
@Nullable MediaSessionService service = serviceReference.get();
|
||||||
|
if (service == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (service == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerInfo controllerInfo =
|
||||||
|
new ControllerInfo(
|
||||||
|
remoteUserInfo,
|
||||||
|
/* controllerVersion= */ request.version,
|
||||||
|
isTrusted,
|
||||||
|
/* controllerCb= */ null,
|
||||||
|
request.connectionHints);
|
||||||
|
|
||||||
|
@Nullable MediaSession session;
|
||||||
|
try {
|
||||||
|
session = service.onGetSession(controllerInfo);
|
||||||
|
if (session == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
service.addSession(session);
|
||||||
|
shouldNotifyDisconnected = false;
|
||||||
|
|
||||||
|
session.handleControllerConnectionFromService(
|
||||||
|
caller,
|
||||||
|
request.version,
|
||||||
|
request.packageName,
|
||||||
|
pid,
|
||||||
|
uid,
|
||||||
|
request.connectionHints);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Don't propagate exception in service to the controller.
|
||||||
|
Log.w(TAG, "Failed to add a session to session service", e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Trick to call onDisconnected() in one place.
|
||||||
|
if (shouldNotifyDisconnected) {
|
||||||
|
try {
|
||||||
|
caller.onDisconnected(/* seq= */ 0);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Controller may be died prematurely.
|
||||||
|
// Not an issue because we'll ignore it anyway.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Binder.restoreCallingIdentity(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
serviceReference.clear();
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,317 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 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 androidx.media3.session;
|
|
||||||
|
|
||||||
import static android.app.Service.START_STICKY;
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
|
||||||
import static androidx.media3.common.util.Util.postOrRun;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import androidx.annotation.GuardedBy;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.collection.ArrayMap;
|
|
||||||
import androidx.media.MediaBrowserServiceCompat;
|
|
||||||
import androidx.media.MediaSessionManager;
|
|
||||||
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
|
||||||
import androidx.media3.common.util.Log;
|
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
|
||||||
import androidx.media3.session.MediaSessionService.MediaNotification;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/** Implementation of {@link MediaSessionService}. */
|
|
||||||
/* package */ class MediaSessionServiceImpl {
|
|
||||||
|
|
||||||
private static final String TAG = "MSSImpl";
|
|
||||||
|
|
||||||
private final Object lock;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private MediaSessionServiceStub stub;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private MediaSessionService instance;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private final Map<String, MediaSession> sessions;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private MediaNotificationHandler notificationHandler;
|
|
||||||
|
|
||||||
public MediaSessionServiceImpl() {
|
|
||||||
lock = new Object();
|
|
||||||
sessions = new ArrayMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreate(MediaSessionService service) {
|
|
||||||
synchronized (lock) {
|
|
||||||
instance = service;
|
|
||||||
stub = new MediaSessionServiceStub(this);
|
|
||||||
notificationHandler = new MediaNotificationHandler(service);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public IBinder onBind(@Nullable Intent intent) {
|
|
||||||
if (intent == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@Nullable String action = intent.getAction();
|
|
||||||
if (action == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
MediaSessionService service = checkStateNotNull(getInstance());
|
|
||||||
switch (action) {
|
|
||||||
case MediaSessionService.SERVICE_INTERFACE:
|
|
||||||
return getServiceBinder();
|
|
||||||
case MediaBrowserServiceCompat.SERVICE_INTERFACE:
|
|
||||||
{
|
|
||||||
ControllerInfo controllerInfo = ControllerInfo.createLegacyControllerInfo();
|
|
||||||
@Nullable MediaSession session = service.onGetSession(controllerInfo);
|
|
||||||
if (session == null) {
|
|
||||||
// Legacy MediaBrowser(Compat) cannot connect to this service.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
addSession(session);
|
|
||||||
// Return a specific session's legacy binder although the Android framework caches
|
|
||||||
// the returned binder here and next binding request may reuse cached binder even
|
|
||||||
// after the session is closed.
|
|
||||||
// Disclaimer: Although MediaBrowserCompat can only get the session that initially
|
|
||||||
// set, it doesn't make things bad. Such limitation had been there between
|
|
||||||
// MediaBrowserCompat and MediaBrowserServiceCompat.
|
|
||||||
return session.getLegacyBrowserServiceBinder();
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDestroy() {
|
|
||||||
synchronized (lock) {
|
|
||||||
instance = null;
|
|
||||||
if (stub != null) {
|
|
||||||
stub.release();
|
|
||||||
stub = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSession(MediaSession session) {
|
|
||||||
@Nullable MediaSession old;
|
|
||||||
synchronized (lock) {
|
|
||||||
old = sessions.get(session.getId());
|
|
||||||
checkArgument(old == null || old == session, "Session ID should be unique");
|
|
||||||
sessions.put(session.getId(), session);
|
|
||||||
}
|
|
||||||
if (old == null) {
|
|
||||||
// Session has returned for the first time. Register callbacks.
|
|
||||||
// TODO(b/191644474): Check whether the session is registered to multiple services.
|
|
||||||
MediaNotificationHandler handler;
|
|
||||||
synchronized (lock) {
|
|
||||||
handler = checkStateNotNull(notificationHandler);
|
|
||||||
}
|
|
||||||
postOrRun(
|
|
||||||
session.getImpl().getApplicationHandler(),
|
|
||||||
() -> {
|
|
||||||
handler.onPlayerInfoChanged(
|
|
||||||
session,
|
|
||||||
/* oldPlayerInfo= */ PlayerInfo.DEFAULT,
|
|
||||||
/* newPlayerInfo= */ session
|
|
||||||
.getImpl()
|
|
||||||
.getPlayerWrapper()
|
|
||||||
.createPlayerInfoForBundling());
|
|
||||||
session.setForegroundServiceEventCallback(handler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSession(MediaSession session) {
|
|
||||||
synchronized (lock) {
|
|
||||||
sessions.remove(session.getId());
|
|
||||||
}
|
|
||||||
postOrRun(
|
|
||||||
session.getImpl().getApplicationHandler(), session::clearForegroundServiceEventCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
|
|
||||||
if (intent == null) {
|
|
||||||
return START_STICKY;
|
|
||||||
}
|
|
||||||
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
|
|
||||||
MediaSessionService instance = checkStateNotNull(getInstance());
|
|
||||||
@Nullable Uri uri = intent.getData();
|
|
||||||
@Nullable MediaSession session = uri != null ? MediaSession.getSession(uri) : null;
|
|
||||||
if (session == null) {
|
|
||||||
ControllerInfo controllerInfo = ControllerInfo.createLegacyControllerInfo();
|
|
||||||
session = instance.onGetSession(controllerInfo);
|
|
||||||
}
|
|
||||||
if (session == null) {
|
|
||||||
return START_STICKY;
|
|
||||||
}
|
|
||||||
KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
|
||||||
if (keyEvent != null) {
|
|
||||||
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return START_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaNotification onUpdateNotification(MediaSession session) {
|
|
||||||
MediaNotificationHandler handler;
|
|
||||||
synchronized (lock) {
|
|
||||||
handler = checkStateNotNull(notificationHandler, "Service hasn't created");
|
|
||||||
}
|
|
||||||
return handler.onUpdateNotification(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MediaSession> getSessions() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return new ArrayList<>(sessions.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private MediaSessionService getInstance() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IBinder getServiceBinder() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return checkStateNotNull(stub).asBinder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class MediaSessionServiceStub extends IMediaSessionService.Stub {
|
|
||||||
|
|
||||||
private final WeakReference<MediaSessionServiceImpl> serviceImpl;
|
|
||||||
|
|
||||||
private final Handler handler;
|
|
||||||
|
|
||||||
private final MediaSessionManager mediaSessionManager;
|
|
||||||
|
|
||||||
public MediaSessionServiceStub(MediaSessionServiceImpl serviceImpl) {
|
|
||||||
this.serviceImpl = new WeakReference<>(serviceImpl);
|
|
||||||
Context context = checkStateNotNull(serviceImpl.getInstance());
|
|
||||||
handler = new Handler(context.getMainLooper());
|
|
||||||
mediaSessionManager = MediaSessionManager.getSessionManager(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connect(
|
|
||||||
@Nullable IMediaController caller, @Nullable Bundle connectionRequestBundle) {
|
|
||||||
if (caller == null || connectionRequestBundle == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.serviceImpl.get() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ConnectionRequest request;
|
|
||||||
try {
|
|
||||||
request = ConnectionRequest.CREATOR.fromBundle(connectionRequestBundle);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
Log.w(TAG, "Ignoring malformed Bundle for ConnectionRequest", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int callingPid = Binder.getCallingPid();
|
|
||||||
int uid = Binder.getCallingUid();
|
|
||||||
long token = Binder.clearCallingIdentity();
|
|
||||||
int pid = (callingPid != 0) ? callingPid : request.pid;
|
|
||||||
RemoteUserInfo remoteUserInfo = new RemoteUserInfo(request.packageName, pid, uid);
|
|
||||||
boolean isTrusted = mediaSessionManager.isTrustedForMediaControl(remoteUserInfo);
|
|
||||||
try {
|
|
||||||
handler.post(
|
|
||||||
() -> {
|
|
||||||
boolean shouldNotifyDisconnected = true;
|
|
||||||
try {
|
|
||||||
@Nullable
|
|
||||||
MediaSessionServiceImpl serviceImpl =
|
|
||||||
MediaSessionServiceStub.this.serviceImpl.get();
|
|
||||||
if (serviceImpl == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@Nullable MediaSessionService service = serviceImpl.getInstance();
|
|
||||||
if (service == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ControllerInfo controllerInfo =
|
|
||||||
new ControllerInfo(
|
|
||||||
remoteUserInfo,
|
|
||||||
/* controllerVersion= */ request.version,
|
|
||||||
isTrusted,
|
|
||||||
/* controllerCb= */ null,
|
|
||||||
request.connectionHints);
|
|
||||||
|
|
||||||
@Nullable MediaSession session;
|
|
||||||
try {
|
|
||||||
session = service.onGetSession(controllerInfo);
|
|
||||||
if (session == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
service.addSession(session);
|
|
||||||
shouldNotifyDisconnected = false;
|
|
||||||
|
|
||||||
session.handleControllerConnectionFromService(
|
|
||||||
caller,
|
|
||||||
request.version,
|
|
||||||
request.packageName,
|
|
||||||
pid,
|
|
||||||
uid,
|
|
||||||
request.connectionHints);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Don't propagate exception in service to the controller.
|
|
||||||
Log.w(TAG, "Failed to add a session to session service", e);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
// Trick to call onDisconnected() in one place.
|
|
||||||
if (shouldNotifyDisconnected) {
|
|
||||||
try {
|
|
||||||
caller.onDisconnected(/* seq= */ 0);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
// Controller may be died prematurely.
|
|
||||||
// Not an issue because we'll ignore it anyway.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
Binder.restoreCallingIdentity(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void release() {
|
|
||||||
serviceImpl.clear();
|
|
||||||
handler.removeCallbacksAndMessages(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user