Repo Created

This commit is contained in:
Fr4nz D13trich 2025-11-15 17:44:12 +01:00
parent eb305e2886
commit a8c22c65db
4784 changed files with 329907 additions and 2 deletions

View file

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
android {
namespace "com.google.android.gms.cast.framework"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
buildFeatures {
aidl = true
}
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
apply from: '../gradle/publish-android.gradle'
description = 'microG implementation of play-services-cast-framework'
dependencies {
// Dependencies from play-services-cast-framework:21.2.0
api 'androidx.appcompat:appcompat:1.0.0'
api 'androidx.collection:collection:1.0.0'
api 'androidx.concurrent:concurrent-futures:1.1.0'
api 'androidx.core:core:1.0.0'
api 'androidx.fragment:fragment:1.0.0'
api 'androidx.media:media:1.6.0'
api 'androidx.mediarouter:mediarouter:1.3.0'
api 'androidx.recyclerview:recyclerview:1.0.0'
// com.google.android.datatransport:transport-api:3.0.0
// com.google.android.datatransport:transport-backend-cct:3.1.3
// com.google.android.datatransport:transport-runtime:3.1.3
api project(':play-services-base')
api project(':play-services-basement')
api project(':play-services-cast')
api project(':play-services-tasks')
api 'com.google.guava:listenablefuture:1.0'
}

View file

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
apply plugin: 'signing'
dependencies {
implementation project(':play-services-cast-framework')
implementation project(':play-services-base-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
}
android {
namespace "org.microg.gms.cast.framework.core"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
buildFeatures {
dataBinding = true
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'MissingTranslation'
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = 1.8
}
}
apply from: '../../gradle/publish-android.gradle'
description = 'microG service implementation for play-services-cast-framework'

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2022 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
</application>
</manifest>

View file

@ -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.util.Log;
import androidx.mediarouter.media.MediaControlIntent;
import androidx.mediarouter.media.MediaRouteSelector;
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<String, ISessionProvider> sessionProviders = new HashMap<String, ISessionProvider>();
public ISessionProvider defaultSessionProvider;
private MediaRouteSelector mergedSelector;
public CastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map<String, IBinder> sessionProviders) throws RemoteException {
this.context = (Context) ObjectWrapper.unwrap(context);
this.options = options;
this.router = router;
for (Map.Entry<String, IBinder> 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);
}
}

View file

@ -0,0 +1,77 @@
/*
* 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.RemoteException;
import android.util.Log;
import com.google.android.gms.cast.framework.CastOptions;
import com.google.android.gms.cast.framework.ICastConnectionController;
import com.google.android.gms.cast.framework.ICastContext;
import com.google.android.gms.cast.framework.ICastSession;
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;
import com.google.android.gms.dynamic.IObjectWrapper;
import java.util.Map;
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 sessionProviders) throws RemoteException {
return new CastContextImpl(context, options, router, sessionProviders);
}
@Override
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 {
return new CastSessionImpl(options, session, controller);
}
@Override
public IMediaNotificationService newMediaNotificationServiceImpl(IObjectWrapper service, IObjectWrapper castContext, IObjectWrapper resources, CastMediaOptions options) throws RemoteException {
Log.d(TAG, "unimplemented Method: newMediaNotificationServiceImpl");
return null;
}
@Override
public IReconnectionService newReconnectionServiceImpl(IObjectWrapper service, IObjectWrapper sessionManager, IObjectWrapper discoveryManager) throws RemoteException {
Log.d(TAG, "unimplemented Method: newReconnectionServiceImpl");
return null;
}
@Override
public IFetchBitmapTask newFetchBitmapTaskImpl(IObjectWrapper asyncTask, IFetchBitmapTaskProgressPublisher progressPublisher, int i1, int i2, boolean b1, long l1, int i3, int i4, int i5) throws RemoteException {
Log.d(TAG, "unimplemented Method: newFetchBitmapTaskImpl");
return null;
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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.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;
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");
}
}

View file

@ -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);
}
}

View file

@ -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<ISessionManagerListener> sessionManagerListeners = new HashSet<ISessionManagerListener>();
private Set<ICastStateListener> castStateListeners = new HashSet<ICastStateListener>();
private Map<String, SessionImpl> routeSessions = new HashMap<String, SessionImpl>();
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());
}
}
}
}

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2017 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<queries>
<package android:name="com.google.android.gms.policy_cast_dynamite"/>
</queries>
<application>
<receiver
android:name="com.google.android.gms.cast.framework.media.MediaIntentReceiver"
android:exported="false"/>
<service
android:name="com.google.android.gms.cast.framework.media.MediaNotificationService"
android:exported="false"
android:foregroundServiceType="mediaPlayback"/>
<service
android:name="com.google.android.gms.cast.framework.ReconnectionService"
android:exported="false"/>
</application>
</manifest>

View file

@ -0,0 +1,3 @@
package com.google.android.gms.cast.framework;
parcelable CastOptions;

View file

@ -0,0 +1,10 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.dynamic.IObjectWrapper;
interface IAppVisibilityListener {
IObjectWrapper getThisObject() = 0;
void onAppEnteredForeground() = 1;
void onAppEnteredBackground() = 2;
int getSupportedVersion() = 3;
}

View file

@ -0,0 +1,11 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.cast.LaunchOptions;
interface ICastConnectionController {
void joinApplication(String applicationId, String sessionId) = 0;
void launchApplication(String applicationId, in LaunchOptions launchOptions) = 1;
void stopApplication(String sessionId) = 2;
void closeConnection(int reason) = 3; // Maybe?
int getSupportedVersion() = 4;
}

View file

@ -0,0 +1,20 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.cast.framework.IAppVisibilityListener;
import com.google.android.gms.cast.framework.ISessionManager;
import com.google.android.gms.cast.framework.IDiscoveryManager;
import com.google.android.gms.dynamic.IObjectWrapper;
interface ICastContext {
Bundle getMergedSelectorAsBundle() = 0;
boolean isApplicationVisible() = 1;
void addVisibilityChangeListener(IAppVisibilityListener listener) = 2;
void removeVisibilityChangeListener(IAppVisibilityListener listener) = 3;
ISessionManager getSessionManagerImpl() = 4;
IDiscoveryManager getDiscoveryManagerImpl() = 5;
void destroy() = 6; // deprecated?
void onActivityResumed(in IObjectWrapper activity) = 7; // deprecated?
void onActivityPaused(in IObjectWrapper activity) = 8; // deprecated?
IObjectWrapper getWrappedThis() = 9;
void setReceiverApplicationId(String receiverApplicationId, in Map/*<String, IBinder>*/ sessionProvidersByCategory) = 10;
}

