blob: adc14a550b7d49b559923c709bd60a298b891f18 [file] [log] [blame]
/*
* 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 android.media.session;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaMetadata;
import android.media.MediaParceledListSlice;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.QueueItem;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import java.util.List;
/**
* Handles incoming commands to {@link MediaController.Callback}.
* @hide
*/
@SystemApi
public final class ControllerCallbackLink implements Parcelable {
final Context mContext;
final CallbackStub mCallbackStub;
final ISessionControllerCallback mIControllerCallback;
/**
* Constructor for stub (Callee)
*/
public ControllerCallbackLink(@NonNull Context context, @NonNull CallbackStub callbackStub) {
mContext = context;
mCallbackStub = callbackStub;
mIControllerCallback = new CallbackStubProxy();
}
/**
* Constructor for interface (Caller)
*/
public ControllerCallbackLink(IBinder binder) {
mContext = null;
mCallbackStub = null;
mIControllerCallback = ISessionControllerCallback.Stub.asInterface(binder);
}
/**
* Notify controller that the connected session is destroyed.
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifySessionDestroyed() {
try {
mIControllerCallback.notifySessionDestroyed();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Notify controller that the connected session sends an event.
*
* @param event the name of the event
* @param extras the extras included with the event
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyEvent(@NonNull String event, @Nullable Bundle extras) {
try {
mIControllerCallback.notifyEvent(event, extras);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Notify controller that the current playback state is changed.
*
* @param state the new playback state
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPlaybackStateChanged(@Nullable PlaybackState state) {
try {
mIControllerCallback.notifyPlaybackStateChanged(state);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Notify controller that the current metadata is changed.
*
* @param metadata the new metadata
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyMetadataChanged(@Nullable MediaMetadata metadata) {
try {
mIControllerCallback.notifyMetadataChanged(metadata);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Notify controller that the current queue is changed.
*
* @param queue the new queue
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
try {
mIControllerCallback.notifyQueueChanged(queue == null ? null :
new MediaParceledListSlice(queue));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Notify controller that the current queue title is changed.
*
* @param title the new queue title
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyQueueTitleChanged(@Nullable CharSequence title) {
try {
mIControllerCallback.notifyQueueTitleChanged(title);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Notify controller that the extras are changed.
*
* @param extras the new extras
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyExtrasChanged(@Nullable Bundle extras) {
try {
mIControllerCallback.notifyExtrasChanged(extras);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Notify controller that the playback info is changed.
*
* @param info the new playback info
*/
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyVolumeInfoChanged(@NonNull PlaybackInfo info) {
try {
mIControllerCallback.notifyVolumeInfoChanged(info);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/** Gets the binder */
@NonNull
public IBinder getBinder() {
return mIControllerCallback.asBinder();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongBinder(mIControllerCallback.asBinder());
}
public static final Parcelable.Creator<ControllerCallbackLink> CREATOR =
new Parcelable.Creator<ControllerCallbackLink>() {
@Override
public ControllerCallbackLink createFromParcel(Parcel in) {
return new ControllerCallbackLink(in.readStrongBinder());
}
@Override
public ControllerCallbackLink[] newArray(int size) {
return new ControllerCallbackLink[size];
}
};
/**
* Class for Stub implementation
*/
public abstract static class CallbackStub {
/** Stub method for ISessionControllerCallback.notifySessionDestroyed */
public void onSessionDestroyed() {
}
/** Stub method for ISessionControllerCallback.notifyEvent */
public void onEvent(@NonNull String event, @Nullable Bundle extras) {
}
/** Stub method for ISessionControllerCallback.notifyPlaybackStateChanged */
public void onPlaybackStateChanged(@Nullable PlaybackState state) {
}
/** Stub method for ISessionControllerCallback.notifyMetadataChanged */
public void onMetadataChanged(@Nullable MediaMetadata metadata) {
}
/** Stub method for ISessionControllerCallback.notifyQueueChanged */
public void onQueueChanged(@Nullable List<QueueItem> queue) {
}
/** Stub method for ISessionControllerCallback.notifyQueueTitleChanged */
public void onQueueTitleChanged(@Nullable CharSequence title) {
}
/** Stub method for ISessionControllerCallback.notifyExtrasChanged */
public void onExtrasChanged(@Nullable Bundle extras) {
}
/** Stub method for ISessionControllerCallback.notifyVolumeInfoChanged */
public void onVolumeInfoChanged(@NonNull PlaybackInfo info) {
}
}
private class CallbackStubProxy extends ISessionControllerCallback.Stub {
@Override
public void notifyEvent(String event, Bundle extras) {
mCallbackStub.onEvent(event, extras);
}
@Override
public void notifySessionDestroyed() {
mCallbackStub.onSessionDestroyed();
}
@Override
public void notifyPlaybackStateChanged(PlaybackState state) {
ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlaybackStateChanged(state);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifyMetadataChanged(MediaMetadata metadata) {
ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onMetadataChanged(metadata);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifyQueueChanged(MediaParceledListSlice queue) {
ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onQueueChanged(queue == null ? null : queue.getList());
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifyQueueTitleChanged(CharSequence title) {
ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onQueueTitleChanged(title);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifyExtrasChanged(Bundle extras) {
mCallbackStub.onExtrasChanged(extras);
}
@Override
public void notifyVolumeInfoChanged(PlaybackInfo info) {
ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onVolumeInfoChanged(info);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void ensureMediaControlPermission() {
// 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 (getCallingUid() == Process.SYSTEM_UID || mContext.checkCallingPermission(
android.Manifest.permission.MEDIA_CONTENT_CONTROL)
== PackageManager.PERMISSION_GRANTED) {
return;
}
throw new SecurityException("Must hold the MEDIA_CONTENT_CONTROL permission.");
}
}
}