/*
ChromeCastService.java
Copyright (c) 2014 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.chromecast;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.RemoteMediaPlayer.MediaChannelResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Status;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastController;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastDiscovery;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastHttpServer;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastMediaPlayer;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastMessage;
import org.deviceconnect.android.deviceplugin.chromecast.profile.ChromeCastServiceDiscoveryProfile;
import org.deviceconnect.android.deviceplugin.chromecast.profile.ChromeCastSystemProfile;
import org.deviceconnect.android.event.Event;
import org.deviceconnect.android.event.EventManager;
import org.deviceconnect.android.event.cache.db.DBCacheController;
import org.deviceconnect.android.message.DConnectMessageService;
import org.deviceconnect.android.message.MessageUtils;
import org.deviceconnect.android.profile.MediaPlayerProfile;
import org.deviceconnect.android.profile.SystemProfile;
import org.deviceconnect.android.service.DConnectService;
import org.deviceconnect.android.service.DConnectServiceListener;
import org.deviceconnect.message.DConnectMessage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* メッセージサービス (Chromecast).
* <p>
* Chromecastデバイスプラグインのサービス
* </p>
* @author NTT DOCOMO, INC.
*/
public class ChromeCastService extends DConnectMessageService implements
ChromeCastMediaPlayer.Callbacks,
ChromeCastMessage.Callbacks,
ChromeCastDiscovery.Callbacks,
ChromeCastController.Result,
DConnectServiceListener {
/**
* Chromecastのサーバポート.
*/
private static final int SERVER_PORT = 38088;
/** Chromecast MediaPlayer. */
private ChromeCastMediaPlayer mMediaPlayer;
/** Chromecast Message. */
private ChromeCastMessage mMessage;
/** ChromecastHttpServer. */
private ChromeCastHttpServer mServer;
/** StatusChange時のServiceId. */
private String mServiceIdOnStatusChange = null;
/** MediaPlayerのステータスアップデートフラグ. */
private boolean mEnableCastMediaPlayerStatusUpdate = false;
/**
* ChromeCastが接続完了してからレスポンスを返すためのCallbackを返す.
* @author NTT DOCOMO, INC.
*/
public interface Callback {
/**
* レスポンス.
* @param connected true : 接続されている, false : 接続されていない
*/
void onResponse(final boolean connected);
}
@Override
public void onCreate() {
super.onCreate();
ChromeCastApplication app = (ChromeCastApplication) getApplication();
app.initialize();
app.getDiscovery().setCallbacks(this);
app.getController().setResult(this);
String appMsgUrn = getString(R.string.application_message_urn);
int portCount = 0;
while (portCount < 500) { // Portを決定する
try {
mServer = new ChromeCastHttpServer(this, "0.0.0.0", SERVER_PORT + portCount);
mServer.start();
break;
} catch (IOException e) {
portCount++;
}
}
mMediaPlayer = new ChromeCastMediaPlayer(app.getController());
mMediaPlayer.setCallbacks(this);
mMessage = new ChromeCastMessage(app.getController(), appMsgUrn);
mMessage.setCallbacks(this);
EventManager.INSTANCE.setController(new DBCacheController(this));
addProfile(new ChromeCastServiceDiscoveryProfile(getServiceProvider()));
}
@Override
public void onDestroy() {
mServer.stop();
super.onDestroy();
}
@Override
protected SystemProfile getSystemProfile() {
return new ChromeCastSystemProfile();
}
@Override
public void onServiceAdded(final DConnectService service) {
if (BuildConfig.DEBUG) {
Log.i("TEST", "onServiceAdded: " + service.getName());
}
// NOP.
}
@Override
public void onServiceRemoved(final DConnectService service) {
if (BuildConfig.DEBUG) {
Log.i("TEST", "onServiceRemoved: " + service.getName());
}
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app != null) {
app.getController().teardown();
}
}
@Override
public void onStatusChange(final DConnectService service) {
if (BuildConfig.DEBUG) {
Log.i("TEST", "onStatusChange: " + service.getName());
}
// NOP.
}
@Override
protected void onManagerUninstalled() {
// Managerアンインストール検知時の処理。
if (BuildConfig.DEBUG) {
Log.i("TEST", "Plug-in : onManagerUninstalled");
}
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app != null) {
app.getController().teardown();
}
}
@Override
protected void onManagerTerminated() {
// Manager正常終了通知受信時の処理。
if (BuildConfig.DEBUG) {
Log.i("TEST", "Plug-in : onManagerTerminated");
}
}
@Override
protected void onManagerEventTransmitDisconnected(String sessionKey) {
// ManagerのEvent送信経路切断通知受信時の処理。
if (BuildConfig.DEBUG) {
Log.i("TEST", "Plug-in : onManagerEventTransmitDisconnected");
}
if (sessionKey != null) {
EventManager.INSTANCE.removeEvents(sessionKey);
} else {
EventManager.INSTANCE.removeAll();
}
}
@Override
protected void onDevicePluginReset() {
// Device Plug-inへのReset要求受信時の処理。
if (BuildConfig.DEBUG) {
Log.i("TEST", "Plug-in : onDevicePluginReset");
}
resetPluginResource();
}
/**
* リソースリセット処理.
*/
private void resetPluginResource() {
/** 全イベント削除. */
EventManager.INSTANCE.removeAll();
onCastDeviceUnselected(null);
}
/**
* ChromeCastDiscoveryを返す.
* @return ChromeCastDiscovery
*/
public ChromeCastDiscovery getChromeCastDiscovery() {
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app == null) {
return null;
}
return app.getDiscovery();
}
/**
* ChromeCastMediaPlayerを返す.
* @return ChromeCastMediaPlayer
*/
public ChromeCastMediaPlayer getChromeCastMediaPlayer() {
return mMediaPlayer;
}
/**
* ChromeCastMessageを返す.
* @return ChromeCastMessage
*/
public ChromeCastMessage getChromeCastMessage() {
return mMessage;
}
/**
* ChromeCastHttpServerを返す.
* @return ChromeCastHttpServer
*/
public ChromeCastHttpServer getChromeCastHttpServer() {
return mServer;
}
/**
* StatusChange通知を有効にする.
*
* @param response レスポンス
* @param serviceId デバイスを識別するID
*/
public void registerOnStatusChange(final Intent response, final String serviceId) {
mServiceIdOnStatusChange = serviceId;
mEnableCastMediaPlayerStatusUpdate = true;
response.putExtra(DConnectMessage.EXTRA_RESULT, DConnectMessage.RESULT_OK);
response.putExtra(DConnectMessage.EXTRA_VALUE, "Register OnStatusChange event");
sendResponse(response);
}
/**
* StatusChange通知を無効にする.
*
* @param response レスポンス
*/
public void unregisterOnStatusChange(final Intent response) {
mServiceIdOnStatusChange = null;
mEnableCastMediaPlayerStatusUpdate = false;
response.putExtra(DConnectMessage.EXTRA_RESULT, DConnectMessage.RESULT_OK);
response.putExtra(DConnectMessage.EXTRA_VALUE, "Unregister OnStatusChange event");
sendResponse(response);
}
@Override
public void onChromeCastMediaPlayerStatusUpdate(final MediaStatus status) {
MediaInfo info = status.getMediaInfo();
ChromeCastDeviceService service = (ChromeCastDeviceService) getServiceProvider().getService(mServiceIdOnStatusChange);
if (service == null) {
return;
}
String playStatusString = service.getMediaPlayerProfile().getPlayStatus(status.getPlayerState());
if (mEnableCastMediaPlayerStatusUpdate) {
List<Event> events = EventManager.INSTANCE.getEventList(mServiceIdOnStatusChange,
MediaPlayerProfile.PROFILE_NAME, null,
MediaPlayerProfile.ATTRIBUTE_ON_STATUS_CHANGE);
for (int i = 0; i < events.size(); i++) {
Event event = events.get(i);
Intent intent = EventManager.createEventMessage(event);
MediaPlayerProfile.setAttribute(intent,
MediaPlayerProfile.ATTRIBUTE_ON_STATUS_CHANGE);
Bundle mediaPlayer = new Bundle();
MediaPlayerProfile.setStatus(mediaPlayer, playStatusString);
if (info != null) {
MediaPlayerProfile.setMediaId(mediaPlayer, info.getContentId());
MediaPlayerProfile.setMIMEType(mediaPlayer, info.getContentType());
} else {
MediaPlayerProfile.setMediaId(mediaPlayer, "");
MediaPlayerProfile.setMIMEType(mediaPlayer, "");
}
MediaPlayerProfile.setPos(mediaPlayer, (int) status.getStreamPosition() / 1000);
MediaPlayerProfile.setVolume(mediaPlayer, status.getStreamVolume());
MediaPlayerProfile.setMediaPlayer(intent, mediaPlayer);
sendEvent(intent, event.getAccessToken());
}
}
}
/**
* ステータスに基づいて、レスポンスする.
* @param response レスポンス
* @param result Chromecastからの状態
* @param message Chromecastの状態
*/
private void onChromeCastResult(final Intent response, final Status result, final String message) {
if (result == null) {
MessageUtils.setIllegalDeviceStateError(response, message);
} else {
if (result.isSuccess()) {
response.putExtra(DConnectMessage.EXTRA_RESULT, DConnectMessage.RESULT_OK);
} else {
if (message == null) {
MessageUtils.setIllegalDeviceStateError(response);
} else {
MessageUtils.setIllegalDeviceStateError(response, message + " is error");
}
}
}
sendResponse(response);
}
@Override
public void onChromeCastMediaPlayerResult(final Intent response,
final MediaChannelResult result, final String message) {
if (result == null) {
MessageUtils.setIllegalDeviceStateError(response, message);
sendResponse(response);
} else {
onChromeCastResult(response, result.getStatus(), message);
}
}
@Override
public void onChromeCastMessageResult(final Intent response, final Status result, final String message) {
onChromeCastResult(response, result, message);
}
@Override
public void onCastDeviceUpdate(final ArrayList<CastDevice> devices) {
if (BuildConfig.DEBUG) {
Log.d("TEST", "onCastDeviceUpdate#");
}
if (devices.size() == 0) {
if (BuildConfig.DEBUG) {
Log.d("TEST", "size:0");
}
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app.getController() != null) {
app.getController().teardown();
}
}
}
@Override
public void onCastDeviceSelected(final CastDevice selectedDevice) {
if (BuildConfig.DEBUG) {
Log.d("TEST", "onCastDeviceSelected#" + selectedDevice.getDeviceId());
}
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app == null) {
return;
}
CastDevice currentDevice = app.getController().getSelectedDevice();
if (currentDevice != null) {
DConnectService castService = getServiceProvider().getService(currentDevice.getDeviceId());
if (castService == null) {
castService = new ChromeCastDeviceService(currentDevice);
getServiceProvider().addService(castService);
}
castService.setOnline(true);
if (!currentDevice.getDeviceId().equals(selectedDevice.getDeviceId())) {
app.getController().setSelectedDevice(selectedDevice);
app.getController().reconnect();
} else {
app.getController().connect();
}
} else {
app.getController().setSelectedDevice(selectedDevice);
app.getController().connect();
}
}
@Override
public void onCastDeviceUnselected(final CastDevice unselectedDevice) {
if (BuildConfig.DEBUG) {
Log.d("TEST", "onCastDeviceUnselected#start");
}
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app == null) {
return;
}
CastDevice currentDevice = unselectedDevice;
if (currentDevice == null) {
if (app.getController().getSelectedDevice() == null) {
return;
}
currentDevice = app.getController().getSelectedDevice();
}
if (BuildConfig.DEBUG) {
Log.d("TEST", "onCastDeviceUnselected#start+ " + currentDevice.getDeviceId());
}
DConnectService castService = getServiceProvider().getService(currentDevice.getDeviceId());
if (castService != null) {
castService.setOnline(false);
}
app.getController().teardown();
}
@Override
public synchronized void onChromeCastConnected() {
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app != null) {
CastDevice currentDevice = app.getController().getSelectedDevice();
DConnectService castService = getServiceProvider().getService(currentDevice.getDeviceId());
if (castService == null) {
castService = new ChromeCastDeviceService(currentDevice);
getServiceProvider().addService(castService);
}
castService.setOnline(true);
}
}
/**
* 選択されたChromeCastと接続する.
* @param serviceId サービスID
* @param callback 非同期処理用Callback
*/
public synchronized void connectChromeCast(final String serviceId,
final Callback callback) {
ChromeCastApplication app = (ChromeCastApplication) getApplication();
if (app == null) {
callback.onResponse(false);
return;
}
if (app.getDiscovery().getSelectedDevice() != null) {
// Whether application that had been started before whether other apps
try {
GoogleApiClient client = app.getController().getGoogleApiClient();
if (client == null || (client != null && !client.isConnected())) {
// Request in connection queuing
callback.onResponse(false);
return;
}
callback.onResponse(true);
} catch (IllegalStateException e) {
callback.onResponse(false);
return;
}
}
}
}