Merge "MediaRouter: add session related apis into provider"

This commit is contained in:
Kyunglyul Hyun
2019-12-30 09:55:59 +00:00
committed by Android (Google) Code Review
13 changed files with 295 additions and 385 deletions

View File

@@ -24,10 +24,14 @@ import android.media.IMediaRoute2ProviderClient;
*/
oneway interface IMediaRoute2Provider {
void setClient(IMediaRoute2ProviderClient client);
void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId);
void requestSelectRoute(String packageName, String id, int seq);
void unselectRoute(String packageName, String id);
void requestCreateSession(String packageName, String routeId,
String controlCategory, int requestId);
void releaseSession(int sessionId);
void addRoute(int sessionId, String routeId);
void removeRoute(int sessionId, String routeId);
void transferRoute(int sessionId, String routeId);
void notifyControlRequestSent(String id, in Intent request);
void requestSetVolume(String id, int volume);
void requestUpdateVolume(String id, int delta);

View File

@@ -26,7 +26,5 @@ import android.os.Bundle;
*/
oneway interface IMediaRoute2ProviderClient {
void updateProviderInfo(in MediaRoute2ProviderInfo info);
void notifyRouteSelected(String packageName, String routeId, in @nullable Bundle controlHints,
int seq);
void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
}

View File

