[Media ML] Let MCS manage MediaSession2
This CL adds - MediaCommunicationManager#notifySession2Created - MediaCommunicationManager#getSession2Tokens , which replaces the same methods in MediaSessionManager to let MediacommunicationService manage MediaSession2. MediaSessionService gets notified of created MediaSession2 instances by adding a callback to MCM. Bug: 180417011 Test: atest MediaSessionManagerTest MediaCommunicationManagerTest Change-Id: Ia5ffdcd15573d1223ca520cfa8eca3b976874118
This commit is contained in:
@@ -15,7 +15,17 @@
|
||||
*/
|
||||
package android.media;
|
||||
|
||||
import android.media.Session2Token;
|
||||
import android.media.IMediaCommunicationServiceCallback;
|
||||
import android.media.MediaParceledListSlice;
|
||||
|
||||
/** {@hide} */
|
||||
interface IMediaCommunicationService {
|
||||
void notifySession2Created(in Session2Token sessionToken);
|
||||
boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid);
|
||||
MediaParceledListSlice getSession2Tokens(int userId);
|
||||
|
||||
void registerCallback(IMediaCommunicationServiceCallback callback, String packageName);
|
||||
void unregisterCallback(IMediaCommunicationServiceCallback callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright 2021 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.Session2Token;
|
||||
import android.media.MediaParceledListSlice;
|
||||
|
||||
/** {@hide} */
|
||||
interface IMediaCommunicationServiceCallback {
|
||||
void onSession2Created(in Session2Token token);
|
||||
void onSession2Changed(in MediaParceledListSlice tokens);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ package android.media {
|
||||
}
|
||||
|
||||
public class MediaCommunicationManager {
|
||||
method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
|
||||
method @IntRange(from=1) public int getVersion();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
// Signature format: 2.0
|
||||
package android.media {
|
||||
|
||||
public class MediaCommunicationManager {
|
||||
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaCommunicationManager.SessionCallback);
|
||||
method public void unregisterSessionCallback(@NonNull android.media.MediaCommunicationManager.SessionCallback);
|
||||
}
|
||||
|
||||
public static interface MediaCommunicationManager.SessionCallback {
|
||||
method public default void onSession2TokenCreated(@NonNull android.media.Session2Token);
|
||||
method public default void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>);
|
||||
}
|
||||
|
||||
public class MediaFrameworkInitializer {
|
||||
method public static void registerServiceWrappers();
|
||||
method public static void setMediaServiceManager(@NonNull android.media.MediaServiceManager);
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.os.ResultReceiver;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Handles incoming commands from {@link MediaSession2} to both {@link MediaController2}.
|
||||
* Handles incoming commands from {@link MediaSession2} to {@link MediaController2}.
|
||||
* @hide
|
||||
*/
|
||||
// @SystemApi
|
||||
|
||||
@@ -15,18 +15,36 @@
|
||||
*/
|
||||
package android.media;
|
||||
|
||||
import static android.Manifest.permission.MEDIA_CONTENT_CONTROL;
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.content.Context;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.service.media.MediaBrowserService;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Provides support for interacting with {@link android.media.MediaSession2 MediaSession2s}
|
||||
* that applications have published to express their ongoing media playback state.
|
||||
*/
|
||||
// TODO: Add notifySession2Created() and sendMessage().
|
||||
@SystemService(Context.MEDIA_COMMUNICATION_SERVICE)
|
||||
public class MediaCommunicationManager {
|
||||
private static final String TAG = "MediaCommunicationManager";
|
||||
@@ -44,6 +62,13 @@ public class MediaCommunicationManager {
|
||||
private final Context mContext;
|
||||
private final IMediaCommunicationService mService;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final CopyOnWriteArrayList<SessionCallbackRecord> mTokenCallbackRecords =
|
||||
new CopyOnWriteArrayList<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private MediaCommunicationServiceCallbackStub mCallbackStub;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -64,4 +89,197 @@ public class MediaCommunicationManager {
|
||||
public @IntRange(from = 1) int getVersion() {
|
||||
return CURRENT_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
|
||||
* created.
|
||||
* @param token newly created session2 token
|
||||
* @hide
|
||||
*/
|
||||
public void notifySession2Created(@NonNull Session2Token token) {
|
||||
Objects.requireNonNull(token, "token shouldn't be null");
|
||||
if (token.getType() != Session2Token.TYPE_SESSION) {
|
||||
throw new IllegalArgumentException("token's type should be TYPE_SESSION");
|
||||
}
|
||||
try {
|
||||
mService.notifySession2Created(token);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the remote user is a trusted app.
|
||||
* <p>
|
||||
* An app is trusted if the app holds the
|
||||
* {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission or has an enabled
|
||||
* notification listener.
|
||||
*
|
||||
* @param userInfo The remote user info from either
|
||||
* {@link MediaSession#getCurrentControllerInfo()} or
|
||||
* {@link MediaBrowserService#getCurrentBrowserInfo()}.
|
||||
* @return {@code true} if the remote user is trusted or {@code false} otherwise.
|
||||
* @hide
|
||||
*/
|
||||
public boolean isTrustedForMediaControl(@NonNull MediaSessionManager.RemoteUserInfo userInfo) {
|
||||
Objects.requireNonNull(userInfo, "userInfo shouldn't be null");
|
||||
if (userInfo.getPackageName() == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mService.isTrusted(
|
||||
userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid());
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Cannot communicate with the service.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This API is not generally intended for third party application developers.
|
||||
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
|
||||
* <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
|
||||
* Library</a> for consistent behavior across all devices.
|
||||
* <p>
|
||||
* Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
|
||||
* current user.
|
||||
* <p>
|
||||
* Although this API can be used without any restriction, each session owners can accept or
|
||||
* reject your uses of {@link MediaSession2}.
|
||||
*
|
||||
* @return A list of {@link Session2Token}.
|
||||
*/
|
||||
@NonNull
|
||||
public List<Session2Token> getSession2Tokens() {
|
||||
return getSession2Tokens(UserHandle.myUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to be notified when the list of active sessions changes.
|
||||
* <p>
|
||||
* This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be
|
||||
* held by the calling app.
|
||||
* </p>
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
@RequiresPermission(MEDIA_CONTENT_CONTROL)
|
||||
public void registerSessionCallback(@CallbackExecutor @NonNull Executor executor,
|
||||
@NonNull SessionCallback callback) {
|
||||
Objects.requireNonNull(executor, "executor must not be null");
|
||||
Objects.requireNonNull(callback, "callback must not be null");
|
||||
|
||||
if (!mTokenCallbackRecords.addIfAbsent(
|
||||
new SessionCallbackRecord(executor, callback))) {
|
||||
Log.w(TAG, "registerSession2TokenCallback: Ignoring the same callback");
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (mCallbackStub == null) {
|
||||
MediaCommunicationServiceCallbackStub callbackStub =
|
||||
new MediaCommunicationServiceCallbackStub();
|
||||
try {
|
||||
mService.registerCallback(callbackStub, mContext.getPackageName());
|
||||
mCallbackStub = callbackStub;
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Failed to register callback.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops receiving active sessions updates on the specified callback.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public void unregisterSessionCallback(@NonNull SessionCallback callback) {
|
||||
if (!mTokenCallbackRecords.remove(
|
||||
new SessionCallbackRecord(null, callback))) {
|
||||
Log.w(TAG, "unregisterSession2TokenCallback: Ignoring an unknown callback.");
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (mCallbackStub != null && mTokenCallbackRecords.isEmpty()) {
|
||||
try {
|
||||
mService.unregisterCallback(mCallbackStub);
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Failed to unregister callback.", ex);
|
||||
}
|
||||
mCallbackStub = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Session2Token> getSession2Tokens(int userId) {
|
||||
try {
|
||||
MediaParceledListSlice slice = mService.getSession2Tokens(userId);
|
||||
return slice == null ? Collections.emptyList() : slice.getList();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to get session tokens", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for listening to changes to the sessions.
|
||||
* @see #registerSessionCallback(Executor, SessionCallback)
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public interface SessionCallback {
|
||||
/**
|
||||
* Called when a new {@link MediaSession2 media session2} is created.
|
||||
* @param token the newly created token
|
||||
*/
|
||||
default void onSession2TokenCreated(@NonNull Session2Token token) {}
|
||||
|
||||
/**
|
||||
* Called when {@link #getSession2Tokens() session tokens} are changed.
|
||||
*/
|
||||
default void onSession2TokensChanged(@NonNull List<Session2Token> tokens) {}
|
||||
}
|
||||
|
||||
private static final class SessionCallbackRecord {
|
||||
public final Executor executor;
|
||||
public final SessionCallback callback;
|
||||
|
||||
SessionCallbackRecord(Executor executor, SessionCallback callback) {
|
||||
this.executor = executor;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof SessionCallbackRecord)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(this.callback, ((SessionCallbackRecord) obj).callback);
|
||||
}
|
||||
}
|
||||
|
||||
class MediaCommunicationServiceCallbackStub extends IMediaCommunicationServiceCallback.Stub {
|
||||
@Override
|
||||
public void onSession2Created(Session2Token token) throws RemoteException {
|
||||
for (SessionCallbackRecord record : mTokenCallbackRecords) {
|
||||
record.executor.execute(() -> record.callback.onSession2TokenCreated(token));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSession2Changed(MediaParceledListSlice tokens) throws RemoteException {
|
||||
List<Session2Token> tokenList = tokens.getList();
|
||||
for (SessionCallbackRecord record : mTokenCallbackRecords) {
|
||||
record.executor.execute(() -> record.callback.onSession2TokensChanged(tokenList));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.annotation.Nullable;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.session.MediaSessionManager.RemoteUserInfo;
|
||||
import android.os.BadParcelableException;
|
||||
import android.os.Bundle;
|
||||
@@ -87,7 +86,7 @@ public class MediaSession2 implements AutoCloseable {
|
||||
private final String mSessionId;
|
||||
private final PendingIntent mSessionActivity;
|
||||
private final Session2Token mSessionToken;
|
||||
private final MediaSessionManager mSessionManager;
|
||||
private final MediaCommunicationManager mCommunicationManager;
|
||||
private final Handler mResultHandler;
|
||||
|
||||
//@GuardedBy("mLock")
|
||||
@@ -115,8 +114,7 @@ public class MediaSession2 implements AutoCloseable {
|
||||
mSessionStub = new Session2Link(this);
|
||||
mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(),
|
||||
mSessionStub, tokenExtras);
|
||||
mSessionManager = (MediaSessionManager) mContext.getSystemService(
|
||||
Context.MEDIA_SESSION_SERVICE);
|
||||
mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class);
|
||||
// NOTE: mResultHandler uses main looper, so this MUST NOT be blocked.
|
||||
mResultHandler = new Handler(context.getMainLooper());
|
||||
mClosed = false;
|
||||
@@ -352,7 +350,7 @@ public class MediaSession2 implements AutoCloseable {
|
||||
|
||||
final ControllerInfo controllerInfo = new ControllerInfo(
|
||||
remoteUserInfo,
|
||||
mSessionManager.isTrustedForMediaControl(remoteUserInfo),
|
||||
mCommunicationManager.isTrustedForMediaControl(remoteUserInfo),
|
||||
controller,
|
||||
connectionHints);
|
||||
mCallbackExecutor.execute(() -> {
|
||||
@@ -608,8 +606,8 @@ public class MediaSession2 implements AutoCloseable {
|
||||
// Notify framework about the newly create session after the constructor is finished.
|
||||
// Otherwise, framework may access the session before the initialization is finished.
|
||||
try {
|
||||
MediaSessionManager manager = (MediaSessionManager) mContext.getSystemService(
|
||||
Context.MEDIA_SESSION_SERVICE);
|
||||
MediaCommunicationManager manager =
|
||||
mContext.getSystemService(MediaCommunicationManager.class);
|
||||
manager.notifySession2Created(session2.getToken());
|
||||
} catch (Exception e) {
|
||||
session2.close();
|
||||
|
||||
@@ -15,27 +15,538 @@
|
||||
*/
|
||||
package com.android.server.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.IMediaCommunicationService;
|
||||
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
|
||||
import static android.os.UserHandle.ALL;
|
||||
import static android.os.UserHandle.getUserHandleForUid;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.IMediaCommunicationService;
|
||||
import android.media.IMediaCommunicationServiceCallback;
|
||||
import android.media.MediaController2;
|
||||
import android.media.MediaParceledListSlice;
|
||||
import android.media.Session2CommandGroup;
|
||||
import android.media.Session2Token;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A system service that managers {@link android.media.MediaSession2} creations
|
||||
* A system service that manages {@link android.media.MediaSession2} creations
|
||||
* and their ongoing media playback state.
|
||||
* @hide
|
||||
*/
|
||||
public class MediaCommunicationService extends SystemService {
|
||||
private static final String TAG = "MediaCommunicationService";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
final Context mContext;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final SparseIntArray mFullUserIds = new SparseIntArray();
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<>();
|
||||
|
||||
private final Executor mRecordExecutor = Executors.newSingleThreadExecutor();
|
||||
@GuardedBy("mLock")
|
||||
private final List<CallbackRecord> mCallbackRecords = new ArrayList<>();
|
||||
final NotificationManager mNotificationManager;
|
||||
|
||||
public MediaCommunicationService(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mNotificationManager = context.getSystemService(NotificationManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.MEDIA_COMMUNICATION_SERVICE, new Stub());
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserStarting(@NonNull TargetUser user) {
|
||||
if (DEBUG) Log.d(TAG, "onUserStarting: " + user);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
|
||||
if (DEBUG) Log.d(TAG, "onUserSwitching: " + to);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserStopped(@NonNull TargetUser targetUser) {
|
||||
int userId = targetUser.getUserHandle().getIdentifier();
|
||||
|
||||
if (DEBUG) Log.d(TAG, "onUserStopped: " + userId);
|
||||
synchronized (mLock) {
|
||||
FullUserRecord user = getFullUserRecordLocked(userId);
|
||||
if (user != null) {
|
||||
if (user.getFullUserId() == userId) {
|
||||
user.destroySessionsForUserLocked(UserHandle.ALL.getIdentifier());
|
||||
mUserRecords.remove(userId);
|
||||
} else {
|
||||
user.destroySessionsForUserLocked(userId);
|
||||
}
|
||||
}
|
||||
updateUser();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
CallbackRecord findCallbackRecordLocked(@Nullable IMediaCommunicationServiceCallback callback) {
|
||||
if (callback == null) {
|
||||
return null;
|
||||
}
|
||||
for (CallbackRecord record : mCallbackRecords) {
|
||||
if (Objects.equals(callback.asBinder(), record.mCallback.asBinder())) {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private FullUserRecord getFullUserRecordLocked(int userId) {
|
||||
int fullUserId = mFullUserIds.get(userId, -1);
|
||||
if (fullUserId < 0) {
|
||||
return null;
|
||||
}
|
||||
return mUserRecords.get(fullUserId);
|
||||
}
|
||||
|
||||
private boolean hasMediaControlPermission(int pid, int uid) {
|
||||
// Check if it's system server or has MEDIA_CONTENT_CONTROL.
|
||||
// Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
|
||||
// check here.
|
||||
if (uid == Process.SYSTEM_UID || mContext.checkPermission(
|
||||
android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
} else if (DEBUG) {
|
||||
Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateUser() {
|
||||
UserManager manager = mContext.getSystemService(UserManager.class);
|
||||
List<UserHandle> allUsers = manager.getUserHandles(/*excludeDying=*/false);
|
||||
|
||||
synchronized (mLock) {
|
||||
mFullUserIds.clear();
|
||||
if (allUsers != null) {
|
||||
for (UserHandle user : allUsers) {
|
||||
UserHandle parent = manager.getProfileParent(user);
|
||||
if (parent != null) {
|
||||
mFullUserIds.put(user.getIdentifier(), parent.getIdentifier());
|
||||
} else {
|
||||
mFullUserIds.put(user.getIdentifier(), user.getIdentifier());
|
||||
if (mUserRecords.get(user.getIdentifier()) == null) {
|
||||
mUserRecords.put(user.getIdentifier(),
|
||||
new FullUserRecord(user.getIdentifier()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure that the current full user exists.
|
||||
int currentFullUserId = ActivityManager.getCurrentUser();
|
||||
FullUserRecord currentFullUserRecord = mUserRecords.get(currentFullUserId);
|
||||
if (currentFullUserRecord == null) {
|
||||
Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
|
||||
currentFullUserRecord = new FullUserRecord(currentFullUserId);
|
||||
mUserRecords.put(currentFullUserId, currentFullUserRecord);
|
||||
}
|
||||
mFullUserIds.put(currentFullUserId, currentFullUserId);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchSessionCreated(Session2Token token) {
|
||||
for (CallbackRecord record : mCallbackRecords) {
|
||||
if (record.mUserId != ALL.getIdentifier()
|
||||
&& record.mUserId != getUserHandleForUid(token.getUid()).getIdentifier()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
record.mCallback.onSession2Created(token);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onSessionDied(Session2Record record) {
|
||||
synchronized (mLock) {
|
||||
destroySessionLocked(record);
|
||||
}
|
||||
}
|
||||
|
||||
private void destroySessionLocked(Session2Record session) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Destroying " + session);
|
||||
}
|
||||
if (session.isClosed()) {
|
||||
Log.w(TAG, "Destroying already destroyed session. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
FullUserRecord user = getFullUserRecordLocked(session.getUserId());
|
||||
|
||||
if (user != null) {
|
||||
user.removeSession(session);
|
||||
}
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
private class Stub extends IMediaCommunicationService.Stub {
|
||||
@Override
|
||||
public void notifySession2Created(Session2Token sessionToken) {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Session2 is created " + sessionToken);
|
||||
}
|
||||
if (uid != sessionToken.getUid()) {
|
||||
throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
|
||||
+ " but actually=" + sessionToken.getUid());
|
||||
}
|
||||
synchronized (mLock) {
|
||||
int userId = getUserHandleForUid(sessionToken.getUid()).getIdentifier();
|
||||
FullUserRecord user = getFullUserRecordLocked(userId);
|
||||
if (user == null) {
|
||||
Log.w(TAG, "notifySession2Created: Ignore session of an unknown user");
|
||||
return;
|
||||
}
|
||||
user.addSession(new Session2Record(MediaCommunicationService.this,
|
||||
sessionToken, mRecordExecutor));
|
||||
mHandler.post(() -> dispatchSessionCreated(sessionToken));
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
|
||||
* permission or an enabled notification listener)
|
||||
*
|
||||
* @param controllerPackageName package name of the controller app
|
||||
* @param controllerPid pid of the controller app
|
||||
* @param controllerUid uid of the controller app
|
||||
*/
|
||||
@Override
|
||||
public boolean isTrusted(String controllerPackageName, int controllerPid,
|
||||
int controllerUid) {
|
||||
final int uid = Binder.getCallingUid();
|
||||
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
// Don't perform check between controllerPackageName and controllerUid.
|
||||
// When an (activity|service) runs on the another apps process by specifying
|
||||
// android:process in the AndroidManifest.xml, then PID and UID would have the
|
||||
// running process' information instead of the (activity|service) that has created
|
||||
// MediaController.
|
||||
// Note that we can use Context#getOpPackageName() instead of
|
||||
// Context#getPackageName() for getting package name that matches with the PID/UID,
|
||||
// but it doesn't tell which package has created the MediaController, so useless.
|
||||
return hasMediaControlPermission(controllerPid, controllerUid)
|
||||
|| hasEnabledNotificationListener(
|
||||
userId, controllerPackageName, controllerUid);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaParceledListSlice getSession2Tokens(int userId) {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
|
||||
try {
|
||||
// Check that they can make calls on behalf of the user and get the final user id
|
||||
int resolvedUserId = handleIncomingUser(pid, uid, userId, null);
|
||||
List<Session2Token> result;
|
||||
synchronized (mLock) {
|
||||
FullUserRecord user = getFullUserRecordLocked(userId);
|
||||
result = user.getSession2Tokens(resolvedUserId);
|
||||
}
|
||||
return new MediaParceledListSlice(result);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCallback(IMediaCommunicationServiceCallback callback,
|
||||
String packageName) throws RemoteException {
|
||||
Objects.requireNonNull(callback, "callback should not be null");
|
||||
Objects.requireNonNull(packageName, "packageName should not be null");
|
||||
|
||||
synchronized (mLock) {
|
||||
if (findCallbackRecordLocked(callback) == null) {
|
||||
|
||||
CallbackRecord record = new CallbackRecord(callback, packageName,
|
||||
Binder.getCallingUid(), Binder.getCallingPid());
|
||||
mCallbackRecords.add(record);
|
||||
try {
|
||||
callback.asBinder().linkToDeath(record, 0);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to register callback", e);
|
||||
mCallbackRecords.remove(record);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "registerCallback is called with already registered callback. "
|
||||
+ "packageName=" + packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCallback(IMediaCommunicationServiceCallback callback)
|
||||
throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
CallbackRecord existingRecord = findCallbackRecordLocked(callback);
|
||||
if (existingRecord != null) {
|
||||
mCallbackRecords.remove(existingRecord);
|
||||
callback.asBinder().unlinkToDeath(existingRecord, 0);
|
||||
} else {
|
||||
Log.e(TAG, "unregisterCallback is called with unregistered callback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasEnabledNotificationListener(int callingUserId,
|
||||
String controllerPackageName, int controllerUid) {
|
||||
int controllerUserId = UserHandle.getUserHandleForUid(controllerUid).getIdentifier();
|
||||
if (callingUserId != controllerUserId) {
|
||||
// Enabled notification listener only works within the same user.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName,
|
||||
UserHandle.getUserHandleForUid(controllerUid))) {
|
||||
return true;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, controllerPackageName + " (uid=" + controllerUid
|
||||
+ ") doesn't have an enabled notification listener");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handles incoming user by checking whether the caller has permission to access the
|
||||
// given user id's information or not. Permission is not necessary if the given user id is
|
||||
// equal to the caller's user id, but if not, the caller needs to have the
|
||||
// INTERACT_ACROSS_USERS_FULL permission. Otherwise, a security exception will be thrown.
|
||||
// The return value will be the given user id, unless the given user id is
|
||||
// UserHandle.CURRENT, which will return the ActivityManager.getCurrentUser() value instead.
|
||||
private int handleIncomingUser(int pid, int uid, int userId, String packageName) {
|
||||
int callingUserId = UserHandle.getUserHandleForUid(uid).getIdentifier();
|
||||
if (userId == callingUserId) {
|
||||
return userId;
|
||||
}
|
||||
|
||||
boolean canInteractAcrossUsersFull = mContext.checkPermission(
|
||||
INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED;
|
||||
if (canInteractAcrossUsersFull) {
|
||||
if (userId == UserHandle.CURRENT.getIdentifier()) {
|
||||
return ActivityManager.getCurrentUser();
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
throw new SecurityException("Permission denied while calling from " + packageName
|
||||
+ " with user id: " + userId + "; Need to run as either the calling user id ("
|
||||
+ callingUserId + "), or with " + INTERACT_ACROSS_USERS_FULL + " permission");
|
||||
}
|
||||
}
|
||||
|
||||
final class CallbackRecord implements IBinder.DeathRecipient {
|
||||
private final IMediaCommunicationServiceCallback mCallback;
|
||||
private final String mPackageName;
|
||||
private final int mUid;
|
||||
private int mPid;
|
||||
private final int mUserId;
|
||||
|
||||
CallbackRecord(IMediaCommunicationServiceCallback callback,
|
||||
String packageName, int uid, int pid) {
|
||||
mCallback = callback;
|
||||
mPackageName = packageName;
|
||||
mUid = uid;
|
||||
mPid = pid;
|
||||
mUserId = (mContext.checkPermission(
|
||||
INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED)
|
||||
? ALL.getIdentifier() : UserHandle.getUserHandleForUid(mUid).getIdentifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CallbackRecord[callback=" + mCallback + ", pkg=" + mPackageName
|
||||
+ ", uid=" + mUid + ", pid=" + mPid + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
synchronized (mLock) {
|
||||
mCallbackRecords.remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class FullUserRecord {
|
||||
private final int mFullUserId;
|
||||
/** Sorted list of media sessions */
|
||||
private final List<Session2Record> mSessionRecords = new ArrayList<>();
|
||||
|
||||
FullUserRecord(int fullUserId) {
|
||||
mFullUserId = fullUserId;
|
||||
}
|
||||
|
||||
public void addSession(Session2Record record) {
|
||||
mSessionRecords.add(record);
|
||||
}
|
||||
|
||||
public void removeSession(Session2Record record) {
|
||||
mSessionRecords.remove(record);
|
||||
//TODO: Handle if the removed session was the media button session.
|
||||
}
|
||||
|
||||
public int getFullUserId() {
|
||||
return mFullUserId;
|
||||
}
|
||||
|
||||
public List<Session2Token> getSession2Tokens(int userId) {
|
||||
return mSessionRecords.stream()
|
||||
.filter(record -> record.isActive()
|
||||
&& (userId == UserHandle.ALL.getIdentifier()
|
||||
|| record.getUserId() == userId))
|
||||
.map(Session2Record::getSessionToken)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void destroySessionsForUserLocked(int userId) {
|
||||
synchronized (mLock) {
|
||||
for (Session2Record record : mSessionRecords) {
|
||||
if (userId == UserHandle.ALL.getIdentifier()
|
||||
|| record.getUserId() == userId) {
|
||||
destroySessionLocked(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class Session2Record {
|
||||
private final Session2Token mSessionToken;
|
||||
private final Object mLock = new Object();
|
||||
private final WeakReference<MediaCommunicationService> mServiceRef;
|
||||
@GuardedBy("mLock")
|
||||
private final MediaController2 mController;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean mIsConnected;
|
||||
@GuardedBy("mLock")
|
||||
private boolean mIsClosed;
|
||||
|
||||
Session2Record(MediaCommunicationService service, Session2Token token,
|
||||
Executor controllerExecutor) {
|
||||
mServiceRef = new WeakReference<>(service);
|
||||
mSessionToken = token;
|
||||
mController = new MediaController2.Builder(service.getContext(), token)
|
||||
.setControllerCallback(controllerExecutor, new Controller2Callback())
|
||||
.build();
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier();
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
synchronized (mLock) {
|
||||
return mIsConnected;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
synchronized (mLock) {
|
||||
return mIsClosed;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (mLock) {
|
||||
mIsClosed = true;
|
||||
// Call close regardless of the mIsConnected. This may be called when it's not yet
|
||||
// connected.
|
||||
mController.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Session2Token getSessionToken() {
|
||||
return mSessionToken;
|
||||
}
|
||||
|
||||
private class Controller2Callback extends MediaController2.ControllerCallback {
|
||||
@Override
|
||||
public void onConnected(MediaController2 controller,
|
||||
Session2CommandGroup allowedCommands) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mIsConnected = true;
|
||||
}
|
||||
MediaCommunicationService service = mServiceRef.get();
|
||||
if (service != null) {
|
||||
//TODO: notify session state changed
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(MediaController2 controller) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "disconnected from " + mSessionToken);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
MediaCommunicationService service = mServiceRef.get();
|
||||
if (service != null) {
|
||||
service.onSessionDied(Session2Record.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24760,7 +24760,7 @@ package android.media.session {
|
||||
method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessions(@Nullable android.content.ComponentName);
|
||||
method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
|
||||
method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
|
||||
method public void notifySession2Created(@NonNull android.media.Session2Token);
|
||||
method @Deprecated public void notifySession2Created(@NonNull android.media.Session2Token);
|
||||
method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
|
||||
method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
|
||||
}
|
||||
|
||||
@@ -38,9 +38,7 @@ import android.view.KeyEvent;
|
||||
interface ISessionManager {
|
||||
ISession createSession(String packageName, in ISessionCallback sessionCb, String tag,
|
||||
in Bundle sessionInfo, int userId);
|
||||
void notifySession2Created(in Session2Token sessionToken);
|
||||
List<MediaSession.Token> getSessions(in ComponentName compName, int userId);
|
||||
ParceledListSlice getSession2Tokens(int userId);
|
||||
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
|
||||
boolean needWakeLock);
|
||||
boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
|
||||
|
||||
@@ -25,9 +25,9 @@ import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.media.AudioManager;
|
||||
import android.media.IRemoteSessionCallback;
|
||||
import android.media.MediaCommunicationManager;
|
||||
import android.media.MediaFrameworkPlatformInitializer;
|
||||
import android.media.MediaSession2;
|
||||
import android.media.Session2Token;
|
||||
@@ -84,6 +84,7 @@ public final class MediaSessionManager {
|
||||
public static final int RESULT_MEDIA_KEY_HANDLED = 1;
|
||||
|
||||
private final ISessionManager mService;
|
||||
private final MediaCommunicationManager mCommunicationManager;
|
||||
private final OnMediaKeyEventDispatchedListenerStub mOnMediaKeyEventDispatchedListenerStub =
|
||||
new OnMediaKeyEventDispatchedListenerStub();
|
||||
private final OnMediaKeyEventSessionChangedListenerStub
|
||||
@@ -128,6 +129,8 @@ public final class MediaSessionManager {
|
||||
.getMediaServiceManager()
|
||||
.getMediaSessionServiceRegisterer()
|
||||
.get());
|
||||
mCommunicationManager = (MediaCommunicationManager) context
|
||||
.getSystemService(Context.MEDIA_COMMUNICATION_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,17 +167,11 @@ public final class MediaSessionManager {
|
||||
* {@link MediaSession2.Builder} instead.
|
||||
*
|
||||
* @param token newly created session2 token
|
||||
* @deprecated Don't use this method. A new media session is notified automatically.
|
||||
*/
|
||||
@Deprecated
|
||||
public void notifySession2Created(@NonNull Session2Token token) {
|
||||
Objects.requireNonNull(token, "token shouldn't be null");
|
||||
if (token.getType() != Session2Token.TYPE_SESSION) {
|
||||
throw new IllegalArgumentException("token's type should be TYPE_SESSION");
|
||||
}
|
||||
try {
|
||||
mService.notifySession2Created(token);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
// Does nothing
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,37 +252,7 @@ public final class MediaSessionManager {
|
||||
*/
|
||||
@NonNull
|
||||
public List<Session2Token> getSession2Tokens() {
|
||||
return getSession2Tokens(UserHandle.myUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
|
||||
* given user.
|
||||
* <p>
|
||||
* The calling application needs to hold the
|
||||
* {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission in order to
|
||||
* retrieve session tokens for user ids that do not belong to current process.
|
||||
*
|
||||
* @param userHandle The user handle to fetch sessions for.
|
||||
* @return A list of {@link Session2Token}
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SuppressLint("UserHandle")
|
||||
public List<Session2Token> getSession2Tokens(@NonNull UserHandle userHandle) {
|
||||
Objects.requireNonNull(userHandle, "userHandle shouldn't be null");
|
||||
return getSession2Tokens(userHandle.getIdentifier());
|
||||
|
||||
}
|
||||
|
||||
private List<Session2Token> getSession2Tokens(int userId) {
|
||||
try {
|
||||
ParceledListSlice slice = mService.getSession2Tokens(userId);
|
||||
return slice == null ? new ArrayList<>() : slice.getList();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to get session tokens", e);
|
||||
}
|
||||
return new ArrayList<>();
|
||||
return mCommunicationManager.getSession2Tokens();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,8 +501,7 @@ public final class MediaSessionManager {
|
||||
}
|
||||
if (shouldRegisterCallback) {
|
||||
try {
|
||||
mService.registerRemoteSessionCallback(
|
||||
mRemoteSessionCallbackStub);
|
||||
mService.registerRemoteSessionCallback(mRemoteSessionCallbackStub);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to register remote volume controller callback", e);
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioPlaybackConfiguration;
|
||||
import android.media.IRemoteSessionCallback;
|
||||
import android.media.MediaCommunicationManager;
|
||||
import android.media.Session2Token;
|
||||
import android.media.session.IActiveSessionsListener;
|
||||
import android.media.session.IOnMediaKeyEventDispatchedListener;
|
||||
@@ -151,6 +151,25 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
private MediaSessionPolicyProvider mCustomMediaSessionPolicyProvider;
|
||||
private MediaKeyDispatcher mCustomMediaKeyDispatcher;
|
||||
|
||||
private MediaCommunicationManager mCommunicationManager;
|
||||
private final MediaCommunicationManager.SessionCallback mSession2TokenCallback =
|
||||
new MediaCommunicationManager.SessionCallback() {
|
||||
@Override
|
||||
public void onSession2TokenCreated(Session2Token token) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Session2 is created " + token);
|
||||
}
|
||||
MediaSession2Record record = new MediaSession2Record(token,
|
||||
MediaSessionService.this, mRecordThread.getLooper(), 0);
|
||||
synchronized (mLock) {
|
||||
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
|
||||
if (user != null) {
|
||||
user.mPriorityStack.addSession(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public MediaSessionService(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
@@ -202,6 +221,19 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
super.onBootPhase(phase);
|
||||
switch (phase) {
|
||||
// This ensures MediaCommunicationService is started
|
||||
case PHASE_BOOT_COMPLETED:
|
||||
mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class);
|
||||
mCommunicationManager.registerSessionCallback(new HandlerExecutor(mHandler),
|
||||
mSession2TokenCallback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mNotificationListenerEnabledChangedReceiver =
|
||||
new BroadcastReceiver() {
|
||||
@Override
|
||||
@@ -1138,31 +1170,6 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifySession2Created(Session2Token sessionToken) throws RemoteException {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Session2 is created " + sessionToken);
|
||||
}
|
||||
if (uid != sessionToken.getUid()) {
|
||||
throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
|
||||
+ " but actually=" + sessionToken.getUid());
|
||||
}
|
||||
MediaSession2Record record = new MediaSession2Record(sessionToken,
|
||||
MediaSessionService.this, mRecordThread.getLooper(), 0);
|
||||
synchronized (mLock) {
|
||||
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
|
||||
user.mPriorityStack.addSession(record);
|
||||
}
|
||||
// Do not immediately notify changes -- do so when framework can dispatch command
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MediaSession.Token> getSessions(ComponentName componentName, int userId) {
|
||||
final int pid = Binder.getCallingPid();
|
||||
@@ -1184,26 +1191,6 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParceledListSlice getSession2Tokens(int userId) {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
|
||||
try {
|
||||
// Check that they can make calls on behalf of the user and get the final user id
|
||||
int resolvedUserId = handleIncomingUser(pid, uid, userId, null);
|
||||
List<Session2Token> result;
|
||||
synchronized (mLock) {
|
||||
FullUserRecord user = getFullUserRecordLocked(userId);
|
||||
result = user.mPriorityStack.getSession2Tokens(resolvedUserId);
|
||||
}
|
||||
return new ParceledListSlice(result);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSessionsListener(IActiveSessionsListener listener,
|
||||
ComponentName componentName, int userId) throws RemoteException {
|
||||
|
||||
Reference in New Issue
Block a user