[go: nahoru, domu]

blob: dd89d401002025f685b54d7a46ba0a2e7e4c8707 [file] [log] [blame]
/*
* Copyright 2020 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 androidx.mediarouter.media;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_ROUTE_CONTROL_REQUEST;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_DATA_ERROR;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_CONTROL_REQUEST_FAILED;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED;
import static androidx.mediarouter.media.MediaRouter.UNSELECT_REASON_UNKNOWN;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRoute2ProviderService;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.collection.ArrayMap;
import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController;
import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor;
import androidx.mediarouter.media.MediaRouteProvider.RouteController;
import androidx.mediarouter.media.MediaRouteProviderService.MediaRouteProviderServiceImplApi30;
import androidx.mediarouter.media.MediaRouteProviderService.MediaRouteProviderServiceImplApi30.ClientRecord;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@RequiresApi(api = Build.VERSION_CODES.R)
class MediaRoute2ProviderServiceAdapter extends MediaRoute2ProviderService {
private static final String TAG = "MR2ProviderService";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Object mLock = new Object();
final MediaRouteProviderServiceImplApi30 mServiceImpl;
@GuardedBy("mLock")
final Map<String, DynamicGroupRouteController> mControllers = new ArrayMap<>();
final SparseArray<String> mSessionIdMap = new SparseArray<>();
//TODO: Remove these when xMR is finished
final Map<String, Messenger> mMessengers = new ArrayMap<>();
private static final String KEY_MESSENGER_BINDER = "binder";
private volatile MediaRouteProviderDescriptor mProviderDescriptor;
// Use a large enough initial value so that it can't be the same as the controller ID in
// RegisteredMediaRouteProvider
private final AtomicInteger mNextControllerId = new AtomicInteger(0x20000000);
@SuppressLint("InlinedApi")
public static final String SERVICE_INTERFACE = MediaRoute2ProviderService.SERVICE_INTERFACE;
MediaRoute2ProviderServiceAdapter(MediaRouteProviderServiceImplApi30 serviceImpl) {
mServiceImpl = serviceImpl;
}
@Override
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
}
@Override
public void onSetRouteVolume(long requestId, @NonNull String routeId, int volume) {
RouteController controller = mServiceImpl.getControllerForRouteId(routeId);
if (controller == null) {
Log.w(TAG, "onSetRouteVolume: Couldn't find a controller for routeId=" + routeId);
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
controller.onSetVolume(volume);
}
@Override
public void onSetSessionVolume(long requestId, @NonNull String sessionId, int volume) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
Log.w(TAG, "onSetSessionVolume: Couldn't find a session");
notifyRequestFailed(requestId, REASON_INVALID_COMMAND);
return;
}
DynamicGroupRouteController controller = getController(sessionId);
if (controller == null) {
Log.w(TAG, "onSetSessionVolume: Couldn't find a controller");
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
controller.onSetVolume(volume);
}
@Override
public void onCreateSession(long requestId, @NonNull String packageName,
@NonNull String routeId, @Nullable Bundle sessionHints) {
MediaRouteProvider provider = getMediaRouteProvider();
MediaRouteDescriptor selectedRoute = getRouteDescriptor(routeId, "onCreateSession");
if (selectedRoute == null) {
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
DynamicGroupRouteController controller;
if (mProviderDescriptor.supportsDynamicGroupRoute()) {
controller = provider.onCreateDynamicGroupRouteController(routeId);
if (controller == null) {
Log.w(TAG, "onCreateSession: Couldn't create a dynamic controller");
notifyRequestFailed(requestId, REASON_REJECTED);
return;
}
} else {
RouteController routeController =
provider.onCreateRouteController(routeId);
if (routeController == null) {
Log.w(TAG, "onCreateSession: Couldn't create a controller");
notifyRequestFailed(requestId, REASON_REJECTED);
return;
}
controller = new DynamicGroupRouteControllerProxy(routeController);
}
String sessionId = assignSessionId(controller);
controller.onSelect();
//TODO: Set the client package name of the selected route.
RoutingSessionInfo.Builder builder =
new RoutingSessionInfo.Builder(sessionId, packageName)
.addSelectedRoute(routeId)
.setName(selectedRoute.getName())
.setVolumeHandling(selectedRoute.getVolumeHandling())
.setVolume(selectedRoute.getVolume())
.setVolumeMax(selectedRoute.getVolumeMax());
Messenger messenger = new Messenger(new IncomingHandler(this, sessionId));
mMessengers.put(sessionId, messenger);
Bundle controlHints = new Bundle();
controlHints.putBinder(KEY_MESSENGER_BINDER, messenger.getBinder());
builder.setControlHints(controlHints).build();
// Dynamic grouping info will be notified by the provider.
RoutingSessionInfo sessionInfo = builder.build();
notifySessionCreated(requestId, sessionInfo);
ClientRecord client = mServiceImpl.getClientForPackageName(packageName);
if (client == null) {
return;
}
int controllerId = mNextControllerId.getAndIncrement();
mSessionIdMap.put(controllerId, sessionId);
client.sendDynamicRouteControllerCreatedWithoutRequest(mProviderDescriptor,
controller, controllerId, routeId);
// Create route controllers for member route
if (selectedRoute.getGroupMemberIds().isEmpty()) {
client.saveRouteController(routeId, controller, mNextControllerId.getAndIncrement());
} else {
for (String memberId : selectedRoute.getGroupMemberIds()) {
client.createRouteController(memberId, sessionId,
mNextControllerId.getAndIncrement());
}
}
}
@Override
public void onReleaseSession(long requestId, @NonNull String sessionId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
return;
}
DynamicGroupRouteController controller;
synchronized (mLock) {
controller = mControllers.remove(sessionId);
mMessengers.remove(sessionId);
}
if (controller == null) {
Log.w(TAG, "onReleaseSession: Couldn't find a controller");
notifyRequestFailed(requestId, REASON_INVALID_COMMAND);
return;
}
controller.onUnselect(UNSELECT_REASON_UNKNOWN);
controller.onRelease();
notifySessionReleased(sessionId);
}
@Override
public void onSelectRoute(long requestId, @NonNull String sessionId,
@NonNull String routeId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
Log.w(TAG, "onSelectRoute: Couldn't find a session");
notifyRequestFailed(requestId, REASON_INVALID_COMMAND);
return;
}
if (getRouteDescriptor(routeId, "onSelectRoute") == null) {
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
DynamicGroupRouteController controller = getController(sessionId);
if (controller == null) {
Log.w(TAG, "onSelectRoute: Couldn't find a controller");
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
controller.onAddMemberRoute(routeId);
}
@Override
public void onDeselectRoute(long requestId, @NonNull String sessionId,
@NonNull String routeId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
Log.w(TAG, "onDeselectRoute: Couldn't find a session");
notifyRequestFailed(requestId, REASON_INVALID_COMMAND);
return;
}
if (getRouteDescriptor(routeId, "onDeselectRoute") == null) {
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
DynamicGroupRouteController controller = getController(sessionId);
if (controller == null) {
Log.w(TAG, "onDeselectRoute: Couldn't find a controller");
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
controller.onRemoveMemberRoute(routeId);
}
@Override
public void onTransferToRoute(long requestId, @NonNull String sessionId,
@NonNull String routeId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
Log.w(TAG, "onTransferToRoute: Couldn't find a session");
notifyRequestFailed(requestId, REASON_INVALID_COMMAND);
return;
}
if (getRouteDescriptor(routeId, "onTransferToRoute") == null) {
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
DynamicGroupRouteController controller = getController(sessionId);
if (controller == null) {
Log.w(TAG, "onTransferToRoute: Couldn't find a controller");
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
controller.onUpdateMemberRoutes(Collections.singletonList(routeId));
}
@Override
public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {
MediaRouteSelector selector = new MediaRouteSelector.Builder()
.addControlCategories(preference.getPreferredFeatures().stream()
.map(MediaRouter2Utils::toControlCategory)
.collect(Collectors.toList())).build();
mServiceImpl.setBaseDiscoveryRequest(new MediaRouteDiscoveryRequest(selector,
preference.shouldPerformActiveScan()));
}
public void setProviderDescriptor(@Nullable MediaRouteProviderDescriptor descriptor) {
mProviderDescriptor = descriptor;
List<MediaRouteDescriptor> routeDescriptors =
(descriptor == null) ? Collections.emptyList() : descriptor.getRoutes();
// Handle duplicated IDs
notifyRoutes(routeDescriptors.stream().map(MediaRouter2Utils::toFwkMediaRoute2Info)
.collect(Collectors.toMap(r -> r.getId(), r -> r, (a, b) -> b)).values());
}
private DynamicGroupRouteController getController(String sessionId) {
synchronized (mLock) {
return mControllers.get(sessionId);
}
}
private MediaRouteDescriptor getRouteDescriptor(String routeId, String description) {
MediaRouteProvider provider = getMediaRouteProvider();
if (provider == null || mProviderDescriptor == null) {
Log.w(TAG, description + ": no provider info");
return null;
}
List<MediaRouteDescriptor> routes = mProviderDescriptor.getRoutes();
for (MediaRouteDescriptor route : routes) {
if (TextUtils.equals(route.getId(), routeId)) {
return route;
}
}
Log.w(TAG, description + ": Couldn't find a route : " + routeId);
return null;
}
public void setDynamicRouteDescriptor(DynamicGroupRouteController controller,
Collection<DynamicRouteDescriptor> descriptors) {
String sessionId = null;
synchronized (mLock) {
for (Map.Entry<String, DynamicGroupRouteController> entry : mControllers.entrySet()) {
if (entry.getValue() == controller) {
sessionId = entry.getKey();
break;
}
}
}
if (sessionId == null) {
Log.w(TAG, "setDynamicRouteDescriptor: Couldn't find a routing session");
return;
}
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
Log.w(TAG, "setDynamicRouteDescriptor: Couldn't find a routing session");
return;
}
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(sessionInfo)
.clearSelectedRoutes()
.clearSelectableRoutes()
.clearDeselectableRoutes()
.clearTransferableRoutes();
boolean hasSelectedRoute = false;
for (DynamicRouteDescriptor descriptor : descriptors) {
String routeId = descriptor.getRouteDescriptor().getId();
if (descriptor.mSelectionState == DynamicRouteDescriptor.SELECTING
|| descriptor.mSelectionState == DynamicRouteDescriptor.SELECTED) {
builder.addSelectedRoute(routeId);
hasSelectedRoute = true;
}
if (descriptor.isGroupable()) {
builder.addSelectableRoute(routeId);
}
if (descriptor.isUnselectable()) {
builder.addDeselectableRoute(routeId);
}
if (descriptor.isTransferable()) {
builder.addTransferableRoute(routeId);
}
}
// This can happen when a new dynamic route controller is just created.
if (!hasSelectedRoute) {
List<String> selectedRoutes = sessionInfo.getSelectedRoutes();
for (String routeId : selectedRoutes) {
builder.addSelectedRoute(routeId);
}
}
notifySessionUpdated(builder.build());
}
//TODO: Remove this
void onControlRequest(Messenger messenger, int requestId, String sessionId,
Intent intent) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
Log.w(TAG, "onCustomCommand: Couldn't find a session");
return;
}
DynamicGroupRouteController controller = getController(sessionId);
if (controller == null) {
Log.w(TAG, "onControlRequest: Couldn't find a controller");
notifyRequestFailed(requestId, REASON_ROUTE_NOT_AVAILABLE);
return;
}
MediaRouter.ControlRequestCallback callback = new MediaRouter.ControlRequestCallback() {
@Override
public void onResult(Bundle data) {
if (DEBUG) {
Log.d(TAG, "Route control request succeeded"
+ ", sessionId=" + sessionId
+ ", intent=" + intent
+ ", data=" + data);
}
sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED,
requestId, 0, data, null);
}
@Override
public void onError(String error, Bundle data) {
if (DEBUG) {
Log.d(TAG, "Route control request failed"
+ ", sessionId=" + sessionId
+ ", intent=" + intent
+ ", error=" + error + ", data=" + data);
}
if (error != null) {
Bundle bundle = new Bundle();
bundle.putString(SERVICE_DATA_ERROR, error);
sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
requestId, 0, data, bundle);
} else {
sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
requestId, 0, data, null);
}
}
void sendReply(Messenger messenger, int what,
int requestId, int arg, Object obj, Bundle data) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = requestId;
msg.arg2 = arg;
msg.obj = obj;
msg.setData(data);
try {
messenger.send(msg);
} catch (DeadObjectException ex) {
// The client died.
} catch (RemoteException ex) {
Log.e(TAG, "Could not send message to the client.", ex);
}
}
};
controller.onControlRequest(intent, callback);
}
void addRouteController(RouteController routeController,
int controllerId, String packageName, String routeId) {
MediaRouteDescriptor descriptor = getRouteDescriptor(routeId, "addRouteController");
if (descriptor == null) {
return;
}
DynamicGroupRouteController controller;
if (routeController instanceof DynamicGroupRouteController) {
controller = (DynamicGroupRouteController) routeController;
} else {
controller = new DynamicGroupRouteControllerProxy(routeController);
}
String sessionId = assignSessionId(controller);
mSessionIdMap.put(controllerId, sessionId);
RoutingSessionInfo.Builder builder =
new RoutingSessionInfo.Builder(sessionId, packageName)
.addSelectedRoute(routeId)
.setName(descriptor.getName())
.setVolumeHandling(descriptor.getVolumeHandling())
.setVolume(descriptor.getVolume())
.setVolumeMax(descriptor.getVolumeMax());
RoutingSessionInfo sessionInfo = builder.build();
notifySessionCreated(REQUEST_ID_NONE, sessionInfo);
}
void removeRouteController(int controllerId) {
String sessionId = mSessionIdMap.get(controllerId);
if (sessionId == null) {
return;
}
mSessionIdMap.remove(controllerId);
synchronized (mLock) {
mControllers.remove(sessionId);
notifySessionReleased(sessionId);
}
}
private MediaRouteProvider getMediaRouteProvider() {
MediaRouteProviderService service = mServiceImpl.getService();
if (service == null) {
return null;
}
return service.getMediaRouteProvider();
}
private String assignSessionId(DynamicGroupRouteController controller) {
String sessionId;
synchronized (mLock) {
do {
//TODO: Consider a better way to create a session ID.
sessionId = UUID.randomUUID().toString();
} while (mControllers.containsKey(sessionId));
mControllers.put(sessionId, controller);
}
return sessionId;
}
private static class DynamicGroupRouteControllerProxy
extends DynamicGroupRouteController {
private final RouteController mRouteController;
DynamicGroupRouteControllerProxy(RouteController routeController) {
mRouteController = routeController;
}
@Override
public void onRelease() {
mRouteController.onRelease();
}
@Override
public void onSelect() {
mRouteController.onSelect();
}
@Override
public void onUnselect(int reason) {
mRouteController.onUnselect(reason);
}
@Override
public void onSetVolume(int volume) {
mRouteController.onSetVolume(volume);
}
@Override
public void onUpdateVolume(int delta) {
mRouteController.onUpdateVolume(delta);
}
@Override
public boolean onControlRequest(Intent intent,
MediaRouter.ControlRequestCallback callback) {
return mRouteController.onControlRequest(intent, callback);
}
@Override
public void onUpdateMemberRoutes(@Nullable List<String> routeIds) {
// Do nothing.
}
@Override
public void onAddMemberRoute(@NonNull String routeId) {
// Do nothing.
}
@Override
public void onRemoveMemberRoute(String routeId) {
// Do nothing.
}
}
//TODO: Remove this and use the existing messenger
static class IncomingHandler extends Handler {
private final MediaRoute2ProviderServiceAdapter mServiceAdapter;
private final String mSessionId;
IncomingHandler(MediaRoute2ProviderServiceAdapter serviceAdapter, String sessionId) {
super(Looper.myLooper());
mServiceAdapter = serviceAdapter;
mSessionId = sessionId;
}
@Override
public void handleMessage(Message msg) {
final Messenger messenger = msg.replyTo;
final int what = msg.what;
final int requestId = msg.arg1;
final Object obj = msg.obj;
switch (what) {
case CLIENT_MSG_ROUTE_CONTROL_REQUEST:
if (obj instanceof Intent) {
mServiceAdapter.onControlRequest(messenger, requestId,
mSessionId, (Intent) obj);
}
break;
}
}
}
}