@@ -55,15 +55,9 @@ interface IMediaRouterService {
void registerManager(IMediaRouter2Manager manager, String packageName);
void unregisterManager(IMediaRouter2Manager manager);
/**
* Changes the selected route of an application.
*
* @param manager the manager that calls the method
* @param packageName the package name of the client that will change the selected route
* @param route the route to be selected
*/
void selectClientRoute2(IMediaRouter2Manager manager, String packageName,
in @nullable MediaRoute2Info route);
void requestCreateClientSession(IMediaRouter2Manager manager, String packageName,
in @nullable MediaRoute2Info route, int requestId);
void requestSetVolume2Manager(IMediaRouter2Manager manager,
in MediaRoute2Info route, int volume);

View File

@@ -22,10 +22,12 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -47,7 +49,6 @@ public abstract class MediaRoute2ProviderService extends Service {
private final Handler mHandler;
private final Object mSessionLock = new Object();
private ProviderStub mStub;
// TODO: Rename this to mService (and accordingly IMediaRoute2ProviderClient to something else)
private IMediaRoute2ProviderClient mClient;
private MediaRoute2ProviderInfo mProviderInfo;
@@ -70,28 +71,6 @@ public abstract class MediaRoute2ProviderService extends Service {
return null;
}
/**
* Called when selectRoute is called on a route of the provider.
* Once the route is ready to be used , call {@link #notifyRouteSelected(SelectToken, Bundle)}
* to notify that.
*
* @param packageName the package name of the application that selected the route
* @param routeId the id of the route being selected
* @param token token that contains select info
*
* @see #notifyRouteSelected
*/
public abstract void onSelectRoute(@NonNull String packageName, @NonNull String routeId,
@NonNull SelectToken token);
/**
* Called when unselectRoute is called on a route of the provider.
*
* @param packageName the package name of the application that has selected the route.
* @param routeId the id of the route being unselected
*/
public abstract void onUnselectRoute(@NonNull String packageName, @NonNull String routeId);
/**
* Called when sendControlRequest is called on a route of the provider
*
@@ -155,6 +134,8 @@ public abstract class MediaRoute2ProviderService extends Service {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
int sessionId = sessionInfo.getSessionId();
//TODO: notify updated session info
synchronized (mSessionLock) {
if (mSessionInfo.containsKey(sessionId)) {
mSessionInfo.put(sessionId, sessionInfo);
@@ -162,6 +143,9 @@ public abstract class MediaRoute2ProviderService extends Service {
Log.w(TAG, "Ignoring unknown session info.");
}
}
if (sessionInfo.getSelectedRoutes().isEmpty()) {
releaseSession(sessionId);
}
}
/**
@@ -169,15 +153,20 @@ public abstract class MediaRoute2ProviderService extends Service {
* controlled, pass a {@link Bundle} that contains how to control it.
*
* @param sessionInfo information of the new session.
* Pass {@code null} to reject the request or inform clients that
* session creation has failed.
* The {@link RouteSessionInfo#getSessionId() id} of the session must be
* unique. Pass {@code null} to reject the request or inform clients that
* session creation is failed.
* @param requestId id of the previous request to create this session
*/
//TODO: fail reason?
public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
//TODO: validate sessionInfo.getSessionId() (it must be in "waiting list")
if (sessionInfo != null) {
int sessionId = sessionInfo.getSessionId();
synchronized (mSessionLock) {
if (mSessionInfo.containsKey(sessionId)) {
Log.w(TAG, "Ignoring duplicate session id.");
return;
}
mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo);
}
}
@@ -200,6 +189,7 @@ public abstract class MediaRoute2ProviderService extends Service {
* @see #onDestroySession(int, RouteSessionInfo)
*/
public final void releaseSession(int sessionId) {
//TODO: notify media router service of release.
RouteSessionInfo sessionInfo;
synchronized (mSessionLock) {
sessionInfo = mSessionInfo.put(sessionId, null);
@@ -283,29 +273,6 @@ public abstract class MediaRoute2ProviderService extends Service {
publishState();
}
/**
* Notifies the client of that the selected route is ready for use. If the selected route can be
* controlled, pass a {@link Bundle} that contains how to control it.
*
* @param token token passed in {@link #onSelectRoute}
* @param controlHints a {@link Bundle} that contains how to control the given route.
* Pass {@code null} if the route is not available.
*/
public final void notifyRouteSelected(@NonNull SelectToken token,
@Nullable Bundle controlHints) {
Objects.requireNonNull(token, "token must not be null");
if (mClient == null) {
return;
}
try {
mClient.notifyRouteSelected(token.mPackageName, token.mRouteId,
controlHints, token.mSeq);
} catch (RemoteException ex) {
Log.w(TAG, "Failed to notify route selected");
}
}
void setClient(IMediaRoute2ProviderClient client) {
mClient = client;
publishState();
@@ -323,68 +290,91 @@ public abstract class MediaRoute2ProviderService extends Service {
}
}
/**
* Route selection information.
*
* @see #notifyRouteSelected
*/
public final class SelectToken {
final String mPackageName;
final String mRouteId;
final int mSeq;
SelectToken(String packageName, String routeId, int seq) {
mPackageName = packageName;
mRouteId = routeId;
mSeq = seq;
}
}
final class ProviderStub extends IMediaRoute2Provider.Stub {
ProviderStub() { }
boolean checkCallerisSystem() {
return Binder.getCallingUid() == Process.SYSTEM_UID;
}
@Override
public void setClient(IMediaRoute2ProviderClient client) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setClient,
MediaRoute2ProviderService.this, client));
}
@Override
public void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId) {
public void requestCreateSession(String packageName, String routeId,
String controlCategory, int requestId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
MediaRoute2ProviderService.this, packageName, routeId, controlCategory,
requestId));
}
@Override
public void requestSelectRoute(String packageName, String routeId, int seq) {
//TODO: call onCreateSession instead
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
MediaRoute2ProviderService.this, packageName, routeId,
new SelectToken(packageName, routeId, seq)));
public void releaseSession(int sessionId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession,
MediaRoute2ProviderService.this, sessionId));
}
@Override
public void unselectRoute(String packageName, String routeId) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUnselectRoute,
MediaRoute2ProviderService.this, packageName, routeId));
public void addRoute(int sessionId, String routeId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onAddRoute,
MediaRoute2ProviderService.this, sessionId, routeId));
}
@Override
public void removeRoute(int sessionId, String routeId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onRemoveRoute,
MediaRoute2ProviderService.this, sessionId, routeId));
}
@Override
public void transferRoute(int sessionId, String routeId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferRoute,
MediaRoute2ProviderService.this, sessionId, routeId));
}
@Override
public void notifyControlRequestSent(String routeId, Intent request) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
MediaRoute2ProviderService.this, routeId, request));
}
@Override
public void requestSetVolume(String routeId, int volume) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
MediaRoute2ProviderService.this, routeId, volume));
}
@Override
public void requestUpdateVolume(String routeId, int delta) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
MediaRoute2ProviderService.this, routeId, delta));
}

View File

