Add RouteProviders to the new Media APIs
Compiles and works with OneMedia. This currently is a rough test of the system for finding, connecting to, and sending messages to routes. This will just connect to the first route it finds when a request to open the route picker is made (and disconnect when another request is made). Change-Id: I5de5521a079471b9e02664be4654c0591dfd9a6d
This commit is contained in:
13
Android.mk
13
Android.mk
@@ -288,11 +288,14 @@ LOCAL_SRC_FILES += \
|
||||
media/java/android/media/IRemoteDisplayProvider.aidl \
|
||||
media/java/android/media/IRemoteVolumeObserver.aidl \
|
||||
media/java/android/media/IRingtonePlayer.aidl \
|
||||
media/java/android/media/session/IMediaController.aidl \
|
||||
media/java/android/media/session/IMediaControllerCallback.aidl \
|
||||
media/java/android/media/session/IMediaSession.aidl \
|
||||
media/java/android/media/session/IMediaSessionCallback.aidl \
|
||||
media/java/android/media/session/IMediaSessionManager.aidl \
|
||||
media/java/android/media/routeprovider/IRouteConnection.aidl \
|
||||
media/java/android/media/routeprovider/IRouteProvider.aidl \
|
||||
media/java/android/media/routeprovider/IRouteProviderCallback.aidl \
|
||||
media/java/android/media/session/ISessionController.aidl \
|
||||
media/java/android/media/session/ISessionControllerCallback.aidl \
|
||||
media/java/android/media/session/ISession.aidl \
|
||||
media/java/android/media/session/ISessionCallback.aidl \
|
||||
media/java/android/media/session/ISessionManager.aidl \
|
||||
telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
|
||||
telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
|
||||
telephony/java/com/android/internal/telephony/ITelephony.aidl \
|
||||
|
||||
249
api/current.txt
249
api/current.txt
@@ -27,6 +27,7 @@ package android {
|
||||
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
|
||||
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
|
||||
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
|
||||
field public static final java.lang.String BIND_ROUTE_PROVIDER = "android.permission.BIND_ROUTE_PROVIDER";
|
||||
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
|
||||
field public static final java.lang.String BIND_TRUST_AGENT_SERVICE = "android.permission.BIND_TRUST_AGENT_SERVICE";
|
||||
field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
|
||||
@@ -14924,24 +14925,68 @@ package android.media.effect {
|
||||
|
||||
}
|
||||
|
||||
package android.media.routeprovider {
|
||||
|
||||
public final class RouteConnection {
|
||||
ctor public RouteConnection(android.media.routeprovider.RouteProviderService, android.media.session.RouteInfo);
|
||||
method public android.media.routeprovider.RouteInterfaceHandler addRouteInterface(java.lang.String);
|
||||
method public android.media.routeprovider.RouteInterfaceHandler getRouteInterface(java.lang.String);
|
||||
method public void shutDown();
|
||||
}
|
||||
|
||||
public final class RouteInterfaceHandler {
|
||||
method public void addListener(android.media.routeprovider.RouteInterfaceHandler.CommandListener, android.os.Handler);
|
||||
method public java.lang.String getName();
|
||||
method public void removeListener(android.media.routeprovider.RouteInterfaceHandler.CommandListener);
|
||||
method public void sendEvent(java.lang.String, android.os.Bundle);
|
||||
method public static void sendResult(android.os.ResultReceiver, int, android.os.Bundle);
|
||||
}
|
||||
|
||||
public static abstract class RouteInterfaceHandler.CommandListener {
|
||||
ctor public RouteInterfaceHandler.CommandListener();
|
||||
method public abstract boolean onCommand(android.media.routeprovider.RouteInterfaceHandler, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
}
|
||||
|
||||
public final class RoutePlaybackControlsHandler {
|
||||
method public void addListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener);
|
||||
method public void addListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener, android.os.Handler);
|
||||
method public static android.media.routeprovider.RoutePlaybackControlsHandler addTo(android.media.routeprovider.RouteConnection);
|
||||
method public void removeListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener);
|
||||
method public void sendPlaybackChangeEvent(int);
|
||||
}
|
||||
|
||||
public static abstract class RoutePlaybackControlsHandler.Listener extends android.media.routeprovider.RouteInterfaceHandler.CommandListener {
|
||||
ctor public RoutePlaybackControlsHandler.Listener();
|
||||
method public boolean fastForward();
|
||||
method public long getCapabilities();
|
||||
method public long getCurrentPosition();
|
||||
method public final boolean onCommand(android.media.routeprovider.RouteInterfaceHandler, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
method public boolean pause();
|
||||
method public void playNow(java.lang.String, android.os.ResultReceiver);
|
||||
method public boolean resume();
|
||||
}
|
||||
|
||||
public abstract class RouteProviderService extends android.app.Service {
|
||||
ctor public RouteProviderService();
|
||||
method public abstract android.media.routeprovider.RouteConnection connect(android.media.session.RouteInfo, android.media.routeprovider.RouteRequest);
|
||||
method public abstract java.util.List<android.media.session.RouteInfo> getMatchingRoutes(java.util.List<android.media.routeprovider.RouteRequest>);
|
||||
method public android.os.IBinder onBind(android.content.Intent);
|
||||
method public void updateDiscoveryRequests(java.util.List<android.media.routeprovider.RouteRequest>);
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "com.android.media.session.MediaRouteProvider";
|
||||
}
|
||||
|
||||
public final class RouteRequest implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public android.media.session.RouteOptions getConnectionOptions();
|
||||
method public android.media.session.SessionInfo getSessionInfo();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.media.session {
|
||||
|
||||
public final class MediaController {
|
||||
method public void addCallback(android.media.session.MediaController.Callback);
|
||||
method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
|
||||
method public static android.media.session.MediaController fromToken(android.media.session.MediaSessionToken);
|
||||
method public android.media.session.TransportController getTransportController();
|
||||
method public void removeCallback(android.media.session.MediaController.Callback);
|
||||
method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
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 onRouteChanged(android.os.Bundle);
|
||||
}
|
||||
|
||||
public final class MediaMetadata implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public android.graphics.Bitmap getBitmap(java.lang.String);
|
||||
@@ -14982,36 +15027,6 @@ package android.media.session {
|
||||
method public android.media.session.MediaMetadata.Builder putString(java.lang.String, java.lang.String);
|
||||
}
|
||||
|
||||
public final class MediaSession {
|
||||
method public void addCallback(android.media.session.MediaSession.Callback);
|
||||
method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
|
||||
method public android.media.session.MediaSessionToken getSessionToken();
|
||||
method public android.media.session.TransportPerformer getTransportPerformer();
|
||||
method public void publish();
|
||||
method public void release();
|
||||
method public void removeCallback(android.media.session.MediaSession.Callback);
|
||||
method public void sendEvent(java.lang.String, android.os.Bundle);
|
||||
method public android.media.session.TransportPerformer setTransportPerformerEnabled();
|
||||
}
|
||||
|
||||
public static abstract class MediaSession.Callback {
|
||||
ctor public MediaSession.Callback();
|
||||
method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
method public void onMediaButton(android.content.Intent);
|
||||
method public void onRequestRouteChange(android.os.Bundle);
|
||||
}
|
||||
|
||||
public final class MediaSessionManager {
|
||||
method public android.media.session.MediaSession createSession(java.lang.String);
|
||||
method public java.util.List<android.media.session.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 final class PlaybackState implements android.os.Parcelable {
|
||||
ctor public PlaybackState();
|
||||
ctor public PlaybackState(android.media.session.PlaybackState);
|
||||
@@ -15040,6 +15055,7 @@ package android.media.session {
|
||||
field public static final long ACTION_STOP = 1L; // 0x1L
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
field public static final int PLAYSTATE_BUFFERING = 6; // 0x6
|
||||
field public static final int PLAYSTATE_CONNECTING = 8; // 0x8
|
||||
field public static final int PLAYSTATE_ERROR = 7; // 0x7
|
||||
field public static final int PLAYSTATE_FAST_FORWARDING = 4; // 0x4
|
||||
field public static final int PLAYSTATE_NONE = 0; // 0x0
|
||||
@@ -15049,11 +15065,44 @@ package android.media.session {
|
||||
field public static final int PLAYSTATE_STOPPED = 1; // 0x1
|
||||
}
|
||||
|
||||
public final class Route {
|
||||
method public android.media.session.RouteInterface getInterface(java.lang.String);
|
||||
method public android.media.session.RouteOptions getOptions();
|
||||
method public android.media.session.RouteInfo getRouteInfo();
|
||||
}
|
||||
|
||||
public final class RouteInfo implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public java.util.List<android.media.session.RouteOptions> getConnectionMethods();
|
||||
method public java.lang.String getId();
|
||||
method public java.lang.String getName();
|
||||
method public java.lang.String getProvider();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
}
|
||||
|
||||
public static final class RouteInfo.Builder {
|
||||
ctor public RouteInfo.Builder(android.media.session.RouteInfo);
|
||||
ctor public RouteInfo.Builder();
|
||||
method public android.media.session.RouteInfo.Builder addRouteOptions(android.media.session.RouteOptions);
|
||||
method public android.media.session.RouteInfo build();
|
||||
method public android.media.session.RouteInfo.Builder clearRouteOptions();
|
||||
method public int getOptionsSize();
|
||||
method public android.media.session.RouteInfo.Builder setId(java.lang.String);
|
||||
method public android.media.session.RouteInfo.Builder setName(java.lang.String);
|
||||
}
|
||||
|
||||
public final class RouteInterface {
|
||||
method public void addListener(android.media.session.RouteInterface.EventListener);
|
||||
method public void addListener(android.media.session.RouteInterface.EventListener, android.os.Handler);
|
||||
method public void removeListener(android.media.session.RouteInterface.EventListener);
|
||||
method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
method public boolean sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
field public static final int RESULT_COMMAND_NOT_SUPPORTED = -3; // 0xfffffffd
|
||||
field public static final int RESULT_ERROR = -1; // 0xffffffff
|
||||
field public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2; // 0xfffffffe
|
||||
field public static final int RESULT_NOT_CONNECTED = -5; // 0xfffffffb
|
||||
field public static final int RESULT_ROUTE_IS_STALE = -4; // 0xfffffffc
|
||||
field public static final int RESULT_SUCCESS = 1; // 0x1
|
||||
}
|
||||
|
||||
public static abstract class RouteInterface.EventListener {
|
||||
@@ -15061,40 +15110,100 @@ package android.media.session {
|
||||
method public abstract void onEvent(java.lang.String, android.os.Bundle);
|
||||
}
|
||||
|
||||
public static abstract class RouteInterface.Stub {
|
||||
ctor public RouteInterface.Stub();
|
||||
method public abstract java.lang.String getName();
|
||||
method public abstract void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
method public final void sendEvent(android.media.session.MediaSession, java.lang.String, android.os.Bundle);
|
||||
public final class RouteOptions implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public android.os.Bundle getConnectionParams();
|
||||
method public java.util.List<java.lang.String> getInterfaceNames();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
}
|
||||
|
||||
public final class RouteTransportControls {
|
||||
method public void addListener(android.media.session.RouteTransportControls.Listener);
|
||||
method public void addListener(android.media.session.RouteTransportControls.Listener, android.os.Handler);
|
||||
method public void fastForward(float);
|
||||
method public static android.media.session.RouteTransportControls from(android.media.session.MediaController);
|
||||
public static final class RouteOptions.Builder {
|
||||
ctor public RouteOptions.Builder();
|
||||
method public android.media.session.RouteOptions.Builder addInterface(java.lang.String);
|
||||
method public android.media.session.RouteOptions build();
|
||||
method public android.media.session.RouteOptions.Builder setParameters(android.os.Bundle);
|
||||
}
|
||||
|
||||
public final class RoutePlaybackControls {
|
||||
method public void addListener(android.media.session.RoutePlaybackControls.Listener);
|
||||
method public void addListener(android.media.session.RoutePlaybackControls.Listener, android.os.Handler);
|
||||
method public void fastForward();
|
||||
method public static android.media.session.RoutePlaybackControls from(android.media.session.Route);
|
||||
method public void getCapabilities(android.os.ResultReceiver);
|
||||
method public void getCurrentPosition(android.os.ResultReceiver);
|
||||
method public void pause();
|
||||
method public void play();
|
||||
method public void removeListener(android.media.session.RouteTransportControls.Listener);
|
||||
field public static final java.lang.String NAME = "android.media.session.RouteTransportControls";
|
||||
method public void playNow(java.lang.String);
|
||||
method public void removeListener(android.media.session.RoutePlaybackControls.Listener);
|
||||
method public void resume();
|
||||
field public static final java.lang.String NAME = "android.media.session.RoutePlaybackControls";
|
||||
}
|
||||
|
||||
public static abstract class RouteTransportControls.Listener {
|
||||
ctor public RouteTransportControls.Listener();
|
||||
method public void onMetadataUpdate(android.os.Bundle);
|
||||
public static abstract class RoutePlaybackControls.Listener extends android.media.session.RouteInterface.EventListener {
|
||||
ctor public RoutePlaybackControls.Listener();
|
||||
method public final void onEvent(java.lang.String, android.os.Bundle);
|
||||
method public void onMetadataUpdate(android.media.session.MediaMetadata);
|
||||
method public void onPlaybackStateChange(int);
|
||||
}
|
||||
|
||||
public static abstract class RouteTransportControls.Stub extends android.media.session.RouteInterface.Stub {
|
||||
ctor public RouteTransportControls.Stub(android.media.session.MediaSession);
|
||||
method public void fastForward(float);
|
||||
method public long getCapabilities();
|
||||
method public long getCurrentPosition();
|
||||
method public java.lang.String getName();
|
||||
public final class Session {
|
||||
method public void addCallback(android.media.session.Session.Callback);
|
||||
method public void addCallback(android.media.session.Session.Callback, android.os.Handler);
|
||||
method public void connect(android.media.session.RouteInfo, android.media.session.RouteOptions);
|
||||
method public void disconnect(android.media.session.RouteInfo);
|
||||
method public android.media.session.SessionToken getSessionToken();
|
||||
method public android.media.session.TransportPerformer getTransportPerformer();
|
||||
method public void publish();
|
||||
method public void release();
|
||||
method public void removeCallback(android.media.session.Session.Callback);
|
||||
method public void sendEvent(java.lang.String, android.os.Bundle);
|
||||
method public void setRouteOptions(java.util.List<android.media.session.RouteOptions>);
|
||||
method public android.media.session.TransportPerformer setTransportPerformerEnabled();
|
||||
}
|
||||
|
||||
public static abstract class Session.Callback {
|
||||
ctor public Session.Callback();
|
||||
method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
method public final void updatePlaybackState(int);
|
||||
method public void onMediaButton(android.content.Intent);
|
||||
method public void onRequestRouteChange(android.media.session.RouteInfo);
|
||||
method public void onRouteConnected(android.media.session.Route);
|
||||
method public void onRouteDisconnected(android.media.session.Route, int);
|
||||
}
|
||||
|
||||
public final class SessionController {
|
||||
method public void addCallback(android.media.session.SessionController.Callback);
|
||||
method public void addCallback(android.media.session.SessionController.Callback, android.os.Handler);
|
||||
method public static android.media.session.SessionController fromToken(android.media.session.SessionToken);
|
||||
method public android.media.session.TransportController getTransportController();
|
||||
method public void removeCallback(android.media.session.SessionController.Callback);
|
||||
method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
|
||||
method public void sendMediaButton(int);
|
||||
method public void showRoutePicker();
|
||||
}
|
||||
|
||||
public static abstract class SessionController.Callback {
|
||||
ctor public SessionController.Callback();
|
||||
method public void onEvent(java.lang.String, android.os.Bundle);
|
||||
method public void onRouteChanged(android.media.session.RouteInfo);
|
||||
}
|
||||
|
||||
public final class SessionInfo implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public java.lang.String getId();
|
||||
method public java.lang.String getPackageName();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
}
|
||||
|
||||
public final class SessionManager {
|
||||
method public android.media.session.Session createSession(java.lang.String);
|
||||
method public java.util.List<android.media.session.SessionController> getActiveSessions();
|
||||
}
|
||||
|
||||
public class SessionToken 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 final class TransportController {
|
||||
|
||||
@@ -67,7 +67,7 @@ import android.location.ILocationManager;
|
||||
import android.location.LocationManager;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaRouter;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.session.SessionManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.IConnectivityManager;
|
||||
import android.net.INetworkPolicyManager;
|
||||
@@ -639,7 +639,7 @@ class ContextImpl extends Context {
|
||||
|
||||
registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
|
||||
public Object createService(ContextImpl ctx) {
|
||||
return new MediaSessionManager(ctx);
|
||||
return new SessionManager(ctx);
|
||||
}
|
||||
});
|
||||
registerService(TRUST_SERVICE, new ServiceFetcher() {
|
||||
|
||||
@@ -2387,10 +2387,10 @@ public abstract class Context {
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService} to retrieve a
|
||||
* {@link android.media.session.MediaSessionManager} for managing media Sessions.
|
||||
* {@link android.media.session.SessionManager} for managing media Sessions.
|
||||
*
|
||||
* @see #getSystemService
|
||||
* @see android.media.session.MediaSessionManager
|
||||
* @see android.media.session.SessionManager
|
||||
*/
|
||||
public static final String MEDIA_SESSION_SERVICE = "media_session";
|
||||
|
||||
|
||||
@@ -2070,6 +2070,14 @@
|
||||
android:description="@string/permdesc_bindTvInput"
|
||||
android:protectionLevel="signature|system" />
|
||||
|
||||
<!-- Must be required by a {@link android.media.routeprovider.RouteProviderService}
|
||||
to ensure that only the system can interact with it.
|
||||
-->
|
||||
<permission android:name="android.permission.BIND_ROUTE_PROVIDER"
|
||||
android:label="@string/permlab_bindRouteProvider"
|
||||
android:description="@string/permdesc_bindRouteProvider"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- Must be required by device administration receiver, to ensure that only the
|
||||
system can interact with it. -->
|
||||
<permission android:name="android.permission.BIND_DEVICE_ADMIN"
|
||||
|
||||
@@ -1076,6 +1076,12 @@
|
||||
<string name="permdesc_bindRemoteViews">Allows the holder to bind to the top-level
|
||||
interface of a widget service. Should never be needed for normal apps.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_bindRouteProvider">bind to a route provider service</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_bindRouteProvider">Allows the holder to bind to any registered
|
||||
route providers. Should never be needed for normal apps.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_bindDeviceAdmin">interact with a device admin</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
|
||||
28
media/java/android/media/routeprovider/IRouteConnection.aidl
Normal file
28
media/java/android/media/routeprovider/IRouteConnection.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.routeprovider;
|
||||
|
||||
import android.media.session.RouteCommand;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
/**
|
||||
* Interface for a specific connected route.
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IRouteConnection {
|
||||
void onCommand(in RouteCommand command, in ResultReceiver cb);
|
||||
void disconnect();
|
||||
}
|
||||
36
media/java/android/media/routeprovider/IRouteProvider.aidl
Normal file
36
media/java/android/media/routeprovider/IRouteProvider.aidl
Normal file
@@ -0,0 +1,36 @@
|
||||
/* 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.routeprovider;
|
||||
|
||||
import android.media.routeprovider.IRouteConnection;
|
||||
import android.media.routeprovider.IRouteProviderCallback;
|
||||
import android.media.routeprovider.RouteRequest;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
/**
|
||||
* Interface to an app's RouteProviderService.
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IRouteProvider {
|
||||
void registerCallback(in IRouteProviderCallback cb);
|
||||
void unregisterCallback(in IRouteProviderCallback cb);
|
||||
void updateDiscoveryRequests(in List<RouteRequest> requests);
|
||||
|
||||
void getAvailableRoutes(in List<RouteRequest> requests, in ResultReceiver cb);
|
||||
void connect(in RouteInfo route, in RouteRequest request, in ResultReceiver cb);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* 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.routeprovider;
|
||||
|
||||
import android.media.routeprovider.IRouteConnection;
|
||||
import android.media.session.RouteEvent;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
/**
|
||||
* System's provider callback interface.
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IRouteProviderCallback {
|
||||
void onRoutesChanged();
|
||||
void onConnectionStateChanged(in IRouteConnection connection, int state);
|
||||
void onConnectionTerminated(in IRouteConnection connection);
|
||||
void onRouteEvent(in RouteEvent event);
|
||||
}
|
||||
164
media/java/android/media/routeprovider/RouteConnection.java
Normal file
164
media/java/android/media/routeprovider/RouteConnection.java
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.routeprovider;
|
||||
|
||||
import android.media.routeprovider.IRouteConnection;
|
||||
import android.media.session.RouteCommand;
|
||||
import android.media.session.RouteEvent;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.RouteInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an ongoing connection between an application and a media route
|
||||
* offered by a media route provider.
|
||||
* <p>
|
||||
* The media route provider should add interfaces to the connection before
|
||||
* returning it to the system in order to receive commands from clients on those
|
||||
* interfaces. Use {@link #addRouteInterface(String)} to add an interface and
|
||||
* {@link #getRouteInterface(String)} to retrieve the interface's handle anytime
|
||||
* after it has been added.
|
||||
*/
|
||||
public final class RouteConnection {
|
||||
private static final String TAG = "RouteConnection";
|
||||
private final ConnectionStub mBinder;
|
||||
private final ArrayList<String> mIfaceNames = new ArrayList<String>();
|
||||
private final ArrayMap<String, RouteInterfaceHandler> mIfaces
|
||||
= new ArrayMap<String, RouteInterfaceHandler>();
|
||||
private final RouteProviderService mProvider;
|
||||
private final RouteInfo mRoute;
|
||||
|
||||
private boolean mPublished;
|
||||
|
||||
/**
|
||||
* Create a new connection for the given Provider and Route.
|
||||
*
|
||||
* @param provider The provider this route is associated with.
|
||||
* @param route The route this is a connection to.
|
||||
*/
|
||||
public RouteConnection(RouteProviderService provider, RouteInfo route) {
|
||||
if (provider == null) {
|
||||
throw new IllegalArgumentException("provider may not be null.");
|
||||
}
|
||||
if (route == null) {
|
||||
throw new IllegalArgumentException("route may not be null.");
|
||||
}
|
||||
mBinder = new ConnectionStub(this);
|
||||
mProvider = provider;
|
||||
mRoute = route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an interface to this route connection. All interfaces must be added
|
||||
* to the connection before the connection is returned to the system.
|
||||
*
|
||||
* @param ifaceName The name of the interface to add
|
||||
* @return The route interface that was registered
|
||||
*/
|
||||
public RouteInterfaceHandler addRouteInterface(String ifaceName) {
|
||||
if (TextUtils.isEmpty(ifaceName)) {
|
||||
throw new IllegalArgumentException("The interface's name may not be empty");
|
||||
}
|
||||
if (mPublished) {
|
||||
throw new IllegalStateException(
|
||||
"Connection has already been published to the system.");
|
||||
}
|
||||
RouteInterfaceHandler iface = mIfaces.get(ifaceName);
|
||||
if (iface == null) {
|
||||
iface = new RouteInterfaceHandler(this, ifaceName);
|
||||
mIfaceNames.add(ifaceName);
|
||||
mIfaces.put(ifaceName, iface);
|
||||
} else {
|
||||
Log.w(TAG, "Attempted to add an interface that already exists");
|
||||
}
|
||||
return iface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interface instance for the specified interface name. If the
|
||||
* interface was not added to this connection null will be returned.
|
||||
*
|
||||
* @param ifaceName The name of the interface to get.
|
||||
* @return The route interface with that name or null.
|
||||
*/
|
||||
public RouteInterfaceHandler getRouteInterface(String ifaceName) {
|
||||
return mIfaces.get(ifaceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection and inform the system that it may no longer be used.
|
||||
*/
|
||||
public void shutDown() {
|
||||
mProvider.disconnect(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void sendEvent(String iface, String event, Bundle extras) {
|
||||
RouteEvent e = new RouteEvent(mBinder, iface, event, extras);
|
||||
mProvider.sendRouteEvent(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
IRouteConnection.Stub getBinder() {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
void publish() {
|
||||
mPublished = true;
|
||||
}
|
||||
|
||||
private static class ConnectionStub extends IRouteConnection.Stub {
|
||||
private final WeakReference<RouteConnection> mConnection;
|
||||
|
||||
public ConnectionStub(RouteConnection connection) {
|
||||
mConnection = new WeakReference<RouteConnection>(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(RouteCommand command, ResultReceiver cb) {
|
||||
RouteConnection connection = mConnection.get();
|
||||
if (connection != null) {
|
||||
RouteInterfaceHandler iface = connection.mIfaces.get(command.getIface());
|
||||
if (iface != null) {
|
||||
iface.onCommand(command.getEvent(), command.getExtras(), cb);
|
||||
} else if (cb != null) {
|
||||
cb.send(RouteInterface.RESULT_INTERFACE_NOT_SUPPORTED, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.routeprovider;
|
||||
|
||||
import android.media.session.Route;
|
||||
import android.media.session.Session;
|
||||
import android.media.session.RouteInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ResultReceiver;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Represents an interface that an application may use to send requests to a
|
||||
* connected media route.
|
||||
* <p>
|
||||
* A {@link RouteProviderService} may expose multiple interfaces on a
|
||||
* {@link RouteConnection} for a {@link Session} to interact with. A
|
||||
* provider creates an interface with
|
||||
* {@link RouteConnection#addRouteInterface(String)} to allow messages to be
|
||||
* routed appropriately. Events are then sent through a specific interface and
|
||||
* all commands being sent on the interface will be sent to any registered
|
||||
* {@link CommandListener}s.
|
||||
* <p>
|
||||
* An interface instance can only be registered on one {@link RouteConnection}.
|
||||
* To use the same interface on multiple connections a new instance must be
|
||||
* created for each connection.
|
||||
* <p>
|
||||
* It is recommended you wrap this interface with a standard implementation to
|
||||
* avoid errors, but for simple interfaces this class may be used directly. TODO
|
||||
* add link to sample code.
|
||||
*/
|
||||
public final class RouteInterfaceHandler {
|
||||
private static final String TAG = "RouteInterfaceHandler";
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final RouteConnection mConnection;
|
||||
private final String mName;
|
||||
|
||||
private ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
|
||||
|
||||
/**
|
||||
* Create a new RouteInterface for a given connection. This can be used to
|
||||
* send events on the given interface and register listeners for commands
|
||||
* from the connected session.
|
||||
*
|
||||
* @param connection The connection this interface sends events on
|
||||
* @param ifaceName The name of this interface
|
||||
* @hide
|
||||
*/
|
||||
public RouteInterfaceHandler(RouteConnection connection, String ifaceName) {
|
||||
if (connection == null) {
|
||||
throw new IllegalArgumentException("connection may not be null");
|
||||
}
|
||||
if (TextUtils.isEmpty(ifaceName)) {
|
||||
throw new IllegalArgumentException("ifaceName can not be empty");
|
||||
}
|
||||
mConnection = connection;
|
||||
mName = ifaceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event on this interface to the connected session.
|
||||
*
|
||||
* @param event The event to send
|
||||
* @param extras Any extras for the event
|
||||
*/
|
||||
public void sendEvent(String event, Bundle extras) {
|
||||
mConnection.sendEvent(mName, event, extras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a result from a command to the specified callback. The result codes
|
||||
* in {@link RouteInterface} must be used. More information
|
||||
* about the result, whether successful or an error, should be included in
|
||||
* the extras.
|
||||
*
|
||||
* @param cb The callback to send the result to
|
||||
* @param resultCode The result code for the call
|
||||
* @param extras Any extras to include
|
||||
*/
|
||||
public static void sendResult(ResultReceiver cb, int resultCode, Bundle extras) {
|
||||
if (cb != null) {
|
||||
cb.send(resultCode, extras);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for this interface. If a handler is specified callbacks
|
||||
* will be performed on the handler's thread, otherwise the callers thread
|
||||
* will be used.
|
||||
*
|
||||
* @param listener The listener to receive calls on.
|
||||
* @param handler The handler whose thread to post calls on or null.
|
||||
*/
|
||||
public void addListener(CommandListener listener, Handler handler) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener may not be null");
|
||||
}
|
||||
Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
|
||||
synchronized (mLock) {
|
||||
if (findIndexOfListenerLocked(listener) != -1) {
|
||||
Log.d(TAG, "Listener is already added, ignoring");
|
||||
return;
|
||||
}
|
||||
mListeners.add(new MessageHandler(looper, listener));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener from this interface.
|
||||
*
|
||||
* @param listener The listener to stop receiving commands on.
|
||||
*/
|
||||
public void removeListener(CommandListener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener may not be null");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
int index = findIndexOfListenerLocked(listener);
|
||||
if (index != -1) {
|
||||
mListeners.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void onCommand(String command, Bundle args, ResultReceiver cb) {
|
||||
synchronized (mLock) {
|
||||
Command cmd = new Command(command, args, cb);
|
||||
for (int i = mListeners.size() - 1; i >= 0; i--) {
|
||||
mListeners.get(i).post(MessageHandler.MSG_COMMAND, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interface name.
|
||||
*
|
||||
* @return The name of this interface
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
private int findIndexOfListenerLocked(CommandListener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("Callback cannot be null");
|
||||
}
|
||||
for (int i = mListeners.size() - 1; i >= 0; i--) {
|
||||
MessageHandler handler = mListeners.get(i);
|
||||
if (listener == handler.mListener) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles commands sent to the interface.
|
||||
* <p>
|
||||
* Register an InterfaceListener using {@link #addListener}.
|
||||
*/
|
||||
public abstract static class CommandListener {
|
||||
/**
|
||||
* This is called when a command is received that matches this
|
||||
* interface. Commands are sent by a {@link Session} that is
|
||||
* connected to the route this interface is registered with.
|
||||
*
|
||||
* @param iface The interface the command was received on.
|
||||
* @param command The command or method to invoke.
|
||||
* @param args Any args that were included with the command. May be
|
||||
* null.
|
||||
* @param cb The callback provided to send a response on. May be null.
|
||||
* @return true if the command was handled, false otherwise. If the
|
||||
* command was not handled an error will be sent automatically.
|
||||
* true may be returned if the command will be handled
|
||||
* asynchronously.
|
||||
* @see Route
|
||||
* @see Session
|
||||
*/
|
||||
public abstract boolean onCommand(RouteInterfaceHandler iface, String command, Bundle args,
|
||||
ResultReceiver cb);
|
||||
}
|
||||
|
||||
private class MessageHandler extends Handler {
|
||||
private static final int MSG_COMMAND = 1;
|
||||
|
||||
private final CommandListener mListener;
|
||||
|
||||
public MessageHandler(Looper looper, CommandListener listener) {
|
||||
super(looper, null, true /* async */);
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_COMMAND:
|
||||
Command cmd = (Command) msg.obj;
|
||||
if (!mListener.onCommand(RouteInterfaceHandler.this, cmd.command, cmd.args, cmd.cb)) {
|
||||
sendResult(cmd.cb, RouteInterface.RESULT_COMMAND_NOT_SUPPORTED,
|
||||
null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void post(int what, Object obj) {
|
||||
obtainMessage(what, obj).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private final static class Command {
|
||||
public final String command;
|
||||
public final Bundle args;
|
||||
public final ResultReceiver cb;
|
||||
|
||||
public Command(String command, Bundle args, ResultReceiver cb) {
|
||||
this.command = command;
|
||||
this.args = args;
|
||||
this.cb = cb;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.routeprovider;
|
||||
|
||||
import android.media.session.RoutePlaybackControls;
|
||||
import android.media.session.RouteInterface;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Standard wrapper for using playback controls over a {@link RouteInterfaceHandler}.
|
||||
* This is the provider half of the interface. Sessions should use
|
||||
* {@link RoutePlaybackControls} to interact with this interface.
|
||||
*/
|
||||
public final class RoutePlaybackControlsHandler {
|
||||
private static final String TAG = "RoutePlaybackControls";
|
||||
|
||||
private final RouteInterfaceHandler mIface;
|
||||
|
||||
private RoutePlaybackControlsHandler(RouteInterfaceHandler iface) {
|
||||
mIface = iface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this interface to the specified route and return a handle for
|
||||
* communicating on the interface.
|
||||
*
|
||||
* @param connection The connection to register this interface on.
|
||||
* @return A handle for communicating on this interface.
|
||||
*/
|
||||
public static RoutePlaybackControlsHandler addTo(RouteConnection connection) {
|
||||
if (connection == null) {
|
||||
throw new IllegalArgumentException("connection may not be null");
|
||||
}
|
||||
RouteInterfaceHandler iface = connection
|
||||
.addRouteInterface(RoutePlaybackControls.NAME);
|
||||
|
||||
return new RoutePlaybackControlsHandler(iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link Listener} to this interface. The listener will receive
|
||||
* commands on the caller's thread.
|
||||
*
|
||||
* @param listener The listener to send commands to.
|
||||
*/
|
||||
public void addListener(Listener listener) {
|
||||
addListener(listener, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link Listener} to this interface. The listener will receive
|
||||
* updates on the handler's thread. If no handler is specified the caller's
|
||||
* thread will be used instead.
|
||||
*
|
||||
* @param listener The listener to send commands to.
|
||||
* @param handler The handler whose thread calls should be posted on. May be
|
||||
* null.
|
||||
*/
|
||||
public void addListener(Listener listener, Handler handler) {
|
||||
mIface.addListener(listener, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a {@link Listener} from this interface.
|
||||
*
|
||||
* @param listener The Listener to remove.
|
||||
*/
|
||||
public void removeListener(Listener listener) {
|
||||
mIface.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish the current playback state to the system and any controllers.
|
||||
* Valid values are defined in {@link PlaybackState}. TODO create
|
||||
* RoutePlaybackState.
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
public void sendPlaybackChangeEvent(int state) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putInt(RoutePlaybackControls.KEY_VALUE1, state);
|
||||
mIface.sendEvent(RoutePlaybackControls.EVENT_PLAYSTATE_CHANGE, extras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command handler for the RoutePlaybackControls interface. You can add a
|
||||
* Listener to the interface using {@link #addListener}.
|
||||
*/
|
||||
public static abstract class Listener extends RouteInterfaceHandler.CommandListener {
|
||||
|
||||
@Override
|
||||
public final boolean onCommand(RouteInterfaceHandler iface, String method, Bundle extras,
|
||||
ResultReceiver cb) {
|
||||
if (RoutePlaybackControls.CMD_FAST_FORWARD.equals(method)) {
|
||||
boolean success = fastForward();
|
||||
// TODO specify type of error
|
||||
RouteInterfaceHandler.sendResult(cb, success
|
||||
? RouteInterface.RESULT_SUCCESS
|
||||
: RouteInterface.RESULT_ERROR, null);
|
||||
return true;
|
||||
} else if (RoutePlaybackControls.CMD_GET_CURRENT_POSITION.equals(method)) {
|
||||
Bundle result = new Bundle();
|
||||
result.putLong(RoutePlaybackControls.KEY_VALUE1, getCurrentPosition());
|
||||
RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
|
||||
result);
|
||||
return true;
|
||||
} else if (RoutePlaybackControls.CMD_GET_CAPABILITIES.equals(method)) {
|
||||
Bundle result = new Bundle();
|
||||
result.putLong(RoutePlaybackControls.KEY_VALUE1, getCapabilities());
|
||||
RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
|
||||
result);
|
||||
return true;
|
||||
} else if (RoutePlaybackControls.CMD_PLAY_NOW.equals(method)) {
|
||||
playNow(extras.getString(RoutePlaybackControls.KEY_VALUE1, null), cb);
|
||||
return true;
|
||||
} else if (RoutePlaybackControls.CMD_RESUME.equals(method)) {
|
||||
boolean success = resume();
|
||||
RouteInterfaceHandler.sendResult(cb, success
|
||||
? RouteInterface.RESULT_SUCCESS
|
||||
: RouteInterface.RESULT_ERROR, null);
|
||||
return true;
|
||||
} else if (RoutePlaybackControls.CMD_PAUSE.equals(method)) {
|
||||
boolean success = pause();
|
||||
RouteInterfaceHandler.sendResult(cb, success
|
||||
? RouteInterface.RESULT_SUCCESS
|
||||
: RouteInterface.RESULT_ERROR, null);
|
||||
return true;
|
||||
} else {
|
||||
// The command wasn't recognized
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle fast forwarding.
|
||||
*
|
||||
* @return true if the request succeeded, false otherwise
|
||||
*/
|
||||
public boolean fastForward() {
|
||||
Log.w(TAG, "fastForward is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle getting the current position of playback in
|
||||
* millis.
|
||||
*
|
||||
* @return The current position in millis or -1
|
||||
*/
|
||||
public long getCurrentPosition() {
|
||||
Log.w(TAG, "getCurrentPosition is not supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle getting the set of capabilities currently
|
||||
* available.
|
||||
*
|
||||
* @return A bit mask of the supported capabilities
|
||||
*/
|
||||
public long getCapabilities() {
|
||||
Log.w(TAG, "getCapabilities is not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle play now requests.
|
||||
*
|
||||
* @param content The uri of the item to play.
|
||||
* @param cb The callback to send the result to.
|
||||
*/
|
||||
public void playNow(String content, ResultReceiver cb) {
|
||||
Log.w(TAG, "playNow is not supported");
|
||||
if (cb != null) {
|
||||
// We do this directly since we don't have a reference to the
|
||||
// iface
|
||||
cb.send(RouteInterface.RESULT_COMMAND_NOT_SUPPORTED, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle resume requests. Return true if the call was
|
||||
* handled, even if it was a no-op.
|
||||
*
|
||||
* @return true if the call was handled.
|
||||
*/
|
||||
public boolean resume() {
|
||||
Log.w(TAG, "resume is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle pause requests. Return true if the call was
|
||||
* handled, even if it was a no-op.
|
||||
*
|
||||
* @return true if the call was handled.
|
||||
*/
|
||||
public boolean pause() {
|
||||
Log.w(TAG, "pause is not supported");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
227
media/java/android/media/routeprovider/RouteProviderService.java
Normal file
227
media/java/android/media/routeprovider/RouteProviderService.java
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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.routeprovider;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.media.routeprovider.IRouteProvider;
|
||||
import android.media.routeprovider.IRouteProviderCallback;
|
||||
import android.media.session.RouteEvent;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.RouteOptions;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for defining a route provider service.
|
||||
* <p>
|
||||
* A route provider offers media routes which represent destinations to which
|
||||
* applications may connect, control, and send content. This provides a means
|
||||
* for Android applications to interact with a variety of media streaming
|
||||
* devices such as speakers or television sets.
|
||||
* <p>
|
||||
* The system will bind to your provider when an active app is interested in
|
||||
* routes that may be discovered through your provider. After binding, the
|
||||
* system will send updates on which routes to discover through
|
||||
* {@link #updateDiscoveryRequests(List)}. The system will call
|
||||
* {@link #getMatchingRoutes(List)} with a subset of filters when a route is
|
||||
* needed for a specific app.
|
||||
* <p>
|
||||
* TODO add documentation for how the sytem knows an app is interested. Maybe
|
||||
* interface declarations in the manifest.
|
||||
* <p>
|
||||
* The system will only start a provider when an app may discover routes through
|
||||
* it. If your service needs to run at other times you are responsible for
|
||||
* managing its lifecycle.
|
||||
* <p>
|
||||
* Declare your route provider service in your application manifest like this:
|
||||
* <p>
|
||||
*
|
||||
* <pre>
|
||||
* <service android:name=".MyRouteProviderService"
|
||||
* android:label="@string/my_route_provider_service">
|
||||
* <intent-filter>
|
||||
* <action android:name="com.android.media.session.MediaRouteProvider" />
|
||||
* </intent-filter>
|
||||
* </service>
|
||||
* </pre>
|
||||
*/
|
||||
public abstract class RouteProviderService extends Service {
|
||||
private static final String TAG = "RouteProvider";
|
||||
/**
|
||||
* A service that implements a RouteProvider must declare that it handles
|
||||
* this action in its AndroidManifest.
|
||||
*/
|
||||
public static final String SERVICE_INTERFACE =
|
||||
"com.android.media.session.MediaRouteProvider";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_ROUTES = "routes";
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_CONNECTION = "connection";
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static final int RESULT_FAILURE = -1;
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static final int RESULT_SUCCESS = 0;
|
||||
|
||||
// The system's callback once it has bound to the service
|
||||
private IRouteProviderCallback mCb;
|
||||
|
||||
/**
|
||||
* If your service overrides onBind it must return super.onBind() in
|
||||
* response to the {@link #SERVICE_INTERFACE} action.
|
||||
*/
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (intent != null && RouteProviderService.SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||
return mBinder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the specified RouteConnection. The system will stop sending
|
||||
* commands to this connection.
|
||||
*
|
||||
* @param connection The connection to disconnect.
|
||||
* @hide
|
||||
*/
|
||||
public final void disconnect(RouteConnection connection) {
|
||||
if (mCb != null) {
|
||||
try {
|
||||
mCb.onConnectionTerminated(connection.getBinder());
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Error in disconnect.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public final void sendRouteEvent(RouteEvent event) {
|
||||
if (mCb != null) {
|
||||
try {
|
||||
mCb.onRouteEvent(event);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Unable to send MediaRouteEvent to system", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle updates to the routes that are of interest. Each
|
||||
* {@link RouteRequest} will specify if it is an active or passive request.
|
||||
* Route discovery may perform more aggressive discovery on behalf of active
|
||||
* requests but should use low power discovery methods otherwise.
|
||||
* <p>
|
||||
* A single app may have more than one request. Your provider is responsible
|
||||
* for deciding the set of features that are important for discovery given
|
||||
* the set of requests. If your provider only has one method of discovery it
|
||||
* may simply verify that one or more requests are valid before starting
|
||||
* discovery.
|
||||
*
|
||||
* @param requests The route requests that are currently relevant.
|
||||
*/
|
||||
public void updateDiscoveryRequests(List<RouteRequest> requests) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of matching routes for the given set of requests. Returning
|
||||
* null or an empty list indicates there are no matches. A route is
|
||||
* considered matching if it supports one or more of the
|
||||
* {@link RouteOptions} specified. Each returned {@link RouteInfo}
|
||||
* should include all the requested connections that it supports.
|
||||
*
|
||||
* @param options The set of requests for routes
|
||||
* @return The routes that this caller may connect to using one or more of
|
||||
* the route options.
|
||||
*/
|
||||
public abstract List<RouteInfo> getMatchingRoutes(List<RouteRequest> options);
|
||||
|
||||
/**
|
||||
* Handle a request to connect to a specific route with a specific request.
|
||||
* The {@link RouteConnection} must be fully defined before being returned,
|
||||
* though the actual connection to the route may be performed in the
|
||||
* background.
|
||||
*
|
||||
* @param route The route to connect to
|
||||
* @param request The connection request parameters
|
||||
* @return A MediaRouteConnection representing the connection to the route
|
||||
*/
|
||||
public abstract RouteConnection connect(RouteInfo route, RouteRequest request);
|
||||
|
||||
private IRouteProvider.Stub mBinder = new IRouteProvider.Stub() {
|
||||
|
||||
@Override
|
||||
public void registerCallback(IRouteProviderCallback cb) throws RemoteException {
|
||||
mCb = cb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCallback(IRouteProviderCallback cb) throws RemoteException {
|
||||
mCb = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiscoveryRequests(List<RouteRequest> requests)
|
||||
throws RemoteException {
|
||||
RouteProviderService.this.updateDiscoveryRequests(requests);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAvailableRoutes(List<RouteRequest> requests, ResultReceiver cb)
|
||||
throws RemoteException {
|
||||
List<RouteInfo> routes = RouteProviderService.this.getMatchingRoutes(requests);
|
||||
ArrayList<RouteInfo> routesArray;
|
||||
if (routes instanceof ArrayList) {
|
||||
routesArray = (ArrayList<RouteInfo>) routes;
|
||||
} else {
|
||||
routesArray = new ArrayList<RouteInfo>(routes);
|
||||
}
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putParcelableArrayList(KEY_ROUTES, routesArray);
|
||||
cb.send(routes == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(RouteInfo route, RouteRequest request, ResultReceiver cb)
|
||||
throws RemoteException {
|
||||
RouteConnection connection = RouteProviderService.this.connect(route, request);
|
||||
Bundle resultData = new Bundle();
|
||||
if (connection != null) {
|
||||
connection.publish();
|
||||
resultData.putBinder(KEY_CONNECTION, connection.getBinder());
|
||||
}
|
||||
|
||||
cb.send(connection == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
|
||||
}
|
||||
};
|
||||
}
|
||||
18
media/java/android/media/routeprovider/RouteRequest.aidl
Normal file
18
media/java/android/media/routeprovider/RouteRequest.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.routeprovider;
|
||||
|
||||
parcelable RouteRequest;
|
||||
96
media/java/android/media/routeprovider/RouteRequest.java
Normal file
96
media/java/android/media/routeprovider/RouteRequest.java
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.routeprovider;
|
||||
|
||||
import android.media.session.RouteOptions;
|
||||
import android.media.session.SessionInfo;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* A request to connect or discover routes with certain capabilities.
|
||||
* <p>
|
||||
* Passed to a {@link RouteProviderService} when a request for discovery or to
|
||||
* connect to a route is made. This identifies the app making the request and
|
||||
* provides the full set of connection parameters they would like to use for a
|
||||
* connection. An app that can connect in multiple ways will be represented by
|
||||
* multiple requests.
|
||||
*/
|
||||
public final class RouteRequest implements Parcelable {
|
||||
private final SessionInfo mSessionInfo;
|
||||
private final RouteOptions mOptions;
|
||||
private final boolean mActive;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public RouteRequest(SessionInfo info, RouteOptions connRequest,
|
||||
boolean active) {
|
||||
mSessionInfo = info;
|
||||
mOptions = connRequest;
|
||||
mActive = active;
|
||||
}
|
||||
|
||||
private RouteRequest(Parcel in) {
|
||||
mSessionInfo = SessionInfo.CREATOR.createFromParcel(in);
|
||||
mOptions = RouteOptions.CREATOR.createFromParcel(in);
|
||||
mActive = in.readInt() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the session making the request.
|
||||
*
|
||||
* @return Info on the session making the request
|
||||
*/
|
||||
public SessionInfo getSessionInfo() {
|
||||
return mSessionInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection options, which includes the interfaces and other
|
||||
* connection params the session wants to use with a route.
|
||||
*
|
||||
* @return The connection options
|
||||
*/
|
||||
public RouteOptions getConnectionOptions() {
|
||||
return mOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
mSessionInfo.writeToParcel(dest, flags);
|
||||
mOptions.writeToParcel(dest, flags);
|
||||
dest.writeInt(mActive ? 1 : 0);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<RouteRequest> CREATOR
|
||||
= new Parcelable.Creator<RouteRequest>() {
|
||||
@Override
|
||||
public RouteRequest createFromParcel(Parcel in) {
|
||||
return new RouteRequest(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteRequest[] newArray(int size) {
|
||||
return new RouteRequest[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -15,25 +15,33 @@
|
||||
|
||||
package android.media.session;
|
||||
|
||||
import android.media.session.IMediaController;
|
||||
import android.media.session.ISessionController;
|
||||
import android.media.session.MediaMetadata;
|
||||
import android.media.session.RouteOptions;
|
||||
import android.media.session.RouteCommand;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
/**
|
||||
* Interface to a MediaSession in the system.
|
||||
* @hide
|
||||
*/
|
||||
interface IMediaSession {
|
||||
interface ISession {
|
||||
void sendEvent(String event, in Bundle data);
|
||||
IMediaController getMediaController();
|
||||
ISessionController getController();
|
||||
void setTransportPerformerEnabled();
|
||||
void setRouteState(in Bundle routeState);
|
||||
void setRoute(in Bundle mediaRouteDescriptor);
|
||||
List<String> getSupportedInterfaces();
|
||||
void publish();
|
||||
void destroy();
|
||||
|
||||
// These commands are for setting up and communicating with routes
|
||||
// Returns true if the route was set for this session
|
||||
boolean setRoute(in RouteInfo route);
|
||||
void setRouteOptions(in List<RouteOptions> options);
|
||||
void connectToRoute(in RouteInfo route, in RouteOptions options);
|
||||
void sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
|
||||
|
||||
// These commands are for the TransportPerformer
|
||||
void setMetadata(in MediaMetadata metadata);
|
||||
void setPlaybackState(in PlaybackState state);
|
||||
@@ -16,6 +16,9 @@
|
||||
package android.media.session;
|
||||
|
||||
import android.media.Rating;
|
||||
import android.media.session.RouteEvent;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.RouteOptions;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
@@ -23,10 +26,13 @@ import android.os.ResultReceiver;
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IMediaSessionCallback {
|
||||
oneway interface ISessionCallback {
|
||||
void onCommand(String command, in Bundle extras, in ResultReceiver cb);
|
||||
void onMediaButton(in Intent mediaRequestIntent);
|
||||
void onRequestRouteChange(in Bundle route);
|
||||
void onMediaButton(in Intent mediaButtonIntent);
|
||||
void onRequestRouteChange(in RouteInfo route);
|
||||
void onRouteConnected(in RouteInfo route, in RouteOptions options);
|
||||
void onRouteStateChange(int state);
|
||||
void onRouteEvent(in RouteEvent event);
|
||||
|
||||
// These callbacks are for the TransportPerformer
|
||||
void onPlay();
|
||||
@@ -17,7 +17,7 @@ package android.media.session;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.media.Rating;
|
||||
import android.media.session.IMediaControllerCallback;
|
||||
import android.media.session.ISessionControllerCallback;
|
||||
import android.media.session.MediaMetadata;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
@@ -28,12 +28,13 @@ import android.view.KeyEvent;
|
||||
* Interface to a MediaSession in the system.
|
||||
* @hide
|
||||
*/
|
||||
interface IMediaController {
|
||||
interface ISessionController {
|
||||
void sendCommand(String command, in Bundle extras, in ResultReceiver cb);
|
||||
void sendMediaButton(in KeyEvent mediaButton);
|
||||
void registerCallbackListener(in IMediaControllerCallback cb);
|
||||
void unregisterCallbackListener(in IMediaControllerCallback cb);
|
||||
void registerCallbackListener(in ISessionControllerCallback cb);
|
||||
void unregisterCallbackListener(in ISessionControllerCallback cb);
|
||||
boolean isTransportControlEnabled();
|
||||
void showRoutePicker();
|
||||
|
||||
// These commands are for the TransportController
|
||||
void play();
|
||||
@@ -16,15 +16,16 @@
|
||||
package android.media.session;
|
||||
|
||||
import android.media.session.MediaMetadata;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IMediaControllerCallback {
|
||||
oneway interface ISessionControllerCallback {
|
||||
void onEvent(String event, in Bundle extras);
|
||||
void onRouteChanged(in Bundle route);
|
||||
void onRouteChanged(in RouteInfo route);
|
||||
|
||||
// These callbacks are for the TransportController
|
||||
void onPlaybackStateChanged(in PlaybackState state);
|
||||
@@ -15,14 +15,14 @@
|
||||
|
||||
package android.media.session;
|
||||
|
||||
import android.media.session.IMediaSession;
|
||||
import android.media.session.IMediaSessionCallback;
|
||||
import android.media.session.ISession;
|
||||
import android.media.session.ISessionCallback;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Interface to the MediaSessionManagerService
|
||||
* @hide
|
||||
*/
|
||||
interface IMediaSessionManager {
|
||||
IMediaSession createSession(String packageName, in IMediaSessionCallback cb, String tag);
|
||||
interface ISessionManager {
|
||||
ISession createSession(String packageName, in ISessionCallback cb, String tag);
|
||||
}
|
||||
@@ -15,12 +15,11 @@
|
||||
*/
|
||||
package android.media.session;
|
||||
|
||||
import android.media.RemoteControlClient;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Playback state for a {@link MediaSession}. This includes a state like
|
||||
* Playback state for a {@link Session}. This includes a state like
|
||||
* {@link PlaybackState#PLAYSTATE_PLAYING}, the current playback position,
|
||||
* and the current control capabilities.
|
||||
*/
|
||||
@@ -147,6 +146,14 @@ public final class PlaybackState implements Parcelable {
|
||||
*/
|
||||
public final static int PLAYSTATE_ERROR = 7;
|
||||
|
||||
/**
|
||||
* State indicating the class doing playback is currently connecting to a
|
||||
* route. Depending on the implementation you may return to the previous
|
||||
* state when the connection finishes or enter {@link #PLAYSTATE_NONE}. If
|
||||
* the connection failed {@link #PLAYSTATE_ERROR} should be used.
|
||||
*/
|
||||
public final static int PLAYSTATE_CONNECTING = 8;
|
||||
|
||||
private int mState;
|
||||
private long mPosition;
|
||||
private long mBufferPosition;
|
||||
|
||||
99
media/java/android/media/session/Route.java
Normal file
99
media/java/android/media/session/Route.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a destination which an application has connected to and may send
|
||||
* media content.
|
||||
* <p>
|
||||
* This allows a session owner to interact with a route it has been connected
|
||||
* to. The MediaRoute must be used to get {@link RouteInterface}
|
||||
* instances which can be used to communicate over a specific interface on the
|
||||
* route.
|
||||
*/
|
||||
public final class Route {
|
||||
private static final String TAG = "Route";
|
||||
private final RouteInfo mInfo;
|
||||
private final Session mSession;
|
||||
private final RouteOptions mOptions;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public Route(RouteInfo info, RouteOptions options, Session session) {
|
||||
if (info == null || options == null) {
|
||||
throw new IllegalStateException("Route info was not valid!");
|
||||
}
|
||||
mInfo = info;
|
||||
mOptions = options;
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link RouteInfo} for this route.
|
||||
*
|
||||
* @return The info for this route.
|
||||
*/
|
||||
public RouteInfo getRouteInfo() {
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link RouteOptions} that were used to connect this route.
|
||||
*
|
||||
* @return The options used to connect to this route.
|
||||
*/
|
||||
public RouteOptions getOptions() {
|
||||
return mOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an interface provided by this route. If the interface is not
|
||||
* supported by the route, returns null.
|
||||
*
|
||||
* @see RouteInterface
|
||||
* @param iface The name of the interface to create
|
||||
* @return A {@link RouteInterface} or null if the interface is
|
||||
* not supported.
|
||||
*/
|
||||
public RouteInterface getInterface(String iface) {
|
||||
if (TextUtils.isEmpty(iface)) {
|
||||
throw new IllegalArgumentException("iface may not be empty.");
|
||||
}
|
||||
List<String> ifaces = mOptions.getInterfaceNames();
|
||||
if (ifaces != null) {
|
||||
for (int i = ifaces.size() - 1; i >= 0; i--) {
|
||||
if (iface.equals(ifaces.get(i))) {
|
||||
return new RouteInterface(this, iface, mSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.e(TAG, "Interface not supported by route");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
Session getSession() {
|
||||
return mSession;
|
||||
}
|
||||
}
|
||||
@@ -15,4 +15,4 @@
|
||||
|
||||
package android.media.session;
|
||||
|
||||
parcelable MediaSessionToken;
|
||||
parcelable RouteCommand;
|
||||
117
media/java/android/media/session/RouteCommand.java
Normal file
117
media/java/android/media/session/RouteCommand.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents a command that an application may send to a route.
|
||||
* <p>
|
||||
* Commands are associated with a specific route and interface supported by that
|
||||
* route and sent through the session. This class isn't used directly by apps.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class RouteCommand implements Parcelable {
|
||||
private final String mRoute;
|
||||
private final String mIface;
|
||||
private final String mEvent;
|
||||
private final Bundle mExtras;
|
||||
|
||||
/**
|
||||
* @param route The id of the route this event is being sent on
|
||||
* @param iface The interface the sender used
|
||||
* @param event The event or command
|
||||
* @param extras Any extras included with the event
|
||||
*/
|
||||
public RouteCommand(String route, String iface, String event, Bundle extras) {
|
||||
mRoute = route;
|
||||
mIface = iface;
|
||||
mEvent = event;
|
||||
mExtras = extras;
|
||||
}
|
||||
|
||||
private RouteCommand(Parcel in) {
|
||||
mRoute = in.readString();
|
||||
mIface = in.readString();
|
||||
mEvent = in.readString();
|
||||
mExtras = in.readBundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id for the route this event was sent on.
|
||||
*
|
||||
* @return The route id this event is using
|
||||
*/
|
||||
public String getRouteInfo() {
|
||||
return mRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interface this event was sent from
|
||||
*
|
||||
* @return The interface for this event
|
||||
*/
|
||||
public String getIface() {
|
||||
return mIface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action/name of the event.
|
||||
*
|
||||
* @return The name of event/command.
|
||||
*/
|
||||
public String getEvent() {
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any extras included with the event.
|
||||
*
|
||||
* @return The bundle included with the event or null
|
||||
*/
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mRoute);
|
||||
dest.writeString(mIface);
|
||||
dest.writeString(mEvent);
|
||||
dest.writeBundle(mExtras);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<RouteCommand> CREATOR
|
||||
= new Parcelable.Creator<RouteCommand>() {
|
||||
@Override
|
||||
public RouteCommand createFromParcel(Parcel in) {
|
||||
return new RouteCommand(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteCommand[] newArray(int size) {
|
||||
return new RouteCommand[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
18
media/java/android/media/session/RouteEvent.aidl
Normal file
18
media/java/android/media/session/RouteEvent.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.session;
|
||||
|
||||
parcelable RouteEvent;
|
||||
120
media/java/android/media/session/RouteEvent.java
Normal file
120
media/java/android/media/session/RouteEvent.java
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.media.routeprovider.RouteConnection;
|
||||
import android.media.routeprovider.RouteProviderService;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents an event that a route provider is sending to a particular
|
||||
* {@link RouteConnection}. Events are associated with a specific interface
|
||||
* supported by the connection and sent through the {@link RouteProviderService}.
|
||||
* This class isn't used directly by apps.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class RouteEvent implements Parcelable {
|
||||
private final IBinder mConnection;
|
||||
private final String mIface;
|
||||
private final String mEvent;
|
||||
private final Bundle mExtras;
|
||||
|
||||
/**
|
||||
* @param connection The connection that this event is for
|
||||
* @param iface The interface the sender used
|
||||
* @param event The event or command
|
||||
* @param extras Any extras included with the event
|
||||
*/
|
||||
public RouteEvent(IBinder connection, String iface, String event, Bundle extras) {
|
||||
mConnection = connection;
|
||||
mIface = iface;
|
||||
mEvent = event;
|
||||
mExtras = extras;
|
||||
}
|
||||
|
||||
private RouteEvent(Parcel in) {
|
||||
mConnection = in.readStrongBinder();
|
||||
mIface = in.readString();
|
||||
mEvent = in.readString();
|
||||
mExtras = in.readBundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection this event was sent on.
|
||||
*
|
||||
* @return The connection this event is using
|
||||
*/
|
||||
public IBinder getConnection() {
|
||||
return mConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interface this event was sent from
|
||||
*
|
||||
* @return The interface for this event
|
||||
*/
|
||||
public String getIface() {
|
||||
return mIface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action/name of the event.
|
||||
*
|
||||
* @return The name of event/command.
|
||||
*/
|
||||
public String getEvent() {
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any extras included with the event.
|
||||
*
|
||||
* @return The bundle included with the event or null
|
||||
*/
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeStrongBinder(mConnection);
|
||||
dest.writeString(mIface);
|
||||
dest.writeString(mEvent);
|
||||
dest.writeBundle(mExtras);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<RouteEvent> CREATOR
|
||||
= new Parcelable.Creator<RouteEvent>() {
|
||||
@Override
|
||||
public RouteEvent createFromParcel(Parcel in) {
|
||||
return new RouteEvent(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteEvent[] newArray(int size) {
|
||||
return new RouteEvent[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
18
media/java/android/media/session/RouteInfo.aidl
Normal file
18
media/java/android/media/session/RouteInfo.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.session;
|
||||
|
||||
parcelable RouteInfo;
|
||||
233
media/java/android/media/session/RouteInfo.java
Normal file
233
media/java/android/media/session/RouteInfo.java
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Information about a route, including its display name, a way to identify it,
|
||||
* and the ways it can be connected to.
|
||||
*/
|
||||
public final class RouteInfo implements Parcelable {
|
||||
private final String mName;
|
||||
private final String mId;
|
||||
private final String mProviderId;
|
||||
private final List<RouteOptions> mOptions;
|
||||
|
||||
private RouteInfo(String id, String name, String providerId,
|
||||
List<RouteOptions> connRequests) {
|
||||
mId = id;
|
||||
mName = name;
|
||||
mProviderId = providerId;
|
||||
mOptions = connRequests;
|
||||
}
|
||||
|
||||
private RouteInfo(Parcel in) {
|
||||
mId = in.readString();
|
||||
mName = in.readString();
|
||||
mProviderId = in.readString();
|
||||
mOptions = new ArrayList<RouteOptions>();
|
||||
in.readTypedList(mOptions, RouteOptions.CREATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable name of this route.
|
||||
*
|
||||
* @return A short, user readable name for this route
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique id for this route.
|
||||
*
|
||||
* @return A unique route id.
|
||||
*/
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the package name of this route's provider.
|
||||
*
|
||||
* @return The package name of this route's provider.
|
||||
*/
|
||||
public String getProvider() {
|
||||
return mProviderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of connections that may be used with this route.
|
||||
*
|
||||
* @return An array of connection requests that may be used to connect
|
||||
*/
|
||||
public List<RouteOptions> getConnectionMethods() {
|
||||
return mOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeString(mName);
|
||||
dest.writeString(mProviderId);
|
||||
dest.writeTypedList(mOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder bob = new StringBuilder();
|
||||
bob.append("RouteInfo: id=").append(mId).append(", name=").append(mName)
|
||||
.append(", provider=").append(mProviderId).append(", options={");
|
||||
for (int i = 0; i < mOptions.size(); i++) {
|
||||
if (i != 0) {
|
||||
bob.append(", ");
|
||||
}
|
||||
bob.append(mOptions.get(i).toString());
|
||||
}
|
||||
bob.append("}");
|
||||
return bob.toString();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<RouteInfo> CREATOR
|
||||
= new Parcelable.Creator<RouteInfo>() {
|
||||
@Override
|
||||
public RouteInfo createFromParcel(Parcel in) {
|
||||
return new RouteInfo(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteInfo[] newArray(int size) {
|
||||
return new RouteInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for creating MediaRouteInfos. A route must have a name and an id.
|
||||
* While options are not strictly required the route cannot be connected to
|
||||
* without at least one set of options.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private String mName;
|
||||
private String mId;
|
||||
private String mProviderPackage;
|
||||
private ArrayList<RouteOptions> mOptions;
|
||||
|
||||
/**
|
||||
* Copies an existing route info object. TODO Remove once we have
|
||||
* helpers for creating route infos.
|
||||
*
|
||||
* @param from The existing info to copy.
|
||||
*/
|
||||
public Builder(RouteInfo from) {
|
||||
mOptions = new ArrayList<RouteOptions>(from.getConnectionMethods());
|
||||
mName = from.mName;
|
||||
mId = from.mId;
|
||||
mProviderPackage = from.mProviderId;
|
||||
}
|
||||
|
||||
public Builder() {
|
||||
mOptions = new ArrayList<RouteOptions>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user visible name for this route.
|
||||
*
|
||||
* @param name The name of the route
|
||||
* @return The builder for easy chaining.
|
||||
*/
|
||||
public Builder setName(String name) {
|
||||
mName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the id of the route. This should be unique to the provider.
|
||||
*
|
||||
* @param id The unique id of the route.
|
||||
* @return The builder for easy chaining.
|
||||
*/
|
||||
public Builder setId(String id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public Builder setProviderId(String packageName) {
|
||||
mProviderPackage = packageName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a set of {@link RouteOptions} to the route. Multiple options
|
||||
* may be added to the same route.
|
||||
*
|
||||
* @param options The options to add to this route.
|
||||
* @return The builder for easy chaining.
|
||||
*/
|
||||
public Builder addRouteOptions(RouteOptions options) {
|
||||
mOptions.add(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the set of {@link RouteOptions} on the route.
|
||||
*
|
||||
* @return The builder for easy chaining
|
||||
*/
|
||||
public Builder clearRouteOptions() {
|
||||
mOptions.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new MediaRouteInfo.
|
||||
*
|
||||
* @return A new MediaRouteInfo with the values that were set.
|
||||
*/
|
||||
public RouteInfo build() {
|
||||
if (TextUtils.isEmpty(mName)) {
|
||||
throw new IllegalArgumentException("Must set a name before building");
|
||||
}
|
||||
if (TextUtils.isEmpty(mId)) {
|
||||
throw new IllegalArgumentException("Must set an id before building");
|
||||
}
|
||||
return new RouteInfo(mId, mName, mProviderPackage, mOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current number of options that have been added to this
|
||||
* builder.
|
||||
*
|
||||
* @return The number of options that have been added.
|
||||
*/
|
||||
public int getOptionsSize() {
|
||||
return mOptions.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,136 +17,161 @@ package android.media.session;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Parcelable;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Routes can support multiple interfaces for MediaSessions to interact with. To
|
||||
* add a standard interface you should implement that interface's RouteInterface
|
||||
* Stub and register it with the session. The set of supported commands is
|
||||
* dependent on the specific interface's implementation.
|
||||
* <p>
|
||||
* A MediaInterface can be registered by calling TODO. Once added an interface
|
||||
* will be used by Sessions to decide how they communicate with a session and
|
||||
* cannot be removed, so all interfaces that you plan to support should be added
|
||||
* when the route is created.
|
||||
* A route can support multiple interfaces for a {@link Session} to
|
||||
* interact with. To use a specific interface with a route a
|
||||
* MediaSessionRouteInterface needs to be retrieved from the route. An
|
||||
* implementation of the specific interface, like
|
||||
* {@link RoutePlaybackControls}, should be used to simplify communication
|
||||
* and reduce errors on that interface.
|
||||
*
|
||||
* @see RouteTransportControls
|
||||
* @see RoutePlaybackControls for an example
|
||||
*/
|
||||
public final class RouteInterface {
|
||||
private static final String TAG = "MediaInterface";
|
||||
private static final String TAG = "RouteInterface";
|
||||
|
||||
private static final String KEY_RESULT = "result";
|
||||
/**
|
||||
* Error indicating the route is currently not connected.
|
||||
*/
|
||||
public static final int RESULT_NOT_CONNECTED = -5;
|
||||
/**
|
||||
* Error indicating the session is no longer using the route this command
|
||||
* was sent to.
|
||||
*/
|
||||
public static final int RESULT_ROUTE_IS_STALE = -4;
|
||||
/**
|
||||
* Error indicating that the interface does not support the command.
|
||||
*/
|
||||
public static final int RESULT_COMMAND_NOT_SUPPORTED = -3;
|
||||
/**
|
||||
* Error indicating that the route does not support the interface.
|
||||
*/
|
||||
public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2;
|
||||
/**
|
||||
* Generic error. Extra information about the error may be included in the
|
||||
* result bundle.
|
||||
*/
|
||||
public static final int RESULT_ERROR = -1;
|
||||
/**
|
||||
* The command was successful. Extra information may be included in the
|
||||
* result bundle.
|
||||
*/
|
||||
public static final int RESULT_SUCCESS = 1;
|
||||
|
||||
private final MediaController mController;
|
||||
private final Route mRoute;
|
||||
private final String mIface;
|
||||
private final Session mSession;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>();
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
RouteInterface(MediaController controller, String iface) {
|
||||
mController = controller;
|
||||
RouteInterface(Route route, String iface, Session session) {
|
||||
mRoute = route;
|
||||
mIface = iface;
|
||||
mSession = session;
|
||||
mSession.addInterfaceListener(iface, mEventListener);
|
||||
}
|
||||
|
||||
public void sendCommand(String command, Bundle params, ResultReceiver cb) {
|
||||
// TODO
|
||||
/**
|
||||
* Send a command using this interface.
|
||||
*
|
||||
* @param command The command to send.
|
||||
* @param extras Any extras to include with the command.
|
||||
* @param cb The callback to receive the result on.
|
||||
* @return true if the command was sent, false otherwise.
|
||||
*/
|
||||
public boolean sendCommand(String command, Bundle extras, ResultReceiver cb) {
|
||||
RouteCommand cmd = new RouteCommand(mRoute.getRouteInfo().getId(), mIface,
|
||||
command, extras);
|
||||
return mSession.sendRouteCommand(cmd, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to this interface. Events will be sent on the caller's
|
||||
* thread.
|
||||
*
|
||||
* @param listener The listener to receive events on.
|
||||
*/
|
||||
public void addListener(EventListener listener) {
|
||||
addListener(listener, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for this interface. If a handler is specified events will
|
||||
* be performed on the handler's thread, otherwise the caller's thread will
|
||||
* be used.
|
||||
*
|
||||
* @param listener The listener to receive events on
|
||||
* @param handler The handler whose thread to post calls on
|
||||
*/
|
||||
public void addListener(EventListener listener, Handler handler) {
|
||||
// TODO See MediaController for add/remove pattern
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener may not be null");
|
||||
}
|
||||
if (handler == null) {
|
||||
handler = new Handler();
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (findIndexOfListenerLocked(listener) != -1) {
|
||||
Log.d(TAG, "Listener is already added, ignoring");
|
||||
return;
|
||||
}
|
||||
mListeners.add(new EventHandler(handler.getLooper(), listener));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener from this interface.
|
||||
*
|
||||
* @param listener The listener to stop receiving events on.
|
||||
*/
|
||||
public void removeListener(EventListener listener) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// TODO decide on list of supported types
|
||||
private static Bundle writeResultToBundle(Object v) {
|
||||
Bundle b = new Bundle();
|
||||
if (v == null) {
|
||||
// Don't send anything if null
|
||||
} else if (v instanceof String) {
|
||||
b.putString(KEY_RESULT, (String) v);
|
||||
} else if (v instanceof Integer) {
|
||||
b.putInt(KEY_RESULT, (Integer) v);
|
||||
} else if (v instanceof Bundle) {
|
||||
// Must be before Parcelable
|
||||
b.putBundle(KEY_RESULT, (Bundle) v);
|
||||
} else if (v instanceof Parcelable) {
|
||||
b.putParcelable(KEY_RESULT, (Parcelable) v);
|
||||
} else if (v instanceof Short) {
|
||||
b.putShort(KEY_RESULT, (Short) v);
|
||||
} else if (v instanceof Long) {
|
||||
b.putLong(KEY_RESULT, (Long) v);
|
||||
} else if (v instanceof Float) {
|
||||
b.putFloat(KEY_RESULT, (Float) v);
|
||||
} else if (v instanceof Double) {
|
||||
b.putDouble(KEY_RESULT, (Double) v);
|
||||
} else if (v instanceof Boolean) {
|
||||
b.putBoolean(KEY_RESULT, (Boolean) v);
|
||||
} else if (v instanceof CharSequence) {
|
||||
// Must be after String
|
||||
b.putCharSequence(KEY_RESULT, (CharSequence) v);
|
||||
} else if (v instanceof boolean[]) {
|
||||
b.putBooleanArray(KEY_RESULT, (boolean[]) v);
|
||||
} else if (v instanceof byte[]) {
|
||||
b.putByteArray(KEY_RESULT, (byte[]) v);
|
||||
} else if (v instanceof String[]) {
|
||||
b.putStringArray(KEY_RESULT, (String[]) v);
|
||||
} else if (v instanceof CharSequence[]) {
|
||||
// Must be after String[] and before Object[]
|
||||
b.putCharSequenceArray(KEY_RESULT, (CharSequence[]) v);
|
||||
} else if (v instanceof IBinder) {
|
||||
b.putBinder(KEY_RESULT, (IBinder) v);
|
||||
} else if (v instanceof Parcelable[]) {
|
||||
b.putParcelableArray(KEY_RESULT, (Parcelable[]) v);
|
||||
} else if (v instanceof int[]) {
|
||||
b.putIntArray(KEY_RESULT, (int[]) v);
|
||||
} else if (v instanceof long[]) {
|
||||
b.putLongArray(KEY_RESULT, (long[]) v);
|
||||
} else if (v instanceof Byte) {
|
||||
b.putByte(KEY_RESULT, (Byte) v);
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener may not be null");
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public abstract static class Stub {
|
||||
|
||||
/**
|
||||
* The name of an interface should be a fully qualified name to prevent
|
||||
* namespace collisions. Example: "com.myproject.MyPlaybackInterface"
|
||||
*
|
||||
* @return The name of this interface
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
/**
|
||||
* This is called when a command is received that matches the interface
|
||||
* you registered. Commands can come from any app with a MediaController
|
||||
* reference to the session.
|
||||
*
|
||||
* @see MediaController
|
||||
* @see MediaSession
|
||||
* @param command The command or method to invoke.
|
||||
* @param args Any args that were included with the command. May be
|
||||
* null.
|
||||
* @param cb The callback provided to send a response on. May be null.
|
||||
*/
|
||||
public abstract void onCommand(String command, Bundle args, ResultReceiver cb);
|
||||
|
||||
public final void sendEvent(MediaSession session, String event, Bundle extras) {
|
||||
// TODO
|
||||
synchronized (mLock) {
|
||||
int index = findIndexOfListenerLocked(listener);
|
||||
if (index != -1) {
|
||||
mListeners.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int findIndexOfListenerLocked(EventListener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("Callback cannot be null");
|
||||
}
|
||||
for (int i = mListeners.size() - 1; i >= 0; i--) {
|
||||
EventHandler handler = mListeners.get(i);
|
||||
if (listener == handler.mListener) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private EventListener mEventListener = new EventListener() {
|
||||
@Override
|
||||
public void onEvent(String event, Bundle args) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mListeners.size() - 1; i >= 0; i--) {
|
||||
mListeners.get(i).postEvent(event, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* An EventListener can be registered by an app with TODO to handle events
|
||||
* sent by the session on a specific interface.
|
||||
@@ -166,9 +191,9 @@ public final class RouteInterface {
|
||||
|
||||
private static final class EventHandler extends Handler {
|
||||
|
||||
private final RouteInterface.EventListener mListener;
|
||||
private final EventListener mListener;
|
||||
|
||||
public EventHandler(Looper looper, RouteInterface.EventListener cb) {
|
||||
public EventHandler(Looper looper, EventListener cb) {
|
||||
super(looper, null, true);
|
||||
mListener = cb;
|
||||
}
|
||||
|
||||
18
media/java/android/media/session/RouteOptions.aidl
Normal file
18
media/java/android/media/session/RouteOptions.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.session;
|
||||
|
||||
parcelable RouteOptions;
|
||||
163
media/java/android/media/session/RouteOptions.java
Normal file
163
media/java/android/media/session/RouteOptions.java
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Specifies options that an application might use when connecting to a route.
|
||||
* This includes things like interfaces, connection parameters, and required
|
||||
* features.
|
||||
* <p>
|
||||
* An application may create several different route options that describe
|
||||
* alternative sets of capabilities that it can use and choose the most
|
||||
* appropriate route options when it is ready to connect to the route. Each
|
||||
* route options instance must specify a complete set of capabilities to request
|
||||
* when the connection is established.
|
||||
*/
|
||||
public final class RouteOptions implements Parcelable {
|
||||
private static final String TAG = "RouteOptions";
|
||||
|
||||
private final ArrayList<String> mIfaces;
|
||||
private final Bundle mConnectionParams;
|
||||
|
||||
private RouteOptions(List<String> ifaces, Bundle params) {
|
||||
mIfaces = new ArrayList<String>(ifaces);
|
||||
mConnectionParams = params;
|
||||
}
|
||||
|
||||
private RouteOptions(Parcel in) {
|
||||
mIfaces = new ArrayList<String>();
|
||||
in.readStringList(mIfaces);
|
||||
mConnectionParams = in.readBundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interfaces this connection wants to use.
|
||||
*
|
||||
* @return The interfaces for this connection
|
||||
*/
|
||||
public List<String> getInterfaceNames() {
|
||||
return mIfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameters that will be used for connecting.
|
||||
*
|
||||
* @return The set of connection parameters this connections uses
|
||||
*/
|
||||
public Bundle getConnectionParams() {
|
||||
return mConnectionParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeStringList(mIfaces);
|
||||
dest.writeBundle(mConnectionParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder bob = new StringBuilder();
|
||||
bob.append("Options: interfaces={");
|
||||
for (int i = 0; i < mIfaces.size(); i++) {
|
||||
if (i != 0) {
|
||||
bob.append(", ");
|
||||
}
|
||||
bob.append(mIfaces.get(i));
|
||||
}
|
||||
bob.append("}");
|
||||
bob.append(", parameters=");
|
||||
bob.append(mConnectionParams == null ? "null" : mConnectionParams.toString());
|
||||
return bob.toString();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<RouteOptions> CREATOR
|
||||
= new Parcelable.Creator<RouteOptions>() {
|
||||
@Override
|
||||
public RouteOptions createFromParcel(Parcel in) {
|
||||
return new RouteOptions(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteOptions[] newArray(int size) {
|
||||
return new RouteOptions[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Builder for creating {@link RouteOptions}.
|
||||
*/
|
||||
public final static class Builder {
|
||||
private ArrayList<String> mIfaces = new ArrayList<String>();
|
||||
private Bundle mConnectionParams;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a required interface to the options.
|
||||
*
|
||||
* @param interfaceName The name of the interface to add.
|
||||
* @return The builder to allow chaining commands.
|
||||
*/
|
||||
public Builder addInterface(String interfaceName) {
|
||||
if (TextUtils.isEmpty(interfaceName)) {
|
||||
throw new IllegalArgumentException("interfaceName cannot be empty");
|
||||
}
|
||||
if (!mIfaces.contains(interfaceName)) {
|
||||
mIfaces.add(interfaceName);
|
||||
} else {
|
||||
Log.w(TAG, "Attempted to add interface that is already added");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection parameters to use with the options. TODO replace
|
||||
* with more specific calls once we decide on the standard way to
|
||||
* express parameters.
|
||||
*
|
||||
* @param parameters The parameters to use.
|
||||
* @return The builder to allow chaining commands.
|
||||
*/
|
||||
public Builder setParameters(Bundle parameters) {
|
||||
mConnectionParams = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a set of options.
|
||||
*
|
||||
* @return The options with the specified components.
|
||||
*/
|
||||
public RouteOptions build() {
|
||||
return new RouteOptions(mIfaces, mConnectionParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
161
media/java/android/media/session/RoutePlaybackControls.java
Normal file
161
media/java/android/media/session/RoutePlaybackControls.java
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
/**
|
||||
* A standard media control interface for Routes that support queueing and
|
||||
* transport controls. Routes may support multiple interfaces for MediaSessions
|
||||
* to interact with.
|
||||
*/
|
||||
public final class RoutePlaybackControls {
|
||||
private static final String TAG = "RoutePlaybackControls";
|
||||
public static final String NAME = "android.media.session.RoutePlaybackControls";
|
||||
|
||||
/** @hide */
|
||||
public static final String KEY_VALUE1 = "value1";
|
||||
|
||||
/** @hide */
|
||||
public static final String CMD_FAST_FORWARD = "fastForward";
|
||||
/** @hide */
|
||||
public static final String CMD_GET_CURRENT_POSITION = "getCurrentPosition";
|
||||
/** @hide */
|
||||
public static final String CMD_GET_CAPABILITIES = "getCapabilities";
|
||||
/** @hide */
|
||||
public static final String CMD_PLAY_NOW = "playNow";
|
||||
/** @hide */
|
||||
public static final String CMD_RESUME = "resume";
|
||||
/** @hide */
|
||||
public static final String CMD_PAUSE = "pause";
|
||||
|
||||
/** @hide */
|
||||
public static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
|
||||
/** @hide */
|
||||
public static final String EVENT_METADATA_CHANGE = "metadataChange";
|
||||
|
||||
private final RouteInterface mIface;
|
||||
|
||||
private RoutePlaybackControls(RouteInterface iface) {
|
||||
mIface = iface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new MediaRoutePlaybackControls instance for sending commands using
|
||||
* this interface. If the provided route doesn't support this interface null
|
||||
* will be returned.
|
||||
*
|
||||
* @param route The route to send commands to.
|
||||
* @return A MediaRoutePlaybackControls instance or null if not supported.
|
||||
*/
|
||||
public static RoutePlaybackControls from(Route route) {
|
||||
RouteInterface iface = route.getInterface(NAME);
|
||||
if (iface != null) {
|
||||
return new RoutePlaybackControls(iface);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a resume command to the route.
|
||||
*/
|
||||
public void resume() {
|
||||
mIface.sendCommand(CMD_RESUME, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a pause command to the route.
|
||||
*/
|
||||
public void pause() {
|
||||
mIface.sendCommand(CMD_PAUSE, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a fast forward command.
|
||||
*/
|
||||
public void fastForward() {
|
||||
Bundle b = new Bundle();
|
||||
mIface.sendCommand(CMD_FAST_FORWARD, b, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current playback position.
|
||||
*
|
||||
* @param cb The callback to receive the result on.
|
||||
*/
|
||||
public void getCurrentPosition(ResultReceiver cb) {
|
||||
mIface.sendCommand(CMD_GET_CURRENT_POSITION, null, cb);
|
||||
}
|
||||
|
||||
public void getCapabilities(ResultReceiver cb) {
|
||||
mIface.sendCommand(CMD_GET_CAPABILITIES, null, cb);
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
mIface.addListener(listener);
|
||||
}
|
||||
|
||||
public void addListener(Listener listener, Handler handler) {
|
||||
mIface.addListener(listener, handler);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
mIface.removeListener(listener);
|
||||
}
|
||||
|
||||
public void playNow(String content) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_VALUE1, content);
|
||||
mIface.sendCommand(CMD_PLAY_NOW, bundle, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this event listener using {@link #addListener} to receive
|
||||
* RoutePlaybackControl events from a session.
|
||||
*/
|
||||
public static abstract class Listener extends RouteInterface.EventListener {
|
||||
@Override
|
||||
public final void onEvent(String event, Bundle args) {
|
||||
if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
|
||||
onPlaybackStateChange(args.getInt(KEY_VALUE1, 0));
|
||||
} else if (EVENT_METADATA_CHANGE.equals(event)) {
|
||||
onMetadataUpdate((MediaMetadata) args.getParcelable(KEY_VALUE1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle updates to the playback state. Valid values are in
|
||||
* {@link TransportPerformer}. 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 MediaMetadata}.
|
||||
*
|
||||
* @param metadata
|
||||
*/
|
||||
public void onMetadataUpdate(MediaMetadata metadata) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.media.RemoteControlClient;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A standard media control interface for Routes. Routes can support multiple
|
||||
* interfaces for MediaSessions to interact with. TODO rewrite for routes
|
||||
*/
|
||||
public final class RouteTransportControls {
|
||||
private static final String TAG = "RouteTransportControls";
|
||||
public static final String NAME = "android.media.session.RouteTransportControls";
|
||||
|
||||
private static final String KEY_VALUE1 = "value1";
|
||||
|
||||
private static final String METHOD_FAST_FORWARD = "fastForward";
|
||||
private static final String METHOD_GET_CURRENT_POSITION = "getCurrentPosition";
|
||||
private static final String METHOD_GET_CAPABILITIES = "getCapabilities";
|
||||
|
||||
private static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
|
||||
private static final String EVENT_METADATA_CHANGE = "metadataChange";
|
||||
|
||||
private final MediaController mController;
|
||||
private final RouteInterface mIface;
|
||||
|
||||
private RouteTransportControls(RouteInterface iface, MediaController controller) {
|
||||
mIface = iface;
|
||||
mController = controller;
|
||||
}
|
||||
|
||||
public static RouteTransportControls from(MediaController controller) {
|
||||
// MediaInterface iface = controller.getInterface(NAME);
|
||||
// if (iface != null) {
|
||||
// return new RouteTransportControls(iface, controller);
|
||||
// }
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a play command to the route. TODO rename resume() and use messaging
|
||||
* protocol, not KeyEvent
|
||||
*/
|
||||
public void play() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a pause command to the session.
|
||||
*/
|
||||
public void pause() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rate at which to fastforward. Valid values are in the range [0,1]
|
||||
* with actual rates depending on the implementation.
|
||||
*
|
||||
* @param rate
|
||||
*/
|
||||
public void fastForward(float rate) {
|
||||
if (rate < 0 || rate > 1) {
|
||||
throw new IllegalArgumentException("Rate must be between 0 and 1 inclusive");
|
||||
}
|
||||
Bundle b = new Bundle();
|
||||
b.putFloat(KEY_VALUE1, rate);
|
||||
mIface.sendCommand(METHOD_FAST_FORWARD, b, null);
|
||||
}
|
||||
|
||||
public void getCurrentPosition(ResultReceiver cb) {
|
||||
mIface.sendCommand(METHOD_GET_CURRENT_POSITION, null, cb);
|
||||
}
|
||||
|
||||
public void getCapabilities(ResultReceiver cb) {
|
||||
mIface.sendCommand(METHOD_GET_CAPABILITIES, null, cb);
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
mIface.addListener(listener.mListener);
|
||||
}
|
||||
|
||||
public void addListener(Listener listener, Handler handler) {
|
||||
mIface.addListener(listener.mListener, handler);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
mIface.removeListener(listener.mListener);
|
||||
}
|
||||
|
||||
public static abstract class Stub extends RouteInterface.Stub {
|
||||
private final MediaSession mSession;
|
||||
|
||||
public Stub(MediaSession session) {
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(String method, Bundle extras, ResultReceiver cb) {
|
||||
if (TextUtils.isEmpty(method)) {
|
||||
return;
|
||||
}
|
||||
Bundle result;
|
||||
if (METHOD_FAST_FORWARD.equals(method)) {
|
||||
fastForward(extras.getFloat(KEY_VALUE1, -1));
|
||||
} else if (METHOD_GET_CURRENT_POSITION.equals(method)) {
|
||||
if (cb != null) {
|
||||
result = new Bundle();
|
||||
result.putLong(KEY_VALUE1, getCurrentPosition());
|
||||
cb.send(0, result);
|
||||
}
|
||||
} else if (METHOD_GET_CAPABILITIES.equals(method)) {
|
||||
if (cb != null) {
|
||||
result = new Bundle();
|
||||
result.putLong(KEY_VALUE1, getCapabilities());
|
||||
cb.send(0, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle fast forwarding. Valid values are [0,1] inclusive.
|
||||
* The interpretation of the rate is up to the implementation. If no
|
||||
* rate was included with the command a rate of -1 will be used by
|
||||
* default.
|
||||
*
|
||||
* @param rate The rate at which to fast forward as a multiplier
|
||||
*/
|
||||
public void fastForward(float rate) {
|
||||
Log.w(TAG, "fastForward is not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle getting the current position of playback in
|
||||
* millis.
|
||||
*
|
||||
* @return The current position in millis or -1
|
||||
*/
|
||||
public long getCurrentPosition() {
|
||||
Log.w(TAG, "getCurrentPosition is not supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle getting the set of capabilities currently
|
||||
* available.
|
||||
*
|
||||
* @return A bit mask of the supported capabilities
|
||||
*/
|
||||
public long getCapabilities() {
|
||||
Log.w(TAG, "getCapabilities is not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 final void updatePlaybackState(int state) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putInt(KEY_VALUE1, state);
|
||||
sendEvent(mSession, EVENT_PLAYSTATE_CHANGE, extras);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this event listener using TODO to receive
|
||||
* TransportControlInterface events from a session.
|
||||
*
|
||||
* @see RouteInterface.EventListener
|
||||
*/
|
||||
public static abstract class Listener {
|
||||
|
||||
private RouteInterface.EventListener mListener = new RouteInterface.EventListener() {
|
||||
@Override
|
||||
public final void onEvent(String event, Bundle args) {
|
||||
if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
|
||||
onPlaybackStateChange(args.getInt(KEY_VALUE1));
|
||||
} else if (EVENT_METADATA_CHANGE.equals(event)) {
|
||||
onMetadataUpdate(args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Override to handle updates to the playback state. Valid values are in
|
||||
* {@link TransportPerformer}. 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 MediaMetadata}.
|
||||
*
|
||||
* @param metadata
|
||||
*/
|
||||
public void onMetadataUpdate(Bundle metadata) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,9 +18,9 @@ package android.media.session;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.media.Rating;
|
||||
import android.media.session.IMediaController;
|
||||
import android.media.session.IMediaSession;
|
||||
import android.media.session.IMediaSessionCallback;
|
||||
import android.media.session.ISessionController;
|
||||
import android.media.session.ISession;
|
||||
import android.media.session.ISessionCallback;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -33,6 +33,7 @@ import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Allows interaction with media controllers, media routes, volume keys, media
|
||||
@@ -44,11 +45,11 @@ import java.util.ArrayList;
|
||||
* 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
|
||||
* {@link SessionManager#createSession(String)}. Once a session is created
|
||||
* apps that have the MEDIA_CONTENT_CONTROL permission can interact with the
|
||||
* session through {@link MediaSessionManager#getActiveSessions()}. The owner of
|
||||
* session through {@link SessionManager#getActiveSessions()}. 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
|
||||
* this permission to create a {@link SessionController} to interact with this
|
||||
* session.
|
||||
* <p>
|
||||
* To receive commands, media keys, and other events a Callback must be set with
|
||||
@@ -59,12 +60,13 @@ import java.util.ArrayList;
|
||||
* <p>
|
||||
* MediaSession objects are thread safe
|
||||
*/
|
||||
public final class MediaSession {
|
||||
private static final String TAG = "MediaSession";
|
||||
public final class Session {
|
||||
private static final String TAG = "Session";
|
||||
|
||||
private static final int MSG_MEDIA_BUTTON = 1;
|
||||
private static final int MSG_COMMAND = 2;
|
||||
private static final int MSG_ROUTE_CHANGE = 3;
|
||||
private static final int MSG_ROUTE_CONNECTED = 4;
|
||||
|
||||
private static final String KEY_COMMAND = "command";
|
||||
private static final String KEY_EXTRAS = "extras";
|
||||
@@ -72,32 +74,33 @@ public final class MediaSession {
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private final MediaSessionToken mSessionToken;
|
||||
private final IMediaSession mBinder;
|
||||
private final SessionToken mSessionToken;
|
||||
private final ISession mBinder;
|
||||
private final CallbackStub mCbStub;
|
||||
|
||||
private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
|
||||
// TODO route interfaces
|
||||
private final ArrayMap<String, RouteInterface.Stub> mInterfaces
|
||||
= new ArrayMap<String, RouteInterface.Stub>();
|
||||
private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners
|
||||
= new ArrayMap<String, RouteInterface.EventListener>();
|
||||
|
||||
private TransportPerformer mPerformer;
|
||||
private Route mRoute;
|
||||
|
||||
private boolean mPublished = false;;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public MediaSession(IMediaSession binder, CallbackStub cbStub) {
|
||||
public Session(ISession binder, CallbackStub cbStub) {
|
||||
mBinder = binder;
|
||||
mCbStub = cbStub;
|
||||
IMediaController controllerBinder = null;
|
||||
ISessionController controllerBinder = null;
|
||||
try {
|
||||
controllerBinder = mBinder.getMediaController();
|
||||
controllerBinder = mBinder.getController();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
|
||||
}
|
||||
mSessionToken = new MediaSessionToken(controllerBinder);
|
||||
mSessionToken = new SessionToken(controllerBinder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,6 +112,13 @@ public final class MediaSession {
|
||||
addCallback(callback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to receive updates for the MediaSession. This includes
|
||||
* events like route updates, media buttons, and focus changes.
|
||||
*
|
||||
* @param callback The callback to receive updates on.
|
||||
* @param handler The handler that events should be posted on.
|
||||
*/
|
||||
public void addCallback(Callback callback, Handler handler) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("Callback cannot be null");
|
||||
@@ -126,6 +136,11 @@ public final class MediaSession {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback. It will no longer receive updates.
|
||||
*
|
||||
* @param callback The callback to remove.
|
||||
*/
|
||||
public void removeCallback(Callback callback) {
|
||||
synchronized (mLock) {
|
||||
removeCallbackLocked(callback);
|
||||
@@ -185,30 +200,6 @@ public final class MediaSession {
|
||||
mPublished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an interface that can be used by MediaSessions. TODO make this a
|
||||
* route provider api
|
||||
*
|
||||
* @see RouteInterface
|
||||
* @param iface The interface to add
|
||||
* @hide
|
||||
*/
|
||||
public void addInterface(RouteInterface.Stub iface) {
|
||||
if (iface == null) {
|
||||
throw new IllegalArgumentException("Stub cannot be null");
|
||||
}
|
||||
String name = iface.getName();
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
throw new IllegalArgumentException("Stub must return a valid name");
|
||||
}
|
||||
if (mInterfaces.containsKey(iface)) {
|
||||
throw new IllegalArgumentException("Interface is already added");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mInterfaces.put(iface.getName(), iface);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a proprietary event to all MediaControllers listening to this
|
||||
* Session. It's up to the Controller/Session owner to determine the meaning
|
||||
@@ -243,16 +234,92 @@ public final class MediaSession {
|
||||
|
||||
/**
|
||||
* Retrieve a token object that can be used by apps to create a
|
||||
* {@link MediaController} for interacting with this session. The owner of
|
||||
* {@link SessionController} 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() {
|
||||
public SessionToken getSessionToken() {
|
||||
return mSessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the current route using the specified request.
|
||||
* <p>
|
||||
* Connection updates will be sent to the callback's
|
||||
* {@link Callback#onRouteConnected(Route)} and
|
||||
* {@link Callback#onRouteDisconnected(Route, int)} methods. If the
|
||||
* connection fails {@link Callback#onRouteDisconnected(Route, int)}
|
||||
* will be called.
|
||||
* <p>
|
||||
* If you already have a connection to this route it will be disconnected
|
||||
* before the new connection is established. TODO add an easy way to compare
|
||||
* MediaRouteOptions.
|
||||
*
|
||||
* @param route The route the app is trying to connect to.
|
||||
* @param request The connection request to use.
|
||||
*/
|
||||
public void connect(RouteInfo route, RouteOptions request) {
|
||||
if (route == null) {
|
||||
throw new IllegalArgumentException("Must specify the route");
|
||||
}
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("Must specify the connection request");
|
||||
}
|
||||
try {
|
||||
mBinder.connectToRoute(route, request);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Error starting connection to route", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the current route. After calling you will be switched
|
||||
* back to the default route.
|
||||
*
|
||||
* @param route The route to disconnect from.
|
||||
*/
|
||||
public void disconnect(RouteInfo route) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of route options your app is interested in connecting to. It
|
||||
* will be used for picking valid routes.
|
||||
*
|
||||
* @param options The set of route options your app may use to connect.
|
||||
*/
|
||||
public void setRouteOptions(List<RouteOptions> options) {
|
||||
try {
|
||||
mBinder.setRouteOptions(options);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Error setting route options.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* TODO allow multiple listeners for the same interface, allow removal
|
||||
*/
|
||||
public void addInterfaceListener(String iface,
|
||||
RouteInterface.EventListener listener) {
|
||||
mInterfaceListeners.put(iface, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean sendRouteCommand(RouteCommand command, ResultReceiver cb) {
|
||||
try {
|
||||
mBinder.sendRouteCommand(command, cb);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Error sending command to route.", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private MessageHandler getHandlerForCallbackLocked(Callback cb) {
|
||||
if (cb == null) {
|
||||
throw new IllegalArgumentException("Callback cannot be null");
|
||||
@@ -297,10 +364,19 @@ public final class MediaSession {
|
||||
}
|
||||
}
|
||||
|
||||
private void postRequestRouteChange(Bundle mediaRouteDescriptor) {
|
||||
private void postRequestRouteChange(RouteInfo route) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||
mCallbacks.get(i).post(MSG_ROUTE_CHANGE, mediaRouteDescriptor);
|
||||
mCallbacks.get(i).post(MSG_ROUTE_CHANGE, route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void postRouteConnected(RouteInfo route, RouteOptions options) {
|
||||
synchronized (mLock) {
|
||||
mRoute = new Route(route, options, this);
|
||||
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||
mCallbacks.get(i).post(MSG_ROUTE_CONNECTED, mRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,26 +422,49 @@ public final class MediaSession {
|
||||
* The app is responsible for connecting to the new route and migrating
|
||||
* ongoing playback if necessary.
|
||||
*
|
||||
* @param descriptor
|
||||
* @param route
|
||||
*/
|
||||
public void onRequestRouteChange(Bundle descriptor) {
|
||||
public void onRequestRouteChange(RouteInfo route) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a route has successfully connected. Calls to the route
|
||||
* are now valid.
|
||||
*
|
||||
* @param route The route that was connected
|
||||
*/
|
||||
public void onRouteConnected(Route route) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a route was disconnected. Further calls to the route will
|
||||
* fail. If available a reason for being disconnected will be provided.
|
||||
* <p>
|
||||
* Valid reasons are:
|
||||
* <ul>
|
||||
* </ul>
|
||||
*
|
||||
* @param route The route that disconnected
|
||||
* @param reason The reason for the disconnect
|
||||
*/
|
||||
public void onRouteDisconnected(Route route, int reason) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static class CallbackStub extends IMediaSessionCallback.Stub {
|
||||
private WeakReference<MediaSession> mMediaSession;
|
||||
public static class CallbackStub extends ISessionCallback.Stub {
|
||||
private WeakReference<Session> mMediaSession;
|
||||
|
||||
public void setMediaSession(MediaSession session) {
|
||||
mMediaSession = new WeakReference<MediaSession>(session);
|
||||
public void setMediaSession(Session session) {
|
||||
mMediaSession = new WeakReference<Session>(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(String command, Bundle extras, ResultReceiver cb)
|
||||
throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
session.postCommand(command, extras, cb);
|
||||
}
|
||||
@@ -373,23 +472,31 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onMediaButton(Intent mediaButtonIntent) throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
session.postMediaButton(mediaButtonIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestRouteChange(Bundle mediaRouteDescriptor) throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
public void onRequestRouteChange(RouteInfo route) throws RemoteException {
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
session.postRequestRouteChange(mediaRouteDescriptor);
|
||||
session.postRequestRouteChange(route);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteConnected(RouteInfo route, RouteOptions options) {
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
session.postRouteConnected(route, options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlay() throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -400,7 +507,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onPause() throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -411,7 +518,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onStop() throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -422,7 +529,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onNext() throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -433,7 +540,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onPrevious() throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -444,7 +551,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onFastForward() throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -455,7 +562,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onRewind() throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -466,7 +573,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onSeekTo(long pos) throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -477,7 +584,7 @@ public final class MediaSession {
|
||||
|
||||
@Override
|
||||
public void onRate(Rating rating) throws RemoteException {
|
||||
MediaSession session = mMediaSession.get();
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
TransportPerformer tp = session.getTransportPerformer();
|
||||
if (tp != null) {
|
||||
@@ -486,12 +593,32 @@ public final class MediaSession {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteEvent(RouteEvent event) throws RemoteException {
|
||||
Session session = mMediaSession.get();
|
||||
if (session != null) {
|
||||
RouteInterface.EventListener iface
|
||||
= session.mInterfaceListeners.get(event.getIface());
|
||||
Log.d(TAG, "Received route event on iface " + event.getIface() + ". Listener is "
|
||||
+ iface);
|
||||
if (iface != null) {
|
||||
iface.onEvent(event.getEvent(), event.getExtras());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteStateChange(int state) throws RemoteException {
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MessageHandler extends Handler {
|
||||
private MediaSession.Callback mCallback;
|
||||
private Session.Callback mCallback;
|
||||
|
||||
public MessageHandler(Looper looper, MediaSession.Callback callback) {
|
||||
public MessageHandler(Looper looper, Session.Callback callback) {
|
||||
super(looper, null, true);
|
||||
mCallback = callback;
|
||||
}
|
||||
@@ -511,11 +638,13 @@ public final class MediaSession {
|
||||
mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
|
||||
break;
|
||||
case MSG_ROUTE_CHANGE:
|
||||
mCallback.onRequestRouteChange((Bundle) msg.obj);
|
||||
mCallback.onRequestRouteChange((RouteInfo) msg.obj);
|
||||
break;
|
||||
case MSG_ROUTE_CONNECTED:
|
||||
mCallback.onRouteConnected((Route) msg.obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
msg.recycle();
|
||||
}
|
||||
|
||||
public void post(int what, Object obj) {
|
||||
@@ -34,21 +34,21 @@ import java.util.ArrayList;
|
||||
* 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
|
||||
* A MediaController can be created through {@link SessionManager} if you
|
||||
* hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
|
||||
* you have a {@link MediaSessionToken} from the session owner.
|
||||
* you have a {@link SessionToken} from the session owner.
|
||||
* <p>
|
||||
* MediaController objects are thread-safe.
|
||||
*/
|
||||
public final class MediaController {
|
||||
private static final String TAG = "MediaController";
|
||||
public final class SessionController {
|
||||
private static final String TAG = "SessionController";
|
||||
|
||||
private static final int MSG_EVENT = 1;
|
||||
private static final int MESSAGE_PLAYBACK_STATE = 2;
|
||||
private static final int MESSAGE_METADATA = 3;
|
||||
private static final int MSG_ROUTE = 4;
|
||||
|
||||
private final IMediaController mSessionBinder;
|
||||
private final ISessionController mSessionBinder;
|
||||
|
||||
private final CallbackStub mCbStub = new CallbackStub(this);
|
||||
private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
|
||||
@@ -58,15 +58,15 @@ public final class MediaController {
|
||||
|
||||
private TransportController mTransportController;
|
||||
|
||||
private MediaController(IMediaController sessionBinder) {
|
||||
private SessionController(ISessionController sessionBinder) {
|
||||
mSessionBinder = sessionBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static MediaController fromBinder(IMediaController sessionBinder) {
|
||||
MediaController controller = new MediaController(sessionBinder);
|
||||
public static SessionController fromBinder(ISessionController sessionBinder) {
|
||||
SessionController controller = new SessionController(sessionBinder);
|
||||
try {
|
||||
controller.mSessionBinder.registerCallbackListener(controller.mCbStub);
|
||||
if (controller.mSessionBinder.isTransportControlEnabled()) {
|
||||
@@ -87,7 +87,7 @@ public final class MediaController {
|
||||
* @param token The session token to use
|
||||
* @return A controller for the session or null
|
||||
*/
|
||||
public static MediaController fromToken(MediaSessionToken token) {
|
||||
public static SessionController fromToken(SessionToken token) {
|
||||
return fromBinder(token.getBinder());
|
||||
}
|
||||
|
||||
@@ -181,10 +181,22 @@ public final class MediaController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that the route picker be shown for this session. This should
|
||||
* generally be called in response to a user action.
|
||||
*/
|
||||
public void showRoutePicker() {
|
||||
try {
|
||||
mSessionBinder.showRoutePicker();
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Dead object in showRoutePicker", e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @hide
|
||||
*/
|
||||
IMediaController getSessionBinder() {
|
||||
ISessionController getSessionBinder() {
|
||||
return mSessionBinder;
|
||||
}
|
||||
|
||||
@@ -247,10 +259,10 @@ public final class MediaController {
|
||||
}
|
||||
}
|
||||
|
||||
private void postRouteChanged(Bundle routeDescriptor) {
|
||||
private void postRouteChanged(RouteInfo route) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||
mCallbacks.get(i).post(MSG_ROUTE, null, routeDescriptor);
|
||||
mCallbacks.get(i).post(MSG_ROUTE, route, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,36 +287,36 @@ public final class MediaController {
|
||||
*
|
||||
* @param route
|
||||
*/
|
||||
public void onRouteChanged(Bundle route) {
|
||||
public void onRouteChanged(RouteInfo route) {
|
||||
}
|
||||
}
|
||||
|
||||
private final static class CallbackStub extends IMediaControllerCallback.Stub {
|
||||
private final WeakReference<MediaController> mController;
|
||||
private final static class CallbackStub extends ISessionControllerCallback.Stub {
|
||||
private final WeakReference<SessionController> mController;
|
||||
|
||||
public CallbackStub(MediaController controller) {
|
||||
mController = new WeakReference<MediaController>(controller);
|
||||
public CallbackStub(SessionController controller) {
|
||||
mController = new WeakReference<SessionController>(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(String event, Bundle extras) {
|
||||
MediaController controller = mController.get();
|
||||
SessionController controller = mController.get();
|
||||
if (controller != null) {
|
||||
controller.postEvent(event, extras);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteChanged(Bundle mediaRouteDescriptor) {
|
||||
MediaController controller = mController.get();
|
||||
public void onRouteChanged(RouteInfo route) {
|
||||
SessionController controller = mController.get();
|
||||
if (controller != null) {
|
||||
controller.postRouteChanged(mediaRouteDescriptor);
|
||||
controller.postRouteChanged(route);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackState state) {
|
||||
MediaController controller = mController.get();
|
||||
SessionController controller = mController.get();
|
||||
if (controller != null) {
|
||||
TransportController tc = controller.getTransportController();
|
||||
if (tc != null) {
|
||||
@@ -315,7 +327,7 @@ public final class MediaController {
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged(MediaMetadata metadata) {
|
||||
MediaController controller = mController.get();
|
||||
SessionController controller = mController.get();
|
||||
if (controller != null) {
|
||||
TransportController tc = controller.getTransportController();
|
||||
if (tc != null) {
|
||||
@@ -327,9 +339,9 @@ public final class MediaController {
|
||||
}
|
||||
|
||||
private final static class MessageHandler extends Handler {
|
||||
private final MediaController.Callback mCallback;
|
||||
private final SessionController.Callback mCallback;
|
||||
|
||||
public MessageHandler(Looper looper, MediaController.Callback cb) {
|
||||
public MessageHandler(Looper looper, SessionController.Callback cb) {
|
||||
super(looper, null, true);
|
||||
mCallback = cb;
|
||||
}
|
||||
@@ -341,7 +353,7 @@ public final class MediaController {
|
||||
mCallback.onEvent((String) msg.obj, msg.getData());
|
||||
break;
|
||||
case MSG_ROUTE:
|
||||
mCallback.onRouteChanged(msg.getData());
|
||||
mCallback.onRouteChanged((RouteInfo) msg.obj);
|
||||
}
|
||||
}
|
||||
|
||||
82
media/java/android/media/session/SessionInfo.java
Normal file
82
media/java/android/media/session/SessionInfo.java
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Information about a media session, including the owner's package name.
|
||||
*/
|
||||
public final class SessionInfo implements Parcelable {
|
||||
private final String mId;
|
||||
private final String mPackageName;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public SessionInfo(String id, String packageName) {
|
||||
mId = id;
|
||||
mPackageName = packageName;
|
||||
}
|
||||
|
||||
private SessionInfo(Parcel in) {
|
||||
mId = in.readString();
|
||||
mPackageName = in.readString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the package name of the owner of this session.
|
||||
*
|
||||
* @return The owner's package name
|
||||
*/
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique id for this session.
|
||||
*
|
||||
* @return The id for the session.
|
||||
*/
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeString(mPackageName);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SessionInfo> CREATOR
|
||||
= new Parcelable.Creator<SessionInfo>() {
|
||||
@Override
|
||||
public SessionInfo createFromParcel(Parcel in) {
|
||||
return new SessionInfo(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionInfo[] newArray(int size) {
|
||||
return new SessionInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
package android.media.session;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.session.IMediaSessionManager;
|
||||
import android.media.session.ISessionManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
@@ -35,37 +35,37 @@ import java.util.List;
|
||||
* get an instance of this class.
|
||||
* <p>
|
||||
*
|
||||
* @see MediaSession
|
||||
* @see MediaController
|
||||
* @see Session
|
||||
* @see SessionController
|
||||
*/
|
||||
public final class MediaSessionManager {
|
||||
private static final String TAG = "MediaSessionManager";
|
||||
public final class SessionManager {
|
||||
private static final String TAG = "SessionManager";
|
||||
|
||||
private final IMediaSessionManager mService;
|
||||
private final ISessionManager mService;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public MediaSessionManager(Context context) {
|
||||
public SessionManager(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);
|
||||
mService = ISessionManager.Stub.asInterface(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session.
|
||||
*
|
||||
* @param tag A short name for debugging purposes
|
||||
* @return a {@link MediaSession} for the new session
|
||||
* @return a {@link Session} for the new session
|
||||
*/
|
||||
public MediaSession createSession(String tag) {
|
||||
public Session createSession(String tag) {
|
||||
try {
|
||||
MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
|
||||
MediaSession session = new MediaSession(mService
|
||||
Session.CallbackStub cbStub = new Session.CallbackStub();
|
||||
Session session = new Session(mService
|
||||
.createSession(mContext.getPackageName(), cbStub, tag), cbStub);
|
||||
cbStub.setMediaSession(session);
|
||||
|
||||
@@ -83,8 +83,8 @@ public final class MediaSessionManager {
|
||||
*
|
||||
* @return a list of controllers for ongoing sessions
|
||||
*/
|
||||
public List<MediaController> getActiveSessions() {
|
||||
public List<SessionController> getActiveSessions() {
|
||||
// TODO
|
||||
return new ArrayList<MediaController>();
|
||||
return new ArrayList<SessionController>();
|
||||
}
|
||||
}
|
||||
18
media/java/android/media/session/SessionToken.aidl
Normal file
18
media/java/android/media/session/SessionToken.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.session;
|
||||
|
||||
parcelable SessionToken;
|
||||
@@ -16,28 +16,28 @@
|
||||
|
||||
package android.media.session;
|
||||
|
||||
import android.media.session.IMediaController;
|
||||
import android.media.session.ISessionController;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class MediaSessionToken implements Parcelable {
|
||||
private IMediaController mBinder;
|
||||
public class SessionToken implements Parcelable {
|
||||
private ISessionController mBinder;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
MediaSessionToken(IMediaController binder) {
|
||||
SessionToken(ISessionController binder) {
|
||||
mBinder = binder;
|
||||
}
|
||||
|
||||
private MediaSessionToken(Parcel in) {
|
||||
mBinder = IMediaController.Stub.asInterface(in.readStrongBinder());
|
||||
private SessionToken(Parcel in) {
|
||||
mBinder = ISessionController.Stub.asInterface(in.readStrongBinder());
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
IMediaController getBinder() {
|
||||
ISessionController getBinder() {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@@ -51,16 +51,16 @@ public class MediaSessionToken implements Parcelable {
|
||||
dest.writeStrongBinder(mBinder.asBinder());
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MediaSessionToken> CREATOR
|
||||
= new Parcelable.Creator<MediaSessionToken>() {
|
||||
public static final Parcelable.Creator<SessionToken> CREATOR
|
||||
= new Parcelable.Creator<SessionToken>() {
|
||||
@Override
|
||||
public MediaSessionToken createFromParcel(Parcel in) {
|
||||
return new MediaSessionToken(in);
|
||||
public SessionToken createFromParcel(Parcel in) {
|
||||
return new SessionToken(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSessionToken[] newArray(int size) {
|
||||
return new MediaSessionToken[size];
|
||||
public SessionToken[] newArray(int size) {
|
||||
return new SessionToken[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -34,12 +34,12 @@ public final class TransportController {
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
|
||||
private final IMediaController mBinder;
|
||||
private final ISessionController mBinder;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public TransportController(IMediaController binder) {
|
||||
public TransportController(ISessionController binder) {
|
||||
mBinder = binder;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ public final class TransportPerformer {
|
||||
private final Object mLock = new Object();
|
||||
private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
|
||||
|
||||
private IMediaSession mBinder;
|
||||
private ISession mBinder;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public TransportPerformer(IMediaSession binder) {
|
||||
public TransportPerformer(ISession binder) {
|
||||
mBinder = binder;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* 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.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.routeprovider.IRouteConnection;
|
||||
import android.media.routeprovider.IRouteProvider;
|
||||
import android.media.routeprovider.IRouteProviderCallback;
|
||||
import android.media.routeprovider.RouteProviderService;
|
||||
import android.media.routeprovider.RouteRequest;
|
||||
import android.media.session.RouteEvent;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.Session;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* System representation and interface to a MediaRouteProvider. This class is
|
||||
* not thread safe so all calls should be made on the main thread.
|
||||
*/
|
||||
public class MediaRouteProviderProxy {
|
||||
private static final String TAG = "MRPProxy";
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
private static final int MAX_RETRIES = 3;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final Context mContext;
|
||||
private final String mId;
|
||||
private final ComponentName mComponentName;
|
||||
private final int mUserId;
|
||||
|
||||
private Intent mBindIntent;
|
||||
// Interfaces declared in the manifest
|
||||
private ArrayList<String> mInterfaces;
|
||||
private ArrayList<RouteConnectionRecord> mConnections = new ArrayList<RouteConnectionRecord>();
|
||||
private Handler mHandler = new Handler();
|
||||
|
||||
private IRouteProvider mBinder;
|
||||
private boolean mRunning;
|
||||
private boolean mInterested;
|
||||
private boolean mBound;
|
||||
private int mRetryCount;
|
||||
|
||||
private RoutesListener mRouteListener;
|
||||
|
||||
public MediaRouteProviderProxy(Context context, String id, ComponentName component, int uid,
|
||||
ArrayList<String> interfaces) {
|
||||
mContext = context;
|
||||
mId = id;
|
||||
mComponentName = component;
|
||||
mUserId = uid;
|
||||
mInterfaces = interfaces;
|
||||
mBindIntent = new Intent(RouteProviderService.SERVICE_INTERFACE);
|
||||
mBindIntent.setComponent(mComponentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any cleanup messages and unbind from the media route provider
|
||||
*/
|
||||
public void stop() {
|
||||
if (mRunning) {
|
||||
mRunning = false;
|
||||
mRetryCount = 0;
|
||||
updateBinding();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to the media route provider and perform any setup needed
|
||||
*/
|
||||
public void start() {
|
||||
if (!mRunning) {
|
||||
mRunning = true;
|
||||
updateBinding();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this provider is currently interesting to the system.
|
||||
* In the future this may take a list of interfaces instead.
|
||||
*
|
||||
* @param interested True if we want to connect to this provider
|
||||
*/
|
||||
public void setInterested(boolean interested) {
|
||||
mInterested = interested;
|
||||
updateBinding();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a listener to get route updates on.
|
||||
*
|
||||
* @param listener The listener to receive updates on.
|
||||
*/
|
||||
public void setRoutesListener(RoutesListener listener) {
|
||||
mRouteListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to the Provider to get all the routes that the session can
|
||||
* use.
|
||||
*
|
||||
* @param record The session to get routes for.
|
||||
* @param requestId An id to identify this request.
|
||||
*/
|
||||
public void getRoutes(MediaSessionRecord record, final int requestId) {
|
||||
// TODO change routes to have a system global id and maintain a mapping
|
||||
// to the original route
|
||||
if (mBinder == null) {
|
||||
Log.wtf(TAG, "Attempted to call getRoutes without a binder connection");
|
||||
return;
|
||||
}
|
||||
List<RouteRequest> requests = record.getRouteRequests();
|
||||
final String sessionId = record.getSessionInfo().getId();
|
||||
try {
|
||||
mBinder.getAvailableRoutes(requests, new ResultReceiver(mHandler) {
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
if (resultCode != RouteProviderService.RESULT_SUCCESS) {
|
||||
// ignore failures, just means no routes were generated
|
||||
return;
|
||||
}
|
||||
ArrayList<RouteInfo> routes
|
||||
= resultData.getParcelableArrayList(RouteProviderService.KEY_ROUTES);
|
||||
ArrayList<RouteInfo> sysRoutes = new ArrayList<RouteInfo>();
|
||||
for (int i = 0; i < routes.size(); i++) {
|
||||
RouteInfo route = routes.get(i);
|
||||
RouteInfo.Builder bob = new RouteInfo.Builder(route);
|
||||
bob.setProviderId(mId);
|
||||
sysRoutes.add(bob.build());
|
||||
}
|
||||
if (mRouteListener != null) {
|
||||
mRouteListener.onRoutesUpdated(sessionId, sysRoutes, requestId);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Error in getRoutes", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try connecting again if we've been disconnected.
|
||||
*/
|
||||
public void rebindIfDisconnected() {
|
||||
if (mBinder == null && shouldBind()) {
|
||||
unbind();
|
||||
bind();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to connect to a route.
|
||||
*
|
||||
* @param session The session that is trying to connect.
|
||||
* @param route The route it is connecting to.
|
||||
* @param request The request with the connection parameters.
|
||||
* @return true if the request was sent, false otherwise.
|
||||
*/
|
||||
public boolean connectToRoute(MediaSessionRecord session, final RouteInfo route,
|
||||
final RouteRequest request) {
|
||||
final String sessionId = session.getSessionInfo().getId();
|
||||
try {
|
||||
mBinder.connect(route, request, new ResultReceiver(mHandler) {
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
if (resultCode != RouteProviderService.RESULT_SUCCESS) {
|
||||
// TODO handle connection failure
|
||||
return;
|
||||
}
|
||||
IBinder binder = resultData.getBinder(RouteProviderService.KEY_CONNECTION);
|
||||
IRouteConnection connection = null;
|
||||
if (binder != null) {
|
||||
connection = IRouteConnection.Stub.asInterface(binder);
|
||||
}
|
||||
|
||||
if (connection != null) {
|
||||
RouteConnectionRecord record = new RouteConnectionRecord(
|
||||
connection);
|
||||
mConnections.add(record);
|
||||
if (mRouteListener != null) {
|
||||
mRouteListener.onRouteConnected(sessionId, route, request, record);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error connecting to route.", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is the provider you're looking for.
|
||||
*/
|
||||
public boolean hasComponentName(String packageName, String className) {
|
||||
return mComponentName.getPackageName().equals(packageName)
|
||||
&& mComponentName.getClassName().equals(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique id for this provider.
|
||||
*
|
||||
* @return The provider's id.
|
||||
*/
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
private void updateBinding() {
|
||||
if (shouldBind()) {
|
||||
bind();
|
||||
} else {
|
||||
unbind();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldBind() {
|
||||
return mRunning && mInterested;
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
if (!mBound) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Binding");
|
||||
}
|
||||
|
||||
try {
|
||||
mBound = mContext.bindServiceAsUser(mBindIntent, mServiceConn,
|
||||
Context.BIND_AUTO_CREATE, new UserHandle(mUserId));
|
||||
if (!mBound && DEBUG) {
|
||||
Slog.d(TAG, this + ": Bind failed");
|
||||
}
|
||||
} catch (SecurityException ex) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Bind failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unbind() {
|
||||
if (mBound) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, this + ": Unbinding");
|
||||
}
|
||||
|
||||
mBound = false;
|
||||
mContext.unbindService(mServiceConn);
|
||||
}
|
||||
}
|
||||
|
||||
private RouteConnectionRecord getConnectionLocked(IBinder binder) {
|
||||
for (int i = mConnections.size() - 1; i >= 0; i--) {
|
||||
RouteConnectionRecord record = mConnections.get(i);
|
||||
if (record.isConnection(binder)) {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ServiceConnection mServiceConn = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mBinder = IRouteProvider.Stub.asInterface(service);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Connected to route provider");
|
||||
}
|
||||
try {
|
||||
mBinder.registerCallback(mCbStub);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error registering callback on route provider. Retry count: "
|
||||
+ mRetryCount, e);
|
||||
if (mRetryCount < MAX_RETRIES) {
|
||||
mRetryCount++;
|
||||
rebindIfDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mBinder = null;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Disconnected from route provider");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private IRouteProviderCallback.Stub mCbStub = new IRouteProviderCallback.Stub() {
|
||||
@Override
|
||||
public void onConnectionStateChanged(IRouteConnection connection, int state)
|
||||
throws RemoteException {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteEvent(RouteEvent event) throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
RouteConnectionRecord record = getConnectionLocked(event.getConnection());
|
||||
Log.d(TAG, "Received route event for record " + record);
|
||||
if (record != null) {
|
||||
record.sendEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionTerminated(IRouteConnection connection) throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
RouteConnectionRecord record = getConnectionLocked(connection.asBinder());
|
||||
if (record != null) {
|
||||
record.disconnect();
|
||||
mConnections.remove(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRoutesChanged() throws RemoteException {
|
||||
// TODO
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listener for receiving responses to route requests on the provider.
|
||||
*/
|
||||
public interface RoutesListener {
|
||||
/**
|
||||
* Called when routes have been returned from a request to getRoutes.
|
||||
*
|
||||
* @param record The session that the routes were requested for.
|
||||
* @param routes The matching routes returned by the provider.
|
||||
* @param reqId The request id this is responding to.
|
||||
*/
|
||||
public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
|
||||
int reqId);
|
||||
|
||||
/**
|
||||
* Called when a route has successfully connected.
|
||||
*
|
||||
* @param session The session that was connected.
|
||||
* @param route The route it connected to.
|
||||
* @param options The options that were used for the connection.
|
||||
* @param connection The connection instance that was created.
|
||||
*/
|
||||
public void onRouteConnected(String sessionId, RouteInfo route,
|
||||
RouteRequest options, RouteConnectionRecord connection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* 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.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.media.routeprovider.RouteProviderService;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Watches for media route provider services to be installed. Adds a provider to
|
||||
* the media session service for each registered service. For now just run all
|
||||
* providers. In the future define a policy for when to run providers.
|
||||
*/
|
||||
public class MediaRouteProviderWatcher {
|
||||
private static final String TAG = "MRPWatcher";
|
||||
private static final boolean DEBUG = true; // Log.isLoggable(TAG,
|
||||
// Log.DEBUG);
|
||||
|
||||
private final Context mContext;
|
||||
private final Callback mCallback;
|
||||
private final Handler mHandler;
|
||||
private final int mUserId;
|
||||
private final PackageManager mPackageManager;
|
||||
|
||||
private final ArrayList<MediaRouteProviderProxy> mProviders =
|
||||
new ArrayList<MediaRouteProviderProxy>();
|
||||
private boolean mRunning;
|
||||
|
||||
public MediaRouteProviderWatcher(Context context, Callback callback, Handler handler,
|
||||
int userId) {
|
||||
mContext = context;
|
||||
mCallback = callback;
|
||||
mHandler = handler;
|
||||
mUserId = userId;
|
||||
mPackageManager = context.getPackageManager();
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
pw.println(prefix + " mUserId=" + mUserId);
|
||||
pw.println(prefix + " mRunning=" + mRunning);
|
||||
pw.println(prefix + " mProviders.size()=" + mProviders.size());
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!mRunning) {
|
||||
mRunning = true;
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
|
||||
filter.addDataScheme("package");
|
||||
mContext.registerReceiverAsUser(mScanPackagesReceiver,
|
||||
new UserHandle(mUserId), filter, null, mHandler);
|
||||
|
||||
// Scan packages.
|
||||
// Also has the side-effect of restarting providers if needed.
|
||||
mHandler.post(mScanPackagesRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mRunning) {
|
||||
mRunning = false;
|
||||
|
||||
mContext.unregisterReceiver(mScanPackagesReceiver);
|
||||
mHandler.removeCallbacks(mScanPackagesRunnable);
|
||||
|
||||
// Stop all providers.
|
||||
for (int i = mProviders.size() - 1; i >= 0; i--) {
|
||||
mProviders.get(i).stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<MediaRouteProviderProxy> getProviders() {
|
||||
return mProviders;
|
||||
}
|
||||
|
||||
public MediaRouteProviderProxy getProvider(String id) {
|
||||
int providerIndex = findProvider(id);
|
||||
if (providerIndex != -1) {
|
||||
return mProviders.get(providerIndex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void scanPackages() {
|
||||
if (!mRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add providers for all new services.
|
||||
// Reorder the list so that providers left at the end will be the ones
|
||||
// to remove.
|
||||
int targetIndex = 0;
|
||||
Intent intent = new Intent(RouteProviderService.SERVICE_INTERFACE);
|
||||
for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
|
||||
intent, 0, mUserId)) {
|
||||
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Checking service " + (serviceInfo == null ? null : serviceInfo.name));
|
||||
}
|
||||
if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
|
||||
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
|
||||
if (sourceIndex < 0) {
|
||||
// TODO get declared interfaces from manifest
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Creating new provider proxy for service");
|
||||
}
|
||||
MediaRouteProviderProxy provider =
|
||||
new MediaRouteProviderProxy(mContext, UUID.randomUUID().toString(),
|
||||
new ComponentName(serviceInfo.packageName, serviceInfo.name),
|
||||
mUserId, null);
|
||||
provider.start();
|
||||
mProviders.add(targetIndex++, provider);
|
||||
mCallback.addProvider(provider);
|
||||
} else if (sourceIndex >= targetIndex) {
|
||||
MediaRouteProviderProxy provider = mProviders.get(sourceIndex);
|
||||
provider.start(); // restart the provider if needed
|
||||
provider.rebindIfDisconnected();
|
||||
Collections.swap(mProviders, sourceIndex, targetIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove providers for missing services.
|
||||
if (targetIndex < mProviders.size()) {
|
||||
for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
|
||||
MediaRouteProviderProxy provider = mProviders.get(i);
|
||||
mCallback.removeProvider(provider);
|
||||
mProviders.remove(provider);
|
||||
provider.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
|
||||
if (serviceInfo.permission == null || !serviceInfo.permission.equals(
|
||||
Manifest.permission.BIND_ROUTE_PROVIDER)) {
|
||||
// If the service does not require this permission then any app
|
||||
// could potentially bind to it and mess with their routes. So we
|
||||
// only want to trust providers that require the
|
||||
// correct permissions.
|
||||
Slog.w(TAG, "Ignoring route provider service because it did not "
|
||||
+ "require the BIND_ROUTE_PROVIDER permission in its manifest: "
|
||||
+ serviceInfo.packageName + "/" + serviceInfo.name);
|
||||
return false;
|
||||
}
|
||||
// Looks good.
|
||||
return true;
|
||||
}
|
||||
|
||||
private int findProvider(String id) {
|
||||
int count = mProviders.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MediaRouteProviderProxy provider = mProviders.get(i);
|
||||
if (TextUtils.equals(id, provider.getId())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int findProvider(String packageName, String className) {
|
||||
int count = mProviders.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MediaRouteProviderProxy provider = mProviders.get(i);
|
||||
if (provider.hasComponentName(packageName, className)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Received package manager broadcast: " + intent);
|
||||
}
|
||||
scanPackages();
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mScanPackagesRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scanPackages();
|
||||
}
|
||||
};
|
||||
|
||||
public interface Callback {
|
||||
void addProvider(MediaRouteProviderProxy provider);
|
||||
|
||||
void removeProvider(MediaRouteProviderProxy provider);
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,20 @@
|
||||
package com.android.server.media;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.media.session.IMediaController;
|
||||
import android.media.session.IMediaControllerCallback;
|
||||
import android.media.session.IMediaSession;
|
||||
import android.media.session.IMediaSessionCallback;
|
||||
import android.media.routeprovider.RouteRequest;
|
||||
import android.media.session.ISessionController;
|
||||
import android.media.session.ISessionControllerCallback;
|
||||
import android.media.session.ISession;
|
||||
import android.media.session.ISessionCallback;
|
||||
import android.media.session.SessionController;
|
||||
import android.media.session.MediaMetadata;
|
||||
import android.media.session.RouteCommand;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.RouteOptions;
|
||||
import android.media.session.RouteEvent;
|
||||
import android.media.session.Session;
|
||||
import android.media.session.SessionInfo;
|
||||
import android.media.session.RouteInterface;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.media.Rating;
|
||||
import android.os.Bundle;
|
||||
@@ -31,37 +40,44 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 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 static final String TAG = "MediaSessionRecord";
|
||||
|
||||
private final MessageHandler mHandler;
|
||||
|
||||
private final int mPid;
|
||||
private final String mPackageName;
|
||||
private final SessionInfo mSessionInfo;
|
||||
private final String mTag;
|
||||
private final ControllerStub mController;
|
||||
private final SessionStub mSession;
|
||||
private final SessionCb mSessionCb;
|
||||
private final MediaSessionService mService;
|
||||
|
||||
private final Object mControllerLock = new Object();
|
||||
private final ArrayList<IMediaControllerCallback> mControllerCallbacks =
|
||||
new ArrayList<IMediaControllerCallback>();
|
||||
private final ArrayList<String> mInterfaces = new ArrayList<String>();
|
||||
private final Object mLock = new Object();
|
||||
private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
|
||||
new ArrayList<ISessionControllerCallback>();
|
||||
private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
|
||||
|
||||
private boolean mTransportPerformerEnabled = false;
|
||||
private Bundle mRoute;
|
||||
private RouteInfo mRoute;
|
||||
private RouteOptions mRequest;
|
||||
private RouteConnectionRecord mConnection;
|
||||
// TODO define a RouteState class with relevant info
|
||||
private int mRouteState;
|
||||
|
||||
// TransportPerformer fields
|
||||
|
||||
@@ -72,10 +88,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
|
||||
private boolean mIsPublished = false;
|
||||
|
||||
public MediaSessionRecord(int pid, String packageName, IMediaSessionCallback cb, String tag,
|
||||
public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag,
|
||||
MediaSessionService service, Handler handler) {
|
||||
mPid = pid;
|
||||
mPackageName = packageName;
|
||||
mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), packageName);
|
||||
mTag = tag;
|
||||
mController = new ControllerStub();
|
||||
mSession = new SessionStub();
|
||||
@@ -84,31 +100,140 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
mHandler = new MessageHandler(handler.getLooper());
|
||||
}
|
||||
|
||||
public IMediaSession getSessionBinder() {
|
||||
/**
|
||||
* Get the binder for the {@link Session}.
|
||||
*
|
||||
* @return The session binder apps talk to.
|
||||
*/
|
||||
public ISession getSessionBinder() {
|
||||
return mSession;
|
||||
}
|
||||
|
||||
public IMediaController getControllerBinder() {
|
||||
/**
|
||||
* Get the binder for the {@link SessionController}.
|
||||
*
|
||||
* @return The controller binder apps talk to.
|
||||
*/
|
||||
public ISessionController getControllerBinder() {
|
||||
return mController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of route requests this session is interested in.
|
||||
*
|
||||
* @return The list of RouteRequests
|
||||
*/
|
||||
public List<RouteRequest> getRouteRequests() {
|
||||
return mRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route this session is currently on.
|
||||
*
|
||||
* @return The route the session is on.
|
||||
*/
|
||||
public RouteInfo getRoute() {
|
||||
return mRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the info for this session.
|
||||
*
|
||||
* @return Info that identifies this session.
|
||||
*/
|
||||
public SessionInfo getSessionInfo() {
|
||||
return mSessionInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected route. This does not connect to the route, just notifies
|
||||
* the app that a new route has been selected.
|
||||
*
|
||||
* @param route The route that was selected.
|
||||
*/
|
||||
public void selectRoute(RouteInfo route) {
|
||||
synchronized (mLock) {
|
||||
if (route != mRoute) {
|
||||
if (mConnection != null) {
|
||||
mConnection.disconnect();
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
mRoute = route;
|
||||
}
|
||||
mSessionCb.sendRouteChange(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of the route this session is using and notify the
|
||||
* session.
|
||||
*
|
||||
* @param state The new state of the route.
|
||||
*/
|
||||
public void setRouteState(int state) {
|
||||
mSessionCb.sendRouteStateChange(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event to this session from the route it is using.
|
||||
*
|
||||
* @param event The event to send.
|
||||
*/
|
||||
public void sendRouteEvent(RouteEvent event) {
|
||||
mSessionCb.sendRouteEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection to use for the selected route and notify the app it is
|
||||
* now connected.
|
||||
*
|
||||
* @param route The route the connection is to.
|
||||
* @param request The request that was used to connect.
|
||||
* @param connection The connection to the route.
|
||||
* @return True if this connection is still valid, false if it is stale.
|
||||
*/
|
||||
public boolean setRouteConnected(RouteInfo route, RouteOptions request,
|
||||
RouteConnectionRecord connection) {
|
||||
synchronized (mLock) {
|
||||
if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
|
||||
Log.w(TAG, "setRouteConnected: connected route is stale");
|
||||
// TODO figure out disconnection path
|
||||
return false;
|
||||
}
|
||||
if (request != mRequest) {
|
||||
Log.w(TAG, "setRouteConnected: connection request is stale");
|
||||
// TODO figure out disconnection path
|
||||
return false;
|
||||
}
|
||||
mConnection = connection;
|
||||
mConnection.setListener(mConnectionListener);
|
||||
mSessionCb.sendRouteConnected();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this session has been published by the app yet.
|
||||
*
|
||||
* @return True if it has been published, false otherwise.
|
||||
*/
|
||||
public boolean isPublished() {
|
||||
return mIsPublished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
mService.sessionDied(this);
|
||||
}
|
||||
|
||||
public boolean isPublished() {
|
||||
return mIsPublished;
|
||||
}
|
||||
|
||||
private void onDestroy() {
|
||||
mService.destroySession(this);
|
||||
}
|
||||
|
||||
private void pushPlaybackStateUpdate() {
|
||||
synchronized (mControllerLock) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
|
||||
IMediaControllerCallback cb = mControllerCallbacks.get(i);
|
||||
ISessionControllerCallback cb = mControllerCallbacks.get(i);
|
||||
try {
|
||||
cb.onPlaybackStateChanged(mPlaybackState);
|
||||
} catch (RemoteException e) {
|
||||
@@ -120,9 +245,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
|
||||
private void pushMetadataUpdate() {
|
||||
synchronized (mControllerLock) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
|
||||
IMediaControllerCallback cb = mControllerCallbacks.get(i);
|
||||
ISessionControllerCallback cb = mControllerCallbacks.get(i);
|
||||
try {
|
||||
cb.onMetadataChanged(mMetadata);
|
||||
} catch (RemoteException e) {
|
||||
@@ -134,9 +259,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
|
||||
private void pushRouteUpdate() {
|
||||
synchronized (mControllerLock) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
|
||||
IMediaControllerCallback cb = mControllerCallbacks.get(i);
|
||||
ISessionControllerCallback cb = mControllerCallbacks.get(i);
|
||||
try {
|
||||
cb.onRouteChanged(mRoute);
|
||||
} catch (RemoteException e) {
|
||||
@@ -148,21 +273,50 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
|
||||
private void pushEvent(String event, Bundle data) {
|
||||
synchronized (mControllerLock) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
|
||||
IMediaControllerCallback cb = mControllerCallbacks.get(i);
|
||||
ISessionControllerCallback cb = mControllerCallbacks.get(i);
|
||||
try {
|
||||
cb.onEvent(event, data);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
|
||||
mControllerCallbacks.remove(i);
|
||||
Log.w(TAG, "Error with callback in pushEvent.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SessionStub extends IMediaSession.Stub {
|
||||
private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
|
||||
synchronized (mLock) {
|
||||
if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) {
|
||||
if (cb != null) {
|
||||
cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mConnection != null) {
|
||||
mConnection.sendCommand(command, cb);
|
||||
} else if (cb != null) {
|
||||
cb.send(RouteInterface.RESULT_NOT_CONNECTED, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final RouteConnectionRecord.Listener mConnectionListener
|
||||
= new RouteConnectionRecord.Listener() {
|
||||
@Override
|
||||
public void onEvent(RouteEvent event) {
|
||||
RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
|
||||
event.getEvent(), event.getExtras());
|
||||
mSessionCb.sendRouteEvent(eventForSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
// TODO
|
||||
}
|
||||
};
|
||||
|
||||
private final class SessionStub extends ISession.Stub {
|
||||
@Override
|
||||
public void destroy() {
|
||||
onDestroy();
|
||||
@@ -174,20 +328,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMediaController getMediaController() {
|
||||
public ISessionController getController() {
|
||||
return mController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRouteState(Bundle routeState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoute(Bundle mediaRouteDescriptor) {
|
||||
mRoute = mediaRouteDescriptor;
|
||||
mHandler.post(MessageHandler.MSG_UPDATE_ROUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish() {
|
||||
mIsPublished = true; // TODO push update to service
|
||||
@@ -197,11 +341,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
mTransportPerformerEnabled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSupportedInterfaces() {
|
||||
return mInterfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMetadata(MediaMetadata metadata) {
|
||||
mMetadata = metadata;
|
||||
@@ -218,12 +357,44 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
public void setRatingType(int type) {
|
||||
mRatingType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRouteCommand(RouteCommand command, ResultReceiver cb) {
|
||||
mHandler.post(MessageHandler.MSG_SEND_COMMAND,
|
||||
new Pair<RouteCommand, ResultReceiver>(command, cb));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRoute(RouteInfo route) throws RemoteException {
|
||||
// TODO decide if allowed to set route and if the route exists
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectToRoute(RouteInfo route, RouteOptions request)
|
||||
throws RemoteException {
|
||||
if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
|
||||
throw new RemoteException("RouteInfo does not match current route");
|
||||
}
|
||||
mService.connectToRoute(MediaSessionRecord.this, route, request);
|
||||
mRequest = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRouteOptions(List<RouteOptions> options) throws RemoteException {
|
||||
mRequests.clear();
|
||||
for (int i = options.size() - 1; i >= 0; i--) {
|
||||
RouteRequest request = new RouteRequest(mSessionInfo, options.get(i),
|
||||
false);
|
||||
mRequests.add(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SessionCb {
|
||||
private final IMediaSessionCallback mCb;
|
||||
private final ISessionCallback mCb;
|
||||
|
||||
public SessionCb(IMediaSessionCallback cb) {
|
||||
public SessionCb(ISessionCallback cb) {
|
||||
mCb = cb;
|
||||
}
|
||||
|
||||
@@ -245,6 +416,38 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendRouteChange(RouteInfo route) {
|
||||
try {
|
||||
mCb.onRequestRouteChange(route);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote failure in sendRouteChange.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendRouteStateChange(int state) {
|
||||
try {
|
||||
mCb.onRouteStateChange(state);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendRouteEvent(RouteEvent event) {
|
||||
try {
|
||||
mCb.onRouteEvent(event);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendRouteConnected() {
|
||||
try {
|
||||
mCb.onRouteConnected(mRoute, mRequest);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void play() {
|
||||
try {
|
||||
mCb.onPlay();
|
||||
@@ -318,7 +521,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
}
|
||||
|
||||
class ControllerStub extends IMediaController.Stub {
|
||||
class ControllerStub extends ISessionController.Stub {
|
||||
@Override
|
||||
public void sendCommand(String command, Bundle extras, ResultReceiver cb)
|
||||
throws RemoteException {
|
||||
@@ -331,8 +534,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCallbackListener(IMediaControllerCallback cb) {
|
||||
synchronized (mControllerLock) {
|
||||
public void registerCallbackListener(ISessionControllerCallback cb) {
|
||||
synchronized (mLock) {
|
||||
if (!mControllerCallbacks.contains(cb)) {
|
||||
mControllerCallbacks.add(cb);
|
||||
}
|
||||
@@ -340,9 +543,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCallbackListener(IMediaControllerCallback cb)
|
||||
public void unregisterCallbackListener(ISessionControllerCallback cb)
|
||||
throws RemoteException {
|
||||
synchronized (mControllerLock) {
|
||||
synchronized (mLock) {
|
||||
mControllerCallbacks.remove(cb);
|
||||
}
|
||||
}
|
||||
@@ -409,9 +612,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransportControlEnabled() throws RemoteException {
|
||||
public boolean isTransportControlEnabled() {
|
||||
return mTransportPerformerEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showRoutePicker() {
|
||||
mService.showRoutePickerForSession(MediaSessionRecord.this);
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageHandler extends Handler {
|
||||
@@ -419,6 +627,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
|
||||
private static final int MSG_UPDATE_ROUTE = 3;
|
||||
private static final int MSG_SEND_EVENT = 4;
|
||||
private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
|
||||
private static final int MSG_SEND_COMMAND = 6;
|
||||
|
||||
public MessageHandler(Looper looper) {
|
||||
super(looper);
|
||||
@@ -438,6 +648,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
case MSG_SEND_EVENT:
|
||||
pushEvent((String) msg.obj, msg.getData());
|
||||
break;
|
||||
case MSG_SEND_COMMAND:
|
||||
Pair<RouteCommand, ResultReceiver> cmd =
|
||||
(Pair<RouteCommand, ResultReceiver>) msg.obj;
|
||||
pushRouteCommand(cmd.first, cmd.second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
package com.android.server.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.session.IMediaSession;
|
||||
import android.media.session.IMediaSessionCallback;
|
||||
import android.media.session.IMediaSessionManager;
|
||||
import android.media.routeprovider.RouteRequest;
|
||||
import android.media.session.ISession;
|
||||
import android.media.session.ISessionCallback;
|
||||
import android.media.session.ISessionManager;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.RouteOptions;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
@@ -38,21 +41,77 @@ public class MediaSessionService extends SystemService {
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final SessionManagerImpl mSessionManagerImpl;
|
||||
private final MediaRouteProviderWatcher mRouteProviderWatcher;
|
||||
|
||||
private final ArrayList<MediaSessionRecord> mSessions
|
||||
= new ArrayList<MediaSessionRecord>();
|
||||
private final ArrayList<MediaRouteProviderProxy> mProviders
|
||||
= new ArrayList<MediaRouteProviderProxy>();
|
||||
private final Object mLock = new Object();
|
||||
// TODO do we want a separate thread for handling mediasession messages?
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
// Used to keep track of the current request to show routes for a specific
|
||||
// session so we drop late callbacks properly.
|
||||
private int mShowRoutesRequestId = 0;
|
||||
|
||||
// TODO refactor to have per user state. See MediaRouterService for an
|
||||
// example
|
||||
|
||||
public MediaSessionService(Context context) {
|
||||
super(context);
|
||||
mSessionManagerImpl = new SessionManagerImpl();
|
||||
mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback,
|
||||
mHandler, context.getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
|
||||
mRouteProviderWatcher.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should trigger showing the Media route picker dialog. Right now it just
|
||||
* kicks off a query to all the providers to get routes.
|
||||
*
|
||||
* @param record The session to show the picker for.
|
||||
*/
|
||||
public void showRoutePickerForSession(MediaSessionRecord record) {
|
||||
// TODO for now just toggle the route to test (we will only have one
|
||||
// match for now)
|
||||
if (record.getRoute() != null) {
|
||||
// For now send null to mean the local route
|
||||
record.selectRoute(null);
|
||||
return;
|
||||
}
|
||||
mShowRoutesRequestId++;
|
||||
ArrayList<MediaRouteProviderProxy> providers = mRouteProviderWatcher.getProviders();
|
||||
for (int i = providers.size() - 1; i >= 0; i--) {
|
||||
MediaRouteProviderProxy provider = providers.get(i);
|
||||
provider.getRoutes(record, mShowRoutesRequestId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a session to the given route.
|
||||
*
|
||||
* @param session The session to connect.
|
||||
* @param route The route to connect to.
|
||||
* @param options The options to use for the connection.
|
||||
*/
|
||||
public void connectToRoute(MediaSessionRecord session, RouteInfo route,
|
||||
RouteOptions options) {
|
||||
synchronized (mLock) {
|
||||
MediaRouteProviderProxy proxy = getProviderLocked(route.getProvider());
|
||||
if (proxy == null) {
|
||||
Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
|
||||
return;
|
||||
}
|
||||
RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
|
||||
// TODO make connect an async call to a ThreadPoolExecutor
|
||||
proxy.connectToRoute(session, route, request);
|
||||
}
|
||||
}
|
||||
|
||||
void sessionDied(MediaSessionRecord session) {
|
||||
@@ -86,14 +145,14 @@ public class MediaSessionService extends SystemService {
|
||||
}
|
||||
|
||||
private MediaSessionRecord createSessionInternal(int pid, String packageName,
|
||||
IMediaSessionCallback cb, String tag) {
|
||||
ISessionCallback cb, String tag) {
|
||||
synchronized (mLock) {
|
||||
return createSessionLocked(pid, packageName, cb, tag);
|
||||
}
|
||||
}
|
||||
|
||||
private MediaSessionRecord createSessionLocked(int pid, String packageName,
|
||||
IMediaSessionCallback cb, String tag) {
|
||||
ISessionCallback cb, String tag) {
|
||||
final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this,
|
||||
mHandler);
|
||||
try {
|
||||
@@ -110,9 +169,82 @@ public class MediaSessionService extends SystemService {
|
||||
return session;
|
||||
}
|
||||
|
||||
class SessionManagerImpl extends IMediaSessionManager.Stub {
|
||||
private MediaRouteProviderProxy getProviderLocked(String providerId) {
|
||||
for (int i = mProviders.size() - 1; i >= 0; i--) {
|
||||
MediaRouteProviderProxy provider = mProviders.get(i);
|
||||
if (TextUtils.equals(providerId, provider.getId())) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int findIndexOfSessionForIdLocked(String sessionId) {
|
||||
for (int i = mSessions.size() - 1; i >= 0; i--) {
|
||||
MediaSessionRecord session = mSessions.get(i);
|
||||
if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
|
||||
= new MediaRouteProviderWatcher.Callback() {
|
||||
@Override
|
||||
public IMediaSession createSession(String packageName, IMediaSessionCallback cb, String tag)
|
||||
public void removeProvider(MediaRouteProviderProxy provider) {
|
||||
synchronized (mLock) {
|
||||
mProviders.remove(provider);
|
||||
provider.setRoutesListener(null);
|
||||
provider.setInterested(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProvider(MediaRouteProviderProxy provider) {
|
||||
synchronized (mLock) {
|
||||
mProviders.add(provider);
|
||||
provider.setRoutesListener(mRoutesCallback);
|
||||
provider.setInterested(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private MediaRouteProviderProxy.RoutesListener mRoutesCallback
|
||||
= new MediaRouteProviderProxy.RoutesListener() {
|
||||
@Override
|
||||
public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
|
||||
int reqId) {
|
||||
// TODO for now select the first route to test, eventually add the
|
||||
// new routes to the dialog if it is still open
|
||||
synchronized (mLock) {
|
||||
int index = findIndexOfSessionForIdLocked(sessionId);
|
||||
if (index != -1 && routes != null && routes.size() > 0) {
|
||||
MediaSessionRecord record = mSessions.get(index);
|
||||
record.selectRoute(routes.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteConnected(String sessionId, RouteInfo route,
|
||||
RouteRequest options, RouteConnectionRecord connection) {
|
||||
synchronized (mLock) {
|
||||
int index = findIndexOfSessionForIdLocked(sessionId);
|
||||
if (index != -1) {
|
||||
MediaSessionRecord session = mSessions.get(index);
|
||||
session.setRouteConnected(route, options.getConnectionOptions(), connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SessionManagerImpl extends ISessionManager.Stub {
|
||||
// TODO add createSessionAsUser, pass user-id to
|
||||
// ActivityManagerNative.handleIncomingUser and stash result for use
|
||||
// when starting services on that session's behalf.
|
||||
@Override
|
||||
public ISession createSession(String packageName, ISessionCallback cb, String tag)
|
||||
throws RemoteException {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.media.routeprovider.IRouteConnection;
|
||||
import android.media.session.RouteCommand;
|
||||
import android.media.session.RouteEvent;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A connection between a Session and a Route.
|
||||
*/
|
||||
public class RouteConnectionRecord {
|
||||
private static final String TAG = "RouteConnRecord";
|
||||
private final IRouteConnection mBinder;
|
||||
private Listener mListener;
|
||||
|
||||
public RouteConnectionRecord(IRouteConnection binder) {
|
||||
mBinder = binder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to get route events on.
|
||||
*
|
||||
* @param listener The listener to get events on.
|
||||
*/
|
||||
public void setListener(Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this connection matches the token given.
|
||||
*
|
||||
* @param binder The token to check
|
||||
* @return True if this is the connection you're looking for, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isConnection(IBinder binder) {
|
||||
return binder != null && binder.equals(mBinder.asBinder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event from this connection.
|
||||
*
|
||||
* @param event The event to send.
|
||||
*/
|
||||
public void sendEvent(RouteEvent event) {
|
||||
if (mListener != null) {
|
||||
mListener.onEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to this connection.
|
||||
*
|
||||
* @param command The command to send.
|
||||
* @param cb The receiver to get a result on.
|
||||
*/
|
||||
public void sendCommand(RouteCommand command, ResultReceiver cb) {
|
||||
try {
|
||||
mBinder.onCommand(command, cb);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error in sendCommand", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the session that the provider has disconnected it.
|
||||
*/
|
||||
public void disconnect() {
|
||||
if (mListener != null) {
|
||||
mListener.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive updates from the provider for this connection.
|
||||
*/
|
||||
public static interface Listener {
|
||||
/**
|
||||
* Called when an event is sent on this connection.
|
||||
*
|
||||
* @param event The event that was sent.
|
||||
*/
|
||||
public void onEvent(RouteEvent event);
|
||||
|
||||
/**
|
||||
* Called when the provider has disconnected the route.
|
||||
*/
|
||||
public void disconnect();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,15 @@
|
||||
android:name="com.android.onemedia.OnePlayerService"
|
||||
android:exported="false"
|
||||
android:process="com.android.onemedia.service" />
|
||||
<service
|
||||
android:name=".provider.OneMediaRouteProvider"
|
||||
android:permission="android.permission.BIND_ROUTE_PROVIDER"
|
||||
android:exported="true"
|
||||
android:process="com.android.onemedia.provider">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.media.session.MediaRouteProvider" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -53,6 +53,12 @@
|
||||
android:layout_weight="1"
|
||||
android:text="@string/play_button" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/route_button"
|
||||
style="@style/BottomBarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/route_button" />
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<string name="start_button">Start</string>
|
||||
<string name="play_button">Play</string>
|
||||
<string name="route_button">Change route</string>
|
||||
<string name="media_content_hint">Content</string>
|
||||
<string name="media_next_hint">Next content</string>
|
||||
<string name="has_video">Is video</string>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
package com.android.onemedia;
|
||||
|
||||
import android.media.session.MediaSessionToken;
|
||||
import android.media.session.SessionToken;
|
||||
|
||||
interface IPlayerCallback {
|
||||
void onSessionChanged(in MediaSessionToken session);
|
||||
void onSessionChanged(in SessionToken session);
|
||||
}
|
||||
@@ -15,14 +15,14 @@
|
||||
|
||||
package com.android.onemedia;
|
||||
|
||||
import android.media.session.MediaSessionToken;
|
||||
import android.media.session.SessionToken;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.onemedia.IPlayerCallback;
|
||||
import com.android.onemedia.playback.IRequestCallback;
|
||||
|
||||
interface IPlayerService {
|
||||
MediaSessionToken getSessionToken();
|
||||
SessionToken getSessionToken();
|
||||
void registerCallback(in IPlayerCallback cb);
|
||||
void unregisterCallback(in IPlayerCallback cb);
|
||||
void sendRequest(String action, in Bundle params, in IRequestCallback cb);
|
||||
|
||||
@@ -37,6 +37,7 @@ public class OnePlayerActivity extends Activity {
|
||||
|
||||
private Button mStartButton;
|
||||
private Button mPlayButton;
|
||||
private Button mRouteButton;
|
||||
private TextView mStatusView;
|
||||
|
||||
private EditText mContentText;
|
||||
@@ -54,6 +55,7 @@ public class OnePlayerActivity extends Activity {
|
||||
|
||||
mStartButton = (Button) findViewById(R.id.start_button);
|
||||
mPlayButton = (Button) findViewById(R.id.play_button);
|
||||
mRouteButton = (Button) findViewById(R.id.route_button);
|
||||
mStatusView = (TextView) findViewById(R.id.status);
|
||||
mContentText = (EditText) findViewById(R.id.content);
|
||||
mNextContentText = (EditText) findViewById(R.id.next_content);
|
||||
@@ -61,6 +63,7 @@ public class OnePlayerActivity extends Activity {
|
||||
|
||||
mStartButton.setOnClickListener(mButtonListener);
|
||||
mPlayButton.setOnClickListener(mButtonListener);
|
||||
mRouteButton.setOnClickListener(mButtonListener);
|
||||
|
||||
}
|
||||
|
||||
@@ -107,6 +110,9 @@ public class OnePlayerActivity extends Activity {
|
||||
Log.d(TAG, "Start button pressed, in state " + mPlaybackState);
|
||||
mPlayer.setContent(mContentText.getText().toString());
|
||||
break;
|
||||
case R.id.route_button:
|
||||
mPlayer.showRoutePicker();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -117,6 +123,7 @@ public class OnePlayerActivity extends Activity {
|
||||
public void onPlaybackStateChange(PlaybackState state) {
|
||||
mPlaybackState = state.getState();
|
||||
boolean enablePlay = false;
|
||||
boolean enableControls = true;
|
||||
StringBuilder statusBuilder = new StringBuilder();
|
||||
switch (mPlaybackState) {
|
||||
case PlaybackState.PLAYSTATE_PLAYING:
|
||||
@@ -143,12 +150,17 @@ public class OnePlayerActivity extends Activity {
|
||||
case PlaybackState.PLAYSTATE_NONE:
|
||||
statusBuilder.append("none");
|
||||
break;
|
||||
case PlaybackState.PLAYSTATE_CONNECTING:
|
||||
statusBuilder.append("connecting");
|
||||
enableControls = false;
|
||||
break;
|
||||
default:
|
||||
statusBuilder.append(mPlaybackState);
|
||||
}
|
||||
statusBuilder.append(" -- At position: ").append(state.getPosition());
|
||||
mStatusView.setText(statusBuilder.toString());
|
||||
mPlayButton.setEnabled(enablePlay);
|
||||
setControlsEnabled(enableControls);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
*/
|
||||
package com.android.onemedia;
|
||||
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.SessionController;
|
||||
import android.media.session.MediaMetadata;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.SessionManager;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.media.session.TransportController;
|
||||
import android.os.Bundle;
|
||||
@@ -39,7 +40,7 @@ public class PlayerController {
|
||||
public static final int STATE_DISCONNECTED = 0;
|
||||
public static final int STATE_CONNECTED = 1;
|
||||
|
||||
protected MediaController mController;
|
||||
protected SessionController mController;
|
||||
protected IPlayerService mBinder;
|
||||
protected TransportController mTransportControls;
|
||||
|
||||
@@ -48,7 +49,7 @@ public class PlayerController {
|
||||
private Listener mListener;
|
||||
private TransportListener mTransportListener = new TransportListener();
|
||||
private SessionCallback mControllerCb;
|
||||
private MediaSessionManager mManager;
|
||||
private SessionManager mManager;
|
||||
private Handler mHandler = new Handler();
|
||||
|
||||
private boolean mResumed;
|
||||
@@ -61,7 +62,7 @@ public class PlayerController {
|
||||
mServiceIntent = serviceIntent;
|
||||
}
|
||||
mControllerCb = new SessionCallback();
|
||||
mManager = (MediaSessionManager) context
|
||||
mManager = (SessionManager) context
|
||||
.getSystemService(Context.MEDIA_SESSION_SERVICE);
|
||||
|
||||
mResumed = false;
|
||||
@@ -121,6 +122,10 @@ public class PlayerController {
|
||||
}
|
||||
}
|
||||
|
||||
public void showRoutePicker() {
|
||||
mController.showRoutePicker();
|
||||
}
|
||||
|
||||
private void unbindFromService() {
|
||||
mContext.unbindService(mServiceConnection);
|
||||
}
|
||||
@@ -150,7 +155,7 @@ public class PlayerController {
|
||||
mBinder = IPlayerService.Stub.asInterface(service);
|
||||
Log.d(TAG, "service is " + service + " binder is " + mBinder);
|
||||
try {
|
||||
mController = MediaController.fromToken(mBinder.getSessionToken());
|
||||
mController = SessionController.fromToken(mBinder.getSessionToken());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error getting session", e);
|
||||
return;
|
||||
@@ -171,9 +176,9 @@ public class PlayerController {
|
||||
}
|
||||
};
|
||||
|
||||
private class SessionCallback extends MediaController.Callback {
|
||||
private class SessionCallback extends SessionController.Callback {
|
||||
@Override
|
||||
public void onRouteChanged(Bundle route) {
|
||||
public void onRouteChanged(RouteInfo route) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package com.android.onemedia;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.media.session.MediaSessionToken;
|
||||
import android.media.session.SessionToken;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
@@ -149,7 +149,7 @@ public class PlayerService extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSessionToken getSessionToken() throws RemoteException {
|
||||
public SessionToken getSessionToken() throws RemoteException {
|
||||
return mSession.getSessionToken();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,13 @@ package com.android.onemedia;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.session.MediaSessionToken;
|
||||
import android.media.session.Route;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.RouteOptions;
|
||||
import android.media.session.RoutePlaybackControls;
|
||||
import android.media.session.Session;
|
||||
import android.media.session.SessionManager;
|
||||
import android.media.session.SessionToken;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.media.session.TransportPerformer;
|
||||
import android.os.Bundle;
|
||||
@@ -27,41 +31,55 @@ import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.android.onemedia.playback.LocalRenderer;
|
||||
import com.android.onemedia.playback.OneMRPRenderer;
|
||||
import com.android.onemedia.playback.Renderer;
|
||||
import com.android.onemedia.playback.RendererFactory;
|
||||
import com.android.onemedia.playback.RequestUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PlayerSession {
|
||||
private static final String TAG = "PlayerSession";
|
||||
|
||||
protected MediaSession mSession;
|
||||
protected Session mSession;
|
||||
protected Context mContext;
|
||||
protected RendererFactory mRendererFactory;
|
||||
protected LocalRenderer mRenderer;
|
||||
protected MediaSession.Callback mCallback;
|
||||
protected Renderer mRenderer;
|
||||
protected Session.Callback mCallback;
|
||||
protected Renderer.Listener mRenderListener;
|
||||
protected TransportPerformer mPerformer;
|
||||
|
||||
protected PlaybackState mPlaybackState;
|
||||
protected Listener mListener;
|
||||
protected ArrayList<RouteOptions> mRouteOptions;
|
||||
protected Route mRoute;
|
||||
protected RoutePlaybackControls mRouteControls;
|
||||
protected RouteListener mRouteListener;
|
||||
|
||||
private String mContent;
|
||||
|
||||
public PlayerSession(Context context) {
|
||||
mContext = context;
|
||||
mRendererFactory = new RendererFactory();
|
||||
mRenderer = new LocalRenderer(context, null);
|
||||
mCallback = new ControllerCb();
|
||||
mCallback = new SessionCb();
|
||||
mRenderListener = new RenderListener();
|
||||
mPlaybackState = new PlaybackState();
|
||||
mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
|
||||
| PlaybackState.ACTION_PLAY);
|
||||
|
||||
mRenderer.registerListener(mRenderListener);
|
||||
|
||||
// TODO need an easier way to build route options
|
||||
mRouteOptions = new ArrayList<RouteOptions>();
|
||||
RouteOptions.Builder bob = new RouteOptions.Builder();
|
||||
bob.addInterface(RoutePlaybackControls.NAME);
|
||||
mRouteOptions.add(bob.build());
|
||||
mRouteListener = new RouteListener();
|
||||
}
|
||||
|
||||
public void createSession() {
|
||||
if (mSession != null) {
|
||||
mSession.release();
|
||||
}
|
||||
MediaSessionManager man = (MediaSessionManager) mContext
|
||||
SessionManager man = (SessionManager) mContext
|
||||
.getSystemService(Context.MEDIA_SESSION_SERVICE);
|
||||
Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
|
||||
mSession = man.createSession("OneMedia");
|
||||
@@ -69,6 +87,7 @@ public class PlayerSession {
|
||||
mPerformer = mSession.setTransportPerformerEnabled();
|
||||
mPerformer.addListener(new TransportListener());
|
||||
mPerformer.setPlaybackState(mPlaybackState);
|
||||
mSession.setRouteOptions(mRouteOptions);
|
||||
mSession.publish();
|
||||
}
|
||||
|
||||
@@ -86,18 +105,24 @@ public class PlayerSession {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public MediaSessionToken getSessionToken() {
|
||||
public SessionToken getSessionToken() {
|
||||
return mSession.getSessionToken();
|
||||
}
|
||||
|
||||
public void setContent(Bundle request) {
|
||||
mRenderer.setContent(request);
|
||||
mContent = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
|
||||
}
|
||||
|
||||
public void setNextContent(Bundle request) {
|
||||
mRenderer.setNextContent(request);
|
||||
}
|
||||
|
||||
private void updateState(int newState) {
|
||||
mPlaybackState.setState(newState);
|
||||
mPerformer.setPlaybackState(mPlaybackState);
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
public void onPlayStateChanged(PlaybackState state);
|
||||
}
|
||||
@@ -145,7 +170,11 @@ public class PlayerSession {
|
||||
mPlaybackState.setErrorMessage("unkown state");
|
||||
break;
|
||||
}
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
if (mRenderer != null) {
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
} else {
|
||||
mPlaybackState.setPosition(-1);
|
||||
}
|
||||
mPerformer.setPlaybackState(mPlaybackState);
|
||||
if (mListener != null) {
|
||||
mListener.onPlayStateChanged(mPlaybackState);
|
||||
@@ -173,8 +202,7 @@ public class PlayerSession {
|
||||
|
||||
}
|
||||
|
||||
private class ControllerCb extends MediaSession.Callback {
|
||||
|
||||
private class SessionCb extends Session.Callback {
|
||||
@Override
|
||||
public void onMediaButton(Intent mediaRequestIntent) {
|
||||
if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) {
|
||||
@@ -192,6 +220,40 @@ public class PlayerSession {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestRouteChange(RouteInfo route) {
|
||||
if (mRenderer != null) {
|
||||
mRenderer.onStop();
|
||||
}
|
||||
if (route == null) {
|
||||
// Use local route
|
||||
mRoute = null;
|
||||
mRenderer = new LocalRenderer(mContext, null);
|
||||
mRenderer.registerListener(mRenderListener);
|
||||
updateState(PlaybackState.PLAYSTATE_NONE);
|
||||
} else {
|
||||
// Use remote route
|
||||
mSession.connect(route, mRouteOptions.get(0));
|
||||
mRenderer = null;
|
||||
updateState(PlaybackState.PLAYSTATE_CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteConnected(Route route) {
|
||||
mRoute = route;
|
||||
mRouteControls = RoutePlaybackControls.from(route);
|
||||
mRouteControls.addListener(mRouteListener);
|
||||
Log.d(TAG, "Connected to route, registering listener");
|
||||
mRenderer = new OneMRPRenderer(mRouteControls);
|
||||
updateState(PlaybackState.PLAYSTATE_NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteDisconnected(Route route, int reason) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class TransportListener extends TransportPerformer.Listener {
|
||||
@@ -206,4 +268,12 @@ public class PlayerSession {
|
||||
}
|
||||
}
|
||||
|
||||
private class RouteListener extends RoutePlaybackControls.Listener {
|
||||
@Override
|
||||
public void onPlaybackStateChange(int state) {
|
||||
Log.d(TAG, "Updating state to " + state);
|
||||
updateState(state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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.onemedia.playback;
|
||||
|
||||
import org.apache.http.Header;
|
||||
@@ -370,6 +385,8 @@ public class LocalRenderer extends Renderer implements OnPreparedListener,
|
||||
* Prepares the player for the given playback request. If the holder is null
|
||||
* it is assumed this is an audio only source. If playOnReady is set to true
|
||||
* the media will begin playing as soon as it can.
|
||||
*
|
||||
* @see RequestUtils for the set of valid keys.
|
||||
*/
|
||||
public void setContent(Bundle request, SurfaceHolder holder) {
|
||||
String source = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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.onemedia.playback;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.android.onemedia.playback;
|
||||
|
||||
import android.media.session.RoutePlaybackControls;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Renderer for communicating with the OneMRP route
|
||||
*/
|
||||
public class OneMRPRenderer extends Renderer {
|
||||
private final RoutePlaybackControls mControls;
|
||||
|
||||
public OneMRPRenderer(RoutePlaybackControls controls) {
|
||||
super(null, null);
|
||||
mControls = controls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(Bundle request) {
|
||||
mControls.playNow(request.getString(RequestUtils.EXTRA_KEY_SOURCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStop() {
|
||||
mControls.pause();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPlay() {
|
||||
mControls.resume();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPause() {
|
||||
mControls.pause();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeekPosition() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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.onemedia.playback;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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.onemedia.playback;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -77,39 +92,54 @@ public abstract class Renderer {
|
||||
}
|
||||
|
||||
public boolean onPlay() {
|
||||
throw new UnsupportedOperationException("play is not supported.");
|
||||
// TODO consider making these log warnings instead of crashes (or
|
||||
// Log.wtf)
|
||||
// throw new UnsupportedOperationException("play is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onPause() {
|
||||
throw new UnsupportedOperationException("pause is not supported.");
|
||||
// throw new UnsupportedOperationException("pause is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onNext() {
|
||||
throw new UnsupportedOperationException("next is not supported.");
|
||||
// throw new UnsupportedOperationException("next is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onPrevious() {
|
||||
throw new UnsupportedOperationException("previous is not supported.");
|
||||
// throw new
|
||||
// UnsupportedOperationException("previous is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onStop() {
|
||||
throw new UnsupportedOperationException("stop is not supported.");
|
||||
// throw new UnsupportedOperationException("stop is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onSeekTo(int time) {
|
||||
throw new UnsupportedOperationException("seekTo is not supported.");
|
||||
// throw new UnsupportedOperationException("seekTo is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getSeekPosition() {
|
||||
throw new UnsupportedOperationException("getSeekPosition is not supported.");
|
||||
// throw new
|
||||
// UnsupportedOperationException("getSeekPosition is not supported.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
throw new UnsupportedOperationException("getDuration is not supported.");
|
||||
// throw new
|
||||
// UnsupportedOperationException("getDuration is not supported.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getPlayState() {
|
||||
throw new UnsupportedOperationException("getPlayState is not supported.");
|
||||
// throw new
|
||||
// UnsupportedOperationException("getPlayState is not supported.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.android.onemedia.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaRouter;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* TODO: Insert description here.
|
||||
*/
|
||||
public class RendererFactory {
|
||||
private static final String TAG = "RendererFactory";
|
||||
|
||||
public Renderer createRenderer(MediaRouter.RouteInfo route, Context context, Bundle params) {
|
||||
if (route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL) {
|
||||
return new LocalRenderer(context, params);
|
||||
}
|
||||
Log.e(TAG, "Unable to create renderer for route of playback type "
|
||||
+ route.getPlaybackType());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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.onemedia.playback;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.onemedia.provider;
|
||||
|
||||
import android.media.routeprovider.RouteConnection;
|
||||
import android.media.routeprovider.RouteInterfaceHandler;
|
||||
import android.media.routeprovider.RoutePlaybackControlsHandler;
|
||||
import android.media.routeprovider.RouteProviderService;
|
||||
import android.media.routeprovider.RouteRequest;
|
||||
import android.media.session.RouteInfo;
|
||||
import android.media.session.RoutePlaybackControls;
|
||||
import android.media.session.RouteInterface;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.onemedia.playback.LocalRenderer;
|
||||
import com.android.onemedia.playback.Renderer;
|
||||
import com.android.onemedia.playback.RequestUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Test of MediaRouteProvider. Show a dummy provider with a simple interface for
|
||||
* playing music.
|
||||
*/
|
||||
public class OneMediaRouteProvider extends RouteProviderService {
|
||||
private static final String TAG = "OneMRP";
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
private Renderer mRenderer;
|
||||
private RenderListener mRenderListener;
|
||||
private PlaybackState mPlaybackState;
|
||||
private RouteConnection mConnection;
|
||||
private RoutePlaybackControlsHandler mControls;
|
||||
private String mRouteId;
|
||||
private Handler mHandler;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
mHandler = new Handler();
|
||||
mRouteId = UUID.randomUUID().toString();
|
||||
mRenderer = new LocalRenderer(this, null);
|
||||
mRenderListener = new RenderListener();
|
||||
mPlaybackState = new PlaybackState();
|
||||
mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
|
||||
| PlaybackState.ACTION_PLAY);
|
||||
|
||||
mRenderer.registerListener(mRenderListener);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onCreate, routeId is " + mRouteId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RouteInfo> getMatchingRoutes(List<RouteRequest> requests) {
|
||||
RouteInfo.Builder bob = new RouteInfo.Builder();
|
||||
bob.setName("OneMedia").setId(mRouteId);
|
||||
// TODO add a helper library for generating route info with the correct
|
||||
// options
|
||||
Log.d(TAG, "Requests:");
|
||||
for (RouteRequest request : requests) {
|
||||
List<String> ifaces = request.getConnectionOptions().getInterfaceNames();
|
||||
Log.d(TAG, " request ifaces:" + ifaces.toString());
|
||||
if (ifaces != null && ifaces.size() == 1
|
||||
&& RoutePlaybackControls.NAME.equals(ifaces.get(0))) {
|
||||
bob.addRouteOptions(request.getConnectionOptions());
|
||||
}
|
||||
}
|
||||
ArrayList<RouteInfo> result = new ArrayList<RouteInfo>();
|
||||
if (bob.getOptionsSize() > 0) {
|
||||
RouteInfo info = bob.build();
|
||||
result.add(info);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "getRoutes returning " + result.toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteConnection connect(RouteInfo route, RouteRequest request) {
|
||||
if (mConnection != null) {
|
||||
disconnect(mConnection);
|
||||
}
|
||||
RouteConnection connection = new RouteConnection(this, route);
|
||||
mControls = RoutePlaybackControlsHandler.addTo(connection);
|
||||
mControls.addListener(new PlayHandler(mRouteId), mHandler);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Connected to route");
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private class PlayHandler extends RoutePlaybackControlsHandler.Listener {
|
||||
private final String mRouteId;
|
||||
|
||||
public PlayHandler(String routeId) {
|
||||
mRouteId = routeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playNow(String content, ResultReceiver cb) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Attempting to play " + content);
|
||||
}
|
||||
// look up the route and send a play command to it
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, content);
|
||||
mRenderer.setContent(bundle);
|
||||
RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resume() {
|
||||
mRenderer.onPlay();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pause() {
|
||||
mRenderer.onPause();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class RenderListener implements Renderer.Listener {
|
||||
|
||||
@Override
|
||||
public void onError(int type, int extra, Bundle extras, Throwable error) {
|
||||
Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
|
||||
if (mControls != null) {
|
||||
mControls.sendPlaybackChangeEvent(PlaybackState.PLAYSTATE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(int newState) {
|
||||
if (newState != Renderer.STATE_ERROR) {
|
||||
mPlaybackState.setErrorMessage(null);
|
||||
}
|
||||
switch (newState) {
|
||||
case Renderer.STATE_ENDED:
|
||||
case Renderer.STATE_STOPPED:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED);
|
||||
break;
|
||||
case Renderer.STATE_INIT:
|
||||
case Renderer.STATE_PREPARING:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING);
|
||||
break;
|
||||
case Renderer.STATE_ERROR:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
|
||||
break;
|
||||
case Renderer.STATE_PAUSED:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
|
||||
break;
|
||||
case Renderer.STATE_PLAYING:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING);
|
||||
break;
|
||||
default:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
|
||||
mPlaybackState.setErrorMessage("unkown state");
|
||||
break;
|
||||
}
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
|
||||
mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusLost() {
|
||||
Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNextStarted() {
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user