From fc74261655883f332ce9a87654e837a03c697e0b Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Wed, 18 Apr 2018 16:01:49 -0400 Subject: [PATCH 01/16] More interface details for the cast framework --- .gitignore | 1 + build.gradle | 2 +- extern/GmsApi | 2 +- extern/GmsLib | 2 +- play-services-core/build.gradle | 2 + .../src/main/AndroidManifest.xml | 8 ++ .../framework/internal/CastContextImpl.java | 126 ++++++++++++++++++ .../internal/CastDynamiteModuleImpl.java | 80 +++-------- .../framework/internal/CastSessionImpl.java | 23 ++++ .../internal/DiscoveryManagerImpl.java | 69 ++++++++++ .../internal/MediaRouterCallbackImpl.java | 49 +++++++ .../cast/framework/internal/SessionImpl.java | 49 +++++++ .../internal/SessionManagerImpl.java | 79 +++++++++++ .../media/CastMediaRouteProviderService.java | 32 +++++ .../gms/cast/CastMediaRouteProvider.java | 103 ++++++++++++++ 15 files changed, 564 insertions(+), 63 deletions(-) create mode 100644 play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastSessionImpl.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/DiscoveryManagerImpl.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/MediaRouterCallbackImpl.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionImpl.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionManagerImpl.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/cast/media/CastMediaRouteProviderService.java create mode 100644 play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java diff --git a/.gitignore b/.gitignore index 6265e60a..25ed3657 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ gen/ bin/ build/ .gradle/ +.idea/ user.gradle local.properties .directory diff --git a/build.gradle b/build.gradle index eb2ee5dc..98380e1f 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ def androidCompileSdk() { return 27 } def androidTargetSdk() { return 27 } -def androidMinSdk() { return 9 } +def androidMinSdk() { return 14 } def versionCode() { def stdout = new ByteArrayOutputStream() diff --git a/extern/GmsApi b/extern/GmsApi index db0be6ba..a811b2f6 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit db0be6ba010f52b4d12e69aed2482c38a3c2406c +Subproject commit a811b2f64572a10e34a0fd9a0eac189735849f5b diff --git a/extern/GmsLib b/extern/GmsLib index 90e9b7b7..a3b7236b 160000 --- a/extern/GmsLib +++ b/extern/GmsLib @@ -1 +1 @@ -Subproject commit 90e9b7b79dcaddfce887f87b94f80adeed8791e6 +Subproject commit a3b7236b5efa09bcd2bcb65c4946b41c63e3e98f diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index b43dfe90..6444474d 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -19,12 +19,14 @@ apply plugin: 'com.android.application' dependencies { implementation "com.android.support:support-v4:$supportLibraryVersion" implementation "com.android.support:appcompat-v7:$supportLibraryVersion" + implementation "com.android.support:mediarouter-v7:$supportLibraryVersion" implementation "com.takisoft.fix:preference-v7:$supportLibraryVersion.0" implementation "de.hdodenhof:circleimageview:1.3.0" implementation "com.squareup.wire:wire-runtime:1.6.1" implementation project(':microg-ui-tools') implementation project(':play-services-api') + implementation project(':play-services-cast-api') implementation project(':play-services-wearable') implementation project(':unifiednlp-base') implementation project(':wearable-lib') diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 2a8b7dd6..5f1ebe13 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -395,6 +395,14 @@ + + + + + + + + CONTROL_FILTERS; + static { + IntentFilter f2 = new IntentFilter(); + f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + f2.addAction(MediaControlIntent.ACTION_PLAY); + + CONTROL_FILTERS = new ArrayList(); + CONTROL_FILTERS.add(f2); + } + + @Override + public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { + Log.d(TAG, "unimplemented Method: onDiscoveryRequestChanged"); + } + + @Override + public RouteController onCreateRouteController(String routeId) { + Log.d(TAG, "unimplemented Method: onCreateRouteController"); + return null; + } + + /** + * TODO: Currently this method simply publishes a single cast route for + * testing. + */ + private void publishRoutes() { + Log.d(TAG, "unimplemented Method: publishRoutes"); + Bundle extras = new Bundle(); + CastDevice castDevice = new CastDevice("abc123"); + castDevice.putInBundle(extras); + MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder( + "abc123", + "Rotue Friendly Name") + .setDescription("Chromecast") + .addControlFilters(CONTROL_FILTERS) + .setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) + .setVolumeMax(20) + .setVolume(0) + .setEnabled(true) + .setExtras(extras) + .setConnectionState(MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED) + .build(); + MediaRouteProviderDescriptor providerDescriptor = + new MediaRouteProviderDescriptor.Builder() + .addRoute(routeDescriptor1) + .build(); + this.setDescriptor(providerDescriptor); + } +} From ae8e82f65620710b14dc6d7e97cf69a2b6ce84fe Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sat, 19 May 2018 15:30:17 -0400 Subject: [PATCH 02/16] Maintain support for API version 9 --- build.gradle | 2 +- play-services-core/src/main/AndroidManifest.xml | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 98380e1f..eb2ee5dc 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ def androidCompileSdk() { return 27 } def androidTargetSdk() { return 27 } -def androidMinSdk() { return 14 } +def androidMinSdk() { return 9 } def versionCode() { def stdout = new ByteArrayOutputStream() diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 5f1ebe13..9b30d7aa 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -91,7 +91,15 @@ - + Date: Wed, 25 Apr 2018 19:25:14 -0400 Subject: [PATCH 03/16] Cast MVP to start videos --- extern/GmsApi | 2 +- play-services-core/build.gradle | 14 ++ .../src/main/AndroidManifest.xml | 7 +- .../framework/internal/CastContextImpl.java | 52 +++++- .../internal/CastDynamiteModuleImpl.java | 27 +-- .../framework/internal/CastSessionImpl.java | 56 ++++++ .../internal/DiscoveryManagerImpl.java | 1 - .../internal/MediaRouterCallbackImpl.java | 25 ++- .../cast/framework/internal/SessionImpl.java | 158 ++++++++++++++++- .../internal/SessionManagerImpl.java | 159 +++++++++++++++++- .../gms/cast/CastDeviceControllerImpl.java | 151 +++++++++++++++++ .../gms/cast/CastDeviceControllerService.java | 50 ++++++ .../gms/cast/CastMediaRouteController.java | 96 +++++++++++ .../gms/cast/CastMediaRouteProvider.java | 117 +++++++++---- proguard.flags | 6 + settings.gradle | 2 +- 16 files changed, 846 insertions(+), 77 deletions(-) create mode 100644 play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java create mode 100644 play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerService.java create mode 100644 play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java diff --git a/extern/GmsApi b/extern/GmsApi index a811b2f6..5af21c0f 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit a811b2f64572a10e34a0fd9a0eac189735849f5b +Subproject commit 5af21c0ff2777e61caba5d111991cbb165167793 diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 6444474d..d4bfe525 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -16,6 +16,10 @@ apply plugin: 'com.android.application' +repositories { + mavenLocal() +} + dependencies { implementation "com.android.support:support-v4:$supportLibraryVersion" implementation "com.android.support:appcompat-v7:$supportLibraryVersion" @@ -23,6 +27,12 @@ dependencies { implementation "com.takisoft.fix:preference-v7:$supportLibraryVersion.0" implementation "de.hdodenhof:circleimageview:1.3.0" implementation "com.squareup.wire:wire-runtime:1.6.1" + implementation "su.litvak.chromecast:api-v2:0.10.3-SNAPSHOT" + + // Specified manually due to + // https://github.com/vitalidze/chromecast-java-api-v2/issues/91 + api "org.slf4j:slf4j-api:1.7.25" + api "uk.uuid.slf4j:slf4j-android:1.7.25-1" implementation project(':microg-ui-tools') implementation project(':play-services-api') @@ -101,6 +111,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + packagingOptions { + exclude 'META-INF/ASL2.0' + } } if (file('user.gradle').exists()) { diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 9b30d7aa..042e4c33 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -595,6 +595,12 @@ + + + + + + @@ -603,7 +609,6 @@ - diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java index bb0ecda0..e0307a2e 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java @@ -18,15 +18,20 @@ package com.google.android.gms.cast.framework.internal; import android.content.Context; import android.os.Bundle; +import android.os.IBinder; import android.os.RemoteException; import android.support.v7.media.MediaControlIntent; import android.support.v7.media.MediaRouteSelector; +import android.support.v7.media.MediaRouter; import android.util.Log; +import com.google.android.gms.cast.CastMediaControlIntent; import com.google.android.gms.cast.framework.CastOptions; +import com.google.android.gms.cast.framework.IAppVisibilityListener; import com.google.android.gms.cast.framework.ICastContext; import com.google.android.gms.cast.framework.IDiscoveryManager; import com.google.android.gms.cast.framework.ISessionManager; +import com.google.android.gms.cast.framework.ISessionProvider; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; @@ -41,22 +46,39 @@ public class CastContextImpl extends ICastContext.Stub { private Context context; private CastOptions options; private IMediaRouter router; - private Map map; + private Map sessionProviders; + public ISessionProvider defaultSessionProvider; private MediaRouteSelector mergedSelector; - public CastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map map) { - Log.d(TAG, "Creating new cast context"); + public CastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map sessionProviders) throws RemoteException { this.context = (Context) ObjectWrapper.unwrap(context); this.options = options; this.router = router; - this.map = map; + this.sessionProviders = sessionProviders; + + String receiverApplicationId = options.getReceiverApplicationId(); + String defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); + + this.defaultSessionProvider = ISessionProvider.Stub.asInterface(this.sessionProviders.get(defaultCategory)); // TODO: This should incorporate passed options this.mergedSelector = new MediaRouteSelector.Builder() .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .build(); + + // TODO: Find a home for this once the rest of the implementation + // becomes more clear. Uncomment this to enable discovery of devices. + // Note that the scan currently isn't ever disabled as part of the + // lifecycle, so we don't want to ship with this. + /* + Bundle selectorBundle = this.mergedSelector.asBundle(); + + router.clearCallbacks(); + router.registerMediaRouterCallbackImpl(selectorBundle, new MediaRouterCallbackImpl(this)); + router.addCallback(selectorBundle, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + */ } @Override @@ -64,6 +86,16 @@ public class CastContextImpl extends ICastContext.Stub { return this.mergedSelector.asBundle(); } + @Override + public void addVisibilityChangeListener(IAppVisibilityListener listener) { + Log.d(TAG, "unimplemented Method: addVisibilityChangeListener"); + } + + @Override + public void removeVisibilityChangeListener(IAppVisibilityListener listener) { + Log.d(TAG, "unimplemented Method: removeVisibilityChangeListener"); + } + @Override public boolean isApplicationVisible() throws RemoteException { Log.d(TAG, "unimplemented Method: isApplicationVisible"); @@ -71,9 +103,9 @@ public class CastContextImpl extends ICastContext.Stub { } @Override - public ISessionManager getSessionManagerImpl() throws RemoteException { + public SessionManagerImpl getSessionManagerImpl() { if (this.sessionManager == null) { - this.sessionManager = new SessionManagerImpl(); + this.sessionManager = new SessionManagerImpl(this); } return this.sessionManager; } @@ -103,8 +135,8 @@ public class CastContextImpl extends ICastContext.Stub { } @Override - public void unknown(String s1, Map m1) throws RemoteException { - Log.d(TAG, "unimplemented Method: unknown"); + public void setReceiverApplicationId(String receiverApplicationId, Map sessionProvidersByCategory) throws RemoteException { + Log.d(TAG, "unimplemented Method: setReceiverApplicationId"); } public Context getContext() { @@ -119,6 +151,10 @@ public class CastContextImpl extends ICastContext.Stub { return this.mergedSelector; } + public CastOptions getOptions() { + return this.options; + } + @Override public IObjectWrapper getWrappedThis() throws RemoteException { return ObjectWrapper.wrap(this); diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastDynamiteModuleImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastDynamiteModuleImpl.java index cd40264f..3bc3bd81 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastDynamiteModuleImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastDynamiteModuleImpl.java @@ -18,6 +18,7 @@ package com.google.android.gms.cast.framework.internal; import android.content.Context; import android.os.Bundle; +import android.os.IBinder; import android.os.RemoteException; import android.support.v7.media.MediaRouter; import android.util.Log; @@ -45,34 +46,18 @@ public class CastDynamiteModuleImpl extends ICastDynamiteModule.Stub { private static final String TAG = CastDynamiteModuleImpl.class.getSimpleName(); @Override - public ICastContext newCastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map map) throws RemoteException { - CastContextImpl castContextImpl = new CastContextImpl(context, options, router, map); - - // TODO: Find a home for this once the rest of the implementation - // becomes more clear. Uncomment this to enable discovery of devices. - // Note that the scan currently isn't ever disabled as part of the - // lifecycle, so we don't want to ship with this. - /* - Bundle selectorBundle = castContextImpl.getMergedSelector().asBundle(); - - router.clearCallbacks(); - router.registerMediaRouterCallbackImpl(selectorBundle, new MediaRouterCallbackImpl()); - router.addCallback(selectorBundle, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); - */ - - return castContextImpl; + public ICastContext newCastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map sessionProviders) throws RemoteException { + return new CastContextImpl(context, options, router, sessionProviders); } @Override - public ISession newSessionImpl(String s1, String s2, ISessionProxy proxy) throws RemoteException { - Log.d(TAG, "unimplemented Method: newSessionImpl"); - return new SessionImpl(); + public ISession newSessionImpl(String category, String sessionId, ISessionProxy proxy) throws RemoteException { + return new SessionImpl(category, sessionId, proxy); } @Override public ICastSession newCastSessionImpl(CastOptions options, IObjectWrapper session, ICastConnectionController controller) throws RemoteException { - Log.d(TAG, "unimplemented Method: newCastSessionImpl"); - return new CastSessionImpl(); + return new CastSessionImpl(options, session, controller); } @Override diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastSessionImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastSessionImpl.java index 993e5da1..cc63b949 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastSessionImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastSessionImpl.java @@ -18,6 +18,62 @@ package com.google.android.gms.cast.framework.internal; import com.google.android.gms.cast.framework.ICastSession; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import com.google.android.gms.cast.ApplicationMetadata; +import com.google.android.gms.cast.framework.CastOptions; +import com.google.android.gms.cast.framework.ICastConnectionController; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.dynamic.IObjectWrapper; +import com.google.android.gms.dynamic.ObjectWrapper; + public class CastSessionImpl extends ICastSession.Stub { private static final String TAG = CastSessionImpl.class.getSimpleName(); + private CastOptions options; + private SessionImpl session; + private ICastConnectionController controller; + + public CastSessionImpl(CastOptions options, IObjectWrapper session, ICastConnectionController controller) throws RemoteException { + this.options = options; + this.session = (SessionImpl) ObjectWrapper.unwrap(session); + this.controller = controller; + + this.session.setCastSession(this); + } + + public void launchApplication() throws RemoteException { + this.controller.launchApplication(this.options.getReceiverApplicationId(), this.options.getLaunchOptions()); + } + + @Override + public void onConnected(Bundle routeInfoExtra) throws RemoteException { + this.controller.launchApplication(this.options.getReceiverApplicationId(), this.options.getLaunchOptions()); + } + + @Override + public void onConnectionSuspended(int reason) { + Log.d(TAG, "unimplemented Method: onConnectionSuspended"); + } + + @Override + public void onConnectionFailed(Status status) { + Log.d(TAG, "unimplemented Method: onConnectionFailed"); + } + + @Override + public void onApplicationConnectionSuccess(ApplicationMetadata applicationMetadata, String applicationStatus, String sessionId, boolean wasLaunched) { + this.session.onApplicationConnectionSuccess(applicationMetadata, applicationStatus, sessionId, wasLaunched); + } + + @Override + public void onApplicationConnectionFailure(int statusCode) { + this.session.onApplicationConnectionFailure(statusCode); + } + + @Override + public void disconnectFromDevice(boolean boolean1, int int1) { + Log.d(TAG, "unimplemented Method: disconnectFromDevice"); + } } diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/DiscoveryManagerImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/DiscoveryManagerImpl.java index 53c67b8b..b0e7b51c 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/DiscoveryManagerImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/DiscoveryManagerImpl.java @@ -36,7 +36,6 @@ public class DiscoveryManagerImpl extends IDiscoveryManager.Stub { private Set discoveryManagerListeners = new HashSet(); public DiscoveryManagerImpl(CastContextImpl castContextImpl) { - Log.d(TAG, "Creating new discovery manager"); this.castContextImpl = castContextImpl; } diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/MediaRouterCallbackImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/MediaRouterCallbackImpl.java index 59ea5742..8bf933f3 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/MediaRouterCallbackImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/MediaRouterCallbackImpl.java @@ -16,12 +16,27 @@ package com.google.android.gms.cast.framework.internal; +import android.content.Intent; import android.os.Bundle; +import android.os.RemoteException; import android.util.Log; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.framework.ISession; +import com.google.android.gms.dynamic.IObjectWrapper; +import com.google.android.gms.dynamic.ObjectWrapper; + +import android.support.v7.media.MediaControlIntent; + public class MediaRouterCallbackImpl extends IMediaRouterCallback.Stub { private static final String TAG = MediaRouterCallbackImpl.class.getSimpleName(); + private CastContextImpl castContext; + + public MediaRouterCallbackImpl(CastContextImpl castContext) { + this.castContext = castContext; + } + @Override public void onRouteAdded(String routeId, Bundle extras) { Log.d(TAG, "unimplemented Method: onRouteAdded"); @@ -35,8 +50,14 @@ public class MediaRouterCallbackImpl extends IMediaRouterCallback.Stub { Log.d(TAG, "unimplemented Method: onRouteRemoved"); } @Override - public void onRouteSelected(String routeId, Bundle extras) { - Log.d(TAG, "unimplemented Method: onRouteSelected"); + public void onRouteSelected(String routeId, Bundle extras) throws RemoteException { + CastDevice castDevice = CastDevice.getFromBundle(extras); + + SessionImpl session = (SessionImpl) ObjectWrapper.unwrap(this.castContext.defaultSessionProvider.getSession(null)); + Bundle routeInfoExtras = this.castContext.getRouter().getRouteInfoExtrasById(routeId); + if (routeInfoExtras != null) { + session.start(this.castContext, castDevice, routeId, routeInfoExtras); + } } @Override public void unknown(String routeId, Bundle extras) { diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionImpl.java index 5d0185c3..954405d8 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionImpl.java @@ -16,24 +16,136 @@ package com.google.android.gms.cast.framework.internal; +import android.os.Bundle; +import android.os.RemoteException; import android.util.Log; + +import com.google.android.gms.cast.ApplicationMetadata; +import com.google.android.gms.cast.CastDevice; import com.google.android.gms.cast.framework.ISession; +import com.google.android.gms.cast.framework.ISessionProxy; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; public class SessionImpl extends ISession.Stub { private static final String TAG = SessionImpl.class.getSimpleName(); + private String category; + private String sessionId; + private ISessionProxy proxy; + + private CastSessionImpl castSession; + + private CastContextImpl castContext; + private CastDevice castDevice; + private Bundle routeInfoExtra; + + private boolean mIsConnecting = false; + private boolean mIsConnected = false; + private String routeId = null; + + public SessionImpl(String category, String sessionId, ISessionProxy proxy) { + this.category = category; + this.sessionId = sessionId; + this.proxy = proxy; + } + + public void start(CastContextImpl castContext, CastDevice castDevice, String routeId, Bundle routeInfoExtra) throws RemoteException { + this.castContext = castContext; + this.castDevice = castDevice; + this.routeInfoExtra = routeInfoExtra; + this.routeId = routeId; + + this.mIsConnecting = true; + this.mIsConnected = false; + this.castContext.getSessionManagerImpl().onSessionStarting(this); + this.proxy.start(routeInfoExtra); + } + + public void onApplicationConnectionSuccess(ApplicationMetadata applicationMetadata, String applicationStatus, String sessionId, boolean wasLaunched) { + this.mIsConnecting = false; + this.mIsConnected = true; + this.castContext.getSessionManagerImpl().onSessionStarted(this, sessionId); + try { + this.castContext.getRouter().selectRouteById(this.getRouteId()); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling selectRouteById: " + ex.getMessage()); + } + } + + public void onApplicationConnectionFailure(int statusCode) { + this.mIsConnecting = false; + this.mIsConnected = false; + this.routeId = null; + this.castContext = null; + this.castDevice = null; + this.routeInfoExtra = null; + this.castContext.getSessionManagerImpl().onSessionStartFailed(this, statusCode); + try { + this.castContext.getRouter().selectDefaultRoute(); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling selectDefaultRoute: " + ex.getMessage()); + } + } + + public void onRouteSelected(Bundle extras) { + } + + public CastSessionImpl getCastSession() { + return this.castSession; + } + + public void setCastSession(CastSessionImpl castSession) { + this.castSession = castSession; + } + + public ISessionProxy getSessionProxy() { + return this.proxy; + } + + public IObjectWrapper getWrappedSession() throws RemoteException { + if (this.proxy == null) { + return ObjectWrapper.wrap(null); + } + return this.proxy.getWrappedSession(); + } + @Override - public void notifySessionEnded(int error) { - Log.d(TAG, "unimplemented Method: notifySessionEnded"); + public String getCategory() { + return this.category; + } + + @Override + public String getSessionId() { + return this.sessionId; + } + + @Override + public String getRouteId() { + return this.routeId; } @Override public boolean isConnected() { - Log.d(TAG, "unimplemented Method: isConnected"); - return true; + return this.mIsConnected; + } + + @Override + public boolean isConnecting() { + return this.mIsConnecting; + } + + @Override + public boolean isDisconnecting() { + Log.d(TAG, "unimplemented Method: isDisconnecting"); + return false; + } + + @Override + public boolean isDisconnected() { + Log.d(TAG, "unimplemented Method: isDisconnected"); + return false; } @Override @@ -43,7 +155,43 @@ public class SessionImpl extends ISession.Stub { } @Override - public IObjectWrapper getWrappedThis() { + public boolean isSuspended() { + Log.d(TAG, "unimplemented Method: isSuspended"); + return false; + } + + @Override + public void notifySessionStarted(String sessionId) { + Log.d(TAG, "unimplemented Method: notifySessionStarted"); + } + + @Override + public void notifyFailedToStartSession(int error) { + Log.d(TAG, "unimplemented Method: notifyFailedToStartSession"); + } + + @Override + public void notifySessionEnded(int error) { + Log.d(TAG, "unimplemented Method: notifySessionEnded"); + } + + @Override + public void notifySessionResumed(boolean wasSuspended) { + Log.d(TAG, "unimplemented Method: notifySessionResumed"); + } + + @Override + public void notifyFailedToResumeSession(int error) { + Log.d(TAG, "unimplemented Method: notifyFailedToResumeSession"); + } + + @Override + public void notifySessionSuspended(int reason) { + Log.d(TAG, "unimplemented Method: notifySessionSuspended"); + } + + @Override + public IObjectWrapper getWrappedObject() { return ObjectWrapper.wrap(this); } } diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionManagerImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionManagerImpl.java index 6b0bc005..d10f8b21 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionManagerImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionManagerImpl.java @@ -16,12 +16,16 @@ package com.google.android.gms.cast.framework.internal; +import android.os.Bundle; import android.os.RemoteException; import android.util.Log; +import com.google.android.gms.cast.framework.CastState; import com.google.android.gms.cast.framework.ICastStateListener; +import com.google.android.gms.cast.framework.ISession; import com.google.android.gms.cast.framework.ISessionManager; import com.google.android.gms.cast.framework.ISessionManagerListener; +import com.google.android.gms.cast.framework.internal.CastContextImpl; import com.google.android.gms.cast.framework.internal.SessionImpl; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; @@ -29,18 +33,33 @@ import com.google.android.gms.dynamic.ObjectWrapper; import java.util.Set; import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; + public class SessionManagerImpl extends ISessionManager.Stub { private static final String TAG = SessionManagerImpl.class.getSimpleName(); - private Set sessionManagerListeners = new HashSet(); - private Set castStateListeners = new HashSet(); + private CastContextImpl castContext; + + private Set sessionManagerListeners = new HashSet(); + private Set castStateListeners = new HashSet(); + + private Map routeSessions = new HashMap(); private SessionImpl currentSession; + private int castState = CastState.NO_DEVICES_AVAILABLE; + + public SessionManagerImpl(CastContextImpl castContext) { + this.castContext = castContext; + } + @Override public IObjectWrapper getWrappedCurrentSession() throws RemoteException { - Log.d(TAG, "unimplemented Method: getWrappedCurrentSession"); - return ObjectWrapper.wrap(this.currentSession); + if (this.currentSession == null) { + return ObjectWrapper.wrap(null); + } + return this.currentSession.getWrappedSession(); } @Override @@ -76,4 +95,136 @@ public class SessionManagerImpl extends ISessionManager.Stub { public IObjectWrapper getWrappedThis() throws RemoteException { return ObjectWrapper.wrap(this); } + + @Override + public int getCastState() { + return this.castState; + } + + @Override + public void startSession(Bundle params) { + Log.d(TAG, "unimplemented Method: startSession"); + String routeId = params.getString("CAST_INTENT_TO_CAST_ROUTE_ID_KEY"); + String sessionId = params.getString("CAST_INTENT_TO_CAST_SESSION_ID_KEY"); + } + + public void onRouteSelected(String routeId, Bundle extras) { + Log.d(TAG, "unimplemented Method: onRouteSelected: " + routeId); + } + + private void setCastState(int castState) { + this.castState = castState; + this.onCastStateChanged(); + } + + public void onCastStateChanged() { + for (ICastStateListener listener : this.castStateListeners) { + try { + listener.onCastStateChanged(this.castState); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onCastStateChanged: " + e.getMessage()); + } + } + } + + public void onSessionStarting(SessionImpl session) { + this.setCastState(CastState.CONNECTING); + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionStarting(session.getSessionProxy().getWrappedSession()); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionStarting: " + e.getMessage()); + } + } + } + + public void onSessionStartFailed(SessionImpl session, int error) { + this.currentSession = null; + this.setCastState(CastState.NOT_CONNECTED); + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionStartFailed(session.getSessionProxy().getWrappedSession(), error); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionStartFailed: " + e.getMessage()); + } + } + } + + public void onSessionStarted(SessionImpl session, String sessionId) { + this.currentSession = session; + this.setCastState(CastState.CONNECTED); + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionStarted(session.getSessionProxy().getWrappedSession(), sessionId); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionStarted: " + e.getMessage()); + } + } + } + + public void onSessionResumed(SessionImpl session, boolean wasSuspended) { + this.setCastState(CastState.CONNECTED); + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionResumed(session.getSessionProxy().getWrappedSession(), wasSuspended); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionResumed: " + e.getMessage()); + } + } + } + + public void onSessionEnding(SessionImpl session) { + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionEnding(session.getSessionProxy().getWrappedSession()); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionEnding: " + e.getMessage()); + } + } + } + + public void onSessionEnded(SessionImpl session, int error) { + this.currentSession = null; + this.setCastState(CastState.NOT_CONNECTED); + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionEnded(session.getSessionProxy().getWrappedSession(), error); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionEnded: " + e.getMessage()); + } + } + } + + public void onSessionResuming(SessionImpl session, String sessionId) { + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionResuming(session.getSessionProxy().getWrappedSession(), sessionId); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionResuming: " + e.getMessage()); + } + } + } + + public void onSessionResumeFailed(SessionImpl session, int error) { + this.currentSession = null; + this.setCastState(CastState.NOT_CONNECTED); + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionResumeFailed(session.getSessionProxy().getWrappedSession(), error); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionResumeFailed: " + e.getMessage()); + } + } + } + + public void onSessionSuspended(SessionImpl session, int reason) { + this.setCastState(CastState.NOT_CONNECTED); + for (ISessionManagerListener listener : this.sessionManagerListeners) { + try { + listener.onSessionSuspended(session.getSessionProxy().getWrappedSession(), reason); + } catch (RemoteException e) { + Log.d(TAG, "Remote exception calling onSessionSuspended: " + e.getMessage()); + } + } + } } diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java new file mode 100644 index 00000000..f163b868 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * 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 org.microg.gms.cast; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; + +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.Base64; +import android.util.Log; + +import com.google.android.gms.cast.ApplicationMetadata; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.JoinOptions; +import com.google.android.gms.cast.LaunchOptions; +import com.google.android.gms.cast.internal.ICastDeviceController; +import com.google.android.gms.cast.internal.ICastDeviceControllerListener; +import com.google.android.gms.common.api.CommonStatusCodes; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.images.WebImage; +import com.google.android.gms.common.internal.BinderWrapper; +import com.google.android.gms.common.internal.GetServiceRequest; + +import su.litvak.chromecast.api.v2.Application; +import su.litvak.chromecast.api.v2.ChromeCast; +import su.litvak.chromecast.api.v2.Namespace; + +public class CastDeviceControllerImpl extends ICastDeviceController.Stub { + private static final String TAG = "GmsCastDeviceControllerImpl"; + + private Context context; + private String packageName; + private CastDevice castDevice; + boolean notificationEnabled; + long castFlags; + ICastDeviceControllerListener listener; + + ChromeCast chromecast; + + String sessionId = null; + + public CastDeviceControllerImpl(Context context, String packageName, Bundle extras) { + this.context = context; + this.packageName = packageName; + + extras.setClassLoader(BinderWrapper.class.getClassLoader()); + this.castDevice = CastDevice.getFromBundle(extras); + this.notificationEnabled = extras.getBoolean("com.google.android.gms.cast.EXTRA_CAST_FRAMEWORK_NOTIFICATION_ENABLED"); + this.castFlags = extras.getLong("com.google.android.gms.cast.EXTRA_CAST_FLAGS"); + BinderWrapper listenerWrapper = (BinderWrapper)extras.get("listener"); + this.listener = ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder); + + this.chromecast = new ChromeCast(this.castDevice.getAddress()); + } + + @Override + public void disconnect() { + Log.d(TAG, "unimplemented Method: disconnect"); + this.sessionId = null; + } + + @Override + public void sendMessage(String namespace, String message, long requestId) { + String response = null; + try { + response = this.chromecast.sendRawRequest(namespace, message, requestId); + } catch (IOException e) { + Log.w(TAG, "Error sending cast message: " + e.getMessage()); + return; + } + try { + this.listener.onSendMessageSuccess(response, requestId); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onSendMessageSuccess: " + ex.getMessage()); + } + } + + @Override + public void stopApplication(String sessionId) { + Log.d(TAG, "unimplemented Method: stopApplication"); + this.sessionId = null; + } + + @Override + public void registerNamespace(String namespace) { + Log.d(TAG, "unimplemented Method: registerNamespace"); + } + + @Override + public void unregisterNamespace(String namespace) { + Log.d(TAG, "unimplemented Method: unregisterNamespace"); + } + + @Override + public void launchApplication(String applicationId, LaunchOptions launchOptions) { + Application app = null; + try { + app = this.chromecast.launchApp(applicationId); + } catch (IOException e) { + Log.w(TAG, "Error launching cast application: " + e.getMessage()); + try { + this.listener.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onApplicationConnectionFailure: " + ex.getMessage()); + } + return; + } + this.sessionId = app.sessionId; + + ApplicationMetadata metadata = new ApplicationMetadata(); + metadata.applicationId = applicationId; + metadata.name = app.name; + Log.d(TAG, "unimplemented: ApplicationMetadata.images"); + metadata.images = new ArrayList(); + metadata.namespaces = new ArrayList(); + Log.d(TAG, "unimplemented: ApplicationMetadata.senderAppLaunchUri"); + for(Namespace namespace : app.namespaces) { + metadata.namespaces.add(namespace.name); + } + metadata.senderAppIdentifier = this.context.getPackageName(); + try { + this.listener.onApplicationConnectionSuccess(metadata, app.statusText, app.sessionId, true); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onApplicationConnectionSuccess: " + e.getMessage()); + } + } + + @Override + public void joinApplication(String applicationId, String sessionId, JoinOptions joinOptions) { + Log.d(TAG, "unimplemented Method: joinApplication"); + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerService.java b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerService.java new file mode 100644 index 00000000..d494a012 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * 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 org.microg.gms.cast; + +import android.os.IBinder; +import android.os.RemoteException; +import android.os.Parcel; +import android.util.ArrayMap; +import android.util.Log; + +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.internal.ICastDeviceControllerListener; +import com.google.android.gms.common.internal.GetServiceRequest; +import com.google.android.gms.common.internal.BinderWrapper; +import com.google.android.gms.common.internal.IGmsCallbacks; + +import org.microg.gms.BaseService; +import org.microg.gms.common.GmsService; + +import su.litvak.chromecast.api.v2.ChromeCast; +import su.litvak.chromecast.api.v2.ChromeCasts; +import su.litvak.chromecast.api.v2.Status; +import su.litvak.chromecast.api.v2.ChromeCastsListener; + +public class CastDeviceControllerService extends BaseService { + private static final String TAG = CastDeviceControllerService.class.getSimpleName(); + + public CastDeviceControllerService() { + super("GmsCastDeviceControllerSvc", GmsService.CAST); + } + + @Override + public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException { + callback.onPostInitComplete(0, new CastDeviceControllerImpl(this, request.packageName, request.extras), null); + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java new file mode 100644 index 00000000..de79d9b4 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * 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 org.microg.gms.cast; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Bundle; +import android.os.AsyncTask; +import android.os.Handler; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouteDescriptor; +import android.support.v7.media.MediaRouteDiscoveryRequest; +import android.support.v7.media.MediaRouteProvider; +import android.support.v7.media.MediaRouteProviderDescriptor; +import android.support.v7.media.MediaRouter; +import android.util.Log; + +import com.google.android.gms.common.images.WebImage; +import com.google.android.gms.cast.CastDevice; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.io.IOException; +import java.lang.Thread; +import java.lang.Runnable; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; + +import su.litvak.chromecast.api.v2.ChromeCast; +import su.litvak.chromecast.api.v2.ChromeCasts; +import su.litvak.chromecast.api.v2.Status; +import su.litvak.chromecast.api.v2.ChromeCastsListener; + +public class CastMediaRouteController extends MediaRouteProvider.RouteController { + private static final String TAG = CastMediaRouteController.class.getSimpleName(); + + private CastMediaRouteProvider provider; + private String routeId; + private ChromeCast chromecast; + + public CastMediaRouteController(CastMediaRouteProvider provider, String routeId, ChromeCast chromecast) { + super(); + + this.provider = provider; + this.routeId = routeId; + this.chromecast = chromecast; + } + + public boolean onControlRequest(Intent intent, MediaRouter.ControlRequestCallback callback) { + Log.d(TAG, "unimplemented Method: onControlRequest: " + this.routeId); + return false; + } + + public void onRelease() { + Log.d(TAG, "unimplemented Method: onRelease: " + this.routeId); + } + + public void onSelect() { + Log.d(TAG, "unimplemented Method: onSelect: " + this.routeId); + } + + public void onSetVolume(int volume) { + Log.d(TAG, "unimplemented Method: onSetVolume: " + this.routeId); + } + + public void onUnselect() { + Log.d(TAG, "unimplemented Method: onUnselect: " + this.routeId); + } + + public void onUnselect(int reason) { + Log.d(TAG, "unimplemented Method: onUnselect: " + this.routeId); + } + + public void onUpdateVolume(int delta) { + Log.d(TAG, "unimplemented Method: onUpdateVolume: " + this.routeId); + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java index efd99955..e7b104eb 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java @@ -20,6 +20,8 @@ import android.content.Context; import android.content.IntentFilter; import android.net.Uri; import android.os.Bundle; +import android.os.AsyncTask; +import android.os.Handler; import android.support.v7.media.MediaControlIntent; import android.support.v7.media.MediaRouteDescriptor; import android.support.v7.media.MediaRouteDiscoveryRequest; @@ -31,19 +33,68 @@ import android.util.Log; import com.google.android.gms.common.images.WebImage; import com.google.android.gms.cast.CastDevice; -import java.util.ArrayList; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.io.IOException; +import java.lang.Thread; +import java.lang.Runnable; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; + +import su.litvak.chromecast.api.v2.ChromeCast; +import su.litvak.chromecast.api.v2.ChromeCasts; +import su.litvak.chromecast.api.v2.Status; +import su.litvak.chromecast.api.v2.ChromeCastsListener; public class CastMediaRouteProvider extends MediaRouteProvider { private static final String TAG = CastMediaRouteProvider.class.getSimpleName(); + private Map chromecasts = new HashMap(); + public CastMediaRouteProvider(Context context) { super(context); - Log.d(TAG, "unimplemented Method: CastMediaRouteProvider"); - // Uncomment this to create the mock device - // publishRoutes(); + // TODO: Uncomment this to manually discover a chromecast on the local + // network. Discovery not yet implemented. + /* + InetAddress addr = null; + try { + addr = InetAddress.getByName("192.168.1.11"); + } catch (UnknownHostException e) { + Log.d(TAG, "Chromecast status exception getting host: " + e.getMessage()); + return; + } + onChromeCastDiscovered(addr); + */ + } + + private void onChromeCastDiscovered(InetAddress address) { + ChromeCast chromecast = new ChromeCast(address.getHostAddress()); + + new Thread(new Runnable() { + public void run() { + Status status = null; + try { + status = chromecast.getStatus(); + } catch (IOException e) { + Log.e(TAG, "Exception getting chromecast status: " + e.getMessage()); + return; + } + + Handler mainHandler = new Handler(CastMediaRouteProvider.this.getContext().getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + String routeId = address.getHostAddress(); + CastMediaRouteProvider.this.chromecasts.put(routeId, chromecast); + publishRoutes(); + } + }); + } + }).start(); } /** @@ -67,37 +118,37 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public RouteController onCreateRouteController(String routeId) { - Log.d(TAG, "unimplemented Method: onCreateRouteController"); - return null; + ChromeCast chromecast = this.chromecasts.get(routeId); + return new CastMediaRouteController(this, routeId, chromecast); } - /** - * TODO: Currently this method simply publishes a single cast route for - * testing. - */ private void publishRoutes() { - Log.d(TAG, "unimplemented Method: publishRoutes"); - Bundle extras = new Bundle(); - CastDevice castDevice = new CastDevice("abc123"); - castDevice.putInBundle(extras); - MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder( - "abc123", - "Rotue Friendly Name") - .setDescription("Chromecast") - .addControlFilters(CONTROL_FILTERS) - .setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV) - .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) - .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) - .setVolumeMax(20) - .setVolume(0) - .setEnabled(true) - .setExtras(extras) - .setConnectionState(MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED) - .build(); - MediaRouteProviderDescriptor providerDescriptor = - new MediaRouteProviderDescriptor.Builder() - .addRoute(routeDescriptor1) - .build(); - this.setDescriptor(providerDescriptor); + MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder(); + for(Map.Entry entry : this.chromecasts.entrySet()) { + String routeId = entry.getKey(); + ChromeCast chromecast = entry.getValue(); + Bundle extras = new Bundle(); + CastDevice castDevice = new CastDevice( + routeId, + chromecast.getAddress() + ); + castDevice.putInBundle(extras); + builder.addRoute(new MediaRouteDescriptor.Builder( + routeId, + routeId) + .setDescription("Chromecast") + .addControlFilters(CONTROL_FILTERS) + .setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) + .setVolumeMax(20) + .setVolume(0) + .setEnabled(true) + .setExtras(extras) + .setConnectionState(MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED) + .build()); + } + + this.setDescriptor(builder.build()); } } diff --git a/proguard.flags b/proguard.flags index 198869bb..6be46b9c 100644 --- a/proguard.flags +++ b/proguard.flags @@ -14,6 +14,7 @@ -dontwarn org.oscim.tiling.source.OkHttpEngine$OkHttpFactory -dontwarn com.caverock.androidsvg.** -dontwarn org.slf4j.** +-dontwarn org.codehaus.jackson.** # Disable ProGuard Notes, they won't help here -dontnote @@ -46,3 +47,8 @@ -keep public class com.squareup.wire.Message -keep public class * extends com.squareup.wire.Message -keep public class * extends com.squareup.wire.Message$Builder { public (...); } + +# Proguard configuration for Jackson 1.x +-keepclassmembers class * { + @org.codehaus.jackson.annotate.* *; +} diff --git a/settings.gradle b/settings.gradle index ff311f8f..eae21e8a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -include ':wearable-lib' +include ':wearable-lib', ':safe-parcel' include ':unifiednlp-api' include ':unifiednlp-base' From 2368bed54b438e6c2b0377148cf419e1ff6285e0 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sat, 23 Jun 2018 14:12:09 -0400 Subject: [PATCH 04/16] Support basic media control: play/pause/seek/etc. --- .../gms/cast/CastDeviceControllerImpl.java | 55 ++++++++++++++++++- .../gms/cast/CastMediaRouteController.java | 11 +++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java index f163b868..f00b11f6 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java @@ -43,8 +43,14 @@ import com.google.android.gms.common.internal.GetServiceRequest; import su.litvak.chromecast.api.v2.Application; import su.litvak.chromecast.api.v2.ChromeCast; import su.litvak.chromecast.api.v2.Namespace; +import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEventListener; +import su.litvak.chromecast.api.v2.ChromeCastRawMessageListener; +import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent; +import su.litvak.chromecast.api.v2.ChromeCastRawMessage; -public class CastDeviceControllerImpl extends ICastDeviceController.Stub { +public class CastDeviceControllerImpl extends ICastDeviceController.Stub + implements ChromeCastSpontaneousEventListener, ChromeCastRawMessageListener +{ private static final String TAG = "GmsCastDeviceControllerImpl"; private Context context; @@ -70,6 +76,45 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub { this.listener = ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder); this.chromecast = new ChromeCast(this.castDevice.getAddress()); + this.chromecast.registerListener(this); + this.chromecast.registerRawMessageListener(this); + } + + @Override + public void spontaneousEventReceived(ChromeCastSpontaneousEvent event) { + switch (event.getType()) { + case MEDIA_STATUS: + Log.d(TAG, "unimplemented Method: spontaneousEventReceived: MEDIA_STATUS"); + break; + case STATUS: + Log.d(TAG, "unimplemented Method: spontaneousEventReceived: STATUS"); + break; + case APPEVENT: + Log.d(TAG, "unimplemented Method: spontaneousEventReceived: APPEVENT"); + break; + case CLOSE: + Log.d(TAG, "unimplemented Method: spontaneousEventReceived: CLOSE"); + break; + default: + Log.d(TAG, "unimplemented Method: spontaneousEventReceived: UNKNOWN"); + break; + } + } + + @Override + public void rawMessageReceived(ChromeCastRawMessage message) { + switch (message.getPayloadType()) { + case STRING: + try { + this.listener.onTextMessageReceived(message.getNamespace(), message.getPayloadUtf8()); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onTextMessageReceived: " + ex.getMessage()); + } + break; + case BINARY: + Log.d(TAG, "unimplemented Method: rawMessageReceived: BINARY"); + break; + } } @Override @@ -97,6 +142,14 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub { @Override public void stopApplication(String sessionId) { Log.d(TAG, "unimplemented Method: stopApplication"); + try { + // TODO: Expose more control of chromecast-java-api-v2 so we can + // make use of our sessionID parameter. + this.chromecast.stopApp(); + } catch (IOException e) { + Log.w(TAG, "Error sending cast message: " + e.getMessage()); + return; + } this.sessionId = null; } diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java index de79d9b4..2f489ecc 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java @@ -71,7 +71,16 @@ public class CastMediaRouteController extends MediaRouteProvider.RouteController } public void onRelease() { - Log.d(TAG, "unimplemented Method: onRelease: " + this.routeId); + new Thread(new Runnable() { + public void run() { + try { + CastMediaRouteController.this.chromecast.stopApp(); + } catch (IOException e) { + Log.w(TAG, "Error stopping cast application: " + e.getMessage()); + return; + } + } + }).start(); } public void onSelect() { From 477f7b2a150a40bab01d576bb971cf43a42543f2 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Thu, 5 Jul 2018 21:34:10 -0400 Subject: [PATCH 05/16] Support for DNS-SD cast discovery --- extern/GmsApi | 2 +- .../gms/cast/CastMediaRouteController.java | 4 +- .../gms/cast/CastMediaRouteProvider.java | 181 +++++++++++------- 3 files changed, 115 insertions(+), 72 deletions(-) diff --git a/extern/GmsApi b/extern/GmsApi index 5af21c0f..540d2922 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit 5af21c0ff2777e61caba5d111991cbb165167793 +Subproject commit 540d2922599af4d9c3ef0174b45ebbc875aafd65 diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java index 2f489ecc..949e0102 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java @@ -57,12 +57,12 @@ public class CastMediaRouteController extends MediaRouteProvider.RouteController private String routeId; private ChromeCast chromecast; - public CastMediaRouteController(CastMediaRouteProvider provider, String routeId, ChromeCast chromecast) { + public CastMediaRouteController(CastMediaRouteProvider provider, String routeId, String address) { super(); this.provider = provider; this.routeId = routeId; - this.chromecast = chromecast; + this.chromecast = new ChromeCast(address); } public boolean onControlRequest(Intent intent, MediaRouter.ControlRequestCallback callback) { diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java index e7b104eb..e74c97ce 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java @@ -19,6 +19,8 @@ package org.microg.gms.cast; import android.content.Context; import android.content.IntentFilter; import android.net.Uri; +import android.net.nsd.NsdManager; +import android.net.nsd.NsdServiceInfo; import android.os.Bundle; import android.os.AsyncTask; import android.os.Handler; @@ -32,70 +34,27 @@ import android.util.Log; import com.google.android.gms.common.images.WebImage; import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastMediaControlIntent; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Inet4Address; import java.net.UnknownHostException; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.lang.Thread; import java.lang.Runnable; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; -import su.litvak.chromecast.api.v2.ChromeCast; -import su.litvak.chromecast.api.v2.ChromeCasts; -import su.litvak.chromecast.api.v2.Status; -import su.litvak.chromecast.api.v2.ChromeCastsListener; - public class CastMediaRouteProvider extends MediaRouteProvider { private static final String TAG = CastMediaRouteProvider.class.getSimpleName(); - private Map chromecasts = new HashMap(); + private Map routes = new HashMap(); - public CastMediaRouteProvider(Context context) { - super(context); - - // TODO: Uncomment this to manually discover a chromecast on the local - // network. Discovery not yet implemented. - /* - InetAddress addr = null; - try { - addr = InetAddress.getByName("192.168.1.11"); - } catch (UnknownHostException e) { - Log.d(TAG, "Chromecast status exception getting host: " + e.getMessage()); - return; - } - onChromeCastDiscovered(addr); - */ - } - - private void onChromeCastDiscovered(InetAddress address) { - ChromeCast chromecast = new ChromeCast(address.getHostAddress()); - - new Thread(new Runnable() { - public void run() { - Status status = null; - try { - status = chromecast.getStatus(); - } catch (IOException e) { - Log.e(TAG, "Exception getting chromecast status: " + e.getMessage()); - return; - } - - Handler mainHandler = new Handler(CastMediaRouteProvider.this.getContext().getMainLooper()); - mainHandler.post(new Runnable() { - @Override - public void run() { - String routeId = address.getHostAddress(); - CastMediaRouteProvider.this.chromecasts.put(routeId, chromecast); - publishRoutes(); - } - }); - } - }).start(); - } + private NsdManager mNsdManager; + private NsdManager.DiscoveryListener mDiscoveryListener; /** * TODO: Mock control filters for chromecast; Will likely need to be @@ -105,38 +64,92 @@ public class CastMediaRouteProvider extends MediaRouteProvider { static { IntentFilter f2 = new IntentFilter(); f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + f2.addCategory(CastMediaControlIntent.CATEGORY_CAST); f2.addAction(MediaControlIntent.ACTION_PLAY); CONTROL_FILTERS = new ArrayList(); CONTROL_FILTERS.add(f2); } - @Override - public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { - Log.d(TAG, "unimplemented Method: onDiscoveryRequestChanged"); + public CastMediaRouteProvider(Context context) { + super(context); + + mNsdManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE); + + mDiscoveryListener = new NsdManager.DiscoveryListener() { + + @Override + public void onDiscoveryStarted(String regType) { + Log.d(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStarted"); + } + + @Override + public void onServiceFound(NsdServiceInfo service) { + mNsdManager.resolveService(service, new NsdManager.ResolveListener() { + @Override + public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { + Log.e(TAG, "DiscoveryListener unimplemented Method: Resolve failed" + errorCode); + } + + @Override + public void onServiceResolved(NsdServiceInfo serviceInfo) { + String name = serviceInfo.getServiceName(); + InetAddress host = serviceInfo.getHost(); + int port = serviceInfo.getPort(); + try { + String id = new String(serviceInfo.getAttributes().get("id"), "UTF-8"); + String deviceVersion = new String(serviceInfo.getAttributes().get("ve"), "UTF-8"); + String friendlyName = new String(serviceInfo.getAttributes().get("fn"), "UTF-8"); + String modelName = new String(serviceInfo.getAttributes().get("md"), "UTF-8"); + String iconPath = new String(serviceInfo.getAttributes().get("ic"), "UTF-8"); + int status = Integer.parseInt(new String(serviceInfo.getAttributes().get("st"), "UTF-8")); + + onChromeCastDiscovered(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status); + } catch (UnsupportedEncodingException ex) { + Log.e(TAG, "Error getting cast details from DNS-SD response", ex); + return; + } + } + }); + } + + @Override + public void onServiceLost(NsdServiceInfo service) { + Log.d(TAG, "DiscoveryListener unimplemented Method: onServiceLost" + service); + } + + @Override + public void onDiscoveryStopped(String serviceType) { + Log.i(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStopped " + serviceType); + } + + @Override + public void onStartDiscoveryFailed(String serviceType, int errorCode) { + Log.e(TAG, "DiscoveryListener unimplemented Method: onStartDiscoveryFailed: Error code:" + errorCode); + } + + @Override + public void onStopDiscoveryFailed(String serviceType, int errorCode) { + Log.e(TAG, "DiscoveryListener unimplemented Method: onStopDiscoveryFailed: Error code:" + errorCode); + } + }; } - @Override - public RouteController onCreateRouteController(String routeId) { - ChromeCast chromecast = this.chromecasts.get(routeId); - return new CastMediaRouteController(this, routeId, chromecast); - } + private void onChromeCastDiscovered( + String id, String name, InetAddress host, int port, String + deviceVersion, String friendlyName, String modelName, String + iconPath, int status) { + if (!this.routes.containsKey(id)) { + // TODO: Capabilities + int capabilities = CastDevice.CAPABILITY_VIDEO_OUT | CastDevice.CAPABILITY_AUDIO_OUT; - private void publishRoutes() { - MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder(); - for(Map.Entry entry : this.chromecasts.entrySet()) { - String routeId = entry.getKey(); - ChromeCast chromecast = entry.getValue(); Bundle extras = new Bundle(); - CastDevice castDevice = new CastDevice( - routeId, - chromecast.getAddress() - ); + CastDevice castDevice = new CastDevice(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status, capabilities); castDevice.putInBundle(extras); - builder.addRoute(new MediaRouteDescriptor.Builder( - routeId, - routeId) - .setDescription("Chromecast") + this.routes.put(id, new MediaRouteDescriptor.Builder( + id, + friendlyName) + .setDescription(modelName) .addControlFilters(CONTROL_FILTERS) .setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV) .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) @@ -149,6 +162,36 @@ public class CastMediaRouteProvider extends MediaRouteProvider { .build()); } + Handler mainHandler = new Handler(this.getContext().getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + publishRoutes(); + } + }); + } + + @Override + public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { + if (request.isValid() && request.isActiveScan()) { + mNsdManager.discoverServices("_googlecast._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener); + } else { + mNsdManager.stopServiceDiscovery(mDiscoveryListener); + } + } + + @Override + public RouteController onCreateRouteController(String routeId) { + MediaRouteDescriptor descriptor = this.routes.get(routeId); + CastDevice castDevice = CastDevice.getFromBundle(descriptor.getExtras()); + return new CastMediaRouteController(this, routeId, castDevice.getAddress()); + } + + private void publishRoutes() { + MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder(); + for(MediaRouteDescriptor route : this.routes.values()) { + builder.addRoute(route); + } this.setDescriptor(builder.build()); } } From 7e711be919eaad796e70ca33875783d9ae330e5b Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 6 Jul 2018 16:44:30 -0400 Subject: [PATCH 06/16] Add most real cast media route control filters --- extern/GmsApi | 2 +- .../gms/cast/CastMediaRouteProvider.java | 143 ++++++++++++++++-- 2 files changed, 130 insertions(+), 15 deletions(-) diff --git a/extern/GmsApi b/extern/GmsApi index 540d2922..6dc88b73 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit 540d2922599af4d9c3ef0174b45ebbc875aafd65 +Subproject commit 6dc88b73574af3b55ab1df01858379a8742ca4cb diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java index e74c97ce..61192f29 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java @@ -56,19 +56,123 @@ public class CastMediaRouteProvider extends MediaRouteProvider { private NsdManager mNsdManager; private NsdManager.DiscoveryListener mDiscoveryListener; - /** - * TODO: Mock control filters for chromecast; Will likely need to be - * adjusted. - */ - private static final ArrayList CONTROL_FILTERS; - static { - IntentFilter f2 = new IntentFilter(); - f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - f2.addCategory(CastMediaControlIntent.CATEGORY_CAST); - f2.addAction(MediaControlIntent.ACTION_PLAY); + private enum State { + NOT_DISCOVERING, + DISCOVERY_REQUESTED, + DISCOVERING, + DISCOVERY_STOP_REQUESTED, + } + private State state = State.NOT_DISCOVERING; - CONTROL_FILTERS = new ArrayList(); - CONTROL_FILTERS.add(f2); + private static final ArrayList CONTROL_FILTERS = new ArrayList(); + static { + IntentFilter filter; + + filter = new IntentFilter(); + filter.addCategory(CastMediaControlIntent.CATEGORY_CAST); + CONTROL_FILTERS.add(filter); + + // TODO: Need to get the application ID here: + /* + filter = new IntentFilter(); + filter.addCategory(CastMediaControlIntent.categoryForCast(applicationId)); + CONTROL_FILTERS.add(filter); + */ + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_PLAY); + filter.addDataScheme("http"); + filter.addDataScheme("https"); + String[] types = { + "image/jpeg", + "image/pjpeg", + "image/jpg", + "image/webp", + "image/png", + "image/gif", + "image/bmp", + "image/vnd.microsoft.icon", + "image/x-icon", + "image/x-xbitmap", + "audio/wav", + "audio/x-wav", + "audio/mp3", + "audio/x-mp3", + "audio/x-m4a", + "audio/mpeg", + "audio/webm", + "audio/ogg", + "audio/x-matroska", + "video/mp4", + "video/x-m4v", + "video/mp2t", + "video/webm", + "video/ogg", + "video/x-matroska", + "application/x-mpegurl", + "application/vnd.apple.mpegurl", + "application/dash+xml", + "application/vnd.ms-sstr+xml", + }; + for (String type : types) { + try { + filter.addDataType(type); + } catch (IntentFilter.MalformedMimeTypeException ex) { + Log.e(TAG, "Error adding filter type " + type); + } + } + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_PAUSE); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_RESUME); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_STOP); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_SEEK); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_GET_STATUS); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_START_SESSION); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + filter.addAction(MediaControlIntent.ACTION_END_SESSION); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(CastMediaControlIntent.CATEGORY_CAST_REMOTE_PLAYBACK); + filter.addAction(CastMediaControlIntent.ACTION_SYNC_STATUS); + CONTROL_FILTERS.add(filter); + + filter = new IntentFilter(); + filter.addCategory(CastMediaControlIntent.CATEGORY_CAST_REMOTE_PLAYBACK); + filter.addAction(CastMediaControlIntent.ACTION_SYNC_STATUS); + CONTROL_FILTERS.add(filter); } public CastMediaRouteProvider(Context context) { @@ -81,6 +185,7 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public void onDiscoveryStarted(String regType) { Log.d(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStarted"); + CastMediaRouteProvider.this.state = State.DISCOVERING; } @Override @@ -116,21 +221,25 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public void onServiceLost(NsdServiceInfo service) { Log.d(TAG, "DiscoveryListener unimplemented Method: onServiceLost" + service); + // TODO: Remove chromecast route. } @Override public void onDiscoveryStopped(String serviceType) { Log.i(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStopped " + serviceType); + CastMediaRouteProvider.this.state = State.NOT_DISCOVERING; } @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { Log.e(TAG, "DiscoveryListener unimplemented Method: onStartDiscoveryFailed: Error code:" + errorCode); + CastMediaRouteProvider.this.state = State.NOT_DISCOVERING; } @Override public void onStopDiscoveryFailed(String serviceType, int errorCode) { Log.e(TAG, "DiscoveryListener unimplemented Method: onStopDiscoveryFailed: Error code:" + errorCode); + CastMediaRouteProvider.this.state = State.DISCOVERING; } }; } @@ -174,9 +283,15 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { if (request.isValid() && request.isActiveScan()) { - mNsdManager.discoverServices("_googlecast._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener); + if (this.state == State.NOT_DISCOVERING) { + mNsdManager.discoverServices("_googlecast._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener); + this.state = State.DISCOVERY_REQUESTED; + } } else { - mNsdManager.stopServiceDiscovery(mDiscoveryListener); + if (this.state == State.DISCOVERING) { + mNsdManager.stopServiceDiscovery(mDiscoveryListener); + this.state = State.DISCOVERY_STOP_REQUESTED; + } } } From d70b47c6c36dfb483d86b9f5772f399e82b397ca Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 6 Jul 2018 20:06:35 -0400 Subject: [PATCH 07/16] Store deserialized session providers --- .../gms/cast/framework/internal/CastContextImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java index e0307a2e..7857d934 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java @@ -36,6 +36,7 @@ import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; import java.util.Map; +import java.util.HashMap; public class CastContextImpl extends ICastContext.Stub { private static final String TAG = CastContextImpl.class.getSimpleName(); @@ -46,7 +47,7 @@ public class CastContextImpl extends ICastContext.Stub { private Context context; private CastOptions options; private IMediaRouter router; - private Map sessionProviders; + private Map sessionProviders = new HashMap(); public ISessionProvider defaultSessionProvider; private MediaRouteSelector mergedSelector; @@ -55,17 +56,20 @@ public class CastContextImpl extends ICastContext.Stub { this.context = (Context) ObjectWrapper.unwrap(context); this.options = options; this.router = router; - this.sessionProviders = sessionProviders; + for (Map.Entry entry : sessionProviders.entrySet()) { + this.sessionProviders.put(entry.getKey(), ISessionProvider.Stub.asInterface(entry.getValue())); + } String receiverApplicationId = options.getReceiverApplicationId(); String defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); - this.defaultSessionProvider = ISessionProvider.Stub.asInterface(this.sessionProviders.get(defaultCategory)); + this.defaultSessionProvider = this.sessionProviders.get(defaultCategory); // TODO: This should incorporate passed options this.mergedSelector = new MediaRouteSelector.Builder() .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) + .addControlCategory(defaultCategory) .build(); // TODO: Find a home for this once the rest of the implementation From c3e611ec9fa43293a54ea5def88b63274a0e5515 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 6 Jul 2018 20:09:43 -0400 Subject: [PATCH 08/16] Include cast app control categories with routeinfo --- extern/GmsApi | 2 +- .../gms/cast/CastMediaRouteProvider.java | 96 +++++++++++-------- 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/extern/GmsApi b/extern/GmsApi index 6dc88b73..3bc73c30 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit 6dc88b73574af3b55ab1df01858379a8742ca4cb +Subproject commit 3bc73c3020f7555a8fc8c03b3e9793de29641dc1 diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java index 61192f29..7b9f0b39 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java @@ -44,6 +44,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.Thread; import java.lang.Runnable; +import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; @@ -51,11 +52,13 @@ import java.util.HashMap; public class CastMediaRouteProvider extends MediaRouteProvider { private static final String TAG = CastMediaRouteProvider.class.getSimpleName(); - private Map routes = new HashMap(); + private Map castDevices = new HashMap(); private NsdManager mNsdManager; private NsdManager.DiscoveryListener mDiscoveryListener; + private List customCategories = new ArrayList(); + private enum State { NOT_DISCOVERING, DISCOVERY_REQUESTED, @@ -64,20 +67,13 @@ public class CastMediaRouteProvider extends MediaRouteProvider { } private State state = State.NOT_DISCOVERING; - private static final ArrayList CONTROL_FILTERS = new ArrayList(); + private static final ArrayList BASE_CONTROL_FILTERS = new ArrayList(); static { IntentFilter filter; filter = new IntentFilter(); filter.addCategory(CastMediaControlIntent.CATEGORY_CAST); - CONTROL_FILTERS.add(filter); - - // TODO: Need to get the application ID here: - /* - filter = new IntentFilter(); - filter.addCategory(CastMediaControlIntent.categoryForCast(applicationId)); - CONTROL_FILTERS.add(filter); - */ + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); @@ -122,57 +118,57 @@ public class CastMediaRouteProvider extends MediaRouteProvider { Log.e(TAG, "Error adding filter type " + type); } } - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_PAUSE); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_RESUME); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_STOP); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_SEEK); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_GET_STATUS); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_START_SESSION); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); filter.addAction(MediaControlIntent.ACTION_END_SESSION); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(CastMediaControlIntent.CATEGORY_CAST_REMOTE_PLAYBACK); filter.addAction(CastMediaControlIntent.ACTION_SYNC_STATUS); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); filter = new IntentFilter(); filter.addCategory(CastMediaControlIntent.CATEGORY_CAST_REMOTE_PLAYBACK); filter.addAction(CastMediaControlIntent.ACTION_SYNC_STATUS); - CONTROL_FILTERS.add(filter); + BASE_CONTROL_FILTERS.add(filter); } public CastMediaRouteProvider(Context context) { @@ -248,27 +244,12 @@ public class CastMediaRouteProvider extends MediaRouteProvider { String id, String name, InetAddress host, int port, String deviceVersion, String friendlyName, String modelName, String iconPath, int status) { - if (!this.routes.containsKey(id)) { + if (!this.castDevices.containsKey(id)) { // TODO: Capabilities int capabilities = CastDevice.CAPABILITY_VIDEO_OUT | CastDevice.CAPABILITY_AUDIO_OUT; - Bundle extras = new Bundle(); CastDevice castDevice = new CastDevice(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status, capabilities); - castDevice.putInBundle(extras); - this.routes.put(id, new MediaRouteDescriptor.Builder( - id, - friendlyName) - .setDescription(modelName) - .addControlFilters(CONTROL_FILTERS) - .setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV) - .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) - .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) - .setVolumeMax(20) - .setVolume(0) - .setEnabled(true) - .setExtras(extras) - .setConnectionState(MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED) - .build()); + this.castDevices.put(id, castDevice); } Handler mainHandler = new Handler(this.getContext().getMainLooper()); @@ -282,7 +263,14 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { - if (request.isValid() && request.isActiveScan()) { + if (request != null && request.isValid() && request.isActiveScan()) { + if (request.getSelector() != null) { + for (String category : request.getSelector().getControlCategories()) { + if (CastMediaControlIntent.isCategoryForCast(category)) { + this.customCategories.add(category); + } + } + } if (this.state == State.NOT_DISCOVERING) { mNsdManager.discoverServices("_googlecast._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener); this.state = State.DISCOVERY_REQUESTED; @@ -297,14 +285,38 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public RouteController onCreateRouteController(String routeId) { - MediaRouteDescriptor descriptor = this.routes.get(routeId); - CastDevice castDevice = CastDevice.getFromBundle(descriptor.getExtras()); + CastDevice castDevice = this.castDevices.get(routeId); return new CastMediaRouteController(this, routeId, castDevice.getAddress()); } private void publishRoutes() { MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder(); - for(MediaRouteDescriptor route : this.routes.values()) { + for (CastDevice castDevice : this.castDevices.values()) { + ArrayList controlFilters = new ArrayList(BASE_CONTROL_FILTERS); + // Include any app-specific control filters that have been requested. + // TODO: Do we need to check with the device? + for (String category : this.customCategories) { + IntentFilter filter = new IntentFilter(); + filter.addCategory(category); + controlFilters.add(filter); + } + + Bundle extras = new Bundle(); + castDevice.putInBundle(extras); + MediaRouteDescriptor route = new MediaRouteDescriptor.Builder( + castDevice.getDeviceId(), + castDevice.getFriendlyName()) + .setDescription(castDevice.getModelName()) + .addControlFilters(controlFilters) + .setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED) + .setVolumeMax(20) + .setVolume(0) + .setEnabled(true) + .setExtras(extras) + .setConnectionState(MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED) + .build(); builder.addRoute(route); } this.setDescriptor(builder.build()); From 84299d4cfde06df681a886056f55871585c10968 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sat, 11 Aug 2018 16:06:37 -0400 Subject: [PATCH 09/16] More dev --- extern/GmsApi | 2 +- .../microg/gms/AbstractGmsServiceBroker.java | 2 +- .../gms/cast/CastDeviceControllerImpl.java | 158 +++++++++++++++--- 3 files changed, 135 insertions(+), 27 deletions(-) diff --git a/extern/GmsApi b/extern/GmsApi index 3bc73c30..e5dfb2e4 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit 3bc73c3020f7555a8fc8c03b3e9793de29641dc1 +Subproject commit e5dfb2e459f0196642c17fa04368874677f4e38b diff --git a/play-services-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java b/play-services-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java index 255cd967..da1def3e 100644 --- a/play-services-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java +++ b/play-services-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java @@ -179,7 +179,7 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { @Override public void getCastService(IGmsCallbacks callback, int versionCode, String packageName, IBinder binder, Bundle params) throws RemoteException { - throw new IllegalArgumentException("Cast service not supported"); + callGetService(GmsService.CAST, callback, versionCode, packageName, params); } @Deprecated diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java index f00b11f6..9082456a 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java @@ -29,7 +29,9 @@ import android.util.Base64; import android.util.Log; import com.google.android.gms.cast.ApplicationMetadata; +import com.google.android.gms.cast.ApplicationStatus; import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastDeviceStatus; import com.google.android.gms.cast.JoinOptions; import com.google.android.gms.cast.LaunchOptions; import com.google.android.gms.cast.internal.ICastDeviceController; @@ -47,9 +49,10 @@ import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEventListener; import su.litvak.chromecast.api.v2.ChromeCastRawMessageListener; import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent; import su.litvak.chromecast.api.v2.ChromeCastRawMessage; +import su.litvak.chromecast.api.v2.AppEvent; public class CastDeviceControllerImpl extends ICastDeviceController.Stub - implements ChromeCastSpontaneousEventListener, ChromeCastRawMessageListener + implements ChromeCastSpontaneousEventListener, ChromeCastRawMessageListener, ICastDeviceControllerListener { private static final String TAG = "GmsCastDeviceControllerImpl"; @@ -73,7 +76,9 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub this.notificationEnabled = extras.getBoolean("com.google.android.gms.cast.EXTRA_CAST_FRAMEWORK_NOTIFICATION_ENABLED"); this.castFlags = extras.getLong("com.google.android.gms.cast.EXTRA_CAST_FLAGS"); BinderWrapper listenerWrapper = (BinderWrapper)extras.get("listener"); - this.listener = ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder); + if (listenerWrapper != null) { + this.listener = ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder); + } this.chromecast = new ChromeCast(this.castDevice.getAddress()); this.chromecast.registerListener(this); @@ -88,9 +93,13 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub break; case STATUS: Log.d(TAG, "unimplemented Method: spontaneousEventReceived: STATUS"); + Application app = ((su.litvak.chromecast.api.v2.Status)event.getData()).getRunningApp(); + if (app != null) { + this.onApplicationStatusChanged(new ApplicationStatus(app.statusText)); + } break; case APPEVENT: - Log.d(TAG, "unimplemented Method: spontaneousEventReceived: APPEVENT"); + Log.d(TAG, "unimplemented Method: spontaneousEventReceived: APPEVENT" + ((AppEvent)event.getData()).message); break; case CLOSE: Log.d(TAG, "unimplemented Method: spontaneousEventReceived: CLOSE"); @@ -102,13 +111,14 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } @Override - public void rawMessageReceived(ChromeCastRawMessage message) { + public void rawMessageReceived(ChromeCastRawMessage message, Long requestId) { switch (message.getPayloadType()) { case STRING: - try { - this.listener.onTextMessageReceived(message.getNamespace(), message.getPayloadUtf8()); - } catch (RemoteException ex) { - Log.e(TAG, "Error calling onTextMessageReceived: " + ex.getMessage()); + String response = message.getPayloadUtf8(); + if (requestId == null) { + this.onTextMessageReceived(message.getNamespace(), response); + } else { + this.onSendMessageSuccess(response, requestId); } break; case BINARY: @@ -125,18 +135,13 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub @Override public void sendMessage(String namespace, String message, long requestId) { - String response = null; + Log.d(TAG, "unimplemented Method: sendMessage" + message); try { - response = this.chromecast.sendRawRequest(namespace, message, requestId); + this.chromecast.sendRawRequest(namespace, message, requestId); } catch (IOException e) { Log.w(TAG, "Error sending cast message: " + e.getMessage()); return; } - try { - this.listener.onSendMessageSuccess(response, requestId); - } catch (RemoteException ex) { - Log.e(TAG, "Error calling onSendMessageSuccess: " + ex.getMessage()); - } } @Override @@ -170,11 +175,7 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub app = this.chromecast.launchApp(applicationId); } catch (IOException e) { Log.w(TAG, "Error launching cast application: " + e.getMessage()); - try { - this.listener.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR); - } catch (RemoteException ex) { - Log.e(TAG, "Error calling onApplicationConnectionFailure: " + ex.getMessage()); - } + this.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR); return; } this.sessionId = app.sessionId; @@ -190,15 +191,122 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub metadata.namespaces.add(namespace.name); } metadata.senderAppIdentifier = this.context.getPackageName(); - try { - this.listener.onApplicationConnectionSuccess(metadata, app.statusText, app.sessionId, true); - } catch (RemoteException e) { - Log.e(TAG, "Error calling onApplicationConnectionSuccess: " + e.getMessage()); - } + this.onApplicationConnectionSuccess(metadata, app.statusText, app.sessionId, true); } @Override public void joinApplication(String applicationId, String sessionId, JoinOptions joinOptions) { Log.d(TAG, "unimplemented Method: joinApplication"); + this.launchApplication(applicationId, new LaunchOptions()); + } + + public void onDisconnected(int reason) { + Log.d(TAG, "unimplemented Method: onDisconnected"); + if (this.listener != null) { + try { + this.listener.onDisconnected(reason); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onDisconnected: " + ex.getMessage()); + } + } + } + + public void onApplicationConnectionSuccess(ApplicationMetadata applicationMetadata, String applicationStatus, String sessionId, boolean wasLaunched) { + Log.d(TAG, "unimplemented Method: onApplicationConnectionSuccess"); + if (this.listener != null) { + try { + this.listener.onApplicationConnectionSuccess(applicationMetadata, applicationStatus, sessionId, wasLaunched); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onApplicationConnectionSuccess: " + ex.getMessage()); + } + } + } + + public void onApplicationConnectionFailure(int statusCode) { + Log.d(TAG, "unimplemented Method: onApplicationConnectionFailure"); + if (this.listener != null) { + try { + this.listener.onApplicationConnectionFailure(statusCode); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onApplicationConnectionFailure: " + ex.getMessage()); + } + } + } + + public void onTextMessageReceived(String namespace, String message) { + Log.d(TAG, "unimplemented Method: onTextMessageReceived: " + message); + if (this.listener != null) { + try { + this.listener.onTextMessageReceived(namespace, message); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onTextMessageReceived: " + ex.getMessage()); + } + } + } + + public void onBinaryMessageReceived(String namespace, byte[] data) { + Log.d(TAG, "unimplemented Method: onBinaryMessageReceived"); + if (this.listener != null) { + try { + this.listener.onBinaryMessageReceived(namespace, data); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onBinaryMessageReceived: " + ex.getMessage()); + } + } + } + + public void onApplicationDisconnected(int paramInt) { + Log.d(TAG, "unimplemented Method: onApplicationDisconnected"); + if (this.listener != null) { + try { + this.listener.onApplicationDisconnected(paramInt); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onApplicationDisconnected: " + ex.getMessage()); + } + } + } + + public void onSendMessageFailure(String response, long requestId, int statusCode) { + Log.d(TAG, "unimplemented Method: onSendMessageFailure"); + if (this.listener != null) { + try { + this.listener.onSendMessageFailure(response, requestId, statusCode); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onSendMessageFailure: " + ex.getMessage()); + } + } + } + + public void onSendMessageSuccess(String response, long requestId) { + Log.d(TAG, "unimplemented Method: onSendMessageSuccess: " + response); + if (this.listener != null) { + try { + this.listener.onSendMessageSuccess(response, requestId); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onSendMessageSuccess: " + ex.getMessage()); + } + } + } + + public void onApplicationStatusChanged(ApplicationStatus applicationStatus) { + Log.d(TAG, "unimplemented Method: onApplicationStatusChanged"); + if (this.listener != null) { + try { + this.listener.onApplicationStatusChanged(applicationStatus); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onApplicationStatusChanged: " + ex.getMessage()); + } + } + } + + public void onDeviceStatusChanged(CastDeviceStatus deviceStatus) { + Log.d(TAG, "unimplemented Method: onDeviceStatusChanged"); + if (this.listener != null) { + try { + this.listener.onDeviceStatusChanged(deviceStatus); + } catch (RemoteException ex) { + Log.e(TAG, "Error calling onDeviceStatusChanged: " + ex.getMessage()); + } + } } } From 3c3e391232346c7a63f0fd2b37f272ec605182c3 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sat, 25 Aug 2018 14:55:04 -0400 Subject: [PATCH 10/16] Discovery cleanup --- extern/GmsApi | 2 +- play-services-core/build.gradle | 2 +- .../framework/internal/CastContextImpl.java | 12 ---- .../gms/cast/CastMediaRouteProvider.java | 63 ++++++++++++------- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/extern/GmsApi b/extern/GmsApi index e5dfb2e4..9b04f236 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit e5dfb2e459f0196642c17fa04368874677f4e38b +Subproject commit 9b04f236b58bd43b5e9d735c6e676b5eb30502ed diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index d4bfe525..dad757df 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -27,7 +27,7 @@ dependencies { implementation "com.takisoft.fix:preference-v7:$supportLibraryVersion.0" implementation "de.hdodenhof:circleimageview:1.3.0" implementation "com.squareup.wire:wire-runtime:1.6.1" - implementation "su.litvak.chromecast:api-v2:0.10.3-SNAPSHOT" + implementation "su.litvak.chromecast:api-v2:0.10.4-SNAPSHOT" // Specified manually due to // https://github.com/vitalidze/chromecast-java-api-v2/issues/91 diff --git a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java index 7857d934..30dd6a22 100644 --- a/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java @@ -71,18 +71,6 @@ public class CastContextImpl extends ICastContext.Stub { .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(defaultCategory) .build(); - - // TODO: Find a home for this once the rest of the implementation - // becomes more clear. Uncomment this to enable discovery of devices. - // Note that the scan currently isn't ever disabled as part of the - // lifecycle, so we don't want to ship with this. - /* - Bundle selectorBundle = this.mergedSelector.asBundle(); - - router.clearCallbacks(); - router.registerMediaRouterCallbackImpl(selectorBundle, new MediaRouterCallbackImpl(this)); - router.addCallback(selectorBundle, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); - */ } @Override diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java index 7b9f0b39..27499a98 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java @@ -53,6 +53,7 @@ public class CastMediaRouteProvider extends MediaRouteProvider { private static final String TAG = CastMediaRouteProvider.class.getSimpleName(); private Map castDevices = new HashMap(); + private Map serviceCastIds = new HashMap(); private NsdManager mNsdManager; private NsdManager.DiscoveryListener mDiscoveryListener; @@ -180,7 +181,6 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public void onDiscoveryStarted(String regType) { - Log.d(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStarted"); CastMediaRouteProvider.this.state = State.DISCOVERING; } @@ -189,7 +189,10 @@ public class CastMediaRouteProvider extends MediaRouteProvider { mNsdManager.resolveService(service, new NsdManager.ResolveListener() { @Override public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { - Log.e(TAG, "DiscoveryListener unimplemented Method: Resolve failed" + errorCode); + if (errorCode == NsdManager.FAILURE_ALREADY_ACTIVE) { + return; + } + Log.e(TAG, "DiscoveryListener Resolve failed. Error code " + errorCode); } @Override @@ -197,16 +200,21 @@ public class CastMediaRouteProvider extends MediaRouteProvider { String name = serviceInfo.getServiceName(); InetAddress host = serviceInfo.getHost(); int port = serviceInfo.getPort(); + Map attributes = serviceInfo.getAttributes(); + if (attributes == null) { + Log.e(TAG, "Error getting service attributes from DNS-SD response"); + return; + } try { - String id = new String(serviceInfo.getAttributes().get("id"), "UTF-8"); - String deviceVersion = new String(serviceInfo.getAttributes().get("ve"), "UTF-8"); - String friendlyName = new String(serviceInfo.getAttributes().get("fn"), "UTF-8"); - String modelName = new String(serviceInfo.getAttributes().get("md"), "UTF-8"); - String iconPath = new String(serviceInfo.getAttributes().get("ic"), "UTF-8"); - int status = Integer.parseInt(new String(serviceInfo.getAttributes().get("st"), "UTF-8")); + String id = new String(attributes.get("id"), "UTF-8"); + String deviceVersion = new String(attributes.get("ve"), "UTF-8"); + String friendlyName = new String(attributes.get("fn"), "UTF-8"); + String modelName = new String(attributes.get("md"), "UTF-8"); + String iconPath = new String(attributes.get("ic"), "UTF-8"); + int status = Integer.parseInt(new String(attributes.get("st"), "UTF-8")); onChromeCastDiscovered(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status); - } catch (UnsupportedEncodingException ex) { + } catch (UnsupportedEncodingException | NullPointerException ex) { Log.e(TAG, "Error getting cast details from DNS-SD response", ex); return; } @@ -215,26 +223,23 @@ public class CastMediaRouteProvider extends MediaRouteProvider { } @Override - public void onServiceLost(NsdServiceInfo service) { - Log.d(TAG, "DiscoveryListener unimplemented Method: onServiceLost" + service); - // TODO: Remove chromecast route. + public void onServiceLost(NsdServiceInfo serviceInfo) { + String name = serviceInfo.getServiceName(); + onChromeCastLost(name); } @Override public void onDiscoveryStopped(String serviceType) { - Log.i(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStopped " + serviceType); CastMediaRouteProvider.this.state = State.NOT_DISCOVERING; } @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { - Log.e(TAG, "DiscoveryListener unimplemented Method: onStartDiscoveryFailed: Error code:" + errorCode); CastMediaRouteProvider.this.state = State.NOT_DISCOVERING; } @Override public void onStopDiscoveryFailed(String serviceType, int errorCode) { - Log.e(TAG, "DiscoveryListener unimplemented Method: onStopDiscoveryFailed: Error code:" + errorCode); CastMediaRouteProvider.this.state = State.DISCOVERING; } }; @@ -250,15 +255,19 @@ public class CastMediaRouteProvider extends MediaRouteProvider { CastDevice castDevice = new CastDevice(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status, capabilities); this.castDevices.put(id, castDevice); + this.serviceCastIds.put(name, id); } - Handler mainHandler = new Handler(this.getContext().getMainLooper()); - mainHandler.post(new Runnable() { - @Override - public void run() { - publishRoutes(); - } - }); + publishRoutesInMainThread(); + } + + private void onChromeCastLost(String name) { + String id = this.serviceCastIds.remove(name); + if (id != null) { + this.castDevices.remove(id); + } + + publishRoutesInMainThread(); } @Override @@ -289,6 +298,16 @@ public class CastMediaRouteProvider extends MediaRouteProvider { return new CastMediaRouteController(this, routeId, castDevice.getAddress()); } + private void publishRoutesInMainThread() { + Handler mainHandler = new Handler(this.getContext().getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + publishRoutes(); + } + }); + } + private void publishRoutes() { MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder(); for (CastDevice castDevice : this.castDevices.values()) { From 1db42b82318b9a004327338c14cc114af9798080 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sat, 25 Aug 2018 18:49:44 -0400 Subject: [PATCH 11/16] More cast device implementation coverage --- extern/GmsApi | 2 +- .../gms/cast/CastDeviceControllerImpl.java | 88 +++++++++++-------- .../gms/cast/CastMediaRouteController.java | 11 +-- 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/extern/GmsApi b/extern/GmsApi index 9b04f236..d1c0bcb9 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit 9b04f236b58bd43b5e9d735c6e676b5eb30502ed +Subproject commit d1c0bcb9f818eb4a9efdc5a498dd26f178f8fe3f diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java index 9082456a..dde86058 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java @@ -45,14 +45,19 @@ import com.google.android.gms.common.internal.GetServiceRequest; import su.litvak.chromecast.api.v2.Application; import su.litvak.chromecast.api.v2.ChromeCast; import su.litvak.chromecast.api.v2.Namespace; +import su.litvak.chromecast.api.v2.ChromeCastConnectionEventListener; import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEventListener; import su.litvak.chromecast.api.v2.ChromeCastRawMessageListener; +import su.litvak.chromecast.api.v2.ChromeCastConnectionEvent; import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent; import su.litvak.chromecast.api.v2.ChromeCastRawMessage; import su.litvak.chromecast.api.v2.AppEvent; -public class CastDeviceControllerImpl extends ICastDeviceController.Stub - implements ChromeCastSpontaneousEventListener, ChromeCastRawMessageListener, ICastDeviceControllerListener +public class CastDeviceControllerImpl extends ICastDeviceController.Stub implements + ChromeCastConnectionEventListener, + ChromeCastSpontaneousEventListener, + ChromeCastRawMessageListener, + ICastDeviceControllerListener { private static final String TAG = "GmsCastDeviceControllerImpl"; @@ -83,29 +88,56 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub this.chromecast = new ChromeCast(this.castDevice.getAddress()); this.chromecast.registerListener(this); this.chromecast.registerRawMessageListener(this); + this.chromecast.registerConnectionListener(this); + } + + @Override + public void connectionEventReceived(ChromeCastConnectionEvent event) { + if (!event.isConnected()) { + this.onDisconnected(CommonStatusCodes.SUCCESS); + } + } + + protected ApplicationMetadata createMetadataFromApplication(Application app) { + if (app == null) { + return null; + } + ApplicationMetadata metadata = new ApplicationMetadata(); + metadata.applicationId = app.id; + metadata.name = app.name; + Log.d(TAG, "unimplemented: ApplicationMetadata.images"); + Log.d(TAG, "unimplemented: ApplicationMetadata.senderAppLaunchUri"); + metadata.images = new ArrayList(); + metadata.namespaces = new ArrayList(); + for(Namespace namespace : app.namespaces) { + metadata.namespaces.add(namespace.name); + } + metadata.senderAppIdentifier = this.context.getPackageName(); + return metadata; } @Override public void spontaneousEventReceived(ChromeCastSpontaneousEvent event) { switch (event.getType()) { case MEDIA_STATUS: - Log.d(TAG, "unimplemented Method: spontaneousEventReceived: MEDIA_STATUS"); break; case STATUS: - Log.d(TAG, "unimplemented Method: spontaneousEventReceived: STATUS"); - Application app = ((su.litvak.chromecast.api.v2.Status)event.getData()).getRunningApp(); + su.litvak.chromecast.api.v2.Status status = (su.litvak.chromecast.api.v2.Status)event.getData(); + Application app = status.getRunningApp(); + ApplicationMetadata metadata = this.createMetadataFromApplication(app); if (app != null) { this.onApplicationStatusChanged(new ApplicationStatus(app.statusText)); } + int activeInputState = status.activeInput ? 1 : 0; + int standbyState = status.standBy ? 1 : 0; + this.onDeviceStatusChanged(new CastDeviceStatus(status.volume.level, status.volume.muted, activeInputState, metadata, standbyState)); break; case APPEVENT: - Log.d(TAG, "unimplemented Method: spontaneousEventReceived: APPEVENT" + ((AppEvent)event.getData()).message); break; case CLOSE: - Log.d(TAG, "unimplemented Method: spontaneousEventReceived: CLOSE"); + this.onApplicationDisconnected(CommonStatusCodes.SUCCESS); break; default: - Log.d(TAG, "unimplemented Method: spontaneousEventReceived: UNKNOWN"); break; } } @@ -119,38 +151,41 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub this.onTextMessageReceived(message.getNamespace(), response); } else { this.onSendMessageSuccess(response, requestId); + this.onTextMessageReceived(message.getNamespace(), response); } break; case BINARY: - Log.d(TAG, "unimplemented Method: rawMessageReceived: BINARY"); + byte[] payload = message.getPayloadBinary(); + this.onBinaryMessageReceived(message.getNamespace(), payload); break; } } @Override public void disconnect() { - Log.d(TAG, "unimplemented Method: disconnect"); - this.sessionId = null; + try { + this.chromecast.disconnect(); + } catch (IOException e) { + Log.e(TAG, "Error disconnecting chromecast: " + e.getMessage()); + return; + } } @Override public void sendMessage(String namespace, String message, long requestId) { - Log.d(TAG, "unimplemented Method: sendMessage" + message); try { this.chromecast.sendRawRequest(namespace, message, requestId); } catch (IOException e) { Log.w(TAG, "Error sending cast message: " + e.getMessage()); + this.onSendMessageFailure("", requestId, CommonStatusCodes.NETWORK_ERROR); return; } } @Override public void stopApplication(String sessionId) { - Log.d(TAG, "unimplemented Method: stopApplication"); try { - // TODO: Expose more control of chromecast-java-api-v2 so we can - // make use of our sessionID parameter. - this.chromecast.stopApp(); + this.chromecast.stopSession(sessionId); } catch (IOException e) { Log.w(TAG, "Error sending cast message: " + e.getMessage()); return; @@ -180,17 +215,7 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } this.sessionId = app.sessionId; - ApplicationMetadata metadata = new ApplicationMetadata(); - metadata.applicationId = applicationId; - metadata.name = app.name; - Log.d(TAG, "unimplemented: ApplicationMetadata.images"); - metadata.images = new ArrayList(); - metadata.namespaces = new ArrayList(); - Log.d(TAG, "unimplemented: ApplicationMetadata.senderAppLaunchUri"); - for(Namespace namespace : app.namespaces) { - metadata.namespaces.add(namespace.name); - } - metadata.senderAppIdentifier = this.context.getPackageName(); + ApplicationMetadata metadata = this.createMetadataFromApplication(app); this.onApplicationConnectionSuccess(metadata, app.statusText, app.sessionId, true); } @@ -201,7 +226,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onDisconnected(int reason) { - Log.d(TAG, "unimplemented Method: onDisconnected"); if (this.listener != null) { try { this.listener.onDisconnected(reason); @@ -212,7 +236,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onApplicationConnectionSuccess(ApplicationMetadata applicationMetadata, String applicationStatus, String sessionId, boolean wasLaunched) { - Log.d(TAG, "unimplemented Method: onApplicationConnectionSuccess"); if (this.listener != null) { try { this.listener.onApplicationConnectionSuccess(applicationMetadata, applicationStatus, sessionId, wasLaunched); @@ -223,7 +246,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onApplicationConnectionFailure(int statusCode) { - Log.d(TAG, "unimplemented Method: onApplicationConnectionFailure"); if (this.listener != null) { try { this.listener.onApplicationConnectionFailure(statusCode); @@ -234,7 +256,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onTextMessageReceived(String namespace, String message) { - Log.d(TAG, "unimplemented Method: onTextMessageReceived: " + message); if (this.listener != null) { try { this.listener.onTextMessageReceived(namespace, message); @@ -245,7 +266,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onBinaryMessageReceived(String namespace, byte[] data) { - Log.d(TAG, "unimplemented Method: onBinaryMessageReceived"); if (this.listener != null) { try { this.listener.onBinaryMessageReceived(namespace, data); @@ -267,7 +287,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onSendMessageFailure(String response, long requestId, int statusCode) { - Log.d(TAG, "unimplemented Method: onSendMessageFailure"); if (this.listener != null) { try { this.listener.onSendMessageFailure(response, requestId, statusCode); @@ -278,7 +297,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onSendMessageSuccess(String response, long requestId) { - Log.d(TAG, "unimplemented Method: onSendMessageSuccess: " + response); if (this.listener != null) { try { this.listener.onSendMessageSuccess(response, requestId); @@ -289,7 +307,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onApplicationStatusChanged(ApplicationStatus applicationStatus) { - Log.d(TAG, "unimplemented Method: onApplicationStatusChanged"); if (this.listener != null) { try { this.listener.onApplicationStatusChanged(applicationStatus); @@ -300,7 +317,6 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub } public void onDeviceStatusChanged(CastDeviceStatus deviceStatus) { - Log.d(TAG, "unimplemented Method: onDeviceStatusChanged"); if (this.listener != null) { try { this.listener.onDeviceStatusChanged(deviceStatus); diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java index 949e0102..4bb034bd 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteController.java @@ -71,16 +71,7 @@ public class CastMediaRouteController extends MediaRouteProvider.RouteController } public void onRelease() { - new Thread(new Runnable() { - public void run() { - try { - CastMediaRouteController.this.chromecast.stopApp(); - } catch (IOException e) { - Log.w(TAG, "Error stopping cast application: " + e.getMessage()); - return; - } - } - }).start(); + Log.d(TAG, "unimplemented Method: onRelease: " + this.routeId); } public void onSelect() { From 00594590ac489743fe45c4a30cf08c816e3601f5 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sun, 2 Sep 2018 10:18:30 -0400 Subject: [PATCH 12/16] Linting cleanup --- .../gms/cast/CastDeviceControllerImpl.java | 2 +- .../microg/gms/cast/CastMediaRouteProvider.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java index dde86058..e93e3c13 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java @@ -59,7 +59,7 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub impleme ChromeCastRawMessageListener, ICastDeviceControllerListener { - private static final String TAG = "GmsCastDeviceControllerImpl"; + private static final String TAG = "GmsCastDeviceController"; private Context context; private String packageName; diff --git a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java index 27499a98..7c5ba7c2 100644 --- a/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java @@ -16,11 +16,13 @@ package org.microg.gms.cast; +import android.annotation.SuppressLint; import android.content.Context; import android.content.IntentFilter; import android.net.Uri; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; +import android.os.Build; import android.os.Bundle; import android.os.AsyncTask; import android.os.Handler; @@ -172,9 +174,15 @@ public class CastMediaRouteProvider extends MediaRouteProvider { BASE_CONTROL_FILTERS.add(filter); } + @SuppressLint("NewApi") public CastMediaRouteProvider(Context context) { super(context); + if (android.os.Build.VERSION.SDK_INT < 16) { + Log.i(TAG, "Cast discovery disabled. Android SDK version 16 or higher required."); + return; + } + mNsdManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE); mDiscoveryListener = new NsdManager.DiscoveryListener() { @@ -270,8 +278,13 @@ public class CastMediaRouteProvider extends MediaRouteProvider { publishRoutesInMainThread(); } + @SuppressLint("NewApi") @Override public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { + if (android.os.Build.VERSION.SDK_INT < 16) { + return; + } + if (request != null && request.isValid() && request.isActiveScan()) { if (request.getSelector() != null) { for (String category : request.getSelector().getControlCategories()) { @@ -295,6 +308,9 @@ public class CastMediaRouteProvider extends MediaRouteProvider { @Override public RouteController onCreateRouteController(String routeId) { CastDevice castDevice = this.castDevices.get(routeId); + if (castDevice == null) { + return null; + } return new CastMediaRouteController(this, routeId, castDevice.getAddress()); } From eaf66d9e19e47d80f05e485e9cd79a6385dc12d6 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Mon, 3 Sep 2018 14:48:24 -0400 Subject: [PATCH 13/16] Switch to chromecast lib raw request branch --- play-services-core/build.gradle | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 892a5e19..8702e52f 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -16,10 +16,6 @@ apply plugin: 'com.android.application' -repositories { - mavenLocal() -} - dependencies { implementation "com.android.support:support-v4:$supportLibraryVersion" implementation "com.android.support:appcompat-v7:$supportLibraryVersion" @@ -27,7 +23,10 @@ dependencies { implementation "com.takisoft.fix:preference-v7:$supportLibraryVersion.0" implementation "de.hdodenhof:circleimageview:1.3.0" implementation "com.squareup.wire:wire-runtime:1.6.1" - implementation "su.litvak.chromecast:api-v2:0.10.4-SNAPSHOT" + // TODO: Switch to upstream once raw requests are merged + // https://github.com/vitalidze/chromecast-java-api-v2/pull/99 + // implementation "su.litvak.chromecast:api-v2:0.10.4" + implementation "info.armills.chromecast-java-api-v2:api-v2-raw-request:0.10.4-raw-request-1" // Specified manually due to // https://github.com/vitalidze/chromecast-java-api-v2/issues/91 From 7627d8ae428695499ba1baac43b27a6b103d9c94 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Sun, 10 Mar 2019 15:05:17 -0400 Subject: [PATCH 14/16] Update GmsApi to master --- extern/GmsApi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/GmsApi b/extern/GmsApi index b7402671..feeff75a 160000 --- a/extern/GmsApi +++ b/extern/GmsApi @@ -1 +1 @@ -Subproject commit b74026712f45392c0f9ac3c0b083896ab15fc927 +Subproject commit feeff75ab10072c89cd17832bf53e859b946cfae From 7c93f8785e399c71904c040da037b0ddf77fbe1f Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Tue, 12 Mar 2019 21:58:19 -0400 Subject: [PATCH 15/16] Remove safe-parcel testing setting --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index eae21e8a..ff311f8f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -include ':wearable-lib', ':safe-parcel' +include ':wearable-lib' include ':unifiednlp-api' include ':unifiednlp-base' From c7e9b299f6b2a903a8227b0251cd5251abf68361 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 27 May 2019 13:21:28 +0200 Subject: [PATCH 16/16] Add multidex support --- play-services-core/build.gradle | 3 +++ play-services-core/src/main/AndroidManifest.xml | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index b2b944b6..2a7ff302 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -23,6 +23,7 @@ def useMapbox() { } dependencies { + implementation 'com.android.support:multidex:1.0.3' implementation "com.android.support:support-v4:$supportLibraryVersion" implementation "com.android.support:appcompat-v7:$supportLibraryVersion" implementation "com.android.support:mediarouter-v7:$supportLibraryVersion" @@ -87,6 +88,8 @@ android { minSdkVersion androidMinSdk() targetSdkVersion androidTargetSdk() + multiDexEnabled true + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64" } diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 6950bed8..d7d9e60f 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -106,7 +106,8 @@ android:allowBackup="false" android:extractNativeLibs="false" android:icon="@mipmap/ic_core_service_app" - android:label="@string/gms_app_name"> + android:label="@string/gms_app_name" + android:name="android.support.multidex.MultiDexApplication">