[go: nahoru, domu]

blob: 7a6e82dbe14259b150607926d0c620f780284ee7 [file] [log] [blame]
/*
* Copyright 2018 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.example.android.supportv7.media;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.res.Resources;
import android.media.AudioManager;
import android.media.MediaRouter;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.collection.ArrayMap;
import androidx.mediarouter.media.MediaRouteDescriptor;
import androidx.mediarouter.media.MediaRouteProvider;
import androidx.mediarouter.media.MediaRouteProviderDescriptor;
import androidx.mediarouter.media.MediaRouter.ControlRequestCallback;
import androidx.mediarouter.media.MediaRouter.RouteInfo;
import com.example.android.supportv7.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Demonstrates how to create a custom media route provider.
*
* @see SampleDynamicGroupMrpService
*/
final class SampleDynamicGroupMrp extends SampleMediaRouteProvider {
private static final String TAG = "SampleDynamicGroupMrp";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String FIXED_VOLUME_ROUTE_ID = "fixed";
private static final String VARIABLE_VOLUME_BASIC_ROUTE_ID = "variable_basic";
private static final String STATIC_GROUP_ROUTE_ID = "static_group";
private static final int MAX_GROUPABLE_TV_COUNT = 1;
private SampleDynamicGroupRouteController mController;
SampleDynamicGroupMrp(Context context) {
super(context);
}
@Override
public RouteController onCreateRouteController(String routeId) {
MediaRouteDescriptor routeDescriptor = mRouteDescriptors.get(routeId);
if (routeDescriptor == null) {
Log.w(TAG, "onCreateRouteController: Unknown route ID " + routeId);
return null;
}
return new SampleDynamicGroupRouteController(routeId, null);
}
private String generateNewRouteId() {
String routeId;
do {
routeId = UUID.randomUUID().toString();
} while (mRouteDescriptors.containsKey(routeId));
return routeId;
}
@Override
public DynamicGroupRouteController onCreateDynamicGroupRouteController(
@NonNull String initialMemberRouteId) {
MediaRouteDescriptor initMemberDescriptor = mRouteDescriptors.get(initialMemberRouteId);
if (initMemberDescriptor == null || !initMemberDescriptor.isValid()) {
Log.w(TAG, "initial route doesn't exist or isn't valid : " + initialMemberRouteId);
return null;
}
mController = new SampleDynamicGroupRouteController(initialMemberRouteId,
initMemberDescriptor.getGroupMemberIds());
return mController;
}
// Make the given route a dynamic group route having a member route that is the given route
// device.
void makeDynamicGroupRoute(String routeId) {
MediaRouteDescriptor routeDescriptor = mRouteDescriptors.get(routeId);
if (routeDescriptor == null || !routeDescriptor.isValid()) {
Log.w(TAG, "makeDynamicGroupRoute: Route doesn't exist or isn't valid : " + routeId);
return;
}
// Create a new route info describing the given route.
String newRouteId = generateNewRouteId();
MediaRouteDescriptor changedDescriptor = new MediaRouteDescriptor.Builder(routeDescriptor)
.setId(newRouteId)
.build();
mRouteDescriptors.put(newRouteId, changedDescriptor);
// Change route ID in static groups
for (String groupRouteId : mRouteDescriptors.keySet()) {
MediaRouteDescriptor groupDescriptor = mRouteDescriptors.get(groupRouteId);
if (groupDescriptor.getGroupMemberIds().contains(routeId)) {
MediaRouteDescriptor newGroupDescriptor =
new MediaRouteDescriptor.Builder(groupDescriptor)
.removeGroupMemberId(routeId)
.addGroupMemberId(newRouteId)
.build();
mRouteDescriptors.put(groupRouteId, newGroupDescriptor);
}
}
mController.addDynamicRouteDescriptor(newRouteId);
MediaRouteDescriptor newDescriptor = new MediaRouteDescriptor.Builder(routeDescriptor)
.setId(routeId)
.setIsDynamicGroupRoute(true)
.setName(routeDescriptor.getName() + " +")
.build();
mRouteDescriptors.put(routeId, newDescriptor);
if (routeDescriptor.getGroupMemberIds().isEmpty()) {
mController.addMember(newRouteId);
} else {
for (String memberId: routeDescriptor.getGroupMemberIds()) {
mController.addMember(memberId);
}
}
}
@Override
protected void initializeRoutes() {
Resources r = getContext().getResources();
Intent settingsIntent = new Intent(Intent.ACTION_MAIN);
settingsIntent.setClass(getContext(), SampleMediaRouteSettingsActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
IntentSender is = PendingIntent.getActivity(getContext(), 99, settingsIntent,
PendingIntent.FLAG_UPDATE_CURRENT).getIntentSender();
mVolumes.put(VARIABLE_VOLUME_BASIC_ROUTE_ID + "1", VOLUME_DEFAULT);
mVolumes.put(VARIABLE_VOLUME_BASIC_ROUTE_ID + "2", VOLUME_DEFAULT);
mVolumes.put(VARIABLE_VOLUME_BASIC_ROUTE_ID + "3", VOLUME_DEFAULT);
mVolumes.put(VARIABLE_VOLUME_BASIC_ROUTE_ID + "4", VOLUME_DEFAULT);
mVolumes.put(FIXED_VOLUME_ROUTE_ID, VOLUME_DEFAULT);
mVolumes.put(STATIC_GROUP_ROUTE_ID, VOLUME_DEFAULT);
MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder(
VARIABLE_VOLUME_BASIC_ROUTE_ID + "1",
r.getString(R.string.dg_tv_route_name1))
.setDescription(r.getString(R.string.sample_route_description))
.addControlFilters(CONTROL_FILTERS_BASIC)
.setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV)
.setPlaybackStream(AudioManager.STREAM_MUSIC)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX)
.setVolume(VOLUME_DEFAULT)
.setCanDisconnect(true)
.setSettingsActivity(is)
.build();
MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder(
VARIABLE_VOLUME_BASIC_ROUTE_ID + "2",
r.getString(R.string.dg_tv_route_name2))
.setDescription(r.getString(R.string.sample_route_description))
.addControlFilters(CONTROL_FILTERS_BASIC)
.setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV)
.setPlaybackStream(AudioManager.STREAM_MUSIC)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX)
.setVolume(VOLUME_DEFAULT)
.setCanDisconnect(true)
.setSettingsActivity(is)
.build();
MediaRouteDescriptor routeDescriptor3 = new MediaRouteDescriptor.Builder(
VARIABLE_VOLUME_BASIC_ROUTE_ID + "3",
r.getString(R.string.dg_speaker_route_name3))
.setDescription(r.getString(R.string.sample_route_description))
.addControlFilters(CONTROL_FILTERS_BASIC)
.setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER)
.setPlaybackStream(AudioManager.STREAM_MUSIC)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX)
.setVolume(VOLUME_DEFAULT)
.setCanDisconnect(true)
.setSettingsActivity(is)
.build();
MediaRouteDescriptor routeDescriptor4 = new MediaRouteDescriptor.Builder(
VARIABLE_VOLUME_BASIC_ROUTE_ID + "4",
r.getString(R.string.dg_speaker_route_name4))
.setDescription(r.getString(R.string.sample_route_description))
.addControlFilters(CONTROL_FILTERS_BASIC)
.setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER)
.setPlaybackStream(AudioManager.STREAM_MUSIC)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX)
.setVolume(VOLUME_DEFAULT)
.setCanDisconnect(true)
.setSettingsActivity(is)
.build();
MediaRouteDescriptor routeDescriptor5 = new MediaRouteDescriptor.Builder(
FIXED_VOLUME_ROUTE_ID,
r.getString(R.string.dg_not_unselectable_route_name5))
.setDescription(r.getString(R.string.sample_route_description))
.addControlFilters(CONTROL_FILTERS_BASIC)
.setPlaybackStream(AudioManager.STREAM_MUSIC)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
.setVolumeMax(VOLUME_MAX)
.setVolume(VOLUME_MAX)
.setCanDisconnect(true)
.setSettingsActivity(is)
.build();
MediaRouteDescriptor routeDescriptor6 = new MediaRouteDescriptor.Builder(
STATIC_GROUP_ROUTE_ID,
r.getString(R.string.dg_static_group_route_name6))
.setDescription(r.getString(R.string.sample_route_description))
.addControlFilters(CONTROL_FILTERS_BASIC)
.addGroupMemberId(routeDescriptor1.getId())
.addGroupMemberId(routeDescriptor3.getId())
.setPlaybackStream(AudioManager.STREAM_MUSIC)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
.setVolumeMax(VOLUME_MAX)
.setVolume(VOLUME_MAX)
.setCanDisconnect(true)
.setSettingsActivity(is)
.build();
mRouteDescriptors.put(routeDescriptor1.getId(), routeDescriptor1);
mRouteDescriptors.put(routeDescriptor2.getId(), routeDescriptor2);
mRouteDescriptors.put(routeDescriptor3.getId(), routeDescriptor3);
mRouteDescriptors.put(routeDescriptor4.getId(), routeDescriptor4);
mRouteDescriptors.put(routeDescriptor5.getId(), routeDescriptor5);
mRouteDescriptors.put(routeDescriptor6.getId(), routeDescriptor6);
}
@Override
protected void publishRoutes() {
MediaRouteProviderDescriptor providerDescriptor = new MediaRouteProviderDescriptor.Builder()
.setSupportsDynamicGroupRoute(true)
.addRoutes(mRouteDescriptors.values())
.build();
setDescriptor(providerDescriptor);
}
final class SampleDynamicGroupRouteController
extends MediaRouteProvider.DynamicGroupRouteController {
private final String mRouteId;
private final RouteControlHelper mHelper;
private final boolean mIsGroup;
private List<String> mMemberRouteIds = new ArrayList<>();
private Map<String, DynamicRouteDescriptor> mDynamicRouteDescriptors = new ArrayMap<>();
private int mTvSelectedCount;
SampleDynamicGroupRouteController(String dynamicGroupRouteId,
List<String> memberIds) {
mRouteId = dynamicGroupRouteId;
mIsGroup = memberIds != null;
MediaRouteDescriptor selectedRouteDescriptor = mRouteDescriptors.get(mRouteId);
mTvSelectedCount = countTvFromRoute(selectedRouteDescriptor);
// Initialize DynamicRouteDescriptor with all the route descriptors.
List<MediaRouteDescriptor> routeDescriptors = getDescriptor().getRoutes();
if (routeDescriptors != null && !routeDescriptors.isEmpty()) {
for (MediaRouteDescriptor descriptor: routeDescriptors) {
String routeId = descriptor.getId();
boolean selected = memberIds != null && memberIds.contains(routeId);
DynamicRouteDescriptor.Builder builder =
new DynamicRouteDescriptor.Builder(descriptor)
.setIsGroupable(true)
.setIsTransferable(true)
.setIsUnselectable(true)
.setSelectionState(selected ? DynamicRouteDescriptor.SELECTED
: DynamicRouteDescriptor.UNSELECTED);
mDynamicRouteDescriptors.put(routeId, builder.build());
}
}
if (memberIds != null) {
mMemberRouteIds.addAll(memberIds);
}
mHelper = new RouteControlHelper(mRouteId);
updateDynamicRouteDescriptors();
if (DEBUG) {
Log.d(TAG, mRouteId + ": Controller created.");
}
}
//////////////////////////////////////////////
// Overrides DynamicGroupRouteController
//////////////////////////////////////////////
@Override
public String getGroupableSelectionTitle() {
return "Add a device";
}
@Override
public String getTransferableSectionTitle() {
return "Play on tv";
}
@Override
public void onUpdateMemberRoutes(List<String> routeIds) {
if (!maybeMakeGroupRoute(mRouteId)) {
Log.w(TAG, "onUpdateMemberRoutes: Can't find the group route : " + mRouteId);
return;
}
for (String routeId : mMemberRouteIds) {
removeMember(routeId);
}
for (String routeId : routeIds) {
addMember(routeId);
}
updateGroupRouteDescriptor();
updateDynamicRouteDescriptors();
}
@Override
public void onAddMemberRoute(@NonNull String routeId) {
if (!maybeMakeGroupRoute(mRouteId)) {
Log.w(TAG, "onAddMemberRoute: Can't find the group route : " + mRouteId);
return;
}
if (!addMember(routeId)) {
Log.w(TAG, "onAddMemberRoute: Failed to add a member route:" + " " + routeId);
return;
}
// Add each member route to the dynamic group
MediaRouteDescriptor routeDescriptor = mRouteDescriptors.get(routeId);
for (String memberRouteId : routeDescriptor.getGroupMemberIds()) {
addMember(memberRouteId);
}
updateGroupRouteDescriptor();
updateDynamicRouteDescriptors();
}
private void updateGroupRouteDescriptor() {
MediaRouteDescriptor.Builder groupDescriptorBuilder =
new MediaRouteDescriptor.Builder(mRouteDescriptors.get(mRouteId))
.clearGroupMemberIds();
for (String memberRouteId : mMemberRouteIds) {
groupDescriptorBuilder.addGroupMemberId(memberRouteId);
}
mRouteDescriptors.put(mRouteId, groupDescriptorBuilder.build());
publishRoutes();
}
@Override
public void onRemoveMemberRoute(@NonNull String routeId) {
if (!maybeMakeGroupRoute(mRouteId)) {
Log.w(TAG, "onRemoveMemberRoute: Can't find the group route : " + mRouteId);
return;
}
if (!removeMember(routeId)) {
Log.w(TAG, "onRemoveMemberRoute: Failed to remove a member route : " + routeId);
return;
}
updateGroupRouteDescriptor();
updateDynamicRouteDescriptors();
}
private boolean addMember(String routeId) {
DynamicRouteDescriptor dynamicDescriptor = mDynamicRouteDescriptors.get(routeId);
if (dynamicDescriptor == null || !dynamicDescriptor.isGroupable()
|| mMemberRouteIds.contains(routeId)) {
return false;
}
mMemberRouteIds.add(routeId);
MediaRouteDescriptor routeDescriptor =
mRouteDescriptors.get(dynamicDescriptor.getRouteDescriptor().getId());
DynamicRouteDescriptor.Builder builder =
new DynamicRouteDescriptor.Builder(dynamicDescriptor);
builder.setSelectionState(DynamicRouteDescriptor.SELECTED);
//TODO: Depending on getVolumeHandling() doesn't look good to identify the route. It
// could be a signal that the current structure should be refactored.
// Use a unique feature of the not unselectable route.
if (routeDescriptor.getVolumeHandling()
== MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) {
builder.setIsUnselectable(false);
} else {
builder.setIsUnselectable(true);
}
mDynamicRouteDescriptors.put(routeId, builder.build());
if (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV) {
mTvSelectedCount++;
}
return true;
}
private boolean removeMember(String routeId) {
DynamicRouteDescriptor dynamicDescriptor = mDynamicRouteDescriptors.get(routeId);
if (dynamicDescriptor == null || !dynamicDescriptor.isUnselectable()
|| !mMemberRouteIds.remove(routeId)) {
return false;
}
DynamicRouteDescriptor.Builder builder =
new DynamicRouteDescriptor.Builder(dynamicDescriptor);
builder.setSelectionState(DynamicRouteDescriptor.UNSELECTED);
mDynamicRouteDescriptors.put(routeId, builder.build());
MediaRouteDescriptor routeDescriptor =
mRouteDescriptors.get(dynamicDescriptor.getRouteDescriptor().getId());
if (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV) {
mTvSelectedCount--;
}
return true;
}
private boolean maybeMakeGroupRoute(String routeId) {
MediaRouteDescriptor routeDescriptor = mRouteDescriptors.get(routeId);
if (routeDescriptor == null) {
return false;
}
if (!routeDescriptor.isDynamicGroupRoute()) {
// We should reinitialize here not to add members twice
mTvSelectedCount = 0;
mMemberRouteIds.clear();
makeDynamicGroupRoute(mRouteId);
}
return true;
}
//////////////////////////////////////////////
// Overrides RouteController
//////////////////////////////////////////////
@Override
public void onRelease() {
Log.d(TAG, mRouteId + ": Controller released");
mHelper.onRelease();
}
@Override
public void onSelect() {
Log.d(TAG, mRouteId + ": Selected");
mHelper.onSelect();
if (mIsGroup) {
MediaRouteDescriptor groupDescriptor = mRouteDescriptors.get(mRouteId);
if (groupDescriptor != null) {
mRouteDescriptors.put(mRouteId,
new MediaRouteDescriptor.Builder(groupDescriptor)
.setConnectionState(RouteInfo.CONNECTION_STATE_CONNECTED)
.build());
publishRoutes();
}
}
}
@Override
public void onUnselect() {
Log.d(TAG, mRouteId + ": Unselected");
mHelper.onUnselect();
if (mIsGroup) {
MediaRouteDescriptor groupDescriptor = mRouteDescriptors.get(mRouteId);
if (groupDescriptor == null) {
return;
}
if (groupDescriptor.isDynamicGroupRoute()) {
mRouteDescriptors.remove(mRouteId);
} else {
mRouteDescriptors.put(mRouteId,
new MediaRouteDescriptor.Builder(groupDescriptor)
.setConnectionState(RouteInfo.CONNECTION_STATE_DISCONNECTED)
.build());
}
publishRoutes();
}
}
@Override
public void onSetVolume(int volume) {
Log.d(TAG, mRouteId + ": Set volume to " + volume);
mHelper.onSetVolume(volume);
}
@Override
public void onUpdateVolume(int delta) {
Log.d(TAG, mRouteId + ": Update volume by " + delta);
mHelper.onUpdateVolume(delta);
}
@Override
public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
Log.d(TAG, mRouteId + ": Received control request " + intent);
return mHelper.onControlRequest(intent, callback);
}
void addDynamicRouteDescriptor(String routeId) {
MediaRouteDescriptor descriptor = mRouteDescriptors.get(routeId);
DynamicRouteDescriptor.Builder builder =
new DynamicRouteDescriptor.Builder(descriptor)
.setIsGroupable(true)
.setIsTransferable(false)
.setSelectionState(DynamicRouteDescriptor.SELECTED);
mDynamicRouteDescriptors.put(routeId, builder.build());
}
// Updates each route's dynamic route descriptor when a route is added to / removed from
// a dynamic group. More specifically, groupable properties should be updated, in this demo.
// Rule 1. TV devices are exclusively selected. So, if a TV device is already selected
// (added) as a member of dynamic group, other TV devices cannot be groupable.
// Rout 2. A static group route, whose members are all already selected, should be
// ungroupable.
private void updateDynamicRouteDescriptors() {
for (DynamicRouteDescriptor dynamicDescriptor : mDynamicRouteDescriptors.values()) {
String routeId = dynamicDescriptor.getRouteDescriptor().getId();
MediaRouteDescriptor routeDescriptor = mRouteDescriptors.get(routeId);
if (mMemberRouteIds.contains(routeId)) {
// Skip selected routes.
continue;
}
boolean isGroupable = true;
boolean isTransferrable = true;
if (mRouteId.equals(routeId)) {
isGroupable = false;
isTransferrable = false;
}
// This route is a group and its member routes are already all selected.
if (!routeDescriptor.getGroupMemberIds().isEmpty()
&& mMemberRouteIds.containsAll(routeDescriptor.getGroupMemberIds())) {
isGroupable = false;
}
if ((countTvFromRoute(routeDescriptor) + mTvSelectedCount)
> MAX_GROUPABLE_TV_COUNT) {
isGroupable = false;
}
if (mMemberRouteIds.contains(routeId)) {
isGroupable = false;
isTransferrable = false;
}
if (isGroupable != dynamicDescriptor.isGroupable()
|| isTransferrable != dynamicDescriptor.isTransferable()) {
DynamicRouteDescriptor.Builder builder =
new DynamicRouteDescriptor.Builder(dynamicDescriptor)
.setIsGroupable(isGroupable)
.setIsTransferable(isTransferrable);
mDynamicRouteDescriptors.put(routeId, builder.build());
}
}
notifyDynamicRoutesChanged(mDynamicRouteDescriptors.values());
}
private int countTvFromRoute(MediaRouteDescriptor routeDescriptor) {
if (routeDescriptor.getGroupMemberIds().isEmpty()) {
return (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV)
? 1 : 0;
}
int count = 0;
for (String routeId : routeDescriptor.getGroupMemberIds()) {
MediaRouteDescriptor descriptor = mRouteDescriptors.get(routeId);
count += countTvFromRoute(descriptor);
}
return count;
}
}
}