Commit 43dcbdf0 authored by Thomas Guilbert's avatar Thomas Guilbert Committed by Commit Bot

Add RemotingAppLauncher and DefaultRemotingApp

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/1306778Reviewed-by: default avatarZhiqiang Zhang <zqzhang@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Cr-Commit-Position: refs/heads/master@{#605389}
parent 4b6be5ff
...@@ -1165,6 +1165,8 @@ android:value="true" /> ...@@ -1165,6 +1165,8 @@ android:value="true" />
controllers with its own list. --> controllers with its own list. -->
<meta-data android:name="org.chromium.content.browser.REMOTE_MEDIA_PLAYERS" <meta-data android:name="org.chromium.content.browser.REMOTE_MEDIA_PLAYERS"
android:value="org.chromium.chrome.browser.media.remote.DefaultMediaRouteController"/> 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 %} {% endblock %}
</application> </application>
......
...@@ -175,11 +175,12 @@ public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks, ...@@ -175,11 +175,12 @@ public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks,
if (mState == State.API_CONNECTION_SUSPENDED) return; if (mState == State.API_CONNECTION_SUSPENDED) return;
try { try {
launchApplication(mApiClient, mSource.getApplicationId(), true) launchApplication(mApiClient, mSource.getApplicationId(), true).setResultCallback(this);
.setResultCallback(this);
mState = State.LAUNCHING_APPLICATION; mState = State.LAUNCHING_APPLICATION;
} catch (Exception e) { } 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(); reportError();
} }
} }
...@@ -209,8 +210,10 @@ public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks, ...@@ -209,8 +210,10 @@ public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks,
Status status = result.getStatus(); Status status = result.getStatus();
if (!status.isSuccess()) { if (!status.isSuccess()) {
Log.e(TAG, "Launch application failed with status: %s, %d, %s", // Do not log appId, as it can contain appIds overriden in downstream code that must not
mSource.getApplicationId(), status.getStatusCode(), status.getStatusMessage()); // be leaked
Log.e(TAG, "Launch application failed with status: %d, %s", status.getStatusCode(),
status.getStatusMessage());
reportError(); reportError();
return; return;
} }
......
// 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.
}
}
// 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;
}
}
...@@ -33,7 +33,7 @@ public class RemotingMediaSource implements MediaSource { ...@@ -33,7 +33,7 @@ public class RemotingMediaSource implements MediaSource {
/** /**
* The Cast application id. * The Cast application id.
*/ */
private final String mApplicationId; private String mApplicationId = null;
/** /**
* The URL to fling to the Cast device. * The URL to fling to the Cast device.
...@@ -61,9 +61,7 @@ public class RemotingMediaSource implements MediaSource { ...@@ -61,9 +61,7 @@ public class RemotingMediaSource implements MediaSource {
return null; return null;
} }
// TODO(avayvod): check the content URL and override the app id if needed. return new RemotingMediaSource(sourceId, mediaUrl);
String applicationId = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
return new RemotingMediaSource(sourceId, applicationId, mediaUrl);
} }
/** /**
...@@ -75,7 +73,7 @@ public class RemotingMediaSource implements MediaSource { ...@@ -75,7 +73,7 @@ public class RemotingMediaSource implements MediaSource {
@Override @Override
public MediaRouteSelector buildRouteSelector() { public MediaRouteSelector buildRouteSelector() {
return new MediaRouteSelector.Builder() return new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(mApplicationId)) .addControlCategory(CastMediaControlIntent.categoryForCast(getApplicationId()))
.build(); .build();
} }
...@@ -84,6 +82,12 @@ public class RemotingMediaSource implements MediaSource { ...@@ -84,6 +82,12 @@ public class RemotingMediaSource implements MediaSource {
*/ */
@Override @Override
public String getApplicationId() { public String getApplicationId() {
if (mApplicationId == null) {
RemotingAppLauncher.RemotingApp app =
RemotingAppLauncher.instance().getRemotingApp(this);
mApplicationId = (app != null) ? app.getApplicationId() : "";
}
return mApplicationId; return mApplicationId;
} }
...@@ -102,9 +106,8 @@ public class RemotingMediaSource implements MediaSource { ...@@ -102,9 +106,8 @@ public class RemotingMediaSource implements MediaSource {
return mMediaUrl; return mMediaUrl;
} }
private RemotingMediaSource(String sourceId, String applicationId, String mediaUrl) { private RemotingMediaSource(String sourceId, String mediaUrl) {
mSourceId = sourceId; mSourceId = sourceId;
mApplicationId = applicationId;
mMediaUrl = mediaUrl; mMediaUrl = mediaUrl;
} }
} }
...@@ -867,6 +867,8 @@ chrome_java_sources = [ ...@@ -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/CastSessionInfo.java",
"java/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManager.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/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/RemotingCastSession.java",
"java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaRouteProvider.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", "java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaSource.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment