Add MediaRouter2#requestSelectRoute and related logics

Added following logics
 - MediaRouter2 does not select the route anymore, but 'requests'
   to the MediaRouter2Service.
 - The request is sent to the MediaRoute2ProviderService.
 - If the provider send the control hints, the route is now
   'selected'. (The method for sending hints is not included in this CL)
 - If the provider didn't send the control hints in timeout,
   the selection request is cancelled.

Tests for this CL will be added when we introduce a new method
MediaRoute2ProviderService#sendControlHints()
(method name is not final).

Test: Builds successfully
Change-Id: I5aafcc29f9d39a88bac05b8e8ebae1280ef1f8fc
This commit is contained in:
Hyundo Moon
2019-11-08 14:47:50 +09:00
parent a3eb427847
commit 4140fee0b2
8 changed files with 204 additions and 39 deletions

View File

@@ -24,7 +24,7 @@ import android.media.IMediaRoute2ProviderClient;
*/
oneway interface IMediaRoute2Provider {
void setClient(IMediaRoute2ProviderClient client);
void selectRoute(String packageName, String id);
void requestSelectRoute(String packageName, String id, int seq);
void unselectRoute(String packageName, String id);
void notifyControlRequestSent(String id, in Intent request);
void requestSetVolume(String id, int volume);

View File

@@ -17,6 +17,7 @@
package android.media;
import android.media.MediaRoute2Info;
import android.os.Bundle;
/**
* @hide
@@ -26,4 +27,5 @@ oneway interface IMediaRouter2Client {
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
void notifyRouteSelected(in MediaRoute2Info route, int reason, in Bundle controlHints);
}

View File

@@ -54,7 +54,7 @@ interface IMediaRouterService {
* @param client the client that changes it's selected route
* @param route the route to be selected
*/
void selectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route);
void requestSelectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route);
void setControlCategories2(IMediaRouter2Client client, in List<String> categories);
void registerManager(IMediaRouter2Manager manager, String packageName);

View File

@@ -128,7 +128,9 @@ public abstract class MediaRoute2ProviderService extends Service {
}
@Override
public void selectRoute(String packageName, String id) {
public void requestSelectRoute(String packageName, String id, int seq) {
// TODO: When introducing MediaRoute2ProviderService#sendConnectionHints(),
// use the sequence number here properly.
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
MediaRoute2ProviderService.this, packageName, id));
}

View File

@@ -30,6 +30,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -103,6 +104,8 @@ public class MediaRouter2 {
private MediaRoute2Info mSelectedRoute;
@GuardedBy("sLock")
private MediaRoute2Info mSelectingRoute;
@GuardedBy("sLock")
private Client mClient;
final Handler mHandler;
@@ -250,24 +253,28 @@ public class MediaRouter2 {
}
/**
* Selects the specified route.
* Request to select the specified route. When the route is selected,
* {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} will be called.
*
* @param route the route to select
*/
//TODO: add a parameter for category (e.g. mirroring/casting)
public void selectRoute(@NonNull MediaRoute2Info route) {
public void requestSelectRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
Client client;
synchronized (sLock) {
mSelectedRoute = route;
if (mSelectingRoute == route) {
Log.w(TAG, "The route selection request is already sent.");
return;
}
mSelectingRoute = route;
client = mClient;
}
if (client != null) {
try {
mMediaRouterService.selectRoute2(client, route);
mMediaRouterService.requestSelectRoute2(client, route);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to select route.", ex);
Log.e(TAG, "Unable to request to select route.", ex);
}
}
}
@@ -443,6 +450,22 @@ public class MediaRouter2 {
}
}
void selectRouteOnHandler(MediaRoute2Info route, int reason, Bundle controlHints) {
synchronized (sLock) {
if (reason == SELECT_REASON_USER_SELECTED) {
if (mSelectingRoute == null
|| !TextUtils.equals(mSelectingRoute.getUniqueId(), route.getUniqueId())) {
Log.w(TAG, "Ignoring invalid or outdated notifyRouteSelected call. "
+ "selectingRoute=" + mSelectingRoute + " route=" + route);
return;
}
}
mSelectingRoute = null;
}
mSelectedRoute = route;
notifyRouteSelected(route, reason, controlHints);
}
private void refreshFilteredRoutes() {
List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
@@ -475,12 +498,17 @@ public class MediaRouter2 {
}
}
private void notifyRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
() -> record.mCallback.onRouteSelected(route, reason, controlHints));
}
}
/**
* Interface for receiving events about media routing changes.
*/
public static class Callback {
//TODO: clean up these callbacks
/**
* Called when routes are added.
* @param routes the list of routes that have been added. It's never empty.
@@ -505,20 +533,19 @@ public class MediaRouter2 {
*/
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
// TODO: Make this callback be called when we add requestSelectRoute().
/**
* Called when a route is selected. Exactly one route can be selected at a time.
* @param route the selected route.
* @param reason the reason why the route is selected.
* @param connectionHints An optional bundle of provider-specific arguments which may be
* used to control the selected route. Can be empty.
* @param controlHints An optional bundle of provider-specific arguments which may be
* used to control the selected route. Can be empty.
* @see #SELECT_REASON_UNKNOWN
* @see #SELECT_REASON_USER_SELECTED
* @see #SELECT_REASON_FALLBACK
* @see #getSelectedRoute()
*/
public void onRouteSelected(@NonNull MediaRoute2Info route, @SelectReason int reason,
@NonNull Bundle connectionHints) {}
@NonNull Bundle controlHints) {}
}
final class CallbackRecord {
@@ -560,5 +587,12 @@ public class MediaRouter2 {
mHandler.sendMessage(obtainMessage(MediaRouter2::changeRoutesOnHandler,
MediaRouter2.this, routes));
}
@Override
public void notifyRouteSelected(MediaRoute2Info route, int reason,
Bundle controlHints) {
mHandler.sendMessage(obtainMessage(MediaRouter2::selectRouteOnHandler,
MediaRouter2.this, route, reason, controlHints));
}
}
}

View File

@@ -84,9 +84,9 @@ final class MediaRoute2ProviderProxy implements ServiceConnection {
mCallback = callback;
}
public void selectRoute(String packageName, String routeId) {
public void requestSelectRoute(String packageName, String routeId, int seq) {
if (mConnectionReady) {
mActiveConnection.selectRoute(packageName, routeId);
mActiveConnection.requestSelectRoute(packageName, routeId, seq);
updateBinding();
}
}
@@ -328,9 +328,9 @@ final class MediaRoute2ProviderProxy implements ServiceConnection {
mClient.dispose();
}
public void selectRoute(String packageName, String routeId) {
public void requestSelectRoute(String packageName, String routeId, int seq) {
try {
mProvider.selectRoute(packageName, routeId);
mProvider.requestSelectRoute(packageName, routeId, seq);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
}

View File

@@ -29,10 +29,13 @@ import android.media.IMediaRouter2Manager;
import android.media.IMediaRouterClient;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRouter2;
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;
@@ -60,6 +63,7 @@ import java.util.Set;
class MediaRouter2ServiceImpl {
private static final String TAG = "MR2ServiceImpl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long ROUTE_SELECTION_REQUEST_TIMEOUT_MS = 5000L;
private final Context mContext;
private final Object mLock = new Object();
@@ -72,6 +76,8 @@ class MediaRouter2ServiceImpl {
private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
@GuardedBy("mLock")
private int mCurrentUserId = -1;
@GuardedBy("mLock")
private int mSelectRouteRequestSequenceNumber = 0;
MediaRouter2ServiceImpl(Context context) {
mContext = context;
@@ -189,12 +195,12 @@ class MediaRouter2ServiceImpl {
}
}
public void selectRoute2(@NonNull IMediaRouter2Client client,
public void requestSelectRoute2(@NonNull IMediaRouter2Client client,
@Nullable MediaRoute2Info route) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
selectRoute2Locked(mAllClientRecords.get(client.asBinder()), route);
requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), route);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -375,24 +381,38 @@ class MediaRouter2ServiceImpl {
}
}
private void selectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) {
private void requestSelectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) {
if (clientRecord != null) {
MediaRoute2Info oldRoute = clientRecord.mSelectedRoute;
clientRecord.mSelectedRoute = route;
clientRecord.mSelectingRoute = route;
UserHandler handler = clientRecord.mUserRecord.mHandler;
//TODO: Handle transfer instead of unselect and select
if (oldRoute != null) {
handler.sendMessage(
obtainMessage(UserHandler::unselectRoute, handler, clientRecord,
oldRoute));
handler.sendMessage(obtainMessage(
UserHandler::unselectRoute, handler, clientRecord.mPackageName, oldRoute));
}
if (route != null) {
handler.sendMessage(
obtainMessage(UserHandler::selectRoute, handler, clientRecord, route));
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);
}
handler.sendMessage(
obtainMessage(UserHandler::updateClientUsage, handler, clientRecord));
}
}
@@ -473,6 +493,11 @@ class MediaRouter2ServiceImpl {
userRecord.mHandler, manager));
for (ClientRecord clientRecord : userRecord.mClientRecords) {
// TODO: Do not use updateClientUsage since it updates all managers.
// Instead, Notify only to the manager that is currently being registered.
// TODO: UserRecord <-> ClientRecord, why do they reference each other?
// How about removing mUserRecord from clientRecord?
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateClientUsage,
clientRecord.mUserRecord.mHandler, clientRecord));
@@ -494,12 +519,13 @@ class MediaRouter2ServiceImpl {
String packageName, MediaRoute2Info route) {
ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
if (managerRecord != null) {
ClientRecord clientRecord = managerRecord.mUserRecord.findClientRecord(packageName);
ClientRecord clientRecord =
managerRecord.mUserRecord.findClientRecordLocked(packageName);
if (clientRecord == null) {
Slog.w(TAG, "Ignoring route selection for unknown client.");
}
if (clientRecord != null && managerRecord.mTrusted) {
selectRoute2Locked(clientRecord, route);
requestSelectRoute2Locked(clientRecord, route);
}
}
}
@@ -598,7 +624,7 @@ class MediaRouter2ServiceImpl {
mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
}
ClientRecord findClientRecord(String packageName) {
ClientRecord findClientRecordLocked(String packageName) {
for (ClientRecord clientRecord : mClientRecords) {
if (TextUtils.equals(clientRecord.mPackageName, packageName)) {
return clientRecord;
@@ -611,12 +637,15 @@ class MediaRouter2ServiceImpl {
class ClientRecord {
public final UserRecord mUserRecord;
public final String mPackageName;
public final List<Integer> mSelectRouteSequenceNumbers;
public List<String> mControlCategories;
public MediaRoute2Info mSelectingRoute;
public MediaRoute2Info mSelectedRoute;
ClientRecord(UserRecord userRecord, String packageName) {
mUserRecord = userRecord;
mPackageName = packageName;
mSelectRouteSequenceNumbers = new ArrayList<>();
mControlCategories = Collections.emptyList();
}
}
@@ -752,6 +781,15 @@ class MediaRouter2ServiceImpl {
sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider));
}
// TODO: When introducing MediaRoute2ProviderService#sendControlHints(),
// Make this method to be called.
public void onRouteSelectionRequestHandled(@NonNull MediaRoute2ProviderProxy provider,
String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) {
sendMessage(PooledLambda.obtainMessage(
UserHandler::updateSelectedRoute, this, provider, clientPackageName, route,
controlHints, seq));
}
private void updateProvider(MediaRoute2ProviderProxy provider) {
int providerIndex = getProviderInfoIndex(provider.getUniqueId());
MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
@@ -834,24 +872,104 @@ class MediaRouter2ServiceImpl {
return -1;
}
private void selectRoute(ClientRecord clientRecord, MediaRoute2Info route) {
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;
}
ClientRecord clientRecord;
synchronized (service.mLock) {
clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
}
if (!(clientRecord instanceof Client2Record)) {
Log.w(TAG, "Ignoring route selection for unknown client.");
unselectRoute(clientPackageName, selectedRoute);
return;
}
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(((Client2Record) clientRecord).mClient,
selectedRoute,
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;
}
ClientRecord clientRecord;
synchronized (service.mLock) {
clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
}
if (!(clientRecord instanceof Client2Record)) {
Log.w(TAG, "Ignoring fallback route selection for unknown client.");
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(((Client2Record) clientRecord).mClient,
fallbackRoute,
MediaRouter2.SELECT_REASON_FALLBACK,
Bundle.EMPTY /* controlHints */);
updateClientUsage(clientRecord);
}
private void requestSelectRoute(String clientPackageName, MediaRoute2Info route, int seq) {
if (route != null) {
MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
if (provider == null) {
Slog.w(TAG, "Ignoring to select route of unknown provider " + route);
} else {
provider.selectRoute(clientRecord.mPackageName, route.getId());
provider.requestSelectRoute(clientPackageName, route.getId(), seq);
}
}
}
private void unselectRoute(ClientRecord clientRecord, MediaRoute2Info route) {
private void unselectRoute(String clientPackageName, MediaRoute2Info route) {
if (route != null) {
MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
if (provider == null) {
Slog.w(TAG, "Ignoring to unselect route of unknown provider " + route);
} else {
provider.unselectRoute(clientRecord.mPackageName, route.getId());
provider.unselectRoute(clientPackageName, route.getId());
}
}
}
@@ -922,6 +1040,15 @@ class MediaRouter2ServiceImpl {
}
}
private void notifyRouteSelectedToClient(IMediaRouter2Client client,
MediaRoute2Info route, int reason, Bundle controlHints) {
try {
client.notifyRouteSelected(route, reason, controlHints);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify routes selected. Client probably died.", ex);
}
}
private void notifyRoutesAddedToClients(List<IMediaRouter2Client> clients,
List<MediaRoute2Info> routes) {
for (IMediaRouter2Client client : clients) {

View File

@@ -459,8 +459,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
public void selectRoute2(IMediaRouter2Client client, MediaRoute2Info route) {
mService2.selectRoute2(client, route);
public void requestSelectRoute2(IMediaRouter2Client client, MediaRoute2Info route) {
mService2.requestSelectRoute2(client, route);
}
// Binder call