Merge "Initial round of MediaSession APIs"
This commit is contained in:
@@ -260,12 +260,17 @@ LOCAL_SRC_FILES += \
|
|||||||
media/java/android/media/IAudioService.aidl \
|
media/java/android/media/IAudioService.aidl \
|
||||||
media/java/android/media/IAudioFocusDispatcher.aidl \
|
media/java/android/media/IAudioFocusDispatcher.aidl \
|
||||||
media/java/android/media/IAudioRoutesObserver.aidl \
|
media/java/android/media/IAudioRoutesObserver.aidl \
|
||||||
|
media/java/android/media/IMediaController.aidl \
|
||||||
|
media/java/android/media/IMediaControllerCallback.aidl \
|
||||||
media/java/android/media/IMediaHTTPConnection.aidl \
|
media/java/android/media/IMediaHTTPConnection.aidl \
|
||||||
media/java/android/media/IMediaHTTPService.aidl \
|
media/java/android/media/IMediaHTTPService.aidl \
|
||||||
media/java/android/media/IMediaRouterClient.aidl \
|
media/java/android/media/IMediaRouterClient.aidl \
|
||||||
media/java/android/media/IMediaRouterService.aidl \
|
media/java/android/media/IMediaRouterService.aidl \
|
||||||
media/java/android/media/IMediaScannerListener.aidl \
|
media/java/android/media/IMediaScannerListener.aidl \
|
||||||
media/java/android/media/IMediaScannerService.aidl \
|
media/java/android/media/IMediaScannerService.aidl \
|
||||||
|
media/java/android/media/IMediaSession.aidl \
|
||||||
|
media/java/android/media/IMediaSessionCallback.aidl \
|
||||||
|
media/java/android/media/IMediaSessionManager.aidl \
|
||||||
media/java/android/media/IRemoteControlClient.aidl \
|
media/java/android/media/IRemoteControlClient.aidl \
|
||||||
media/java/android/media/IRemoteControlDisplay.aidl \
|
media/java/android/media/IRemoteControlDisplay.aidl \
|
||||||
media/java/android/media/IRemoteDisplayCallback.aidl \
|
media/java/android/media/IRemoteDisplayCallback.aidl \
|
||||||
|
|||||||
@@ -6317,6 +6317,7 @@ package android.content {
|
|||||||
field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
|
field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
|
||||||
field public static final java.lang.String LOCATION_SERVICE = "location";
|
field public static final java.lang.String LOCATION_SERVICE = "location";
|
||||||
field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
|
field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
|
||||||
|
field public static final java.lang.String MEDIA_SESSION_SERVICE = "media_session";
|
||||||
field public static final int MODE_APPEND = 32768; // 0x8000
|
field public static final int MODE_APPEND = 32768; // 0x8000
|
||||||
field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
|
field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
|
||||||
field public static final int MODE_MULTI_PROCESS = 4; // 0x4
|
field public static final int MODE_MULTI_PROCESS = 4; // 0x4
|
||||||
@@ -13156,6 +13157,23 @@ package android.media {
|
|||||||
method public static final android.media.MediaCodecInfo getCodecInfoAt(int);
|
method public static final android.media.MediaCodecInfo getCodecInfoAt(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class MediaController {
|
||||||
|
ctor public MediaController(android.media.MediaSessionToken);
|
||||||
|
method public void addCallback(android.media.MediaController.Callback);
|
||||||
|
method public void addCallback(android.media.MediaController.Callback, android.os.Handler);
|
||||||
|
method public void removeCallback(android.media.MediaController.Callback);
|
||||||
|
method public void sendCommand(java.lang.String, android.os.Bundle);
|
||||||
|
method public void sendMediaButton(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class MediaController.Callback {
|
||||||
|
ctor public MediaController.Callback();
|
||||||
|
method public void onEvent(java.lang.String, android.os.Bundle);
|
||||||
|
method public void onMetadataUpdate(android.os.Bundle);
|
||||||
|
method public void onPlaybackStateChange(int);
|
||||||
|
method public void onRouteChanged(android.os.Bundle);
|
||||||
|
}
|
||||||
|
|
||||||
public final class MediaCrypto {
|
public final class MediaCrypto {
|
||||||
ctor public MediaCrypto(java.util.UUID, byte[]) throws android.media.MediaCryptoException;
|
ctor public MediaCrypto(java.util.UUID, byte[]) throws android.media.MediaCryptoException;
|
||||||
method public static final boolean isCryptoSchemeSupported(java.util.UUID);
|
method public static final boolean isCryptoSchemeSupported(java.util.UUID);
|
||||||
@@ -13722,6 +13740,33 @@ package android.media {
|
|||||||
method public abstract void onScanCompleted(java.lang.String, android.net.Uri);
|
method public abstract void onScanCompleted(java.lang.String, android.net.Uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class MediaSession {
|
||||||
|
method public void addCallback(android.media.MediaSession.Callback);
|
||||||
|
method public void addCallback(android.media.MediaSession.Callback, android.os.Handler);
|
||||||
|
method public android.media.MediaSessionToken getSessionToken();
|
||||||
|
method public void release();
|
||||||
|
method public void removeCallback(android.media.MediaSession.Callback);
|
||||||
|
method public void setPlaybackState(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class MediaSession.Callback {
|
||||||
|
ctor public MediaSession.Callback();
|
||||||
|
method public void onCommand(java.lang.String, android.os.Bundle);
|
||||||
|
method public void onMediaButton(android.content.Intent);
|
||||||
|
method public void onRequestRouteChange(android.os.Bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class MediaSessionManager {
|
||||||
|
method public android.media.MediaSession createSession(java.lang.String);
|
||||||
|
method public java.util.List<android.media.MediaController> getActiveSessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MediaSessionToken implements android.os.Parcelable {
|
||||||
|
method public int describeContents();
|
||||||
|
method public void writeToParcel(android.os.Parcel, int);
|
||||||
|
field public static final android.os.Parcelable.Creator CREATOR;
|
||||||
|
}
|
||||||
|
|
||||||
public class MediaSyncEvent {
|
public class MediaSyncEvent {
|
||||||
method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException;
|
method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException;
|
||||||
method public int getAudioSessionId();
|
method public int getAudioSessionId();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package android.app;
|
package android.app;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import com.android.internal.policy.PolicyManager;
|
import com.android.internal.policy.PolicyManager;
|
||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ import android.location.ILocationManager;
|
|||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.MediaRouter;
|
import android.media.MediaRouter;
|
||||||
|
import android.media.MediaSessionManager;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.IConnectivityManager;
|
import android.net.IConnectivityManager;
|
||||||
import android.net.INetworkPolicyManager;
|
import android.net.INetworkPolicyManager;
|
||||||
@@ -587,6 +589,12 @@ class ContextImpl extends Context {
|
|||||||
public Object createService(ContextImpl ctx) {
|
public Object createService(ContextImpl ctx) {
|
||||||
return new ConsumerIrManager(ctx);
|
return new ConsumerIrManager(ctx);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
|
||||||
|
public Object createService(ContextImpl ctx) {
|
||||||
|
return new MediaSessionManager(ctx);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static ContextImpl getImpl(Context context) {
|
static ContextImpl getImpl(Context context) {
|
||||||
|
|||||||
@@ -1989,7 +1989,8 @@ public abstract class Context {
|
|||||||
USER_SERVICE,
|
USER_SERVICE,
|
||||||
//@hide: APP_OPS_SERVICE
|
//@hide: APP_OPS_SERVICE
|
||||||
CAMERA_SERVICE,
|
CAMERA_SERVICE,
|
||||||
PRINT_SERVICE
|
PRINT_SERVICE,
|
||||||
|
MEDIA_SESSION_SERVICE,
|
||||||
})
|
})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface ServiceName {}
|
public @interface ServiceName {}
|
||||||
@@ -2349,6 +2350,15 @@ public abstract class Context {
|
|||||||
*/
|
*/
|
||||||
public static final String MEDIA_ROUTER_SERVICE = "media_router";
|
public static final String MEDIA_ROUTER_SERVICE = "media_router";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use with {@link #getSystemService} to retrieve a
|
||||||
|
* {@link android.media.MediaSessionManager} for managing media Sessions.
|
||||||
|
*
|
||||||
|
* @see #getSystemService
|
||||||
|
* @see android.media.MediaSessionManager
|
||||||
|
*/
|
||||||
|
public static final String MEDIA_SESSION_SERVICE = "media_session";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use with {@link #getSystemService} to retrieve a
|
* Use with {@link #getSystemService} to retrieve a
|
||||||
* {@link android.telephony.TelephonyManager} for handling management the
|
* {@link android.telephony.TelephonyManager} for handling management the
|
||||||
|
|||||||
@@ -1849,6 +1849,30 @@ public class KeyEvent extends InputEvent implements Parcelable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this key is a media key, which can be send to apps that are
|
||||||
|
* interested in media key events.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final boolean isMediaKey(int keyCode) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||||
|
case KeyEvent.KEYCODE_MUTE:
|
||||||
|
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_REWIND:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_RECORD:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public final int getDeviceId() {
|
public final int getDeviceId() {
|
||||||
|
|||||||
34
media/java/android/media/IMediaController.aidl
Normal file
34
media/java/android/media/IMediaController.aidl
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/* Copyright (C) 2014 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.content.Intent;
|
||||||
|
import android.media.IMediaControllerCallback;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to a MediaSession in the system.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
interface IMediaController {
|
||||||
|
void sendCommand(String command, in Bundle extras);
|
||||||
|
void sendMediaButton(in KeyEvent mediaButton);
|
||||||
|
void registerCallbackListener(in IMediaControllerCallback cb);
|
||||||
|
void unregisterCallbackListener(in IMediaControllerCallback cb);
|
||||||
|
int getPlaybackState();
|
||||||
|
}
|
||||||
28
media/java/android/media/IMediaControllerCallback.aidl
Normal file
28
media/java/android/media/IMediaControllerCallback.aidl
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* Copyright (C) 2014 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.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
oneway interface IMediaControllerCallback {
|
||||||
|
void onEvent(String event, in Bundle extras);
|
||||||
|
void onMetadataUpdate(in Bundle metadata);
|
||||||
|
void onPlaybackUpdate(int newState);
|
||||||
|
void onRouteChanged(in Bundle route);
|
||||||
|
}
|
||||||
33
media/java/android/media/IMediaSession.aidl
Normal file
33
media/java/android/media/IMediaSession.aidl
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/* Copyright (C) 2014 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.IMediaController;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to a MediaSession in the system.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
interface IMediaSession {
|
||||||
|
void sendEvent(in Bundle data);
|
||||||
|
IMediaController getMediaSessionToken();
|
||||||
|
void setPlaybackState(int state);
|
||||||
|
void setMetadata(in Bundle metadata);
|
||||||
|
void setRouteState(in Bundle routeState);
|
||||||
|
void setRoute(in Bundle mediaRouteDescriptor);
|
||||||
|
void destroy();
|
||||||
|
}
|
||||||
29
media/java/android/media/IMediaSessionCallback.aidl
Normal file
29
media/java/android/media/IMediaSessionCallback.aidl
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/* Copyright (C) 2014 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.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
oneway interface IMediaSessionCallback {
|
||||||
|
void onCommand(String command, in Bundle extras);
|
||||||
|
void onMediaButton(in Intent mediaRequestIntent);
|
||||||
|
void onRequestRouteChange(in Bundle route);
|
||||||
|
}
|
||||||
28
media/java/android/media/IMediaSessionManager.aidl
Normal file
28
media/java/android/media/IMediaSessionManager.aidl
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* Copyright (C) 2014 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.IMediaSession;
|
||||||
|
import android.media.IMediaSessionCallback;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to the MediaSessionManagerService
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
interface IMediaSessionManager {
|
||||||
|
IMediaSession createSession(String packageName, in IMediaSessionCallback cb, String tag);
|
||||||
|
}
|
||||||
363
media/java/android/media/MediaController.java
Normal file
363
media/java/android/media/MediaController.java
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows an app to interact with an ongoing media session. Media buttons and
|
||||||
|
* other commands can be sent to the session. A callback may be registered to
|
||||||
|
* receive updates from the session, such as metadata and play state changes.
|
||||||
|
* <p>
|
||||||
|
* A MediaController can be created through {@link MediaSessionManager} if you
|
||||||
|
* hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
|
||||||
|
* you have a {@link MediaSessionToken} from the session owner.
|
||||||
|
* <p>
|
||||||
|
* MediaController objects are thread-safe.
|
||||||
|
*/
|
||||||
|
public final class MediaController {
|
||||||
|
private static final String TAG = "MediaController";
|
||||||
|
|
||||||
|
private static final int MESSAGE_EVENT = 1;
|
||||||
|
private static final int MESSAGE_PLAYBACK_STATE = 2;
|
||||||
|
private static final int MESSAGE_METADATA = 3;
|
||||||
|
private static final int MESSAGE_ROUTE = 4;
|
||||||
|
|
||||||
|
private static final String KEY_EVENT = "event";
|
||||||
|
private static final String KEY_EXTRAS = "extras";
|
||||||
|
|
||||||
|
private final IMediaController mSessionBinder;
|
||||||
|
|
||||||
|
private final CallbackStub mCbStub = new CallbackStub();
|
||||||
|
private final ArrayList<Callback> mCbs = new ArrayList<Callback>();
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
private boolean mCbRegistered = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you have a {@link MediaSessionToken} from the owner of the session a
|
||||||
|
* controller can be created directly. It is up to the session creator to
|
||||||
|
* handle token distribution if desired.
|
||||||
|
*
|
||||||
|
* @see MediaSession#getSessionToken()
|
||||||
|
* @param token A token from the creator of the session
|
||||||
|
*/
|
||||||
|
public MediaController(MediaSessionToken token) {
|
||||||
|
mSessionBinder = token.getBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public MediaController(IMediaController sessionBinder) {
|
||||||
|
mSessionBinder = sessionBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a generic command to the session. It is up to the session creator
|
||||||
|
* to decide what commands and parameters they will support. As such,
|
||||||
|
* commands should only be sent to sessions that the controller owns.
|
||||||
|
*
|
||||||
|
* @param command The command to send
|
||||||
|
* @param params Any parameters to include with the command
|
||||||
|
*/
|
||||||
|
public void sendCommand(String command, Bundle params) {
|
||||||
|
if (TextUtils.isEmpty(command)) {
|
||||||
|
throw new IllegalArgumentException("command cannot be null or empty");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mSessionBinder.sendCommand(command, params);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d(TAG, "Dead object in sendCommand.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the specified media button to the session. Only media keys can be
|
||||||
|
* sent using this method.
|
||||||
|
*
|
||||||
|
* @param keycode The media button keycode, such as
|
||||||
|
* {@link KeyEvent#KEYCODE_MEDIA_BUTTON_PLAY}.
|
||||||
|
*/
|
||||||
|
public void sendMediaButton(int keycode) {
|
||||||
|
if (!KeyEvent.isMediaKey(keycode)) {
|
||||||
|
throw new IllegalArgumentException("May only send media buttons through "
|
||||||
|
+ "sendMediaButton");
|
||||||
|
}
|
||||||
|
// TODO do something better than key down/up events
|
||||||
|
KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keycode);
|
||||||
|
try {
|
||||||
|
mSessionBinder.sendMediaButton(event);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d(TAG, "Dead object in sendMediaButton", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a callback to receive updates from the Session. Updates will be
|
||||||
|
* posted on the caller's thread.
|
||||||
|
*
|
||||||
|
* @param cb The callback object, must not be null
|
||||||
|
*/
|
||||||
|
public void addCallback(Callback cb) {
|
||||||
|
addCallback(cb, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a callback to receive updates from the session. Updates will be
|
||||||
|
* posted on the specified handler.
|
||||||
|
*
|
||||||
|
* @param cb Cannot be null.
|
||||||
|
* @param handler The handler to post updates on, if null the callers thread
|
||||||
|
* will be used
|
||||||
|
*/
|
||||||
|
public void addCallback(Callback cb, Handler handler) {
|
||||||
|
if (handler == null) {
|
||||||
|
handler = new Handler();
|
||||||
|
}
|
||||||
|
synchronized (mLock) {
|
||||||
|
addCallbackLocked(cb, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop receiving updates on the specified callback. If an update has
|
||||||
|
* already been posted you may still receive it after calling this method.
|
||||||
|
*
|
||||||
|
* @param cb The callback to remove
|
||||||
|
*/
|
||||||
|
public void removeCallback(Callback cb) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
removeCallbackLocked(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
IMediaController getSessionBinder() {
|
||||||
|
return mSessionBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCallbackLocked(Callback cb, Handler handler) {
|
||||||
|
if (cb == null) {
|
||||||
|
throw new IllegalArgumentException("Callback cannot be null");
|
||||||
|
}
|
||||||
|
if (handler == null) {
|
||||||
|
throw new IllegalArgumentException("Handler cannot be null");
|
||||||
|
}
|
||||||
|
if (mCbs.contains(cb)) {
|
||||||
|
Log.w(TAG, "Callback is already added, ignoring");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.setHandler(handler);
|
||||||
|
mCbs.add(cb);
|
||||||
|
|
||||||
|
// Only register one cb binder, track callbacks internally and notify
|
||||||
|
if (!mCbRegistered) {
|
||||||
|
try {
|
||||||
|
mSessionBinder.registerCallbackListener(mCbStub);
|
||||||
|
mCbRegistered = true;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d(TAG, "Dead object in registerCallback", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeCallbackLocked(Callback cb) {
|
||||||
|
if (cb == null) {
|
||||||
|
throw new IllegalArgumentException("Callback cannot be null");
|
||||||
|
}
|
||||||
|
mCbs.remove(cb);
|
||||||
|
|
||||||
|
if (mCbs.size() == 0 && mCbRegistered) {
|
||||||
|
try {
|
||||||
|
mSessionBinder.unregisterCallbackListener(mCbStub);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d(TAG, "Dead object in unregisterCallback", e);
|
||||||
|
}
|
||||||
|
mCbRegistered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushOnEventLocked(String event, Bundle extras) {
|
||||||
|
for (int i = mCbs.size() - 1; i >= 0; i--) {
|
||||||
|
mCbs.get(i).postEvent(event, extras);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushOnMetadataUpdateLocked(Bundle metadata) {
|
||||||
|
for (int i = mCbs.size() - 1; i >= 0; i--) {
|
||||||
|
mCbs.get(i).postMetadataUpdate(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushOnPlaybackUpdateLocked(int newState) {
|
||||||
|
for (int i = mCbs.size() - 1; i >= 0; i--) {
|
||||||
|
mCbs.get(i).postPlaybackStateChange(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushOnRouteChangedLocked(Bundle routeDescriptor) {
|
||||||
|
for (int i = mCbs.size() - 1; i >= 0; i--) {
|
||||||
|
mCbs.get(i).postRouteChanged(routeDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MediaSession callbacks will be posted on the thread that created the
|
||||||
|
* Callback object.
|
||||||
|
*/
|
||||||
|
public static abstract class Callback {
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to handle custom events sent by the session owner.
|
||||||
|
* Controllers should only handle these for sessions they own.
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
public void onEvent(String event, Bundle extras) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to handle updates to the playback state. Valid values are in
|
||||||
|
* {@link RemoteControlClient}. TODO put playstate values somewhere more
|
||||||
|
* generic.
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
|
public void onPlaybackStateChange(int state) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to handle metadata changes for this session's media. The
|
||||||
|
* default supported fields are those in {@link MediaMetadataRetriever}.
|
||||||
|
*
|
||||||
|
* @param metadata
|
||||||
|
*/
|
||||||
|
public void onMetadataUpdate(Bundle metadata) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to handle route changes for this session.
|
||||||
|
*
|
||||||
|
* @param route
|
||||||
|
*/
|
||||||
|
public void onRouteChanged(Bundle route) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHandler(Handler handler) {
|
||||||
|
mHandler = new MessageHandler(handler.getLooper(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postEvent(String event, Bundle extras) {
|
||||||
|
Bundle eventBundle = new Bundle();
|
||||||
|
eventBundle.putString(KEY_EVENT, event);
|
||||||
|
eventBundle.putBundle(KEY_EXTRAS, extras);
|
||||||
|
Message msg = mHandler.obtainMessage(MESSAGE_EVENT, eventBundle);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postPlaybackStateChange(final int state) {
|
||||||
|
Message msg = mHandler.obtainMessage(MESSAGE_PLAYBACK_STATE, state, 0);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postMetadataUpdate(final Bundle metadata) {
|
||||||
|
Message msg = mHandler.obtainMessage(MESSAGE_METADATA, metadata);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postRouteChanged(final Bundle descriptor) {
|
||||||
|
Message msg = mHandler.obtainMessage(MESSAGE_ROUTE, descriptor);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class CallbackStub extends IMediaControllerCallback.Stub {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(String event, Bundle extras) throws RemoteException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
pushOnEventLocked(event, extras);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetadataUpdate(Bundle metadata) throws RemoteException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
pushOnMetadataUpdateLocked(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackUpdate(final int newState) throws RemoteException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
pushOnPlaybackUpdateLocked(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRouteChanged(Bundle mediaRouteDescriptor) throws RemoteException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
pushOnRouteChangedLocked(mediaRouteDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static class MessageHandler extends Handler {
|
||||||
|
private final MediaController.Callback mCb;
|
||||||
|
|
||||||
|
public MessageHandler(Looper looper, MediaController.Callback cb) {
|
||||||
|
super(looper);
|
||||||
|
mCb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MESSAGE_EVENT:
|
||||||
|
Bundle eventBundle = (Bundle) msg.obj;
|
||||||
|
String event = eventBundle.getString(KEY_EVENT);
|
||||||
|
Bundle extras = eventBundle.getBundle(KEY_EXTRAS);
|
||||||
|
mCb.onEvent(event, extras);
|
||||||
|
break;
|
||||||
|
case MESSAGE_PLAYBACK_STATE:
|
||||||
|
mCb.onPlaybackStateChange(msg.arg1);
|
||||||
|
break;
|
||||||
|
case MESSAGE_METADATA:
|
||||||
|
mCb.onMetadataUpdate((Bundle) msg.obj);
|
||||||
|
break;
|
||||||
|
case MESSAGE_ROUTE:
|
||||||
|
mCb.onRouteChanged((Bundle) msg.obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -262,7 +262,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForServer di =
|
final DisplayInfoForServer di =
|
||||||
(DisplayInfoForServer) displayIterator.next();
|
displayIterator.next();
|
||||||
if (di.mClientNotifListComp != null) {
|
if (di.mClientNotifListComp != null) {
|
||||||
boolean wasEnabled = di.mEnabled;
|
boolean wasEnabled = di.mEnabled;
|
||||||
di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
|
di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
|
||||||
@@ -538,7 +538,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
// evaluated it, traversal order doesn't matter here)
|
// evaluated it, traversal order doesn't matter here)
|
||||||
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
|
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
|
||||||
while(stackIterator.hasNext()) {
|
while(stackIterator.hasNext()) {
|
||||||
FocusRequester fr = (FocusRequester)stackIterator.next();
|
FocusRequester fr = stackIterator.next();
|
||||||
if(fr.hasSameClient(clientToRemove)) {
|
if(fr.hasSameClient(clientToRemove)) {
|
||||||
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
|
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
|
||||||
+ clientToRemove);
|
+ clientToRemove);
|
||||||
@@ -562,7 +562,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
// evaluated it, traversal order doesn't matter here)
|
// evaluated it, traversal order doesn't matter here)
|
||||||
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
|
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
|
||||||
while(stackIterator.hasNext()) {
|
while(stackIterator.hasNext()) {
|
||||||
FocusRequester fr = (FocusRequester)stackIterator.next();
|
FocusRequester fr = stackIterator.next();
|
||||||
if(fr.hasSameBinder(cb)) {
|
if(fr.hasSameBinder(cb)) {
|
||||||
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb);
|
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb);
|
||||||
stackIterator.remove();
|
stackIterator.remove();
|
||||||
@@ -930,33 +930,11 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isMediaKeyCode(int keyCode) {
|
|
||||||
switch (keyCode) {
|
|
||||||
case KeyEvent.KEYCODE_MUTE:
|
|
||||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_REWIND:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_RECORD:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_CLOSE:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_EJECT:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
|
private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
|
||||||
if (keyEvent == null) {
|
if (keyEvent == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode());
|
return KeyEvent.isMediaKey(keyEvent.getKeyCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1383,7 +1361,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
synchronized(mRCStack) {
|
synchronized(mRCStack) {
|
||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = displayIterator.next();
|
||||||
pw.println(" IRCD: " + di.mRcDisplay +
|
pw.println(" IRCD: " + di.mRcDisplay +
|
||||||
" -- w:" + di.mArtworkExpectedWidth +
|
" -- w:" + di.mArtworkExpectedWidth +
|
||||||
" -- h:" + di.mArtworkExpectedHeight +
|
" -- h:" + di.mArtworkExpectedHeight +
|
||||||
@@ -1410,7 +1388,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
// (using an iterator on the stack so we can safely remove an entry after having
|
// (using an iterator on the stack so we can safely remove an entry after having
|
||||||
// evaluated it, traversal order doesn't matter here)
|
// evaluated it, traversal order doesn't matter here)
|
||||||
while(stackIterator.hasNext()) {
|
while(stackIterator.hasNext()) {
|
||||||
RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
|
RemoteControlStackEntry rcse = stackIterator.next();
|
||||||
if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) {
|
if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) {
|
||||||
// a stack entry is from the package being removed, remove it from the stack
|
// a stack entry is from the package being removed, remove it from the stack
|
||||||
stackIterator.remove();
|
stackIterator.remove();
|
||||||
@@ -2075,7 +2053,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
// remove the display from the list
|
// remove the display from the list
|
||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = displayIterator.next();
|
||||||
if (di.mRcDisplay == mRcDisplay) {
|
if (di.mRcDisplay == mRcDisplay) {
|
||||||
if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
|
if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
|
||||||
displayIterator.remove();
|
displayIterator.remove();
|
||||||
@@ -2099,7 +2077,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
|
private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
|
||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = displayIterator.next();
|
||||||
try {
|
try {
|
||||||
rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
|
rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
|
||||||
di.mArtworkExpectedHeight);
|
di.mArtworkExpectedHeight);
|
||||||
@@ -2137,7 +2115,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
|
private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
|
||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = displayIterator.next();
|
||||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2216,7 +2194,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
boolean displayWasPluggedIn = false;
|
boolean displayWasPluggedIn = false;
|
||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext() && !displayWasPluggedIn) {
|
while (displayIterator.hasNext() && !displayWasPluggedIn) {
|
||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = displayIterator.next();
|
||||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||||
displayWasPluggedIn = true;
|
displayWasPluggedIn = true;
|
||||||
di.release();
|
di.release();
|
||||||
@@ -2258,7 +2236,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
boolean artworkSizeUpdate = false;
|
boolean artworkSizeUpdate = false;
|
||||||
while (displayIterator.hasNext() && !artworkSizeUpdate) {
|
while (displayIterator.hasNext() && !artworkSizeUpdate) {
|
||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = displayIterator.next();
|
||||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||||
if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
|
if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
|
||||||
di.mArtworkExpectedWidth = w;
|
di.mArtworkExpectedWidth = w;
|
||||||
@@ -2305,7 +2283,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
// (display stack traversal order doesn't matter).
|
// (display stack traversal order doesn't matter).
|
||||||
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = displayIterator.next();
|
||||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||||
di.mWantsPositionSync = wantsSync;
|
di.mWantsPositionSync = wantsSync;
|
||||||
rcdRegistered = true;
|
rcdRegistered = true;
|
||||||
|
|||||||
302
media/java/android/media/MediaSession.java
Normal file
302
media/java/android/media/MediaSession.java
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.content.Intent;
|
||||||
|
import android.media.IMediaSession;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows interaction with media controllers, media routes, volume keys, media
|
||||||
|
* buttons, and transport controls.
|
||||||
|
* <p>
|
||||||
|
* A MediaSession should be created when an app wants to publish media playback
|
||||||
|
* information or negotiate with a media route. In general an app only needs one
|
||||||
|
* session for all playback, though multiple sessions can be created for sending
|
||||||
|
* media to multiple routes or to provide finer grain controls of media.
|
||||||
|
* <p>
|
||||||
|
* A MediaSession is created by calling
|
||||||
|
* {@link MediaSessionManager#createSession(String)}. Once a session is created
|
||||||
|
* apps that have the MEDIA_CONTENT_CONTROL permission can interact with the
|
||||||
|
* session through {@link MediaSessionManager#listActiveSessions()}. The owner
|
||||||
|
* of the session may also use {@link #getSessionToken()} to allow apps without
|
||||||
|
* this permission to create a {@link MediaController} to interact with this
|
||||||
|
* session.
|
||||||
|
* <p>
|
||||||
|
* To receive commands, media keys, and other events a Callback must be set with
|
||||||
|
* {@link #addCallback(Callback)}.
|
||||||
|
* <p>
|
||||||
|
* When an app is finished performing playback it must call {@link #release()}
|
||||||
|
* to clean up the session and notify any controllers.
|
||||||
|
* <p>
|
||||||
|
* MediaSession objects are thread safe
|
||||||
|
*/
|
||||||
|
public final class MediaSession {
|
||||||
|
private static final String TAG = "MediaSession";
|
||||||
|
|
||||||
|
private static final int MESSAGE_MEDIA_BUTTON = 1;
|
||||||
|
private static final int MESSAGE_COMMAND = 2;
|
||||||
|
private static final int MESSAGE_ROUTE_CHANGE = 3;
|
||||||
|
|
||||||
|
private static final String KEY_COMMAND = "command";
|
||||||
|
private static final String KEY_EXTRAS = "extras";
|
||||||
|
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
private final MediaSessionToken mSessionToken;
|
||||||
|
private final IMediaSession mBinder;
|
||||||
|
private final CallbackStub mCbStub;
|
||||||
|
|
||||||
|
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public MediaSession(IMediaSession binder, CallbackStub cbStub) {
|
||||||
|
mBinder = binder;
|
||||||
|
mCbStub = cbStub;
|
||||||
|
IMediaController controllerBinder = null;
|
||||||
|
try {
|
||||||
|
controllerBinder = mBinder.getMediaSessionToken();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
|
||||||
|
}
|
||||||
|
mSessionToken = new MediaSessionToken(controllerBinder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the callback to receive updates on.
|
||||||
|
*
|
||||||
|
* @param callback The callback object
|
||||||
|
*/
|
||||||
|
public void addCallback(Callback callback) {
|
||||||
|
addCallback(callback, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCallback(Callback callback, Handler handler) {
|
||||||
|
if (callback == null) {
|
||||||
|
throw new IllegalArgumentException("Callback cannot be null");
|
||||||
|
}
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mCallbacks.contains(callback)) {
|
||||||
|
Log.w(TAG, "Callback is already added, ignoring");
|
||||||
|
}
|
||||||
|
if (handler == null) {
|
||||||
|
handler = new Handler();
|
||||||
|
}
|
||||||
|
MessageHandler msgHandler = new MessageHandler(handler.getLooper(), callback);
|
||||||
|
callback.setHandler(msgHandler);
|
||||||
|
mCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCallback(Callback callback) {
|
||||||
|
mCallbacks.remove(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish the current playback state to the system and any controllers.
|
||||||
|
* Valid values are defined in {@link RemoteControlClient}. TODO move play
|
||||||
|
* states somewhere else.
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
|
public void setPlaybackState(int state) {
|
||||||
|
try {
|
||||||
|
mBinder.setPlaybackState(state);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Dead object in setPlaybackState: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This must be called when an app has finished performing playback. If
|
||||||
|
* playback is expected to start again shortly the session can be left open,
|
||||||
|
* but it must be released if your activity or service is being destroyed.
|
||||||
|
*/
|
||||||
|
public void release() {
|
||||||
|
try {
|
||||||
|
mBinder.destroy();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Dead object in onDestroy: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a token object that can be used by apps to create a
|
||||||
|
* {@link MediaController} for interacting with this session. The owner of
|
||||||
|
* the session is responsible for deciding how to distribute these tokens.
|
||||||
|
*
|
||||||
|
* @return A token that can be used to create a MediaController for this
|
||||||
|
* session
|
||||||
|
*/
|
||||||
|
public MediaSessionToken getSessionToken() {
|
||||||
|
return mSessionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postCommand(String command, Bundle extras) {
|
||||||
|
Bundle commandBundle = new Bundle();
|
||||||
|
commandBundle.putString(KEY_COMMAND, command);
|
||||||
|
commandBundle.putBundle(KEY_EXTRAS, extras);
|
||||||
|
synchronized (mLock) {
|
||||||
|
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||||
|
Callback cb = mCallbacks.get(i);
|
||||||
|
Message msg = cb.mHandler.obtainMessage(MESSAGE_COMMAND, commandBundle);
|
||||||
|
cb.mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postMediaButton(Intent mediaButtonIntent) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||||
|
Callback cb = mCallbacks.get(i);
|
||||||
|
Message msg = cb.mHandler.obtainMessage(MESSAGE_MEDIA_BUTTON, mediaButtonIntent);
|
||||||
|
cb.mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postRequestRouteChange(Bundle mediaRouteDescriptor) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||||
|
Callback cb = mCallbacks.get(i);
|
||||||
|
Message msg = cb.mHandler.obtainMessage(MESSAGE_ROUTE_CHANGE, mediaRouteDescriptor);
|
||||||
|
cb.mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives commands or updates from controllers and routes. An app can
|
||||||
|
* specify what commands and buttons it supports by setting them on the
|
||||||
|
* MediaSession (TODO).
|
||||||
|
*/
|
||||||
|
public abstract static class Callback {
|
||||||
|
private MessageHandler mHandler;
|
||||||
|
|
||||||
|
public Callback() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a media button is pressed and this session has the
|
||||||
|
* highest priority or a controller sends a media button event to the
|
||||||
|
* session. TODO determine if using Intents identical to the ones
|
||||||
|
* RemoteControlClient receives is useful
|
||||||
|
* <p>
|
||||||
|
* The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
|
||||||
|
* KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
|
||||||
|
*
|
||||||
|
* @param mediaButtonIntent an intent containing the KeyEvent as an
|
||||||
|
* extra
|
||||||
|
*/
|
||||||
|
public void onMediaButton(Intent mediaButtonIntent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a controller has sent a custom command to this session.
|
||||||
|
* The owner of the session may handle custom commands but is not
|
||||||
|
* required to.
|
||||||
|
*
|
||||||
|
* @param command
|
||||||
|
* @param extras optional
|
||||||
|
*/
|
||||||
|
public void onCommand(String command, Bundle extras) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the user has selected a different route to connect to.
|
||||||
|
* The app is responsible for connecting to the new route and migrating
|
||||||
|
* ongoing playback if necessary.
|
||||||
|
*
|
||||||
|
* @param descriptor
|
||||||
|
*/
|
||||||
|
public void onRequestRouteChange(Bundle descriptor) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHandler(MessageHandler handler) {
|
||||||
|
mHandler = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static class CallbackStub extends IMediaSessionCallback.Stub {
|
||||||
|
private MediaSession mMediaSession;
|
||||||
|
|
||||||
|
public void setMediaSession(MediaSession session) {
|
||||||
|
mMediaSession = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCommand(String command, Bundle extras) throws RemoteException {
|
||||||
|
mMediaSession.postCommand(command, extras);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMediaButton(Intent mediaButtonIntent) throws RemoteException {
|
||||||
|
mMediaSession.postMediaButton(mediaButtonIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestRouteChange(Bundle mediaRouteDescriptor) throws RemoteException {
|
||||||
|
mMediaSession.postRequestRouteChange(mediaRouteDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageHandler extends Handler {
|
||||||
|
private MediaSession.Callback mCallback;
|
||||||
|
|
||||||
|
public MessageHandler(Looper looper, MediaSession.Callback callback) {
|
||||||
|
super(looper);
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mCallback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (msg.what) {
|
||||||
|
case MESSAGE_MEDIA_BUTTON:
|
||||||
|
mCallback.onMediaButton((Intent) msg.obj);
|
||||||
|
break;
|
||||||
|
case MESSAGE_COMMAND:
|
||||||
|
Bundle commandBundle = (Bundle) msg.obj;
|
||||||
|
String command = commandBundle.getString(KEY_COMMAND);
|
||||||
|
Bundle extras = commandBundle.getBundle(KEY_EXTRAS);
|
||||||
|
mCallback.onCommand(command, extras);
|
||||||
|
break;
|
||||||
|
case MESSAGE_ROUTE_CHANGE:
|
||||||
|
mCallback.onRequestRouteChange((Bundle) msg.obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
media/java/android/media/MediaSessionManager.java
Normal file
89
media/java/android/media/MediaSessionManager.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.content.Context;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.ServiceManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MediaSessionManager allows the creation and control of MediaSessions in the
|
||||||
|
* system. A MediaSession enables publishing information about ongoing media and
|
||||||
|
* interacting with MediaControllers and MediaRoutes.
|
||||||
|
* <p>
|
||||||
|
* Use <code>Context.getSystemService(Context.MEDIA_SESSION_SERVICE)</code> to
|
||||||
|
* get an instance of this class.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @see MediaSession
|
||||||
|
* @see MediaController
|
||||||
|
*/
|
||||||
|
public final class MediaSessionManager {
|
||||||
|
private static final String TAG = "MediaSessionManager";
|
||||||
|
|
||||||
|
private final IMediaSessionManager mService;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public MediaSessionManager(Context context) {
|
||||||
|
// Consider rewriting like DisplayManagerGlobal
|
||||||
|
// Decide if we need context
|
||||||
|
mContext = context;
|
||||||
|
IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
|
||||||
|
mService = IMediaSessionManager.Stub.asInterface(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new session.
|
||||||
|
*
|
||||||
|
* @param tag A short name for debugging purposes
|
||||||
|
* @return a {@link MediaSession} for the new session
|
||||||
|
*/
|
||||||
|
public MediaSession createSession(String tag) {
|
||||||
|
try {
|
||||||
|
MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
|
||||||
|
MediaSession session = new MediaSession(mService
|
||||||
|
.createSession(mContext.getPackageName(), cbStub, tag), cbStub);
|
||||||
|
cbStub.setMediaSession(session);
|
||||||
|
|
||||||
|
return session;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Failed to create session: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of controllers for all ongoing sessions. This requires the
|
||||||
|
* android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
|
||||||
|
* the calling app.
|
||||||
|
*
|
||||||
|
* @return a list of controllers for ongoing sessions
|
||||||
|
*/
|
||||||
|
public List<MediaController> getActiveSessions() {
|
||||||
|
// TODO
|
||||||
|
return new ArrayList<MediaController>();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
media/java/android/media/MediaSessionToken.aidl
Normal file
18
media/java/android/media/MediaSessionToken.aidl
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* Copyright 2014, 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;
|
||||||
|
|
||||||
|
parcelable MediaSessionToken;
|
||||||
65
media/java/android/media/MediaSessionToken.java
Normal file
65
media/java/android/media/MediaSessionToken.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public class MediaSessionToken implements Parcelable {
|
||||||
|
private IMediaController mBinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
MediaSessionToken(IMediaController binder) {
|
||||||
|
mBinder = binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaSessionToken(Parcel in) {
|
||||||
|
mBinder = IMediaController.Stub.asInterface(in.readStrongBinder());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
IMediaController getBinder() {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeStrongBinder(mBinder.asBinder());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<MediaSessionToken> CREATOR
|
||||||
|
= new Parcelable.Creator<MediaSessionToken>() {
|
||||||
|
@Override
|
||||||
|
public MediaSessionToken createFromParcel(Parcel in) {
|
||||||
|
return new MediaSessionToken(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaSessionToken[] newArray(int size) {
|
||||||
|
return new MediaSessionToken[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -264,7 +264,7 @@ public final class RemoteController
|
|||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
*/
|
*/
|
||||||
public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException {
|
public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException {
|
||||||
if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) {
|
if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
|
||||||
throw new IllegalArgumentException("not a media key event");
|
throw new IllegalArgumentException("not a media key event");
|
||||||
}
|
}
|
||||||
final PendingIntent pi;
|
final PendingIntent pi;
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.server.media;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.media.IMediaController;
|
||||||
|
import android.media.IMediaControllerCallback;
|
||||||
|
import android.media.IMediaSession;
|
||||||
|
import android.media.IMediaSessionCallback;
|
||||||
|
import android.media.RemoteControlClient;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the system implementation of a Session. Apps will interact with the
|
||||||
|
* MediaSession wrapper class instead.
|
||||||
|
*/
|
||||||
|
public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||||
|
private static final String TAG = "MediaSessionImpl";
|
||||||
|
|
||||||
|
private final int mPid;
|
||||||
|
private final String mPackageName;
|
||||||
|
private final String mTag;
|
||||||
|
private final ControllerStub mController;
|
||||||
|
private final SessionStub mSession;
|
||||||
|
private final SessionCb mSessionCb;
|
||||||
|
private final MediaSessionService mService;
|
||||||
|
|
||||||
|
private final ArrayList<IMediaControllerCallback> mSessionCallbacks =
|
||||||
|
new ArrayList<IMediaControllerCallback>();
|
||||||
|
|
||||||
|
private int mPlaybackState = RemoteControlClient.PLAYSTATE_NONE;
|
||||||
|
|
||||||
|
public MediaSessionRecord(int pid, String packageName, IMediaSessionCallback cb, String tag,
|
||||||
|
MediaSessionService service) {
|
||||||
|
mPid = pid;
|
||||||
|
mPackageName = packageName;
|
||||||
|
mTag = tag;
|
||||||
|
mController = new ControllerStub();
|
||||||
|
mSession = new SessionStub();
|
||||||
|
mSessionCb = new SessionCb(cb);
|
||||||
|
mService = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMediaSession getSessionBinder() {
|
||||||
|
return mSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMediaController getControllerBinder() {
|
||||||
|
return mController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaybackStateInternal(int state) {
|
||||||
|
mPlaybackState = state;
|
||||||
|
for (int i = mSessionCallbacks.size() - 1; i >= 0; i--) {
|
||||||
|
IMediaControllerCallback cb = mSessionCallbacks.get(i);
|
||||||
|
try {
|
||||||
|
cb.onPlaybackUpdate(state);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d(TAG, "SessionCallback object dead in setPlaybackState.", e);
|
||||||
|
mSessionCallbacks.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void binderDied() {
|
||||||
|
mService.sessionDied(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDestroy() {
|
||||||
|
mService.destroySession(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class SessionStub extends IMediaSession.Stub {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlaybackState(int state) throws RemoteException {
|
||||||
|
setPlaybackStateInternal(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() throws RemoteException {
|
||||||
|
onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendEvent(Bundle data) throws RemoteException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMediaController getMediaSessionToken() throws RemoteException {
|
||||||
|
return mController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMetadata(Bundle metadata) throws RemoteException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRouteState(Bundle routeState) throws RemoteException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRoute(Bundle medaiRouteDescriptor) throws RemoteException {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionCb {
|
||||||
|
private final IMediaSessionCallback mCb;
|
||||||
|
|
||||||
|
public SessionCb(IMediaSessionCallback cb) {
|
||||||
|
mCb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMediaButton(KeyEvent keyEvent) {
|
||||||
|
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||||
|
mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
|
||||||
|
try {
|
||||||
|
mCb.onMediaButton(mediaButtonIntent);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d(TAG, "Controller object dead in sendMediaRequest.", e);
|
||||||
|
onDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCommand(String command, Bundle extras) {
|
||||||
|
try {
|
||||||
|
mCb.onCommand(command, extras);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d(TAG, "Controller object dead in sendCommand.", e);
|
||||||
|
onDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerCallbackListener(IMediaSessionCallback cb) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ControllerStub extends IMediaController.Stub {
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sendCommand(String command, Bundle extras) throws RemoteException {
|
||||||
|
mSessionCb.sendCommand(command, extras);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMediaButton(KeyEvent mediaButtonIntent) {
|
||||||
|
mSessionCb.sendMediaButton(mediaButtonIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void registerCallbackListener(IMediaControllerCallback cb) throws RemoteException {
|
||||||
|
if (!mSessionCallbacks.contains(cb)) {
|
||||||
|
mSessionCallbacks.add(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void unregisterCallbackListener(IMediaControllerCallback cb)
|
||||||
|
throws RemoteException {
|
||||||
|
mSessionCallbacks.remove(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getPlaybackState() throws RemoteException {
|
||||||
|
return mPlaybackState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.server.media;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.IMediaSession;
|
||||||
|
import android.media.IMediaSessionCallback;
|
||||||
|
import android.media.IMediaSessionManager;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.server.SystemService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System implementation of MediaSessionManager
|
||||||
|
*/
|
||||||
|
public class MediaSessionService extends SystemService {
|
||||||
|
private static final String TAG = "MediaSessionService";
|
||||||
|
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||||
|
|
||||||
|
private final SessionManagerImpl mSessionManagerImpl;
|
||||||
|
|
||||||
|
private final ArrayList<MediaSessionRecord> mSessions
|
||||||
|
= new ArrayList<MediaSessionRecord>();
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
public MediaSessionService(Context context) {
|
||||||
|
super(context);
|
||||||
|
mSessionManagerImpl = new SessionManagerImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sessionDied(MediaSessionRecord session) {
|
||||||
|
synchronized (mSessions) {
|
||||||
|
destroySessionLocked(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroySession(MediaSessionRecord session) {
|
||||||
|
synchronized (mSessions) {
|
||||||
|
destroySessionLocked(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void destroySessionLocked(MediaSessionRecord session) {
|
||||||
|
mSessions.remove(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforcePackageName(String packageName, int uid) {
|
||||||
|
if (TextUtils.isEmpty(packageName)) {
|
||||||
|
throw new IllegalArgumentException("packageName may not be empty");
|
||||||
|
}
|
||||||
|
String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
|
||||||
|
final int packageCount = packages.length;
|
||||||
|
for (int i = 0; i < packageCount; i++) {
|
||||||
|
if (packageName.equals(packages[i])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("packageName is not owned by the calling process");
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaSessionRecord createSessionInternal(int pid, String packageName,
|
||||||
|
IMediaSessionCallback cb, String tag) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
return createSessionLocked(pid, packageName, cb, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaSessionRecord createSessionLocked(int pid, String packageName,
|
||||||
|
IMediaSessionCallback cb, String tag) {
|
||||||
|
final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this);
|
||||||
|
try {
|
||||||
|
cb.asBinder().linkToDeath(session, 0);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new RuntimeException("Media Session owner died prematurely.", e);
|
||||||
|
}
|
||||||
|
synchronized (mSessions) {
|
||||||
|
mSessions.add(session);
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Created session for package " + packageName + " with tag " + tag);
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionManagerImpl extends IMediaSessionManager.Stub {
|
||||||
|
@Override
|
||||||
|
public IMediaSession createSession(String packageName, IMediaSessionCallback cb, String tag)
|
||||||
|
throws RemoteException {
|
||||||
|
final int pid = Binder.getCallingPid();
|
||||||
|
final int uid = Binder.getCallingUid();
|
||||||
|
final long token = Binder.clearCallingIdentity();
|
||||||
|
try {
|
||||||
|
enforcePackageName(packageName, uid);
|
||||||
|
if (cb == null) {
|
||||||
|
throw new IllegalArgumentException("Controller callback cannot be null");
|
||||||
|
}
|
||||||
|
return createSessionInternal(pid, packageName, cb, tag).getSessionBinder();
|
||||||
|
} finally {
|
||||||
|
Binder.restoreCallingIdentity(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -62,6 +62,7 @@ import com.android.server.input.InputManagerService;
|
|||||||
import com.android.server.lights.LightsManager;
|
import com.android.server.lights.LightsManager;
|
||||||
import com.android.server.lights.LightsService;
|
import com.android.server.lights.LightsService;
|
||||||
import com.android.server.media.MediaRouterService;
|
import com.android.server.media.MediaRouterService;
|
||||||
|
import com.android.server.media.MediaSessionService;
|
||||||
import com.android.server.net.NetworkPolicyManagerService;
|
import com.android.server.net.NetworkPolicyManagerService;
|
||||||
import com.android.server.net.NetworkStatsService;
|
import com.android.server.net.NetworkStatsService;
|
||||||
import com.android.server.notification.NotificationManagerService;
|
import com.android.server.notification.NotificationManagerService;
|
||||||
@@ -881,6 +882,13 @@ public final class SystemServer {
|
|||||||
reportWtf("starting Print Service", e);
|
reportWtf("starting Print Service", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Slog.i(TAG, "MediaSessionService");
|
||||||
|
mSystemServiceManager.startService(MediaSessionService.class);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
reportWtf("starting MediaSessionService", e);
|
||||||
|
}
|
||||||
|
|
||||||
if (!disableNonCoreServices) {
|
if (!disableNonCoreServices) {
|
||||||
try {
|
try {
|
||||||
Slog.i(TAG, "Media Router Service");
|
Slog.i(TAG, "Media Router Service");
|
||||||
|
|||||||
Reference in New Issue
Block a user