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/play-services-core/build.gradle b/play-services-core/build.gradle
index 14ad88de..2a7ff302 100644
--- a/play-services-core/build.gradle
+++ b/play-services-core/build.gradle
@@ -23,15 +23,27 @@ 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"
implementation "com.squareup.wire:wire-runtime:1.6.1"
implementation "com.takisoft.fix:preference-v7:$supportLibraryVersion.0"
implementation "de.hdodenhof:circleimageview:1.3.0"
implementation "org.conscrypt:conscrypt-android:2.0.0"
+ // 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
+ 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')
+ implementation project(':play-services-cast-api')
implementation project(':play-services-wearable')
implementation project(':unifiednlp-base')
implementation project(':wearable-lib')
@@ -76,6 +88,8 @@ android {
minSdkVersion androidMinSdk()
targetSdkVersion androidTargetSdk()
+ multiDexEnabled true
+
ndk {
abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
@@ -102,6 +116,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 0f0e3887..d7d9e60f 100644
--- a/play-services-core/src/main/AndroidManifest.xml
+++ b/play-services-core/src/main/AndroidManifest.xml
@@ -92,13 +92,22 @@
-
+
+ android:label="@string/gms_app_name"
+ android:name="android.support.multidex.MultiDexApplication">
@@ -392,6 +401,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -619,7 +642,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
new file mode 100644
index 00000000..30dd6a22
--- /dev/null
+++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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 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;
+
+import java.util.Map;
+import java.util.HashMap;
+
+public class CastContextImpl extends ICastContext.Stub {
+ private static final String TAG = CastContextImpl.class.getSimpleName();
+
+ private SessionManagerImpl sessionManager;
+ private DiscoveryManagerImpl discoveryManager;
+
+ private Context context;
+ private CastOptions options;
+ private IMediaRouter router;
+ private Map sessionProviders = new HashMap();
+ public ISessionProvider defaultSessionProvider;
+
+ private MediaRouteSelector mergedSelector;
+
+ public CastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map sessionProviders) throws RemoteException {
+ this.context = (Context) ObjectWrapper.unwrap(context);
+ this.options = options;
+ this.router = router;
+ 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 = 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();
+ }
+
+ @Override
+ public Bundle getMergedSelectorAsBundle() throws RemoteException {
+ 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");
+ return true;
+ }
+
+ @Override
+ public SessionManagerImpl getSessionManagerImpl() {
+ if (this.sessionManager == null) {
+ this.sessionManager = new SessionManagerImpl(this);
+ }
+ return this.sessionManager;
+ }
+
+ @Override
+ public IDiscoveryManager getDiscoveryManagerImpl() throws RemoteException {
+ if (this.discoveryManager == null) {
+ this.discoveryManager = new DiscoveryManagerImpl(this);
+ }
+ return this.discoveryManager;
+ }
+
+ @Override
+ public void destroy() throws RemoteException {
+ Log.d(TAG, "unimplemented Method: destroy");
+ }
+
+ @Override
+ public void onActivityResumed(IObjectWrapper activity) throws RemoteException {
+ Log.d(TAG, "unimplemented Method: onActivityResumed");
+
+ }
+
+ @Override
+ public void onActivityPaused(IObjectWrapper activity) throws RemoteException {
+ Log.d(TAG, "unimplemented Method: onActivityPaused");
+ }
+
+ @Override
+ public void setReceiverApplicationId(String receiverApplicationId, Map sessionProvidersByCategory) throws RemoteException {
+ Log.d(TAG, "unimplemented Method: setReceiverApplicationId");
+ }
+
+ public Context getContext() {
+ return this.context;
+ }
+
+ public IMediaRouter getRouter() {
+ return this.router;
+ }
+
+ public MediaRouteSelector getMergedSelector() {
+ 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 374deded..71bd3345 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
@@ -16,7 +16,9 @@
package com.google.android.gms.cast.framework.internal;
+import android.content.Context;
import android.os.RemoteException;
+import android.support.v7.media.MediaRouter;
import android.util.Log;
import com.google.android.gms.cast.framework.CastOptions;
@@ -27,6 +29,10 @@ import com.google.android.gms.cast.framework.IReconnectionService;
import com.google.android.gms.cast.framework.ISession;
import com.google.android.gms.cast.framework.ISessionProxy;
import com.google.android.gms.cast.framework.media.CastMediaOptions;
+import com.google.android.gms.cast.framework.internal.CastContextImpl;
+import com.google.android.gms.cast.framework.internal.CastSessionImpl;
+import com.google.android.gms.cast.framework.internal.MediaRouterCallbackImpl;
+import com.google.android.gms.cast.framework.internal.SessionImpl;
import com.google.android.gms.cast.framework.media.IMediaNotificationService;
import com.google.android.gms.cast.framework.media.internal.IFetchBitmapTask;
import com.google.android.gms.cast.framework.media.internal.IFetchBitmapTaskProgressPublisher;
@@ -38,21 +44,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 {
- Log.d(TAG, "unimplemented Method: newCastContextImpl");
- return null;
+ 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 null;
+ 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 null;
+ 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
new file mode 100644
index 00000000..cc63b949
--- /dev/null
+++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/CastSessionImpl.java
@@ -0,0 +1,79 @@
+/*
+ * 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 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
new file mode 100644
index 00000000..b0e7b51c
--- /dev/null
+++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/DiscoveryManagerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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 com.google.android.gms.cast.framework.internal;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.google.android.gms.cast.framework.IDiscoveryManager;
+import com.google.android.gms.cast.framework.IDiscoveryManagerListener;
+import com.google.android.gms.cast.framework.internal.CastContextImpl;
+import com.google.android.gms.dynamic.IObjectWrapper;
+import com.google.android.gms.dynamic.ObjectWrapper;
+
+import java.util.Set;
+import java.util.HashSet;
+
+public class DiscoveryManagerImpl extends IDiscoveryManager.Stub {
+ private static final String TAG = DiscoveryManagerImpl.class.getSimpleName();
+
+ private CastContextImpl castContextImpl;
+
+ private Set discoveryManagerListeners = new HashSet();
+
+ public DiscoveryManagerImpl(CastContextImpl castContextImpl) {
+ this.castContextImpl = castContextImpl;
+ }
+
+ @Override
+ public void startDiscovery() {
+ Log.d(TAG, "unimplemented Method: startDiscovery");
+ }
+
+ @Override
+ public void stopDiscovery() {
+ Log.d(TAG, "unimplemented Method: stopDiscovery");
+ }
+
+ @Override
+ public void addDiscoveryManagerListener(IDiscoveryManagerListener listener) {
+ Log.d(TAG, "unimplemented Method: addDiscoveryManagerListener");
+ this.discoveryManagerListeners.add(listener);
+ }
+
+ @Override
+ public void removeDiscoveryManagerListener(IDiscoveryManagerListener listener) {
+ Log.d(TAG, "unimplemented Method: removeDiscoveryManagerListener");
+ this.discoveryManagerListeners.remove(listener);
+ }
+
+ @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/MediaRouterCallbackImpl.java b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/MediaRouterCallbackImpl.java
new file mode 100644
index 00000000..8bf933f3
--- /dev/null
+++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/MediaRouterCallbackImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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 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");
+ }
+ @Override
+ public void onRouteChanged(String routeId, Bundle extras) {
+ Log.d(TAG, "unimplemented Method: onRouteChanged");
+ }
+ @Override
+ public void onRouteRemoved(String routeId, Bundle extras) {
+ Log.d(TAG, "unimplemented Method: onRouteRemoved");
+ }
+ @Override
+ 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) {
+ Log.d(TAG, "unimplemented Method: unknown");
+ }
+ @Override
+ public void onRouteUnselected(String routeId, Bundle extras, int reason) {
+ Log.d(TAG, "unimplemented Method: onRouteUnselected");
+ }
+}
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
new file mode 100644
index 00000000..954405d8
--- /dev/null
+++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionImpl.java
@@ -0,0 +1,197 @@
+/*
+ * 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 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 String getCategory() {
+ return this.category;
+ }
+
+ @Override
+ public String getSessionId() {
+ return this.sessionId;
+ }
+
+ @Override
+ public String getRouteId() {
+ return this.routeId;
+ }
+
+ @Override
+ public boolean isConnected() {
+ 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
+ public boolean isResuming() {
+ Log.d(TAG, "unimplemented Method: isResuming");
+ return false;
+ }
+
+ @Override
+ 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
new file mode 100644
index 00000000..d10f8b21
--- /dev/null
+++ b/play-services-core/src/main/java/com/google/android/gms/cast/framework/internal/SessionManagerImpl.java
@@ -0,0 +1,230 @@
+/*
+ * 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 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;
+
+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 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 {
+ if (this.currentSession == null) {
+ return ObjectWrapper.wrap(null);
+ }
+ return this.currentSession.getWrappedSession();
+ }
+
+ @Override
+ public void endCurrentSession(boolean b, boolean stopCasting) throws RemoteException {
+ Log.d(TAG, "unimplemented Method: endCurrentSession");
+ }
+
+ @Override
+ public void addSessionManagerListener(ISessionManagerListener listener) {
+ Log.d(TAG, "unimplemented Method: addSessionManagerListener");
+ this.sessionManagerListeners.add(listener);
+ }
+
+ @Override
+ public void removeSessionManagerListener(ISessionManagerListener listener) {
+ Log.d(TAG, "unimplemented Method: removeSessionManagerListener");
+ this.sessionManagerListeners.remove(listener);
+ }
+
+ @Override
+ public void addCastStateListener(ICastStateListener listener) {
+ Log.d(TAG, "unimplemented Method: addCastStateListener");
+ this.castStateListeners.add(listener);
+ }
+
+ @Override
+ public void removeCastStateListener(ICastStateListener listener) {
+ Log.d(TAG, "unimplemented Method: removeCastStateListener");
+ this.castStateListeners.remove(listener);
+ }
+
+ @Override
+ 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/com/google/android/gms/cast/media/CastMediaRouteProviderService.java b/play-services-core/src/main/java/com/google/android/gms/cast/media/CastMediaRouteProviderService.java
new file mode 100644
index 00000000..47d18154
--- /dev/null
+++ b/play-services-core/src/main/java/com/google/android/gms/cast/media/CastMediaRouteProviderService.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.google.android.gms.cast.media;
+
+import org.microg.gms.cast.CastMediaRouteProvider;
+
+import android.support.v7.media.MediaRouteProviderService;
+import android.support.v7.media.MediaRouteProvider;
+import android.util.Log;
+
+public class CastMediaRouteProviderService extends MediaRouteProviderService {
+ private static final String TAG = CastMediaRouteProviderService.class.getSimpleName();
+
+ @Override
+ public MediaRouteProvider onCreateMediaRouteProvider() {
+ return new CastMediaRouteProvider(this);
+ }
+}
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
new file mode 100644
index 00000000..e93e3c13
--- /dev/null
+++ b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java
@@ -0,0 +1,328 @@
+/*
+ * 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.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;
+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;
+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
+ ChromeCastConnectionEventListener,
+ ChromeCastSpontaneousEventListener,
+ ChromeCastRawMessageListener,
+ ICastDeviceControllerListener
+{
+ private static final String TAG = "GmsCastDeviceController";
+
+ 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");
+ if (listenerWrapper != null) {
+ this.listener = ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder);
+ }
+
+ 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:
+ break;
+ case STATUS:
+ 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:
+ break;
+ case CLOSE:
+ this.onApplicationDisconnected(CommonStatusCodes.SUCCESS);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void rawMessageReceived(ChromeCastRawMessage message, Long requestId) {
+ switch (message.getPayloadType()) {
+ case STRING:
+ String response = message.getPayloadUtf8();
+ if (requestId == null) {
+ this.onTextMessageReceived(message.getNamespace(), response);
+ } else {
+ this.onSendMessageSuccess(response, requestId);
+ this.onTextMessageReceived(message.getNamespace(), response);
+ }
+ break;
+ case BINARY:
+ byte[] payload = message.getPayloadBinary();
+ this.onBinaryMessageReceived(message.getNamespace(), payload);
+ break;
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ 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) {
+ 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) {
+ try {
+ this.chromecast.stopSession(sessionId);
+ } catch (IOException e) {
+ Log.w(TAG, "Error sending cast message: " + e.getMessage());
+ return;
+ }
+ 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());
+ this.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR);
+ return;
+ }
+ this.sessionId = app.sessionId;
+
+ ApplicationMetadata metadata = this.createMetadataFromApplication(app);
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ if (this.listener != null) {
+ try {
+ this.listener.onDeviceStatusChanged(deviceStatus);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Error calling onDeviceStatusChanged: " + ex.getMessage());
+ }
+ }
+ }
+}
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..4bb034bd
--- /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, String address) {
+ super();
+
+ this.provider = provider;
+ this.routeId = routeId;
+ this.chromecast = new ChromeCast(address);
+ }
+
+ 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
new file mode 100644
index 00000000..7c5ba7c2
--- /dev/null
+++ b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java
@@ -0,0 +1,359 @@
+/*
+ * 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.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;
+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 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.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+
+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;
+
+ private List customCategories = new ArrayList();
+
+ private enum State {
+ NOT_DISCOVERING,
+ DISCOVERY_REQUESTED,
+ DISCOVERING,
+ DISCOVERY_STOP_REQUESTED,
+ }
+ private State state = State.NOT_DISCOVERING;
+
+ private static final ArrayList BASE_CONTROL_FILTERS = new ArrayList();
+ static {
+ IntentFilter filter;
+
+ filter = new IntentFilter();
+ filter.addCategory(CastMediaControlIntent.CATEGORY_CAST);
+ BASE_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);
+ }
+ }
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_PAUSE);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_RESUME);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_STOP);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_SEEK);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_GET_STATUS);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_START_SESSION);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ filter.addAction(MediaControlIntent.ACTION_END_SESSION);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(CastMediaControlIntent.CATEGORY_CAST_REMOTE_PLAYBACK);
+ filter.addAction(CastMediaControlIntent.ACTION_SYNC_STATUS);
+ BASE_CONTROL_FILTERS.add(filter);
+
+ filter = new IntentFilter();
+ filter.addCategory(CastMediaControlIntent.CATEGORY_CAST_REMOTE_PLAYBACK);
+ filter.addAction(CastMediaControlIntent.ACTION_SYNC_STATUS);
+ 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() {
+
+ @Override
+ public void onDiscoveryStarted(String regType) {
+ CastMediaRouteProvider.this.state = State.DISCOVERING;
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo service) {
+ mNsdManager.resolveService(service, new NsdManager.ResolveListener() {
+ @Override
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ if (errorCode == NsdManager.FAILURE_ALREADY_ACTIVE) {
+ return;
+ }
+ Log.e(TAG, "DiscoveryListener Resolve failed. Error code " + errorCode);
+ }
+
+ @Override
+ public void onServiceResolved(NsdServiceInfo serviceInfo) {
+ 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(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 | NullPointerException ex) {
+ Log.e(TAG, "Error getting cast details from DNS-SD response", ex);
+ return;
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ String name = serviceInfo.getServiceName();
+ onChromeCastLost(name);
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ CastMediaRouteProvider.this.state = State.NOT_DISCOVERING;
+ }
+
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ CastMediaRouteProvider.this.state = State.NOT_DISCOVERING;
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ CastMediaRouteProvider.this.state = State.DISCOVERING;
+ }
+ };
+ }
+
+ private void onChromeCastDiscovered(
+ String id, String name, InetAddress host, int port, String
+ deviceVersion, String friendlyName, String modelName, String
+ iconPath, int status) {
+ if (!this.castDevices.containsKey(id)) {
+ // TODO: Capabilities
+ int capabilities = CastDevice.CAPABILITY_VIDEO_OUT | CastDevice.CAPABILITY_AUDIO_OUT;
+
+ CastDevice castDevice = new CastDevice(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status, capabilities);
+ this.castDevices.put(id, castDevice);
+ this.serviceCastIds.put(name, id);
+ }
+
+ publishRoutesInMainThread();
+ }
+
+ private void onChromeCastLost(String name) {
+ String id = this.serviceCastIds.remove(name);
+ if (id != null) {
+ this.castDevices.remove(id);
+ }
+
+ 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()) {
+ 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;
+ }
+ } else {
+ if (this.state == State.DISCOVERING) {
+ mNsdManager.stopServiceDiscovery(mDiscoveryListener);
+ this.state = State.DISCOVERY_STOP_REQUESTED;
+ }
+ }
+ }
+
+ @Override
+ public RouteController onCreateRouteController(String routeId) {
+ CastDevice castDevice = this.castDevices.get(routeId);
+ if (castDevice == null) {
+ return null;
+ }
+ 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()) {
+ 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());
+ }
+}
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.* *;
+}