View file

@ -0,0 +1,15 @@
package com.google.android.gms.cast.framework;
import android.os.Bundle;
import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.common.api.Status;
interface ICastSession {
void onConnected(in Bundle routeInfoExtra) = 0;
void onConnectionSuspended(int reason) = 1;
void onConnectionFailed(in Status status) = 2;
void onApplicationConnectionSuccess(in ApplicationMetadata applicationMetadata, String applicationStatus, String sessionId, boolean wasLaunched) = 3;
void onApplicationConnectionFailure(int statusCode) = 4;
void disconnectFromDevice(boolean boolean1, int int1) = 5;
}

View file

@ -0,0 +1,9 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.dynamic.IObjectWrapper;
interface ICastStateListener {
IObjectWrapper getWrappedThis() = 0;
void onCastStateChanged(int newState) = 1;
int getSupportedVersion() = 2;
}

View file

@ -0,0 +1,12 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.cast.framework.IDiscoveryManagerListener;
import com.google.android.gms.dynamic.IObjectWrapper;
interface IDiscoveryManager {
void startDiscovery() = 0; // Maybe?
void stopDiscovery() = 1; // Maybe?
void addDiscoveryManagerListener(IDiscoveryManagerListener listener) = 2;
void removeDiscoveryManagerListener(IDiscoveryManagerListener listener) = 3;
IObjectWrapper getWrappedThis() = 4;
}

View file

@ -0,0 +1,8 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.dynamic.IObjectWrapper;
interface IDiscoveryManagerListener {
IObjectWrapper getWrappedThis() = 0;
void onDeviceAvailabilityChanged(boolean deviceAvailable) = 1;
}

View file

@ -0,0 +1,8 @@
package com.google.android.gms.cast.framework;
interface IReconnectionService {
void onCreate() = 0;
int onStartCommand(in Intent intent, int flags, int startId) = 1;
IBinder onBind(in Intent intent) = 2;
void onDestroy() = 3;
}

View file

@ -0,0 +1,22 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.dynamic.IObjectWrapper;
interface ISession {
IObjectWrapper getWrappedObject() = 0;
String getCategory() = 1;
String getSessionId() = 2;
String getRouteId() = 3;
boolean isConnected() = 4;
boolean isConnecting() = 5;
boolean isDisconnecting() = 6;
boolean isDisconnected() = 7;
boolean isResuming() = 8;
boolean isSuspended() = 9;
void notifySessionStarted(String sessionId) = 10;
void notifyFailedToStartSession(int error) = 11;
void notifySessionEnded(int error) = 12;
void notifySessionResumed(boolean wasSuspended) = 13;
void notifyFailedToResumeSession(int error) = 14;
void notifySessionSuspended(int reason) = 15;
}

View file

@ -0,0 +1,19 @@
package com.google.android.gms.cast.framework;
import android.os.Bundle;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.cast.framework.ISessionManagerListener;
import com.google.android.gms.cast.framework.ICastStateListener;
interface ISessionManager {
IObjectWrapper getWrappedCurrentSession() = 0;
void addSessionManagerListener(ISessionManagerListener listener) = 1;
void removeSessionManagerListener(ISessionManagerListener listener) = 2;
void addCastStateListener(ICastStateListener listener) = 3;
void removeCastStateListener(ICastStateListener listener) = 4;
void endCurrentSession(boolean b, boolean stopCasting) = 5;
IObjectWrapper getWrappedThis() = 6;
int getCastState() = 7;
void startSession(in Bundle options) = 8;
}

View file

@ -0,0 +1,17 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.dynamic.IObjectWrapper;
interface ISessionManagerListener {
IObjectWrapper getWrappedThis() = 0;
void onSessionStarting(IObjectWrapper session) = 1;
void onSessionStarted(IObjectWrapper session, String sessionId) = 2;
void onSessionStartFailed(IObjectWrapper session, int error) = 3;
void onSessionEnding(IObjectWrapper session) = 4;
void onSessionEnded(IObjectWrapper session, int error) = 5;
void onSessionResuming(IObjectWrapper session, String sessionId) = 6;
void onSessionResumed(IObjectWrapper session, boolean wasSuspended) = 7;
void onSessionResumeFailed(IObjectWrapper session, int error) = 8;
void onSessionSuspended(IObjectWrapper session, int reason) = 9;
int getSupportedVersion() = 10;
}

View file

@ -0,0 +1,10 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.dynamic.IObjectWrapper;
interface ISessionProvider {
IObjectWrapper getSession(String sessionId) = 0;
boolean isSessionRecoverable() = 1;
String getCategory() = 2;
int getSupportedVersion() = 3;
}

View file

@ -0,0 +1,14 @@
package com.google.android.gms.cast.framework;
import com.google.android.gms.dynamic.IObjectWrapper;
interface ISessionProxy {
IObjectWrapper getWrappedSession() = 0;
void start(in Bundle extras) = 1;
void resume(in Bundle extras) = 2;
void end(boolean paramBoolean) = 3;
long getSessionRemainingTimeMs() = 4;
int getSupportedVersion() = 5;
void onStarting(in Bundle routeInfoExtra) = 6;
void onResuming(in Bundle routeInfoExtra) = 7;
}

View file

