2018-04-25 23:25:14 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
2018-08-11 20:06:37 +00:00
|
|
|
import com.google.android.gms.cast.ApplicationStatus;
|
2018-04-25 23:25:14 +00:00
|
|
|
import com.google.android.gms.cast.CastDevice;
|
2018-08-11 20:06:37 +00:00
|
|
|
import com.google.android.gms.cast.CastDeviceStatus;
|
2018-04-25 23:25:14 +00:00
|
|
|
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;
|
2018-08-25 22:49:44 +00:00
|
|
|
import su.litvak.chromecast.api.v2.ChromeCastConnectionEventListener;
|
2018-06-23 18:12:09 +00:00
|
|
|
import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEventListener;
|
|
|
|
import su.litvak.chromecast.api.v2.ChromeCastRawMessageListener;
|
2018-08-25 22:49:44 +00:00
|
|
|
import su.litvak.chromecast.api.v2.ChromeCastConnectionEvent;
|
2018-06-23 18:12:09 +00:00
|
|
|
import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent;
|
|
|
|
import su.litvak.chromecast.api.v2.ChromeCastRawMessage;
|
2018-08-11 20:06:37 +00:00
|
|
|
import su.litvak.chromecast.api.v2.AppEvent;
|
2018-06-23 18:12:09 +00:00
|
|
|
|
2018-08-25 22:49:44 +00:00
|
|
|
public class CastDeviceControllerImpl extends ICastDeviceController.Stub implements
|
|
|
|
ChromeCastConnectionEventListener,
|
|
|
|
ChromeCastSpontaneousEventListener,
|
|
|
|
ChromeCastRawMessageListener,
|
|
|
|
ICastDeviceControllerListener
|
2018-06-23 18:12:09 +00:00
|
|
|
{
|
2018-09-02 14:18:30 +00:00
|
|
|
private static final String TAG = "GmsCastDeviceController";
|
2018-04-25 23:25:14 +00:00
|
|
|
|
|
|
|
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");
|
2018-08-11 20:06:37 +00:00
|
|
|
if (listenerWrapper != null) {
|
|
|
|
this.listener = ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder);
|
|
|
|
}
|
2018-04-25 23:25:14 +00:00
|
|
|
|
|
|
|
this.chromecast = new ChromeCast(this.castDevice.getAddress());
|
2018-06-23 18:12:09 +00:00
|
|
|
this.chromecast.registerListener(this);
|
|
|
|
this.chromecast.registerRawMessageListener(this);
|
2018-08-25 22:49:44 +00:00
|
|
|
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<WebImage>();
|
|
|
|
metadata.namespaces = new ArrayList<String>();
|
|
|
|
for(Namespace namespace : app.namespaces) {
|
|
|
|
metadata.namespaces.add(namespace.name);
|
|
|
|
}
|
|
|
|
metadata.senderAppIdentifier = this.context.getPackageName();
|
|
|
|
return metadata;
|
2018-06-23 18:12:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void spontaneousEventReceived(ChromeCastSpontaneousEvent event) {
|
|
|
|
switch (event.getType()) {
|
|
|
|
case MEDIA_STATUS:
|
|
|
|
break;
|
|
|
|
case STATUS:
|
2018-08-25 22:49:44 +00:00
|
|
|
su.litvak.chromecast.api.v2.Status status = (su.litvak.chromecast.api.v2.Status)event.getData();
|
|
|
|
Application app = status.getRunningApp();
|
|
|
|
ApplicationMetadata metadata = this.createMetadataFromApplication(app);
|
2018-08-11 20:06:37 +00:00
|
|
|
if (app != null) {
|
|
|
|
this.onApplicationStatusChanged(new ApplicationStatus(app.statusText));
|
|
|
|
}
|
2018-08-25 22:49:44 +00:00
|
|
|
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));
|
2018-06-23 18:12:09 +00:00
|
|
|
break;
|
|
|
|
case APPEVENT:
|
|
|
|
break;
|
|
|
|
case CLOSE:
|
2018-08-25 22:49:44 +00:00
|
|
|
this.onApplicationDisconnected(CommonStatusCodes.SUCCESS);
|
2018-06-23 18:12:09 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2018-08-11 20:06:37 +00:00
|
|
|
public void rawMessageReceived(ChromeCastRawMessage message, Long requestId) {
|
2018-06-23 18:12:09 +00:00
|
|
|
switch (message.getPayloadType()) {
|
|
|
|
case STRING:
|
2018-08-11 20:06:37 +00:00
|
|
|
String response = message.getPayloadUtf8();
|
|
|
|
if (requestId == null) {
|
|
|
|
this.onTextMessageReceived(message.getNamespace(), response);
|
|
|
|
} else {
|
|
|
|
this.onSendMessageSuccess(response, requestId);
|
2018-08-25 22:49:44 +00:00
|
|
|
this.onTextMessageReceived(message.getNamespace(), response);
|
2018-06-23 18:12:09 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case BINARY:
|
2018-08-25 22:49:44 +00:00
|
|
|
byte[] payload = message.getPayloadBinary();
|
|
|
|
this.onBinaryMessageReceived(message.getNamespace(), payload);
|
2018-06-23 18:12:09 +00:00
|
|
|
break;
|
|
|
|
}
|
2018-04-25 23:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void disconnect() {
|
2018-08-25 22:49:44 +00:00
|
|
|
try {
|
|
|
|
this.chromecast.disconnect();
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.e(TAG, "Error disconnecting chromecast: " + e.getMessage());
|
|
|
|
return;
|
|
|
|
}
|
2018-04-25 23:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void sendMessage(String namespace, String message, long requestId) {
|
|
|
|
try {
|
2018-08-11 20:06:37 +00:00
|
|
|
this.chromecast.sendRawRequest(namespace, message, requestId);
|
2018-04-25 23:25:14 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, "Error sending cast message: " + e.getMessage());
|
2018-08-25 22:49:44 +00:00
|
|
|
this.onSendMessageFailure("", requestId, CommonStatusCodes.NETWORK_ERROR);
|
2018-04-25 23:25:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void stopApplication(String sessionId) {
|
2018-06-23 18:12:09 +00:00
|
|
|
try {
|
2018-08-25 22:49:44 +00:00
|
|
|
this.chromecast.stopSession(sessionId);
|
2018-06-23 18:12:09 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, "Error sending cast message: " + e.getMessage());
|
|
|
|
return;
|
|
|
|
}
|
2018-04-25 23:25:14 +00:00
|
|
|
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());
|
2018-08-11 20:06:37 +00:00
|
|
|
this.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR);
|
2018-04-25 23:25:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.sessionId = app.sessionId;
|
|
|
|
|
2018-08-25 22:49:44 +00:00
|
|
|
ApplicationMetadata metadata = this.createMetadataFromApplication(app);
|
2018-08-11 20:06:37 +00:00
|
|
|
this.onApplicationConnectionSuccess(metadata, app.statusText, app.sessionId, true);
|
2018-04-25 23:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void joinApplication(String applicationId, String sessionId, JoinOptions joinOptions) {
|
|
|
|
Log.d(TAG, "unimplemented Method: joinApplication");
|
2018-08-11 20:06:37 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
2018-04-25 23:25:14 +00:00
|
|
|
}
|
|
|
|
}
|