@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
@@ -40,6 +41,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
@@ -66,6 +68,8 @@ public class MediaRouter2Manager {
@NonNull
final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>();
private AtomicInteger mNextRequestId = new AtomicInteger(1);
/**
* Gets an instance of media router manager that controls media route of other applications.
*
@@ -204,7 +208,7 @@ public class MediaRouter2Manager {
* Selects media route for the specified package name.
*
* @param packageName the package name of the application that should change it's media route
* @param route the route to be selected
* @param route the route to be selected.
*/
public void selectRoute(@NonNull String packageName, @NonNull MediaRoute2Info route) {
Objects.requireNonNull(packageName, "packageName must not be null");
@@ -216,26 +220,10 @@ public class MediaRouter2Manager {
}
if (client != null) {
try {
mMediaRouterService.selectClientRoute2(client, packageName, route);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to select media route", ex);
}
}
}
/**
* Unselects media route for the specified package name.
*
* @param packageName the package name of the application that should stop routing
*/
public void unselectRoute(@NonNull String packageName) {
Client client;
synchronized (sLock) {
client = mClient;
}
if (client != null) {
try {
mMediaRouterService.selectClientRoute2(client, packageName, null);
//TODO: make request id globally unique
int requestId = Process.myPid() * 10000 + mNextRequestId.getAndIncrement();
mMediaRouterService.requestCreateClientSession(
client, packageName, route, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to select media route", ex);
}

View File

@@ -49,10 +49,13 @@ public class RouteSessionInfo implements Parcelable {
final int mSessionId;
final String mPackageName;
final String mControlCategory;
@Nullable
final String mProviderId;
final List<String> mSelectedRoutes;
final List<String> mDeselectableRoutes;
final List<String> mGroupableRoutes;
final List<String> mTransferrableRoutes;
@Nullable
final Bundle mControlHints;
RouteSessionInfo(@NonNull Builder builder) {
@@ -61,6 +64,7 @@ public class RouteSessionInfo implements Parcelable {
mSessionId = builder.mSessionId;
mPackageName = builder.mPackageName;
mControlCategory = builder.mControlCategory;
mProviderId = builder.mProviderId;
mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes);
mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes);
@@ -76,6 +80,7 @@ public class RouteSessionInfo implements Parcelable {
mSessionId = src.readInt();
mPackageName = ensureString(src.readString());
mControlCategory = ensureString(src.readString());
mProviderId = src.readString();
mSelectedRoutes = ensureList(src.createStringArrayList());
mDeselectableRoutes = ensureList(src.createStringArrayList());
@@ -116,6 +121,14 @@ public class RouteSessionInfo implements Parcelable {
return mSessionId;
}
/**
* Gets the client package name of the session
*/
@NonNull
public String getPackageName() {
return mPackageName;
}
/**
* Gets the control category of the session.
* Routes that don't support the category can't be added to the session.
@@ -125,6 +138,15 @@ public class RouteSessionInfo implements Parcelable {
return mControlCategory;
}
/**
* Gets the provider id of the session.
* @hide
*/
@Nullable
public String getProviderId() {
return mProviderId;
}
/**
* Gets the list of ids of selected routes for the session. It shouldn't be empty.
*/
@@ -175,6 +197,7 @@ public class RouteSessionInfo implements Parcelable {
dest.writeInt(mSessionId);
dest.writeString(mPackageName);
dest.writeString(mControlCategory);
dest.writeString(mProviderId);
dest.writeStringList(mSelectedRoutes);
dest.writeStringList(mDeselectableRoutes);
dest.writeStringList(mGroupableRoutes);
@@ -187,13 +210,20 @@ public class RouteSessionInfo implements Parcelable {
StringBuilder result = new StringBuilder()
.append("RouteSessionInfo{ ")
.append("sessionId=").append(mSessionId)
.append(", selectedRoutes={");
for (int i = 0; i < mSelectedRoutes.size(); i++) {
if (i > 0) result.append(", ");
result.append(mSelectedRoutes.get(i));
}
result.append("}");
result.append(" }");
.append(", controlCategory=").append(mControlCategory)
.append(", selectedRoutes={")
.append(String.join(",", mSelectedRoutes))
.append("}")
.append(", deselectableRoutes={")
.append(String.join(",", mDeselectableRoutes))
.append("}")
.append(", groupableRoutes={")
.append(String.join(",", mGroupableRoutes))
.append("}")
.append(", transferrableRoutes={")
.append(String.join(",", mTransferrableRoutes))
.append("}")
.append(" }");
return result.toString();
}
@@ -204,6 +234,7 @@ public class RouteSessionInfo implements Parcelable {
final String mPackageName;
final int mSessionId;
final String mControlCategory;
String mProviderId;
final List<String> mSelectedRoutes;
final List<String> mDeselectableRoutes;
final List<String> mGroupableRoutes;
@@ -227,6 +258,7 @@ public class RouteSessionInfo implements Parcelable {
mSessionId = sessionInfo.mSessionId;
mPackageName = sessionInfo.mPackageName;
mControlCategory = sessionInfo.mControlCategory;
mProviderId = sessionInfo.mProviderId;
mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
@@ -236,6 +268,15 @@ public class RouteSessionInfo implements Parcelable {
mControlHints = sessionInfo.mControlHints;
}
/**
* Sets the provider id of the session.
*/
@NonNull
public Builder setProviderId(String providerId) {
mProviderId = providerId;
return this;
}
/**
* Clears the selected routes.
*/

View File

@@ -24,7 +24,6 @@ import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
import android.media.RouteSessionInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
@@ -62,6 +61,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
public static final int SESSION_ID_1 = 1000;
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
Map<String, Integer> mRouteSessionMap = new HashMap<>();
private void initializeRoutes() {
MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
@@ -112,31 +112,6 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
return super.onBind(intent);
}
@Override
public void onSelectRoute(String packageName, String routeId, SelectToken token) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null) {
return;
}
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(packageName)
.build());
publishRoutes();
notifyRouteSelected(token, Bundle.EMPTY);
}
@Override
public void onUnselectRoute(String packageName, String routeId) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null) {
return;
}
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(null)
.build());
publishRoutes();
}
@Override
public void onControlRequest(String routeId, Intent request) {
String action = request.getAction();
@@ -180,26 +155,56 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
@Override
public void onCreateSession(String packageName, String routeId, String controlCategory,
int requestId) {
if (TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
// Tell the router that session cannot be created by passing null as sessionInfo.
notifySessionCreated(/* sessionInfo= */ null, requestId);
return;
}
maybeRemoveRoute(routeId);
int sessionId = SESSION_ID_1;
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(packageName)
.build());
mRouteSessionMap.put(routeId, sessionId);
RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
SESSION_ID_1, packageName, controlCategory)
sessionId, packageName, controlCategory)
.addSelectedRoute(routeId)
.build();
notifySessionCreated(sessionInfo, requestId);
notifySessionCreated(sessionInfo, requestId);
publishRoutes();
}
@Override
public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {}
public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {
for (String routeId : lastSessionInfo.getSelectedRoutes()) {
mRouteSessionMap.remove(routeId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route != null) {
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(null)
.build());
}
}
}
@Override
public void onAddRoute(int sessionId, String routeId) {
RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
//TODO: we may want to remove route if it belongs to another session
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || sessionInfo == null) {
return;
}
maybeRemoveRoute(routeId);
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(sessionInfo.getPackageName())
.build());
mRouteSessionMap.put(routeId, sessionId);
RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
.addSelectedRoute(routeId)
.build();
@@ -210,6 +215,16 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
@Override
public void onRemoveRoute(int sessionId, String routeId) {
RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
mRouteSessionMap.remove(routeId);
if (sessionInfo == null || route == null) {
return;
}
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(null)
.build());
RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
.removeSelectedRoute(routeId)
.build();
@@ -228,6 +243,15 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
publishRoutes();
}
void maybeRemoveRoute(String routeId) {
if (!mRouteSessionMap.containsKey(routeId)) {
return;
}
int sessionId = mRouteSessionMap.get(routeId);
onRemoveRoute(sessionId, routeId);
}
void publishRoutes() {
MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
.addRoutes(mRoutes.values())

View File

@@ -226,7 +226,8 @@ public class MediaRouterManagerTest {
mManager.selectRoute(mPackageName, routeToSelect);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
mManager.unselectRoute(mPackageName);
//TODO: release the session
//mManager.selectRoute(mPackageName, null);
}
}
@@ -234,7 +235,7 @@ public class MediaRouterManagerTest {
* Tests if MR2Manager.Callback.onRouteSelected is called
* when a route is selected by MR2Manager.
*/
@Test
//TODO: test session created callback instead of onRouteSelected
public void testManagerOnRouteSelected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
@@ -257,14 +258,14 @@ public class MediaRouterManagerTest {
mManager.selectRoute(mPackageName, routeToSelect);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
mManager.unselectRoute(mPackageName);
//TODO: release the session
//mManager.selectRoute(mPackageName, null);
}
}
@Test
//TODO: enable this when "releasing session" is implemented
public void testGetActiveRoutes() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
addRouterCallback(new RouteCallback());
@@ -278,6 +279,7 @@ public class MediaRouterManagerTest {
}
});
//TODO: it fails due to not releasing session
assertEquals(0, mManager.getActiveRoutes().size());
mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
@@ -285,17 +287,20 @@ public class MediaRouterManagerTest {
assertEquals(1, mManager.getActiveRoutes().size());
//TODO: release the session
/*
awaitOnRouteChangedManager(
() -> mManager.unselectRoute(mPackageName),
() -> mManager.selectRoute(mPackageName, null),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), null));
assertEquals(0, mManager.getActiveRoutes().size());
*/
}
/**
* Tests selecting and unselecting routes of a single provider.
*/
@Test
//TODO: @Test when session is released
public void testSingleProviderSelect() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
addRouterCallback(new RouteCallback());
@@ -310,10 +315,14 @@ public class MediaRouterManagerTest {
ROUTE_ID2,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
//TODO: release the session
/*
awaitOnRouteChangedManager(
() -> mManager.unselectRoute(mPackageName),
() -> mManager.selectRoute(mPackageName, null),
ROUTE_ID2,
route -> TextUtils.equals(route.getClientPackageName(), null));
*/
}
@Test

View File

@@ -23,7 +23,6 @@ import android.content.Intent;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.RouteSessionInfo;
import android.os.Bundle;
import java.util.Objects;
@@ -45,8 +44,12 @@ abstract class MediaRoute2Provider {
public abstract void requestCreateSession(String packageName, String routeId,
String controlCategory, int requestId);
public abstract void requestSelectRoute(String packageName, String routeId, int seq);
public abstract void unselectRoute(String packageName, String routeId);
public abstract void releaseSession(int sessionId);
public abstract void addRoute(int sessionId, MediaRoute2Info route);
public abstract void removeRoute(int sessionId, MediaRoute2Info route);
public abstract void transferRoute(int sessionId, MediaRoute2Info route);
public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
public abstract void requestSetVolume(MediaRoute2Info route, int volume);
public abstract void requestUpdateVolume(MediaRoute2Info route, int delta);
@@ -82,9 +85,6 @@ abstract class MediaRoute2Provider {
public interface Callback {
void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
@NonNull String clientPackageName, @NonNull MediaRoute2Info route,
@Nullable Bundle controlHints, int seq);
void onSessionCreated(@NonNull MediaRoute2Provider provider,
@Nullable RouteSessionInfo sessionInfo, int requestId);
}

View File

@@ -28,7 +28,6 @@ import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
import android.media.RouteSessionInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -86,18 +85,31 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
}
@Override
public void requestSelectRoute(String packageName, String routeId, int seq) {
public void releaseSession(int sessionId) {
if (mConnectionReady) {
mActiveConnection.requestSelectRoute(packageName, routeId, seq);
mActiveConnection.releaseSession(sessionId);
updateBinding();
}
}
@Override
public void unselectRoute(String packageName, String routeId) {
public void addRoute(int sessionId, MediaRoute2Info route) {
if (mConnectionReady) {
mActiveConnection.unselectRoute(packageName, routeId);
updateBinding();
mActiveConnection.addRoute(sessionId, route.getId());
}
}
@Override
public void removeRoute(int sessionId, MediaRoute2Info route) {
if (mConnectionReady) {
mActiveConnection.removeRoute(sessionId, route.getId());
}
}
@Override
public void transferRoute(int sessionId, MediaRoute2Info route) {
if (mConnectionReady) {
mActiveConnection.transferRoute(sessionId, route.getId());
}
}
@@ -266,20 +278,6 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
setAndNotifyProviderInfo(info);
}
private void onRouteSelected(Connection connection,
String packageName, String routeId, Bundle controlHints, int seq) {
if (mActiveConnection != connection) {
return;
}
MediaRoute2ProviderInfo providerInfo = getProviderInfo();
MediaRoute2Info route = (providerInfo == null) ? null : providerInfo.getRoute(routeId);
if (route == null) {
Slog.w(TAG, this + ": Unknown route " + routeId + " is selected from remove provider");
return;
}
mCallback.onRouteSelected(this, packageName, route, controlHints, seq);
}
private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
int requestId) {
if (mActiveConnection != connection) {
@@ -331,25 +329,42 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
public void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId) {
try {
mProvider.requestCreateSession(packageName, routeId, controlCategory, requestId);
mProvider.requestCreateSession(packageName, routeId,
controlCategory, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to create a session.", ex);
}
}
public void requestSelectRoute(String packageName, String routeId, int seq) {
public void releaseSession(int sessionId) {
try {
mProvider.requestSelectRoute(packageName, routeId, seq);
mProvider.releaseSession(sessionId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
Slog.e(TAG, "Failed to deliver request to release a session.", ex);
}
}
public void unselectRoute(String packageName, String routeId) {
public void addRoute(int sessionId, String routeId) {
try {
mProvider.unselectRoute(packageName, routeId);
mProvider.addRoute(sessionId, routeId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
Slog.e(TAG, "Failed to deliver request to add a route to a session.", ex);
}
}
public void removeRoute(int sessionId, String routeId) {
try {
mProvider.removeRoute(sessionId, routeId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to remove a route from a session.", ex);
}
}
public void transferRoute(int sessionId, String routeId) {
try {
mProvider.transferRoute(sessionId, routeId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to transfer a session to a route.", ex);
}
}
@@ -386,11 +401,6 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
mHandler.post(() -> onProviderInfoUpdated(Connection.this, info));
}
void postRouteSelected(String packageName, String routeId, Bundle controlHints, int seq) {
mHandler.post(() -> onRouteSelected(Connection.this,
packageName, routeId, controlHints, seq));
}
void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
requestId));
@@ -416,15 +426,6 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
}
}
@Override
public void notifyRouteSelected(String packageName, String routeId,
Bundle controlHints, int seq) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postRouteSelected(packageName, routeId, controlHints, seq);
}
}
@Override
public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
Connection connection = mConnectionRef.get();

View File

@@ -28,14 +28,12 @@ import android.media.IMediaRouter2Client;
import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRouter2;
import android.media.RouteSessionInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -77,8 +75,6 @@ class MediaRouter2ServiceImpl {
private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
@GuardedBy("mLock")
private int mCurrentUserId = -1;
@GuardedBy("mLock")
private int mSelectRouteRequestSequenceNumber = 1;
MediaRouter2ServiceImpl(Context context) {
mContext = context;
@@ -246,12 +242,12 @@ class MediaRouter2ServiceImpl {
}
}
public void selectClientRoute2(@NonNull IMediaRouter2Manager manager,
String packageName, @Nullable MediaRoute2Info route) {
public void requestCreateClientSession(IMediaRouter2Manager manager, String packageName,
MediaRoute2Info route, int requestId) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
selectClientRoute2Locked(manager, packageName, route);
requestClientCreateSessionLocked(manager, packageName, route, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -374,42 +370,6 @@ class MediaRouter2ServiceImpl {
}
}
private void requestSelectRoute2Locked(Client2Record clientRecord, boolean selectedByManager,
MediaRoute2Info route) {
if (clientRecord != null) {
MediaRoute2Info oldRoute = clientRecord.mSelectedRoute;
clientRecord.mSelectingRoute = route;
clientRecord.mIsManagerSelecting = selectedByManager;
UserHandler handler = clientRecord.mUserRecord.mHandler;
//TODO: Handle transfer instead of unselect and select
if (oldRoute != null) {
handler.sendMessage(obtainMessage(
UserHandler::unselectRoute, handler, clientRecord.mPackageName, oldRoute));
}
if (route != null) {
final int seq = mSelectRouteRequestSequenceNumber;
mSelectRouteRequestSequenceNumber++;
handler.sendMessage(obtainMessage(
UserHandler::requestSelectRoute, handler, clientRecord.mPackageName,
route, seq));
// Remove all previous timeout messages
for (int previousSeq : clientRecord.mSelectRouteSequenceNumbers) {
clientRecord.mUserRecord.mHandler.removeMessages(previousSeq);
}
clientRecord.mSelectRouteSequenceNumbers.clear();
// When the request is not handled in timeout, set the client's route to default.
Message timeoutMsg = obtainMessage(UserHandler::handleRouteSelectionTimeout,
handler, clientRecord.mPackageName, route);
timeoutMsg.what = seq; // Make the message cancelable.
handler.sendMessageDelayed(timeoutMsg, ROUTE_SELECTION_REQUEST_TIMEOUT_MS);
clientRecord.mSelectRouteSequenceNumbers.add(seq);
}
}
}
private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
if (clientRecord != null) {
if (clientRecord.mControlCategories.equals(categories)) {
@@ -512,17 +472,19 @@ class MediaRouter2ServiceImpl {
}
}
private void selectClientRoute2Locked(IMediaRouter2Manager manager,
String packageName, MediaRoute2Info route) {
private void requestClientCreateSessionLocked(IMediaRouter2Manager manager,
String packageName, MediaRoute2Info route, int requestId) {
ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
if (managerRecord != null) {
Client2Record clientRecord =
managerRecord.mUserRecord.findClientRecordLocked(packageName);
if (clientRecord == null) {
Slog.w(TAG, "Ignoring route selection for unknown client.");
Slog.w(TAG, "Ignoring session creation for unknown client.");
}
if (clientRecord != null && managerRecord.mTrusted) {
requestSelectRoute2Locked(clientRecord, true, route);
//TODO: select category properly
requestCreateSessionLocked(clientRecord.mClient, route,
route.getSupportedCategories().get(0), requestId);
}
}
}
@@ -551,7 +513,6 @@ class MediaRouter2ServiceImpl {
}
}
private void initializeUserLocked(UserRecord userRecord) {
if (DEBUG) {
Slog.d(TAG, userRecord + ": Initialized");
@@ -737,14 +698,6 @@ class MediaRouter2ServiceImpl {
sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider));
}
@Override
public void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) {
sendMessage(PooledLambda.obtainMessage(
UserHandler::updateSelectedRoute, this, provider, clientPackageName, route,
controlHints, seq));
}
@Override
public void onSessionCreated(@NonNull MediaRoute2Provider provider,
@Nullable RouteSessionInfo sessionInfo, int requestId) {
@@ -865,6 +818,7 @@ class MediaRouter2ServiceImpl {
@NonNull MediaRoute2Provider provider, @Nullable RouteSessionInfo sessionInfo,
int requestId) {
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
if (request.mRequestId == requestId
&& TextUtils.equals(
@@ -880,26 +834,34 @@ class MediaRouter2ServiceImpl {
return;
}
mSessionCreationRequests.remove(matchingRequest);
if (sessionInfo == null) {
// Failed
notifySessionCreationFailed(matchingRequest.mClientRecord, requestId);
return;
}
RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
.setProviderId(provider.getUniqueId())
.build();
String originalRouteId = matchingRequest.mRoute.getId();
String originalCategory = matchingRequest.mControlCategory;
if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
|| !TextUtils.equals(originalCategory, sessionInfo.getControlCategory())) {
if (!sessionInfoWithProviderId.getSelectedRoutes().contains(originalRouteId)
|| !TextUtils.equals(originalCategory,
sessionInfoWithProviderId.getControlCategory())) {
Slog.w(TAG, "Created session doesn't match the original request."
+ " originalRouteId=" + originalRouteId
+ ", originalCategory=" + originalCategory
+ ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
+ ", originalCategory=" + originalCategory + ", requestId=" + requestId
+ ", sessionInfo=" + sessionInfoWithProviderId);
notifySessionCreationFailed(matchingRequest.mClientRecord, requestId);
return;
}
// Succeeded
notifySessionCreated(matchingRequest.mClientRecord, sessionInfo, requestId);
notifySessionCreated(matchingRequest.mClientRecord,
sessionInfoWithProviderId, requestId);
// TODO: Tell managers for the session creation
}
@@ -922,109 +884,6 @@ class MediaRouter2ServiceImpl {
}
}
private void updateSelectedRoute(MediaRoute2ProviderProxy provider,
String clientPackageName, MediaRoute2Info selectedRoute, Bundle controlHints,
int seq) {
if (selectedRoute == null
|| !TextUtils.equals(clientPackageName, selectedRoute.getClientPackageName())) {
Log.w(TAG, "Ignoring route selection which has non-matching clientPackageName.");
return;
}
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
return;
}
Client2Record clientRecord;
synchronized (service.mLock) {
clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
}
//TODO: handle a case such that controlHints is null. (How should we notify MR2?)
if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
clientRecord.mSelectingRoute.getUniqueId(), selectedRoute.getUniqueId())) {
Log.w(TAG, "Ignoring invalid updateSelectedRoute call. selectingRoute="
+ clientRecord.mSelectingRoute + " route=" + selectedRoute);
unselectRoute(clientPackageName, selectedRoute);
return;
}
clientRecord.mSelectingRoute = null;
clientRecord.mSelectedRoute = selectedRoute;
notifyRouteSelectedToClient(clientRecord.mClient,
selectedRoute,
clientRecord.mIsManagerSelecting
? MediaRouter2.SELECT_REASON_SYSTEM_SELECTED :
MediaRouter2.SELECT_REASON_USER_SELECTED,
controlHints);
updateClientUsage(clientRecord);
// Remove the fallback route selection message.
removeMessages(seq);
}
private void handleRouteSelectionTimeout(String clientPackageName,
MediaRoute2Info selectingRoute) {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
return;
}
Client2Record clientRecord;
synchronized (service.mLock) {
clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
}
if (clientRecord == null) {
Log.w(TAG, "The client has gone. packageName=" + clientPackageName
+ " selectingRoute=" + selectingRoute);
return;
}
if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
clientRecord.mSelectingRoute.getUniqueId(), selectingRoute.getUniqueId())) {
Log.w(TAG, "Ignoring invalid selectFallbackRoute call. "
+ "Current selectingRoute=" + clientRecord.mSelectingRoute
+ " , original selectingRoute=" + selectingRoute);
return;
}
clientRecord.mSelectingRoute = null;
// TODO: When the default route is introduced, make mSelectedRoute always non-null.
MediaRoute2Info fallbackRoute = null;
clientRecord.mSelectedRoute = fallbackRoute;
notifyRouteSelectedToClient(clientRecord.mClient,
fallbackRoute,
MediaRouter2.SELECT_REASON_FALLBACK,
Bundle.EMPTY /* controlHints */);
updateClientUsage(clientRecord);
}
private void requestSelectRoute(String clientPackageName, MediaRoute2Info route, int seq) {
if (route != null) {
MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
Slog.w(TAG, "Ignoring to select route of unknown provider " + route);
} else {
provider.requestSelectRoute(clientPackageName, route.getId(), seq);
}
}
}
private void unselectRoute(String clientPackageName, MediaRoute2Info route) {
if (route != null) {
MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
Slog.w(TAG, "Ignoring to unselect route of unknown provider " + route);
} else {
provider.unselectRoute(clientPackageName, route.getId());
}
}
}
private void sendControlRequest(MediaRoute2Info route, Intent request) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {

View File

@@ -487,11 +487,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
public void selectClientRoute2(IMediaRouter2Manager manager,
String packageName, MediaRoute2Info route) {
mService2.selectClientRoute2(manager, packageName, route);
public void requestCreateClientSession(IMediaRouter2Manager manager, String packageName,
MediaRoute2Info route, int requestId) {
mService2.requestCreateClientSession(manager, packageName, route, requestId);
}
// Binder call
@Override
public void setControlCategories(IMediaRouter2Client client, List<String> categories) {

View File

@@ -94,21 +94,24 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
// Do nothing
}
//TODO: implement method
@Override
public void requestSelectRoute(@NonNull String packageName, @NonNull String routeId, int seq) {
try {
mAudioService.setBluetoothA2dpOn(
!TextUtils.equals(routeId, mDefaultRoute.getId()));
} catch (RemoteException ex) {
Log.e(TAG, "Error changing Bluetooth A2DP route");
}
public void releaseSession(int sessionId) {
// Do nothing
}
//TODO: implement method
@Override
public void unselectRoute(@NonNull String packageName, @NonNull String routeId) {
// does nothing..?
public void addRoute(int sessionId, MediaRoute2Info route) {
//TODO: implement method
}
@Override
public void removeRoute(int sessionId, MediaRoute2Info route) {
//TODO: implement method
}
@Override
public void transferRoute(int sessionId, MediaRoute2Info route) {
//TODO: implement method
}
//TODO: implement method