blob: 843bbbc066b269c5a41b532f61c09eec80b301c9 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.media.router.cast;
import android.net.Uri;
import android.support.v7.media.MediaRouteSelector;
import com.google.android.gms.cast.CastMediaControlIntent;
import org.chromium.chrome.browser.media.router.MediaSource;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
/**
* Abstracts parsing the Cast application id and other parameters from the source ID.
*/
// Reused in CafMRP. No need to migrate. See https://crbug.com/711860.
public class CastMediaSource implements MediaSource {
public static final String AUTOJOIN_CUSTOM_CONTROLLER_SCOPED = "custom_controller_scoped";
public static final String AUTOJOIN_TAB_AND_ORIGIN_SCOPED = "tab_and_origin_scoped";
public static final String AUTOJOIN_ORIGIN_SCOPED = "origin_scoped";
public static final String AUTOJOIN_PAGE_SCOPED = "page_scoped";
private static final List<String> AUTOJOIN_POLICIES =
Arrays.asList(AUTOJOIN_CUSTOM_CONTROLLER_SCOPED, AUTOJOIN_TAB_AND_ORIGIN_SCOPED,
AUTOJOIN_ORIGIN_SCOPED, AUTOJOIN_PAGE_SCOPED);
private static final String CAST_SOURCE_ID_SEPARATOR = "/";
private static final String CAST_SOURCE_ID_APPLICATION_ID = "__castAppId__";
private static final String CAST_SOURCE_ID_CLIENT_ID = "__castClientId__";
private static final String CAST_SOURCE_ID_AUTOJOIN_POLICY = "__castAutoJoinPolicy__";
private static final String CAST_APP_CAPABILITIES_PREFIX = "(";
private static final String CAST_APP_CAPABILITIES_SUFFIX = ")";
private static final String CAST_APP_CAPABILITIES_SEPARATOR = ",";
private static final List<String> CAST_APP_CAPABILITIES =
Arrays.asList("video_out", "audio_out", "video_in", "audio_in", "multizone_group");
/**
* The protocol for Cast Presentation URLs.
*/
private static final String CAST_URL_PROTOCOL = "cast:";
/**
* The query parameter key for Cast Client ID in a Cast Presentation URL.
*/
private static final String CAST_URL_CLIENT_ID = "clientId";
/**
* The query parameter key for autojoin policy in a Cast Presentation URL.
*/
private static final String CAST_URL_AUTOJOIN_POLICY = "autoJoinPolicy";
/**
* The query parameter key for app capabilities in a Cast Presentation URL.
*/
private static final String CAST_URL_CAPABILITIES = "capabilities";
/**
* The original presentation URL that the {@link CastMediaSource} object was created from.
*/
private final String mSourceId;
/**
* The Cast application id, can be invalid in which case {@link CastMediaRouteProvider}
* will explicitly report no sinks available.
*/
private final String mApplicationId;
/**
* A numeric identifier for the Cast Web SDK, unique for the frame providing the
* presentation URL. Can be null.
*/
private final String mClientId;
/**
* Defines Cast-specific behavior for {@link CastMediaRouteProvider#joinRoute}. Defaults to
* {@link CastMediaSource#AUTOJOIN_TAB_AND_ORIGIN_SCOPED}.
*/
private final String mAutoJoinPolicy;
/**
* Defines the capabilities of the particular application id. Can be null.
*/
private final String[] mCapabilities;
/**
* Initializes the media source from the source id.
* @param sourceId the source id for the Cast media source (a presentation url).
* @return an initialized media source if the id is valid, null otherwise.
*/
@Nullable
public static CastMediaSource from(String sourceId) {
assert sourceId != null;
return sourceId.startsWith(CAST_URL_PROTOCOL) ? fromCastUrl(sourceId)
: fromLegacyUrl(sourceId);
}
/**
* Returns a new {@link MediaRouteSelector} to use for Cast device filtering for this
* particular media source or null if the application id is invalid.
*
* @return an initialized route selector or null.
*/
@Override
public MediaRouteSelector buildRouteSelector() {
try {
return new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(mApplicationId))
.build();
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* @return the Cast application id corresponding to the source.
*/
@Override
public String getApplicationId() {
return mApplicationId;
}
/**
* @return the client id if passed in the source id. Can be null.
*/
@Nullable
public String getClientId() {
return mClientId;
}
/**
* @return the auto join policy which must be one of the AUTOJOIN constants defined above.
*/
public String getAutoJoinPolicy() {
return mAutoJoinPolicy;
}
/**
* @return the id identifying the media source
*/
@Override
public String getSourceId() {
return mSourceId;
}
/**
* @return application capabilities
*/
public String[] getCapabilities() {
return mCapabilities == null ? null : Arrays.copyOf(mCapabilities, mCapabilities.length);
}
private CastMediaSource(String sourceId, String applicationId, String clientId,
String autoJoinPolicy, String[] capabilities) {
mSourceId = sourceId;
mApplicationId = applicationId;
mClientId = clientId;
mAutoJoinPolicy = autoJoinPolicy == null ? AUTOJOIN_TAB_AND_ORIGIN_SCOPED : autoJoinPolicy;
mCapabilities = capabilities;
}
@Nullable
private static String extractParameter(String[] fragments, String key) {
String keyPrefix = key + "=";
for (String parameter : fragments) {
if (parameter.startsWith(keyPrefix)) return parameter.substring(keyPrefix.length());
}
return null;
}
@Nullable
private static String[] extractCapabilities(String capabilitiesParameter) {
if (capabilitiesParameter.length()
< CAST_APP_CAPABILITIES_PREFIX.length() + CAST_APP_CAPABILITIES_SUFFIX.length()) {
return null;
}
if (!capabilitiesParameter.startsWith(CAST_APP_CAPABILITIES_PREFIX)
|| !capabilitiesParameter.endsWith(CAST_APP_CAPABILITIES_SUFFIX)) {
return null;
}
String capabilitiesList =
capabilitiesParameter.substring(CAST_APP_CAPABILITIES_PREFIX.length(),
capabilitiesParameter.length() - CAST_APP_CAPABILITIES_SUFFIX.length());
String[] capabilities = capabilitiesList.split(CAST_APP_CAPABILITIES_SEPARATOR);
for (String capability : capabilities) {
if (!CAST_APP_CAPABILITIES.contains(capability)) return null;
}
return capabilities;
}
/**
* Helper method to create a MediaSource object from a Cast (cast:) presentation URL.
* @param sourceId the source id for the Cast media source.
* @return an initialized media source if the uri is a valid Cast presentation URL, null
* otherwise.
*/
@Nullable
private static CastMediaSource fromCastUrl(String sourceId) {
// Strip the scheme as the Uri parser works better without it.
Uri sourceUri = Uri.parse(sourceId.substring(CAST_URL_PROTOCOL.length()));
String applicationId = sourceUri.getPath();
if (applicationId == null) return null;
String clientId = sourceUri.getQueryParameter(CAST_URL_CLIENT_ID);
String autoJoinPolicy = sourceUri.getQueryParameter(CAST_URL_AUTOJOIN_POLICY);
if (autoJoinPolicy != null && !AUTOJOIN_POLICIES.contains(autoJoinPolicy)) {
return null;
}
String[] capabilities = null;
String capabilitiesParam = sourceUri.getQueryParameter(CAST_URL_CAPABILITIES);
if (capabilitiesParam != null) {
capabilities = capabilitiesParam.split(CAST_APP_CAPABILITIES_SEPARATOR);
for (String capability : capabilities) {
if (!CAST_APP_CAPABILITIES.contains(capability)) return null;
}
}
return new CastMediaSource(sourceId, applicationId, clientId, autoJoinPolicy, capabilities);
}
/**
* @deprecated Legacy Cast Presentation URLs are deprecated in favor of cast: URLs.
* TODO(crbug.com/757358): remove this method when we drop support for legacy URLs.
* Helper method to create a MediaSource object from a legacy (https:) presentation URL.
* @param sourceId the source id for the Cast media source.
* @return an initialized media source if the uri is a valid https presentation URL, null
* otherwise.
*/
@Deprecated
@Nullable
private static CastMediaSource fromLegacyUrl(String sourceId) {
Uri sourceUri = Uri.parse(sourceId);
String uriFragment = sourceUri.getFragment();
if (uriFragment == null) return null;
String[] parameters = uriFragment.split(CAST_SOURCE_ID_SEPARATOR);
String applicationId = extractParameter(parameters, CAST_SOURCE_ID_APPLICATION_ID);
if (applicationId == null) return null;
String[] capabilities = null;
int capabilitiesIndex = applicationId.indexOf(CAST_APP_CAPABILITIES_PREFIX);
if (capabilitiesIndex != -1) {
capabilities = extractCapabilities(applicationId.substring(capabilitiesIndex));
if (capabilities == null) return null;
applicationId = applicationId.substring(0, capabilitiesIndex);
}
String clientId = extractParameter(parameters, CAST_SOURCE_ID_CLIENT_ID);
String autoJoinPolicy = extractParameter(parameters, CAST_SOURCE_ID_AUTOJOIN_POLICY);
if (autoJoinPolicy != null && !AUTOJOIN_POLICIES.contains(autoJoinPolicy)) {
return null;
}
return new CastMediaSource(sourceId, applicationId, clientId, autoJoinPolicy, capabilities);
}
}