@ -0,0 +1,24 @@
package com.google.android.gms.cast.framework.internal;
import com.google.android.gms.cast.framework.CastOptions;
import com.google.android.gms.cast.framework.ICastConnectionController;
import com.google.android.gms.cast.framework.ICastContext;
import com.google.android.gms.cast.framework.ICastSession;
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.internal.IMediaRouter;
import com.google.android.gms.cast.framework.media.CastMediaOptions;
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;
import com.google.android.gms.dynamic.IObjectWrapper;
interface ICastDynamiteModule {
ICastContext newCastContextImpl(in IObjectWrapper context, in CastOptions options, IMediaRouter router, in Map sessionProviders) = 0;
ISession newSessionImpl(String category, String sessionId, ISessionProxy proxy) = 1;
ICastSession newCastSessionImpl(in CastOptions options, in IObjectWrapper session, ICastConnectionController controller) = 2;
IMediaNotificationService newMediaNotificationServiceImpl(in IObjectWrapper service, in IObjectWrapper castContext, in IObjectWrapper resources, in CastMediaOptions options) = 3;
IReconnectionService newReconnectionServiceImpl(in IObjectWrapper service, in IObjectWrapper sessionManager, in IObjectWrapper discoveryManager) = 4;
IFetchBitmapTask newFetchBitmapTaskImpl(in IObjectWrapper asyncTask, IFetchBitmapTaskProgressPublisher progressPublisher, int i1, int i2, boolean b1, long l1, int i3, int i4, int i5) = 5;
}

View file

@ -0,0 +1,19 @@
package com.google.android.gms.cast.framework.internal;
import android.os.Bundle;
import com.google.android.gms.cast.framework.internal.IMediaRouterCallback;
interface IMediaRouter {
void registerMediaRouterCallbackImpl(in Bundle selector, IMediaRouterCallback callback) = 0;
void addCallback(in Bundle selector, int flags) = 1;
void removeCallback(in Bundle selector) = 2;
boolean isRouteAvailable(in Bundle selector, int flags) = 3;
void selectRouteById(String routeId) = 4;
void selectDefaultRoute() = 5;
boolean isDefaultRouteSelected() = 6; // Maybe?
Bundle getRouteInfoExtrasById(String routeId) = 7;
String getSelectedRouteId() = 8; // Maybe?
int getSupportedVersion() = 9;
void clearCallbacks() = 10;
}

View file

@ -0,0 +1,12 @@
package com.google.android.gms.cast.framework.internal;
import android.os.Bundle;
interface IMediaRouterCallback {
void onRouteAdded(String routeId, in Bundle extras) = 0;
void onRouteChanged(String routeId, in Bundle extras) = 1;
void onRouteRemoved(String routeId, in Bundle extras) = 2;
void onRouteSelected(String routeId, in Bundle extras) = 3;
void unknown(String routeId, in Bundle extras) = 4;
void onRouteUnselected(String routeId, in Bundle extras, int reason) = 5;
}

View file

@ -0,0 +1,3 @@
package com.google.android.gms.cast.framework.media;
parcelable CastMediaOptions;

View file

@ -0,0 +1,11 @@
package com.google.android.gms.cast.framework.media;
import com.google.android.gms.common.images.WebImage;
import com.google.android.gms.dynamic.IObjectWrapper;
interface IImagePicker {
// WebImage onPickImage(MediaMetadata metadata, int int1) = 0;
IObjectWrapper getWrappedClientObject() = 1;
int unknown1() = 2;
// WebImage onPickImage(MediaMetadata metadata, ImageHints imageHints) = 3;
}

View file

@ -0,0 +1,5 @@
package com.google.android.gms.cast.framework.media;
interface IMediaNotificationService {
}

View file

@ -0,0 +1,5 @@
package com.google.android.gms.cast.framework.media;
interface INotificationActionsProvider {
}

View file

@ -0,0 +1,3 @@
package com.google.android.gms.cast.framework.media;
parcelable NotificationOptions;

View file

@ -0,0 +1,5 @@
package com.google.android.gms.cast.framework.media.internal;
interface IFetchBitmapTask {
}

View file

