Commit fc8716ea authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

WebLayer MediaRouter: add services and notification IDs.

The services will be foregrounded and the notifications will be visible
when a cast session is ongoing. The service/notification ID design
matches that of MediaSession. The notification IDs are defined in client
xml, so //components/media_router code needs to ask the embedder for
these values.

Casting is still not functional in WebLayer even after this change as
a CastOptionsProvider is required.

Bug: 1057100
Change-Id: I4d6fa44d1073a0a26c618c501e58251f118379c1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2467197Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Auto-Submit: Evan Stade <estade@chromium.org>
Commit-Queue: Evan Stade <estade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#817314}
parent 25f747b7
......@@ -26,6 +26,12 @@
<!-- Media playback notification -->
<item type="id" name="media_playback_notification" />
<!-- Remote Playback API notification -->
<item type="id" name="remote_playback_notification" />
<!-- Presentation API notification -->
<item type="id" name="presentation_notification" />
<!-- Sync UI constants -->
<item type="id" name="passphrase_type_list" />
......
......@@ -12,6 +12,7 @@ import androidx.fragment.app.FragmentManager;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.document.ChromeIntentUtil;
import org.chromium.chrome.browser.media.ui.ChromeMediaNotificationManager;
import org.chromium.chrome.browser.tab.Tab;
......@@ -41,6 +42,16 @@ public class ChromeMediaRouterClient extends MediaRouterClient {
ChromeMediaNotificationManager.show(notificationInfo);
}
@Override
public int getPresentationNotificationId() {
return R.id.presentation_notification;
}
@Override
public int getRemotingNotificationId() {
return R.id.remote_playback_notification;
}
@Override
public FragmentManager getSupportFragmentManager(WebContents initiator) {
FragmentActivity currentActivity =
......
......@@ -187,7 +187,7 @@ class ChromeMediaNotificationControllerDelegate implements MediaNotificationCont
* This class is used internally but has to be public to be able to launch the service.
*/
public static final class CastListenerServiceImpl extends ListenerServiceImpl {
static final int NOTIFICATION_ID = R.id.remote_notification;
static final int NOTIFICATION_ID = R.id.remote_playback_notification;
public CastListenerServiceImpl() {
super(NOTIFICATION_ID);
......
......@@ -168,7 +168,6 @@ android_resources("java_resources") {
"java/res/drawable/ic_cast_dark_chrome.xml",
"java/res/layout/caf_controller_media_route_button.xml",
"java/res/layout/expanded_cast_controller.xml",
"java/res/values/ids.xml",
"java/res/values/styles.xml",
]
deps = [
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 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.
-->
<resources>
<!-- RemotePlayback API notification -->
<item type="id" name="remote_notification" />
<!-- Presentation API notification -->
<item type="id" name="presentation_notification" />
</resources>
......@@ -49,6 +49,12 @@ public abstract class MediaRouterClient {
*/
public abstract void showNotification(MediaNotificationInfo notificationInfo);
/** Returns the ID to be used for Presentation API notifications. */
public abstract int getPresentationNotificationId();
/** Returns the ID to be used for Remote Playback API notifications. */
public abstract int getRemotingNotificationId();
/**
* @param initiator the web contents that initiated the request.
* @return a {@link FragmentManager} suitable for displaying a media router {@link
......
......@@ -31,6 +31,16 @@ public class TestMediaRouterClient extends MediaRouterClient {
@Override
public void showNotification(MediaNotificationInfo notificationInfo) {}
@Override
public int getPresentationNotificationId() {
return 2;
}
@Override
public int getRemotingNotificationId() {
return 3;
}
@Override
public FragmentManager getSupportFragmentManager(WebContents initiator) {
return null;
......
......@@ -7,7 +7,7 @@ package org.chromium.components.media_router.caf;
import android.content.Intent;
import org.chromium.components.browser_ui.media.MediaNotificationUma;
import org.chromium.components.media_router.R;
import org.chromium.components.media_router.MediaRouterClient;
/** NotificationController implementation for presentation. */
public class CafNotificationController extends BaseNotificationController {
......@@ -28,6 +28,6 @@ public class CafNotificationController extends BaseNotificationController {
@Override
public int getNotificationId() {
return R.id.presentation_notification;
return MediaRouterClient.getInstance().getPresentationNotificationId();
}
}
......@@ -8,7 +8,7 @@ import android.content.Intent;
import org.chromium.base.ContextUtils;
import org.chromium.components.browser_ui.media.MediaNotificationUma;
import org.chromium.components.media_router.R;
import org.chromium.components.media_router.MediaRouterClient;
import org.chromium.components.media_router.caf.BaseNotificationController;
import org.chromium.components.media_router.caf.BaseSessionController;
......@@ -30,6 +30,6 @@ public class RemotingNotificationController extends BaseNotificationController {
@Override
public int getNotificationId() {
return R.id.remote_notification;
return MediaRouterClient.getInstance().getRemotingNotificationId();
}
}
......@@ -638,7 +638,7 @@ content::ControllerPresentationServiceDelegate*
ContentBrowserClientImpl::GetControllerPresentationServiceDelegate(
content::WebContents* web_contents) {
#if defined(OS_ANDROID)
if (WebLayerFactoryImplAndroid::GetClientMajorVersion() < 87)
if (WebLayerFactoryImplAndroid::GetClientMajorVersion() < 88)
return nullptr;
if (base::FeatureList::IsEnabled(features::kMediaRouter)) {
......
......@@ -150,6 +150,7 @@ android_library("java") {
"org/chromium/weblayer_private/media/MediaRouteDialogFragmentImpl.java",
"org/chromium/weblayer_private/media/MediaRouterClientImpl.java",
"org/chromium/weblayer_private/media/MediaSessionManager.java",
"org/chromium/weblayer_private/media/MediaSessionNotificationHelper.java",
"org/chromium/weblayer_private/media/MediaStreamManager.java",
"org/chromium/weblayer_private/metrics/MetricsServiceClient.java",
"org/chromium/weblayer_private/metrics/UmaUtils.java",
......@@ -362,6 +363,7 @@ android_library("interfaces_java") {
"org/chromium/weblayer_private/interfaces/NavigationState.java",
"org/chromium/weblayer_private/interfaces/NewTabType.java",
"org/chromium/weblayer_private/interfaces/ObjectWrapper.java",
"org/chromium/weblayer_private/interfaces/RemoteMediaServiceConstants.java",
"org/chromium/weblayer_private/interfaces/ScrollNotificationType.java",
"org/chromium/weblayer_private/interfaces/SettingType.java",
"org/chromium/weblayer_private/interfaces/SiteSettingsFragmentArgs.java",
......
......@@ -79,6 +79,7 @@ import org.chromium.weblayer_private.interfaces.IWebLayerClient;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import org.chromium.weblayer_private.media.MediaRouteDialogFragmentImpl;
import org.chromium.weblayer_private.media.MediaRouterClientImpl;
import org.chromium.weblayer_private.media.MediaSessionManager;
import org.chromium.weblayer_private.media.MediaStreamManager;
import org.chromium.weblayer_private.metrics.MetricsServiceClient;
......@@ -404,6 +405,19 @@ public final class WebLayerImpl extends IWebLayer.Stub {
MediaSessionManager.serviceDestroyed();
}
@Override
public void onRemoteMediaServiceStarted(IObjectWrapper sessionService, Intent intent) {
StrictModeWorkaround.apply();
MediaRouterClientImpl.serviceStarted(
ObjectWrapper.unwrap(sessionService, Service.class), intent);
}
@Override
public void onRemoteMediaServiceDestroyed(int id) {
StrictModeWorkaround.apply();
MediaRouterClientImpl.serviceDestroyed(id);
}
@Override
public IBinder initializeImageDecoder(IObjectWrapper appContext, IObjectWrapper remoteContext) {
StrictModeWorkaround.apply();
......@@ -513,6 +527,42 @@ public final class WebLayerImpl extends IWebLayer.Stub {
}
}
public static Intent createRemoteMediaServiceIntent() {
if (sClient == null) {
throw new IllegalStateException("WebLayer should have been initialized already.");
}
try {
return sClient.createRemoteMediaServiceIntent();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
public static int getPresentationApiNotificationId() {
if (sClient == null) {
throw new IllegalStateException("WebLayer should have been initialized already.");
}
try {
return sClient.getPresentationApiNotificationId();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
public static int getRemotePlaybackApiNotificationId() {
if (sClient == null) {
throw new IllegalStateException("WebLayer should have been initialized already.");
}
try {
return sClient.getRemotePlaybackApiNotificationId();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
public static String getClientApplicationName() {
Context context = ContextUtils.getApplicationContext();
return new StringBuilder()
......
......@@ -104,4 +104,8 @@ interface IWebLayer {
IObjectWrapper getApplicationContext() = 20;
IMediaRouteDialogFragment createMediaRouteDialogFragmentImpl(
in IRemoteFragmentClient remoteFragmentClient) = 21;
// Added in Version 88.
void onRemoteMediaServiceStarted(in IObjectWrapper sessionService, in Intent intent) = 22;
void onRemoteMediaServiceDestroyed(int id) = 23;
}
......@@ -18,4 +18,7 @@ interface IWebLayerClient {
long getClassLoaderCreationTime() = 4;
long getContextCreationTime() = 5;
long getWebLayerLoaderCreationTime() = 6;
Intent createRemoteMediaServiceIntent() = 7;
int getPresentationApiNotificationId() = 8;
int getRemotePlaybackApiNotificationId() = 9;
}
// Copyright 2020 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.weblayer_private.interfaces;
/** Keys for remote media service intent extras. */
public interface RemoteMediaServiceConstants {
String NOTIFICATION_ID_KEY = "remote_media_service_notification_id_key";
}
......@@ -4,23 +4,46 @@
package org.chromium.weblayer_private.media;
import android.app.Service;
import android.content.Intent;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.fragment.app.FragmentManager;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.components.browser_ui.media.MediaNotificationController;
import org.chromium.components.browser_ui.media.MediaNotificationInfo;
import org.chromium.components.browser_ui.media.MediaNotificationManager;
import org.chromium.components.browser_ui.notifications.NotificationWrapper;
import org.chromium.components.browser_ui.notifications.NotificationWrapperBuilder;
import org.chromium.components.media_router.MediaRouterClient;
import org.chromium.content_public.browser.WebContents;
import org.chromium.weblayer_private.IntentUtils;
import org.chromium.weblayer_private.TabImpl;
import org.chromium.weblayer_private.WebLayerImpl;
import org.chromium.weblayer_private.interfaces.RemoteMediaServiceConstants;
/** Provides WebLayer-specific behavior for Media Router. */
@JNINamespace("weblayer")
public class MediaRouterClientImpl extends MediaRouterClient {
static int sPresentationNotificationId;
static int sRemotingNotificationId;
private MediaRouterClientImpl() {}
public static void serviceStarted(Service service, Intent intent) {
int notificationId = intent.getIntExtra(RemoteMediaServiceConstants.NOTIFICATION_ID_KEY, 0);
if (notificationId == 0) {
throw new RuntimeException("Invalid RemoteMediaService notification id");
}
MediaSessionNotificationHelper.serviceStarted(service, intent, notificationId);
}
public static void serviceDestroyed(int notificationId) {
MediaSessionNotificationHelper.serviceDestroyed(notificationId);
}
@Override
public int getTabId(WebContents webContents) {
TabImpl tab = TabImpl.fromWebContents(webContents);
......@@ -34,7 +57,19 @@ public class MediaRouterClientImpl extends MediaRouterClient {
@Override
public void showNotification(MediaNotificationInfo notificationInfo) {
// TODO: implement.
MediaNotificationManager.show(notificationInfo, () -> {
return new MediaRouterNotificationControllerDelegate(notificationInfo.id);
});
}
@Override
public int getPresentationNotificationId() {
return getPresentationNotificationIdFromClient();
}
@Override
public int getRemotingNotificationId() {
return getRemotingNotificationIdFromClient();
}
@Override
......@@ -51,4 +86,62 @@ public class MediaRouterClientImpl extends MediaRouterClient {
MediaRouterClient.setInstance(new MediaRouterClientImpl());
}
private static class MediaRouterNotificationControllerDelegate
implements MediaNotificationController.Delegate {
// The ID distinguishes between Presentation and Remoting services/notifications.
private final int mNotificationId;
MediaRouterNotificationControllerDelegate(int notificationId) {
mNotificationId = notificationId;
}
@Override
public Intent createServiceIntent() {
return WebLayerImpl.createRemoteMediaServiceIntent().putExtra(
RemoteMediaServiceConstants.NOTIFICATION_ID_KEY, mNotificationId);
}
@Override
public String getAppName() {
return WebLayerImpl.getClientApplicationName();
}
@Override
public String getNotificationGroupName() {
if (mNotificationId == getPresentationNotificationIdFromClient()) {
return "org.chromium.weblayer.PresentationApi";
}
assert mNotificationId == getRemotingNotificationIdFromClient();
return "org.chromium.weblayer.RemotePlaybackApi";
}
@Override
public NotificationWrapperBuilder createNotificationWrapperBuilder() {
return MediaSessionNotificationHelper.createNotificationWrapperBuilder(mNotificationId);
}
@Override
public void onMediaSessionUpdated(MediaSessionCompat session) {
// TODO(estade): implement.
}
@Override
public void logNotificationShown(NotificationWrapper notification) {}
}
private static int getPresentationNotificationIdFromClient() {
if (sPresentationNotificationId == 0) {
sPresentationNotificationId = WebLayerImpl.getPresentationApiNotificationId();
}
return sPresentationNotificationId;
}
private static int getRemotingNotificationIdFromClient() {
if (sRemotingNotificationId == 0) {
sRemotingNotificationId = WebLayerImpl.getRemotePlaybackApiNotificationId();
}
return sRemotingNotificationId;
}
}
......@@ -12,14 +12,10 @@ import org.chromium.components.browser_ui.media.MediaNotificationController;
import org.chromium.components.browser_ui.media.MediaNotificationInfo;
import org.chromium.components.browser_ui.media.MediaNotificationManager;
import org.chromium.components.browser_ui.media.MediaSessionHelper;
import org.chromium.components.browser_ui.notifications.ForegroundServiceUtils;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.NotificationWrapper;
import org.chromium.components.browser_ui.notifications.NotificationWrapperBuilder;
import org.chromium.weblayer_private.IntentUtils;
import org.chromium.weblayer_private.WebLayerImpl;
import org.chromium.weblayer_private.WebLayerNotificationChannels;
import org.chromium.weblayer_private.WebLayerNotificationWrapperBuilder;
/**
* A glue class for MediaSession.
......@@ -31,24 +27,11 @@ public class MediaSessionManager {
private static int sNotificationId;
public static void serviceStarted(Service service, Intent intent) {
MediaNotificationController controller = getController();
if (controller != null && controller.processIntent(service, intent)) return;
// The service has been started with startForegroundService() but the
// notification hasn't been shown. See similar logic in {@link
// ChromeMediaNotificationControllerDelegate}.
MediaNotificationController.finishStartingForegroundServiceOnO(
service, createNotificationWrapperBuilder().buildNotificationWrapper());
// Call stopForeground to guarantee Android unset the foreground bit.
ForegroundServiceUtils.getInstance().stopForeground(
service, Service.STOP_FOREGROUND_REMOVE);
service.stopSelf();
MediaSessionNotificationHelper.serviceStarted(service, intent, getNotificationId());
}
public static void serviceDestroyed() {
MediaNotificationController controller = getController();
if (controller != null) controller.onServiceDestroyed();
MediaNotificationManager.clear(getNotificationId());
MediaSessionNotificationHelper.serviceDestroyed(getNotificationId());
}
public static MediaSessionHelper.Delegate createMediaSessionHelperDelegate(int tabId) {
......@@ -60,7 +43,7 @@ public class MediaSessionManager {
@Override
public boolean fetchLargeFaviconImage() {
// TODO(crbug.com/1076463): WebLayer doesn't support favicons.
// TODO(crbug.com/1137625): implement.
return false;
}
......@@ -108,7 +91,8 @@ public class MediaSessionManager {
@Override
public NotificationWrapperBuilder createNotificationWrapperBuilder() {
return MediaSessionManager.createNotificationWrapperBuilder();
return MediaSessionNotificationHelper.createNotificationWrapperBuilder(
getNotificationId());
}
@Override
......@@ -120,22 +104,8 @@ public class MediaSessionManager {
public void logNotificationShown(NotificationWrapper notification) {}
}
private static NotificationWrapperBuilder createNotificationWrapperBuilder() {
// Only the null tag will work as expected, because {@link Service#startForeground()} only
// takes an ID and no tag. If we pass a tag here, then the notification that's used to
// display a paused state (no foreground service) will not be identified as the same one
// that's used with the foreground service.
return WebLayerNotificationWrapperBuilder.create(
WebLayerNotificationChannels.ChannelId.MEDIA_PLAYBACK,
new NotificationMetadata(0, null /*notificationTag*/, getNotificationId()));
}
private static int getNotificationId() {
if (sNotificationId == 0) sNotificationId = WebLayerImpl.getMediaSessionNotificationId();
return sNotificationId;
}
private static MediaNotificationController getController() {
return MediaNotificationManager.getController(getNotificationId());
}
}
// Copyright 2020 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.weblayer_private.media;
import android.app.Service;
import android.content.Intent;
import org.chromium.components.browser_ui.media.MediaNotificationController;
import org.chromium.components.browser_ui.media.MediaNotificationManager;
import org.chromium.components.browser_ui.notifications.ForegroundServiceUtils;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.NotificationWrapperBuilder;
import org.chromium.weblayer_private.WebLayerNotificationChannels;
import org.chromium.weblayer_private.WebLayerNotificationWrapperBuilder;
/**
* A helper class for management of MediaSession (local device), Presentation API and Remote
* Playback API (casting) notifications and foreground services.
*/
class MediaSessionNotificationHelper {
static void serviceStarted(Service service, Intent intent, int notificationId) {
MediaNotificationController controller =
MediaNotificationManager.getController(notificationId);
if (controller != null && controller.processIntent(service, intent)) return;
// The service has been started with startForegroundService() but the
// notification hasn't been shown. See similar logic in {@link
// ChromeMediaNotificationControllerDelegate}.
MediaNotificationController.finishStartingForegroundServiceOnO(service,
createNotificationWrapperBuilder(notificationId).buildNotificationWrapper());
// Call stopForeground to guarantee Android unset the foreground bit.
ForegroundServiceUtils.getInstance().stopForeground(
service, Service.STOP_FOREGROUND_REMOVE);
service.stopSelf();
}
static void serviceDestroyed(int notificationId) {
MediaNotificationController controller =
MediaNotificationManager.getController(notificationId);
if (controller != null) controller.onServiceDestroyed();
MediaNotificationManager.clear(notificationId);
}
static NotificationWrapperBuilder createNotificationWrapperBuilder(int notificationId) {
// Only the null tag will work as expected, because {@link Service#startForeground()} only
// takes an ID and no tag. If we pass a tag here, then the notification that's used to
// display a paused state (no foreground service) will not be identified as the same one
// that's used with the foreground service.
return WebLayerNotificationWrapperBuilder.create(
WebLayerNotificationChannels.ChannelId.MEDIA_PLAYBACK,
new NotificationMetadata(0, null /*notificationTag*/, notificationId));
}
}
......@@ -85,6 +85,13 @@
</intent-filter>
</service>
<service android:name="org.chromium.weblayer.RemoteMediaService"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<!-- Service for decoding images in a sandboxed process. -->
<service
android:name="org.chromium.weblayer.ImageDecoderService"
......
......@@ -70,6 +70,7 @@ android_library("java") {
"org/chromium/weblayer/LoadError.java",
"org/chromium/weblayer/MediaCaptureCallback.java",
"org/chromium/weblayer/MediaCaptureController.java",
"org/chromium/weblayer/MediaPlaybackBaseService.java",
"org/chromium/weblayer/MediaRouteDialogFragment.java",
"org/chromium/weblayer/MediaSessionService.java",
"org/chromium/weblayer/NavigateParams.java",
......@@ -82,6 +83,7 @@ android_library("java") {
"org/chromium/weblayer/ObserverList.java",
"org/chromium/weblayer/Profile.java",
"org/chromium/weblayer/RemoteFragment.java",
"org/chromium/weblayer/RemoteMediaService.java",
"org/chromium/weblayer/ScrollNotificationType.java",
"org/chromium/weblayer/ScrollOffsetCallback.java",
"org/chromium/weblayer/SettingType.java",
......
// Copyright 2020 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.weblayer;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Base class that provides common functionality for media playback services that are associated
* with an active media session and Android notification.
*/
abstract class MediaPlaybackBaseService extends Service {
// True when the start command has been forwarded to the impl.
boolean mStarted;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if (!WebLayer.hasWebLayerInitializationStarted()) {
stopSelf();
return;
}
init();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
WebLayer webLayer = getWebLayer();
if (webLayer == null) {
stopSelf();
} else {
try {
forwardStartCommandToImpl(webLayer, intent);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
mStarted = true;
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (!mStarted) return;
try {
forwardDestroyToImpl();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/** Called to do initialization when the service is created. */
void init() {}
/**
* Called to forward {@link onStartCommand()} to the WebLayer implementation.
*
* @param webLayer the implementation.
* @param intent the intent that started the service.
*/
abstract void forwardStartCommandToImpl(@NonNull WebLayer webLayer, Intent intent)
throws RemoteException;
/**
* Called to forward {@link onDestroy()} to the WebLayer implementation.
*
* This will only be called if {@link forwardStartCommandToImpl()} was previously called, and
* there should always be a loaded {@link WebLayer} available via {@link getWebLayer()}.
*/
abstract void forwardDestroyToImpl() throws RemoteException;
/** Returns the loaded {@link WebLayer}, or null if none is loaded. */
@Nullable
WebLayer getWebLayer() {
WebLayer webLayer;
try {
webLayer = WebLayer.getLoadedWebLayer(getApplication());
} catch (UnsupportedVersionException e) {
throw new RuntimeException(e);
}
return webLayer;
}
}
......@@ -9,9 +9,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
/**
......@@ -21,24 +22,20 @@ import org.chromium.weblayer_private.interfaces.ObjectWrapper;
* foreground when the MediaSession is active.
* @since 85
*/
public class MediaSessionService extends Service {
public class MediaSessionService extends MediaPlaybackBaseService {
// A helper to automatically pause the media session when a user removes headphones.
private BroadcastReceiver mAudioBecomingNoisyReceiver;
@Override
public IBinder onBind(Intent intent) {
return null;
public void onDestroy() {
super.onDestroy();
if (mAudioBecomingNoisyReceiver != null) {
unregisterReceiver(mAudioBecomingNoisyReceiver);
}
}
@Override
public void onCreate() {
super.onCreate();
if (!WebLayer.hasWebLayerInitializationStarted()) {
stopSelf();
return;
}
void init() {
mAudioBecomingNoisyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
......@@ -57,43 +54,13 @@ public class MediaSessionService extends Service {
}
@Override
public void onDestroy() {
super.onDestroy();
if (mAudioBecomingNoisyReceiver == null) return;
try {
getWebLayer().getImpl().onMediaSessionServiceDestroyed();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
unregisterReceiver(mAudioBecomingNoisyReceiver);
void forwardStartCommandToImpl(@NonNull WebLayer webLayer, Intent intent)
throws RemoteException {
webLayer.getImpl().onMediaSessionServiceStarted(ObjectWrapper.wrap(this), intent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
WebLayer webLayer = getWebLayer();
if (webLayer == null) {
stopSelf();
} else {
webLayer.getImpl().onMediaSessionServiceStarted(ObjectWrapper.wrap(this), intent);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
return START_NOT_STICKY;
}
private WebLayer getWebLayer() {
WebLayer webLayer;
try {
webLayer = WebLayer.getLoadedWebLayer(getApplication());
} catch (UnsupportedVersionException e) {
throw new RuntimeException(e);
}
return webLayer;
void forwardDestroyToImpl() throws RemoteException {
getWebLayer().getImpl().onMediaSessionServiceDestroyed();
}
}
// Copyright 2020 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.weblayer;
import android.content.Intent;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
import org.chromium.weblayer_private.interfaces.RemoteMediaServiceConstants;
/**
* A foreground {@link Service} for Presentation API and Remote Playback API.
*
* Like {@link MediaSessionService}, this class is associated with a notification for an ongoing
* media session. The difference is that the media for this service is played back on a remote
* device, i.e. casting.
*
* @since 88
*/
public class RemoteMediaService extends MediaPlaybackBaseService {
private int mId;
@Override
void forwardStartCommandToImpl(@NonNull WebLayer webLayer, Intent intent)
throws RemoteException {
mId = intent.getIntExtra(RemoteMediaServiceConstants.NOTIFICATION_ID_KEY, 0);
if (mId == 0) throw new RuntimeException("Invalid RemoteMediaService notification id");
webLayer.getImpl().onRemoteMediaServiceStarted(ObjectWrapper.wrap(this), intent);
}
@Override
void forwardDestroyToImpl() throws RemoteException {
getWebLayer().getImpl().onRemoteMediaServiceDestroyed(mId);
}
}
......@@ -749,6 +749,26 @@ public class WebLayer {
public long getWebLayerLoaderCreationTime() {
return sWebLayerLoaderCreationTime;
}
@Override
public Intent createRemoteMediaServiceIntent() {
StrictModeWorkaround.apply();
return new Intent(WebLayer.getAppContext(), RemoteMediaService.class);
}
@Override
public int getPresentationApiNotificationId() {
StrictModeWorkaround.apply();
// The id is part of the public library to avoid conflicts.
return R.id.weblayer_presentation_api_notification;
}
@Override
public int getRemotePlaybackApiNotificationId() {
StrictModeWorkaround.apply();
// The id is part of the public library to avoid conflicts.
return R.id.weblayer_remote_playback_api_notification;
}
}
@VerifiesOnO
......
......@@ -5,4 +5,6 @@
<resources>
<item type="id" name="weblayer_media_session_notification" />
<item type="id" name="weblayer_presentation_api_notification" />
<item type="id" name="weblayer_remote_playback_api_notification" />
</resources>
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