Skip to content

Commit

Permalink
Add RemotingAppLauncher and DefaultRemotingApp
Browse files Browse the repository at this point in the history
The RemotingAppLauncher instantiates RemotingApps at runtime. It uses
the app manifest to find class names of RemotingApps, and loads them via
reflection.

The DefaultRemotingApp is a simple wrapper around the default media
receiver application.

Bug: 790766
Change-Id: I01f51ebe90de361b947d67fe904a2faddc35f5de
Reviewed-on: https://chromium-review.googlesource.com/c/1306778
Reviewed-by: Zhiqiang Zhang <zqzhang@chromium.org>
Reviewed-by: David Trainor <dtrainor@chromium.org>
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Cr-Commit-Position: refs/heads/master@{#605389}
  • Loading branch information
tguilbert-google authored and Commit Bot committed Nov 5, 2018
1 parent 4b6be5f commit 43dcbdf
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 12 deletions.
2 changes: 2 additions & 0 deletions chrome/android/java/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,8 @@ android:value="true" />
controllers with its own list. -->
<meta-data android:name="org.chromium.content.browser.REMOTE_MEDIA_PLAYERS"
android:value="org.chromium.chrome.browser.media.remote.DefaultMediaRouteController"/>
<meta-data android:name="org.chromium.content.browser.REMOTE_PLAYBACK_APPS"
android:value="org.chromium.chrome.browser.media.router.cast.remoting.DefaultRemotingApp"/>

{% endblock %}
</application>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,12 @@ public void onConnected(Bundle connectionHint) {
if (mState == State.API_CONNECTION_SUSPENDED) return;

try {
launchApplication(mApiClient, mSource.getApplicationId(), true)
.setResultCallback(this);
launchApplication(mApiClient, mSource.getApplicationId(), true).setResultCallback(this);
mState = State.LAUNCHING_APPLICATION;
} catch (Exception e) {
Log.e(TAG, "Launch application failed: %s", mSource.getApplicationId(), e);
// Do not log appId, as it can contain appIds overriden in downstream code that must not
// be leaked
Log.e(TAG, "Launch application failed", e);
reportError();
}
}
Expand Down Expand Up @@ -209,8 +210,10 @@ public void onResult(Cast.ApplicationConnectionResult result) {

Status status = result.getStatus();
if (!status.isSuccess()) {
Log.e(TAG, "Launch application failed with status: %s, %d, %s",
mSource.getApplicationId(), status.getStatusCode(), status.getStatusMessage());
// Do not log appId, as it can contain appIds overriden in downstream code that must not
// be leaked
Log.e(TAG, "Launch application failed with status: %d, %s", status.getStatusCode(),
status.getStatusMessage());
reportError();
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2018 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.remoting;

import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.common.api.GoogleApiClient;

import org.chromium.base.annotations.UsedByReflection;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.media.router.MediaSource;

import java.net.URI;
import java.net.URISyntaxException;

/**
* App corresponding to the default media receiver application.
*/
@UsedByReflection("RemotingAppLauncher.java")
public class DefaultRemotingApp implements RemotingAppLauncher.RemotingApp {
private static final String TAG = "DefaultRmtApp";

public DefaultRemotingApp() {}

// RemotingApp implementation
@Override
public boolean canPlayMedia(RemotingMediaSource source) {
try {
String scheme = new URI(source.getMediaUrl()).getScheme();
if (scheme == null) return false;

return scheme.equals(UrlConstants.HTTP_SCHEME)
|| scheme.equals(UrlConstants.HTTPS_SCHEME);
} catch (URISyntaxException e) {
return false;
}
}

@Override
public String getApplicationId() {
// Can be overriden downstream.
return CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
}

@Override
public boolean hasCustomLoad() {
// Can be overriden downstream.
return false;
}

@Override
public void load(GoogleApiClient client, long startTime, MediaSource source) {
// Can be overriden downstream.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2018 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.remoting;

import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;

import com.google.android.gms.common.api.GoogleApiClient;

import org.chromium.base.ApplicationStatus;
import org.chromium.base.Log;
import org.chromium.chrome.browser.media.router.MediaSource;

import java.util.ArrayList;
import java.util.List;

/**
* Class that manages the instantiation of RemotingApps.
*
* A RemotingApp is essentially a wrapper around an application ID. We use them to protect certain
* application IDs in downstream code, and to override some app specific logic. The IDs correspond
* to Cast receiver apps.
*
* The types of RemotingApps are discovered by reflection. We find the class names via the
* REMOTE_PLAYBACK_APPS_KEY in AndroidManifest.xml. This allows us to specify different apps in the
* default chromium manifest, versus the official Chrome on Android manifest.
*/
public class RemotingAppLauncher {
private static final String TAG = "RemoteAppLnchr";

// This is a key for meta-data in the package manifest.
private static final String REMOTE_PLAYBACK_APPS_KEY =
"org.chromium.content.browser.REMOTE_PLAYBACK_APPS";

private static RemotingAppLauncher sInstance;

/**
* Interface that represents a Cast receiver app, that is compatible with remote playback.
*/
public interface RemotingApp {
/**
* Returns true if the given app supports the source
*/
public boolean canPlayMedia(RemotingMediaSource source);

/**
* Returns the application ID of the receiver app.
* NOTE: This string should not be added to logs, since this can be overriden
* by downstream and we would leak internal app IDs.
*/
public String getApplicationId();

/**
* Returns true if RemoteMediaPlayer.load() should be avoided, and RemotingApp.load() should
* be called instead.
*/
public boolean hasCustomLoad();

/**
* Loads the given source, at the given start time, for the already connected client.
*/
public void load(GoogleApiClient client, long startTime, MediaSource source);
}

private List<RemotingApp> mRemotingApps = new ArrayList<RemotingApp>();

/**
* The private constructor to make sure the object is only created by the instance() method.
*/
private RemotingAppLauncher() {}

private void createRemotingApps() {
// We only need to do this once
if (!mRemotingApps.isEmpty()) return;
try {
Activity currentActivity = ApplicationStatus.getLastTrackedFocusedActivity();
ApplicationInfo ai = currentActivity.getPackageManager().getApplicationInfo(
currentActivity.getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
String classNameString = bundle.getString(REMOTE_PLAYBACK_APPS_KEY);

if (classNameString != null) {
String[] classNames = classNameString.split(",");
for (String className : classNames) {
Log.d(TAG, "Adding remoting app %s", className.trim());
Class<?> remotingAppClass = Class.forName(className.trim());
Object remotingApp = remotingAppClass.newInstance();
mRemotingApps.add((RemotingApp) remotingApp);
}
}
} catch (NameNotFoundException | ClassNotFoundException | SecurityException
| InstantiationException | IllegalAccessException | IllegalArgumentException e) {
// Should never happen, implies corrupt AndroidManifest
Log.e(TAG, "Couldn't instatiate RemotingApps", e);
assert false;
}
}

/**
* Returns the first RemotingApp that can support the given source, or null if none are found.
*/
public RemotingApp getRemotingApp(MediaSource source) {
if (!(source instanceof RemotingMediaSource)) return null;

for (RemotingApp app : mRemotingApps) {
if (app.canPlayMedia((RemotingMediaSource) source)) {
return app;
}
}
return null;
}

/**
* The singleton instance access method.
*/
public static RemotingAppLauncher instance() {
if (sInstance == null) {
sInstance = new RemotingAppLauncher();
sInstance.createRemotingApps();
}

return sInstance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class RemotingMediaSource implements MediaSource {
/**
* The Cast application id.
*/
private final String mApplicationId;
private String mApplicationId = null;

/**
* The URL to fling to the Cast device.
Expand Down Expand Up @@ -61,9 +61,7 @@ public static RemotingMediaSource from(String sourceId) {
return null;
}

// TODO(avayvod): check the content URL and override the app id if needed.
String applicationId = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
return new RemotingMediaSource(sourceId, applicationId, mediaUrl);
return new RemotingMediaSource(sourceId, mediaUrl);
}

/**
Expand All @@ -75,7 +73,7 @@ public static RemotingMediaSource from(String sourceId) {
@Override
public MediaRouteSelector buildRouteSelector() {
return new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(mApplicationId))
.addControlCategory(CastMediaControlIntent.categoryForCast(getApplicationId()))
.build();
}

Expand All @@ -84,6 +82,12 @@ public MediaRouteSelector buildRouteSelector() {
*/
@Override
public String getApplicationId() {
if (mApplicationId == null) {
RemotingAppLauncher.RemotingApp app =
RemotingAppLauncher.instance().getRemotingApp(this);

mApplicationId = (app != null) ? app.getApplicationId() : "";
}
return mApplicationId;
}

Expand All @@ -102,9 +106,8 @@ public String getMediaUrl() {
return mMediaUrl;
}

private RemotingMediaSource(String sourceId, String applicationId, String mediaUrl) {
private RemotingMediaSource(String sourceId, String mediaUrl) {
mSourceId = sourceId;
mApplicationId = applicationId;
mMediaUrl = mediaUrl;
}
}
2 changes: 2 additions & 0 deletions chrome/android/java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/media/router/cast/CastSessionInfo.java",
"java/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManager.java",
"java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java",
"java/src/org/chromium/chrome/browser/media/router/cast/remoting/DefaultRemotingApp.java",
"java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingAppLauncher.java",
"java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingCastSession.java",
"java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaRouteProvider.java",
"java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaSource.java",
Expand Down

0 comments on commit 43dcbdf

Please sign in to comment.