@ -0,0 +1,5 @@
package com.google.android.gms.cast.framework.media.internal;
interface IFetchBitmapTaskProgressPublisher {
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.cast.framework;
public class CastButtonFactory {
}

View file

@ -0,0 +1,191 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.cast.framework.internal.IMediaRouter;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import org.microg.gms.cast.CastDynamiteModule;
import org.microg.gms.cast.CastSessionProvider;
import org.microg.gms.common.PublicApi;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
public class CastContext {
/**
* The metadata key to specify the fully qualified name of the {@link OptionsProvider} implementation in the
* {@code AndroidManifest.xml}.
*/
public static final String OPTIONS_PROVIDER_CLASS_NAME_KEY = "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME";
/**
* Returns the shared instance of {@link CastContext}. This method must be called after {@link CastContext} is initialized through
* {@link #getSharedInstance(Context, Executor)}. Otherwise, this method will return {@code null}.
*
* @throws IllegalStateException If this method is not called on the main thread.
*/
public static CastContext getSharedInstance() {
return sharedInstance;
}
/**
* Returns a shared instance of {@link CastContext}. The shared instance will be initialized on the first time this method is called.
*
* @param context An application {@link Context}. If this is not an application {@link Context}, {@link Context#getApplicationContext()} will be called on
* the given context, to retrieve it.
* @throws IllegalStateException If any of the following:
* <ul>
* <li>This method is not called on the main thread.</li>
* <li>
* The fully qualified name of the {@link OptionsProvider} implementation is not specified as a metadata in the
* {@code AndroidManifest.xml} with key {@link #OPTIONS_PROVIDER_CLASS_NAME_KEY}.
* </li>
* <li>{@code optionsProviderClass} or its nullary constructor is not accessible.</li>
* <li>Instantiation of {@link OptionsProvider} fails for some other reason.</li>
* </ul>
* @deprecated Use {@link #getSharedInstance(Context, Executor)} instead to handle the exception when Cast SDK fails to load the internal
* Cast module.
*/
@Deprecated
public static CastContext getSharedInstance(Context context) {
if (sharedInstance == null) {
Context appContext = context.getApplicationContext();
OptionsProvider optionsProvider = getOptionsProvider(appContext);
CastOptions castOptions = optionsProvider.getCastOptions(appContext);
try {
sharedInstance = new CastContext(appContext, castOptions, optionsProvider.getAdditionalSessionProviders(appContext));
} catch (ModuleUnavailableException e) {
throw new RuntimeException(e);
}
}
return sharedInstance;
}
/**
* Returns an asynchronous Task API call on the shared instance of {@link CastContext}. The shared instance will be initialized
* on the first time this method is called.
* <p>
* Note that {@link #getSharedInstance(Context, Executor)} should be called in the {@link Activity#onCreate(Bundle)} method
* of the activities that might display a Cast button. The Cast SDK provides {@link CastButtonFactory} to set up a Cast button.
* <p>
* Note that {@link ModuleUnavailableException} could be thrown when the SDK fails to load the internal Cast module. The
* caller will get the exception from {@link Task#getException()} when the task completes.
*
* @param context An application {@link Context}. If this is not an application {@link Context}, {@link Context#getApplicationContext()} will be called on
* the given context, to retrieve it.
* @param executor An {@link Executor} to load the internal Cast module.
* @throws IllegalStateException If any of the following:
* <ul>
* <li>This method is not called on the main thread.</li>
* <li>
* The fully qualified name of the {@link OptionsProvider} implementation is not specified as a metadata in the
* {@code AndroidManifest.xml} with key {@link #OPTIONS_PROVIDER_CLASS_NAME_KEY}.
* </li>
* <li>{@code optionsProviderClass} or its nullary constructor is not accessible.</li>
* <li>Instantiation of {@link OptionsProvider} fails for some other reason.</li>
* </ul>
*/
public static Task<CastContext> getSharedInstance(Context context, Executor executor) {
if (sharedInstance != null) {
return Tasks.forResult(sharedInstance);
}
Context appContext = context.getApplicationContext();
OptionsProvider optionsProvider = getOptionsProvider(appContext);
CastOptions castOptions = optionsProvider.getCastOptions(appContext);
return Tasks.call(executor, () -> {
sharedInstance = new CastContext(appContext, castOptions, optionsProvider.getAdditionalSessionProviders(appContext));
return sharedInstance;
});
}
/**
* Returns the {@link SessionManager}, never returns {@code null}.
*
* @throws IllegalStateException If this method is not called on the main thread.
*/
@NonNull
public SessionManager getSessionManager() {
return sessionManager;
}
private static volatile CastContext sharedInstance;
private Context appContext;
private CastOptions castOptions;
private IMediaRouter mediaRouter;
private List<SessionProvider> additionalSessionProviders;
private CastSessionProvider castSessionProvider;
private ICastContext delegate;
private SessionManager sessionManager;
private DiscoveryManager discoveryManager;
private CastContext(Context appContext, CastOptions castOptions, @Nullable List<SessionProvider> additionalSessionProviders) throws ModuleUnavailableException {
this.appContext = appContext;
this.castOptions = castOptions;
this.mediaRouter = null; // TODO
this.additionalSessionProviders = additionalSessionProviders;
this.castSessionProvider = new CastSessionProvider(appContext, castOptions);
try {
this.delegate = CastDynamiteModule.newCastContext(appContext, castOptions, mediaRouter, getSessionProviderMap());
this.sessionManager = new SessionManager(appContext, delegate.getSessionManagerImpl());
this.discoveryManager = new DiscoveryManager(appContext, delegate.getDiscoveryManagerImpl());
} catch (RemoteException e) {
throw new IllegalStateException("Failed to call dynamite module", e);
}
}
private Map<String, IBinder> getSessionProviderMap() {
Map<String, IBinder> map = new HashMap<>();
if (castSessionProvider != null) {
map.put(castSessionProvider.getCategory(), castSessionProvider.asBinder());
}
List<SessionProvider> list = this.additionalSessionProviders;
if (list != null) {
for (SessionProvider sessionProvider : list) {
if (sessionProvider == null) throw new IllegalArgumentException("Additional SessionProvider must not be null.");
if (sessionProvider.getCategory() == null || sessionProvider.getCategory().isEmpty())
throw new IllegalArgumentException("Category for SessionProvider must not be null or empty string.");
if (map.containsKey(sessionProvider.getCategory()))
throw new IllegalArgumentException("SessionProvider for category " + sessionProvider.getCategory() + " already added");
map.put(sessionProvider.getCategory(), sessionProvider.asBinder());
}
}
return map;
}
private static OptionsProvider getOptionsProvider(Context context) {
try {
Bundle metaData = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData;
String optionsProviderClassName = metaData.getString(OPTIONS_PROVIDER_CLASS_NAME_KEY);
if (optionsProviderClassName != null) {
return Class.forName(optionsProviderClassName).asSubclass(OptionsProvider.class).getDeclaredConstructor().newInstance();
}
throw new IllegalStateException("The fully qualified name of the implementation of OptionsProvider must be provided as a metadata in the AndroidManifest.xml with key com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME.");
} catch (PackageManager.NameNotFoundException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException | NullPointerException e) {
throw new IllegalStateException("Failed to initialize CastContext.", e);
}
}
@NonNull
DiscoveryManager getDiscoveryManager() {
return discoveryManager;
}
}

View file

@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: 2016 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework;
import androidx.annotation.NonNull;
import org.microg.safeparcel.AutoSafeParcelable;
import org.microg.safeparcel.SafeParceled;
import com.google.android.gms.cast.framework.media.CastMediaOptions;
import com.google.android.gms.cast.LaunchOptions;
import java.util.ArrayList;
import java.util.List;
/**
* Configuration parameters for initializing the {@link CastContext}. The {@link CastOptions.Builder} is used to create an instance of
* {@link CastOptions}, and so contains the corresponding setter methods.
*/
public class CastOptions extends AutoSafeParcelable {
@Field(1)
private int versionCode = 1;
@Field(2)
private String receiverApplicationId;
@Field(3)
private ArrayList<String> supportedNamespaces;
@Field(4)
private boolean stopReceiverApplicationWhenEndingSession;
@Field(5)
private LaunchOptions launchOptions;
@Field(6)
private boolean resumeSavedSession;
@Field(7)
private CastMediaOptions castMediaOptions;
@Field(8)
private boolean enableReconnectionService;
@Field(9)
private double volumeDeltaBeforeIceCreamSandwich;
@Field(10)
private boolean enableIpv6Support;
@Field(11)
private boolean outputSwitcherEnabled;
@Field(12)
private boolean isRemoteToLocalEnabled;
@Field(13)
private List<String> routeDiscoveryReceiverApplicationIds;
@Field(14)
private boolean sessionTransferEnabled;
@Field(15)
private int persistCastButtonEnabled;
@Field(16)
private boolean resumeSessionAfterTransferEnabled;
/**
* Returns the {@link CastMediaOptions} that is used to configure a media session.
*/
public CastMediaOptions getCastMediaOptions() {
return castMediaOptions;
}
/**
* Returns {@code true} if {@link ReconnectionService} should be enabled when needed to better handle session recovery.
*/
public boolean getEnableReconnectionService() {
return enableReconnectionService;
}
/**
* Returns the {@link LaunchOptions}.
*/
public LaunchOptions getLaunchOptions() {
return launchOptions;
}
/**
* Returns the cast receiver application ID. This ID is used in discovering supported receivers, and launching an application
* when starting a new session.
*/
public String getReceiverApplicationId() {
return receiverApplicationId;
}
/**
* Returns {@code true} if the saved session should be resumed if it was dropped unexpectedly. Returns {@code false} if the saved
* session should not be resumed in this case.
*/
public boolean getResumeSavedSession() {
return resumeSavedSession;
}
/**
* Returns {@code true} if the receiver application should be stopped when the session is ended by the user. Returns {@code false} if the
* receiver application should not be stopped in this case.
*/
public boolean getStopReceiverApplicationWhenEndingSession() {
return stopReceiverApplicationWhenEndingSession;
}
/**
* Returns the list of supported cast namespaces.
*/
@NonNull
public List<String> getSupportedNamespaces() {
return supportedNamespaces;
}
/**
* Returns the amount of receiver device volume to increase or decrease when the physical volume key is pressed on an
* Android device older than ICE CREAM SANDWICH. The SDK will call this method to obtain the volume delta when
* {@link CastContext#onDispatchVolumeKeyEventBeforeJellyBean(KeyEvent)} is called. The return value is not used on
* Android devices running ICE CREAM SANDWICH or newer.
*/
@Deprecated
public double getVolumeDeltaBeforeIceCreamSandwich() {
return volumeDeltaBeforeIceCreamSandwich;
}
public static Creator<CastOptions> CREATOR = new AutoCreator<CastOptions>(CastOptions.class);
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.cast.framework;
public class CastSession extends Session{
}

View file

@ -0,0 +1,39 @@
/*
* 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;
public final class CastState {
public static final int NO_DEVICES_AVAILABLE = 1;
public static final int NOT_CONNECTED = 2;
public static final int CONNECTING = 3;
public static final int CONNECTED = 4;
public static String toString(int castState) {
switch (castState) {
case NO_DEVICES_AVAILABLE:
return "NO_DEVICES_AVAILABLE";
case NOT_CONNECTED:
return "NOT_CONNECTED";
case CONNECTING:
return "CONNECTING";
case CONNECTED:
return "CONNECTED";
default:
return "UNKNOWN";
}
}
}

View file

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.cast.framework;
import android.content.Context;
import android.os.RemoteException;
import com.google.android.gms.cast.framework.IDiscoveryManager;
import com.google.android.gms.cast.framework.ISessionManager;
import com.google.android.gms.dynamic.IObjectWrapper;
class DiscoveryManager {
private Context context;
private IDiscoveryManager delegate;
public DiscoveryManager(Context context, IDiscoveryManager delegate) {
this.context = context;
this.delegate = delegate;
}
public IObjectWrapper getWrappedThis() {
try {
return delegate.getWrappedThis();
} catch (RemoteException e) {
return null;
}
}
}

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework;
/**
* An exception thrown when the internal Cast module fails to load.
*/
public class ModuleUnavailableException extends Exception {
public ModuleUnavailableException(Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
/**
* Developers should implement this interface to provide options needed to create and initialize {@link CastContext}. The
* implementation class must have a constructor without argument. The SDK will call that constructor to instantiate a new
* instance.
*/
public interface OptionsProvider {
/**
* Provides a list of custom {@link SessionProvider} instances for non-Cast devices. This is optional.
*
* @param appContext The application {@link Context}.
* @return the list of {@link SessionProvider} instances, may be {@code null}.
*/
@Nullable
List<SessionProvider> getAdditionalSessionProviders(@NonNull Context appContext);
/**
* Provides {@link CastOptions}, which affects discovery and session management of a Cast device.
*
* @param appContext The application {@link Context}.
* @return the {@link CastOptions}, must not be {@code null}.
*/
@NonNull
CastOptions getCastOptions(@NonNull Context appContext);
}

View file

@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.cast.framework;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import org.microg.gms.cast.CastDynamiteModule;
public class ReconnectionService extends Service {
private IReconnectionService delegate;
@Override
public void onCreate() {
CastContext castContext = CastContext.getSharedInstance(this);
delegate = CastDynamiteModule.newReconnectionService(this, castContext.getSessionManager().getWrappedThis(), castContext.getDiscoveryManager().getWrappedThis());
if (delegate != null) {
try {
delegate.onCreate();
} catch (RemoteException e) {
// Ignore
}
}
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (delegate != null) {
try {
delegate.onStartCommand(intent, flags, startId);
} catch (RemoteException e) {
// Ignore
}
}
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (delegate != null) {
try {
return delegate.onBind(intent);
} catch (RemoteException e) {
// Ignore
}
}
return null;
}
@Override
public void onDestroy() {
if (delegate != null) {
try {
delegate.onDestroy();
} catch (RemoteException e) {
// Ignore
}
}
super.onDestroy();
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.cast.framework;
public class Session {
}

View file

@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework;
import android.content.Context;
import android.os.RemoteException;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper;
public class SessionManager {
private Context context;
private ISessionManager delegate;
SessionManager(Context context, ISessionManager delegate) {
this.context = context;
this.delegate = delegate;
}
/**
* Ends the current session.
*
* @param stopCasting Should the receiver application be stopped when ending the current Session.
* @throws IllegalStateException If this method is not called on the main thread.
*/
public void endCurrentSession(boolean stopCasting) {
try {
delegate.endCurrentSession(true, stopCasting);
} catch (RemoteException e) {
// Ignore
}
}
/**
* Returns the current session if it is an instance of {@link CastSession}, otherwise returns {@code null}.
*
* @throws IllegalStateException If this method is not called on the main thread.
*/
public CastSession getCurrentCastSession() {
Session currentSession = getCurrentSession();
if (currentSession instanceof CastSession) {
return (CastSession) currentSession;
}
return null;
}
/**
* Returns the currently active session. Returns {@code null} if no session is active.
*
* @throws IllegalStateException If this method is not called on the main thread.
*/
public Session getCurrentSession() {
try {
return ObjectWrapper.unwrapTyped(delegate.getWrappedCurrentSession(), Session.class);
} catch (RemoteException e) {
return null;
}
}
IObjectWrapper getWrappedThis() {
try {
return delegate.getWrappedThis();
} catch (RemoteException e) {
return null;
}
}
}

View file

@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework;
import android.content.Context;
import android.os.IBinder;
import org.microg.gms.cast.ISessionProviderImpl;
/**
* An abstract base class for performing session construction. The SDK uses a subclass of {@link SessionProvider} to
* construct {@link CastSession} internally. If your app wants to support other types of {@link Session} then you should subclass this
* class. Subclasses must implement {@link #createSession(String)} and {@link #isSessionRecoverable()}, which will be called by
* the Cast SDK during the lifecycle of the session. All methods must be called from the main thread.
*/
public abstract class SessionProvider {
private Context context;
private String category;
private ISessionProvider bindable = new ISessionProviderImpl(this);
/**
* Constructs a {@link SessionProvider} with a category string. The category uniquely identifies a {@link Session} created by this
* provider.
*
* @param applicationContext The application Context of the calling app.
* @param category The category string used to create {@link Session}.
*/
protected SessionProvider(Context applicationContext, String category) {
this.context = applicationContext;
this.category = category;
}
/**
* Constructs a new {@link Session}. This method is called by the SDK to create a new session.
*/
public abstract Session createSession(String sessionId);
/**
* Returns the category string for this {@link SessionProvider}.
*/
public final String getCategory() {
return category;
}
/**
* Returns the application {@link Context} used to construct this instance.
*/
public final Context getContext() {
return context;
}
/**
* Returns {@code true} if a previously constructed session can be resumed. Subclasses should check any persisted information
* about the previous session, such as a session ID, and return true only if it is possible to resume that session. This
* method is called by the SDK when it tries to resume a previously saved session.
*/
public abstract boolean isSessionRecoverable();
public IBinder asBinder() {
return bindable.asBinder();
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.media;
import org.microg.safeparcel.AutoSafeParcelable;
import org.microg.safeparcel.SafeParceled;
public class CastMediaOptions extends AutoSafeParcelable {
@Field(1)
private int versionCode = 1;
@Field(2)
public String mediaIntentReceiverClassName;
@Field(3)
public String expandedControllerActivityClassName;
@Field(4)
public IImagePicker imagePicker;
@Field(5)
public NotificationOptions notificationOptions;
@Field(6)
public boolean bool6;
@Field(7)
public boolean bool7;
public static Creator<CastMediaOptions> CREATOR = new AutoCreator<CastMediaOptions>(CastMediaOptions.class);
}

View file

@ -0,0 +1,198 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework.media;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.view.KeyEvent;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.Session;
import com.google.android.gms.cast.framework.SessionManager;
public class MediaIntentReceiver extends BroadcastReceiver {
/**
* The action for ending the current session and disconnecting from the receiver app.
*/
public static final String ACTION_DISCONNECT = "com.google.android.gms.cast.framework.action.DISCONNECT";
/**
* The action for forwarding the current item. When building an Intent with this action, use {@link #EXTRA_SKIP_STEP_MS} to set the
* time to forward in milliseconds.
*/
public static final String ACTION_FORWARD = "com.google.android.gms.cast.framework.action.FORWARD";
/**
* The action for rewinding the current item. When building an Intent with this action, use {@link #EXTRA_SKIP_STEP_MS} to set the
* time to rewind in milliseconds.
*/
public static final String ACTION_REWIND = "com.google.android.gms.cast.framework.action.REWIND";
/**
* The action for skipping to the next item in the queue.
*/
public static final String ACTION_SKIP_NEXT = "com.google.android.gms.cast.framework.action.SKIP_NEXT";
/**
* The action for skipping to the previous item in the queue.
*/
public static final String ACTION_SKIP_PREV = "com.google.android.gms.cast.framework.action.SKIP_PREV";
/**
* The action for ending the current session and stopping the receiver app.
*/
public static final String ACTION_STOP_CASTING = "com.google.android.gms.cast.framework.action.STOP_CASTING";
/**
* The action for toggling remote media playback.
*/
public static final String ACTION_TOGGLE_PLAYBACK = "com.google.android.gms.cast.framework.action.TOGGLE_PLAYBACK";
/**
* The extra key for specifying how much the currently playing item should be forwarded or rewinded to handle
* {@link #ACTION_FORWARD} and {@link #ACTION_REWIND}.
*/
public static final String EXTRA_SKIP_STEP_MS = "googlecast-extra_skip_step_ms";
@Override
public void onReceive(Context context, Intent intent) {
SessionManager sessionManager = CastContext.getSharedInstance(context).getSessionManager();
Session currentSession = sessionManager.getCurrentSession();
if (intent.getAction() != null && currentSession != null) {
switch (intent.getAction()) {
case ACTION_TOGGLE_PLAYBACK:
onReceiveActionTogglePlayback(currentSession);
break;
case ACTION_SKIP_NEXT:
onReceiveActionSkipNext(currentSession);
break;
case ACTION_SKIP_PREV:
onReceiveActionSkipPrev(currentSession);
break;
case ACTION_FORWARD:
onReceiveActionForward(currentSession, intent.getLongExtra(EXTRA_SKIP_STEP_MS, 0));
break;
case ACTION_REWIND:
onReceiveActionRewind(currentSession, intent.getLongExtra(EXTRA_SKIP_STEP_MS, 0));
break;
case ACTION_STOP_CASTING:
sessionManager.endCurrentSession(true);
break;
case ACTION_DISCONNECT:
sessionManager.endCurrentSession(false);
break;
case Intent.ACTION_MEDIA_BUTTON:
onReceiveActionMediaButton(currentSession, intent);
break;
default:
onReceiveOtherAction(context, intent.getAction(), intent);
break;
}
}
}
/**
* Called when {@link #ACTION_FORWARD} is received. The default implementation forwards the current playing item by
* {@code forwardStepMs} if {@code currentSession} is a {@link CastSession}. Subclasses can override this method to change the behavior
* or handle other type of {@link Session}. Subclasses should call through to super to let the SDK handle the action if
* {@code currentSession} is a {@link CastSession}
*
* @param currentSession The current {@link Session}.
* @param forwardStepMs Time to forward in milliseconds.
*/
protected void onReceiveActionForward(Session currentSession, long forwardStepMs) {
if (!(currentSession instanceof CastSession)) return;
// TODO Seek forwardStepMs
}
/**
* Called when {@link Intent#ACTION_MEDIA_BUTTON} is received. The default implementation toggles playback state if
* {@code currentSession} is a {@link CastSession}. Subclasses can override this method to change the behavior or handle other type
* of {@link Session}. Subclasses should call through to super to let the SDK handle the action if {@code currentSession} is a
* {@link CastSession}
*
* @param currentSession The current {@link Session}.
* @param intent The Intent of this action.
*/
protected void onReceiveActionMediaButton(Session currentSession, Intent intent) {
if (!(currentSession instanceof CastSession)) return;
if (intent.hasExtra(Intent.EXTRA_KEY_EVENT)) {
KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
// TODO Toggle Playback
}
}
}
/**
* Called when {@link #ACTION_REWIND} is received. The default implementation forwards the current playing item by
* {@code rewindStepMs} if {@code currentSession} is a {@link CastSession}. Subclasses can override this method to change the behavior or
* handle other type of {@link Session}. Subclasses should call through to super to let the SDK handle the action if
* {@code currentSession} is a {@link CastSession}
*
* @param currentSession The current {@link Session}.
* @param rewindStepMs Time to rewind in milliseconds.
*/
protected void onReceiveActionRewind(Session currentSession, long rewindStepMs) {
if (!(currentSession instanceof CastSession)) return;
// TODO Seek -rewindStepMs
}
/**
* Called when {@link #ACTION_SKIP_NEXT} is received. The default implementation plays the next item in the queue if
* {@code currentSession} is a {@link CastSession} and there is a next item. Subclasses can override this method to change the
* behavior or handle other type of {@link Session}. Subclasses should call through to super to let the SDK handle the action if
* {@code currentSession} is a {@link CastSession}
*
* @param currentSession The current {@link Session}.
*/
protected void onReceiveActionSkipNext(Session currentSession) {
if (!(currentSession instanceof CastSession)) return;
// TODO Queue next
}
/**
* Called when {@link #ACTION_SKIP_PREV} is received. The default implementation plays the previous item in the queue if
* {@code currentSession} is a {@link CastSession} and there is a previous item. Subclasses can override this method to change the
* behavior or handle other type of {@link Session}. Subclasses should call through to super to let the SDK handle the action if
* {@code currentSession} is a {@link CastSession}
*
* @param currentSession The current {@link Session}.
*/
protected void onReceiveActionSkipPrev(Session currentSession) {
if (!(currentSession instanceof CastSession)) return;
// TODO Queue prev
}
/**
* Called when {@link #ACTION_TOGGLE_PLAYBACK} is received. The default implementation toggles playback state if
* {@code currentSession} is a {@link CastSession}. Subclasses can override this method to change the
* behavior or handle other type of {@link Session}. Subclasses should call through to super to let the SDK handle the action if
* {@code currentSession} is a {@link CastSession}
*
* @param currentSession The current {@link Session}.
*/
protected void onReceiveActionTogglePlayback(Session currentSession) {
if (!(currentSession instanceof CastSession)) return;
// TODO Toggle Playback
}
/**
* @deprecated Override {@link #onReceiveOtherAction(Context, String, Intent)} instead.
*/
@Deprecated
protected void onReceiveOtherAction(String action, Intent intent) {
onReceiveOtherAction(null, action, intent);
}
/**
* Called when other type of actions are received. The default implementation does nothing.
*
* @param context The Context in which the receiver is running.
* @param action The action.
* @param intent The Intent of this action.
*/
protected void onReceiveOtherAction(Context context, String action, Intent intent) {
}
}

View file

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.cast.framework.media;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
public class MediaNotificationService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View file

@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2018 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License.
* See https://developers.google.com/readme/policies for details.
*/
package com.google.android.gms.cast.framework.media;
import java.util.List;
import android.app.Activity;
import org.microg.safeparcel.AutoSafeParcelable;
/**
* Configuration parameters for building the media notification. The {@link NotificationOptions.Builder} is used to create an
* instance of {@link NotificationOptions}, and so contains the corresponding setter methods.
*/
public class NotificationOptions extends AutoSafeParcelable {
/**
* Constant for notification skip step, ten seconds in milliseconds.
*/
public static final long SKIP_STEP_TEN_SECONDS_IN_MS = 10000;
/**
* Constant for notification skip step, thirty seconds in milliseconds.
*/
public static final long SKIP_STEP_THIRTY_SECONDS_IN_MS = 30000;
@Field(1)
private int versionCode = 1;
@Field(2)
private List<String> actions;
@Field(3)
private int[] compatActionIndices;
@Field(4)
private long skipStepMs;
@Field(5)
private String targetActivityClassName;
@Field(6)
private int getSmallIconDrawableResId;
@Field(7)
private int getStopLiveStreamDrawableResId;
@Field(8)
private int getPauseDrawableResId;
@Field(9)
private int getPlayDrawableResId;
@Field(10)
private int getSkipNextDrawableResId;
@Field(11)
private int getSkipPrevDrawableResId;
@Field(12)
private int getForwardDrawableResId;
@Field(13)
private int getForward10DrawableResId;
@Field(14)
private int getForward30DrawableResId;
@Field(15)
private int getRewindDrawableResId;
@Field(16)
private int getRewind10DrawableResId;
@Field(17)
private int getRewind30DrawableResId;
@Field(18)
private int getDisconnectDrawableResId;
@Field(19)
private int imageSizeDimenResId;
@Field(20)
private int getCastingToDeviceStringResId;
@Field(21)
private int getStopLiveStreamTitleResId;
@Field(22)
private int pauseTitleResId;
@Field(23)
private int playTitleResId;
@Field(24)
private int skipNextTitleResId;
@Field(25)
private int skipPrevTitleResId;
@Field(26)
private int forwardTitleResId;
@Field(27)
private int forward10TitleResId;
@Field(28)
private int forward30TitleResId;
@Field(29)
private int rewindTitleResId;
@Field(30)
private int rewind10TitleResId;
@Field(31)
private int rewind30TitleResId;
@Field(32)
private int disconnectTitleResId;
@Field(33)
private INotificationActionsProvider notificationActionsProvider;
@Field(34)
private boolean skipToPrevSlotReserved;
@Field(35)
private boolean skipToNextSlotReserved;
/**
* Returns the list of actions to show in the notification.
*/
public List<String> getActions() {
return actions;
}
/**
* Returns the amount to jump if {@link MediaIntentReceiver#ACTION_FORWARD} or {@link MediaIntentReceiver#ACTION_REWIND}
* are included in the notification actions. Any tap on those actions will result in moving the media position forward or
* backward by {@code skipStepMs} milliseconds. The default value is {@link #SKIP_STEP_TEN_SECONDS_IN_MS}.
*/
public long getSkipStepMs() {
return skipStepMs;
}
/**
* Returns the name of the {@link Activity} that will be launched when user taps on the content area of the notification.
*/
public String getTargetActivityClassName() {
return targetActivityClassName;
}
public static Creator<NotificationOptions> CREATOR = new AutoCreator<NotificationOptions>(NotificationOptions.class);
}

View file

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.cast;
import android.app.Service;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import com.google.android.gms.cast.framework.CastOptions;
import com.google.android.gms.cast.framework.ICastContext;
import com.google.android.gms.cast.framework.IReconnectionService;
import com.google.android.gms.cast.framework.ModuleUnavailableException;
import com.google.android.gms.cast.framework.internal.ICastDynamiteModule;
import com.google.android.gms.cast.framework.internal.IMediaRouter;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper;
import com.google.android.gms.dynamite.DynamiteModule;
import java.util.Map;
public class CastDynamiteModule {
public static ICastContext newCastContext(Context context, CastOptions castOptions, IMediaRouter mediaRouter, Map<String, IBinder> sessionProviderMap) throws ModuleUnavailableException, RemoteException {
return getInterface(context).newCastContextImpl(ObjectWrapper.wrap(context), castOptions, mediaRouter, sessionProviderMap);
}
public static IReconnectionService newReconnectionService(Service service, IObjectWrapper sessionManager, IObjectWrapper discoveryManager) {
try {
return getInterface(service.getApplicationContext()).newReconnectionServiceImpl(ObjectWrapper.wrap(service), sessionManager, discoveryManager);
} catch (RemoteException | ModuleUnavailableException e) {
return null;
}
}
@NonNull
private static ICastDynamiteModule getInterface(Context context) throws ModuleUnavailableException {
try {
IBinder binder = DynamiteModule.load(context, DynamiteModule.PREFER_REMOTE, "com.google.android.gms.cast.framework.dynamite").instantiate("com.google.android.gms.cast.framework.internal.CastDynamiteModuleImpl");
return ICastDynamiteModule.Stub.asInterface(binder);
} catch (DynamiteModule.LoadingException e) {
throw new ModuleUnavailableException(e);
}
}
}

View file

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.cast;
import android.content.Context;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.framework.CastOptions;
import com.google.android.gms.cast.framework.Session;
import com.google.android.gms.cast.framework.SessionProvider;
public class CastSessionProvider extends SessionProvider {
private CastOptions castOptions;
public CastSessionProvider(Context applicationContext, CastOptions castOptions) {
super(applicationContext, castOptions.getSupportedNamespaces().isEmpty() ? CastMediaControlIntent.categoryForCast(castOptions.getReceiverApplicationId()) : CastMediaControlIntent.categoryForCast(castOptions.getReceiverApplicationId(), castOptions.getSupportedNamespaces()));
this.castOptions = castOptions;
}
@Override
public Session createSession(String sessionId) {
return null;
}
@Override
public boolean isSessionRecoverable() {
return false;
}
}

View file

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.cast;
import android.os.RemoteException;
import com.google.android.gms.cast.framework.ISessionProvider;
import com.google.android.gms.cast.framework.SessionProvider;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper;
import org.microg.gms.common.Constants;
public class ISessionProviderImpl extends ISessionProvider.Stub {
private SessionProvider delegate;
public ISessionProviderImpl(SessionProvider delegate) {
this.delegate = delegate;
}
@Override
public IObjectWrapper getSession(String sessionId) throws RemoteException {
return ObjectWrapper.wrap(delegate.createSession(sessionId));
}
@Override
public boolean isSessionRecoverable() throws RemoteException {
return delegate.isSessionRecoverable();
}
@Override
public String getCategory() throws RemoteException {
return delegate.getCategory();
}
@Override
public int getSupportedVersion() throws RemoteException {
return Constants.GMS_VERSION_CODE;
}
}