/* ChromeCastApplication.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.core; import android.content.Context; import android.os.Bundle; import android.util.Log; import com.google.android.gms.cast.Cast; import com.google.android.gms.cast.Cast.ApplicationConnectionResult; import com.google.android.gms.cast.CastDevice; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import org.deviceconnect.android.deviceplugin.chromecast.BuildConfig; import java.util.ArrayList; /** * Chromecast Controller クラス. * <p> * アプリケーションIDに対応したReceiverアプリのコントロール * </p> * @author NTT DOCOMO, INC. */ public class ChromeCastController implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { /** ロックオブジェクト. */ private final Object mLockObj = new Object(); /** 出力するログのタグ名. */ private static final String TAG = ChromeCastController.class.getSimpleName(); /** 選択したデバイスの情報. */ private CastDevice mSelectedDevice; /** GoogleAPIClient. */ private GoogleApiClient mApiClient; /** Castのリスナー. */ private Cast.Listener mCastListener; /** コンテキスト. */ private Context mContext; /** アプリID. */ private String mAppId; /** ChromecastAttach/Detach用のコールバック群. */ private ArrayList<Callbacks> mCallbacks; /** Chromecast接続完了用のコールバック群. */ private Result mResult; /** Application接続フラグ. */ private boolean mIsApplicationDisconnected = false; /** * Chromecastとの接続結果を通知するコールバックのインターフェース. * * @author NTT DOCOMO, INC. */ public interface Result { /** * ChromeCastと接続されたことを通知する。 */ void onChromeCastConnected(); } /** * ChromecastAttach/Detachイベントを通知するコールバックのインターフェース. * @author NTT DOCOMO, INC. */ public interface Callbacks { /** * Chromecast Applicationにアタッチする. */ void onAttach(); /** * Chromecast Applicationにデタッチする. */ void onDetach(); } /** * コンストラクタ. * * @param context コンテキスト * @param appId ReceiverアプリのアプリケーションID */ public ChromeCastController(final Context context, final String appId) { this.mContext = context; this.mAppId = appId; this.mSelectedDevice = null; mCallbacks = new ArrayList<Callbacks>(); } @Override public void onConnected(final Bundle connectionHint) { synchronized (mLockObj) { mLockObj.notifyAll(); } if (BuildConfig.DEBUG) { Log.d(TAG, "onConnected:"); } if (mApiClient == null) { return; } if (connectionHint != null && connectionHint.getBoolean(Cast.EXTRA_APP_NO_LONGER_RUNNING)) { teardown(); } else { launchApplication(); } } @Override public void onConnectionSuspended(final int cause) { if (BuildConfig.DEBUG) { Log.d(TAG, "onConnectionSuspended$cause: " + cause); } } @Override public void onConnectionFailed(final ConnectionResult result) { if (BuildConfig.DEBUG) { Log.d(TAG, "onConnectionFailed$result: " + result.toString()); } teardown(); } /** * GoogleApiClientを取得する. * * @return GoogleApiClient */ public GoogleApiClient getGoogleApiClient() { if (!mApiClient.isConnected()) { // 一度切断する mApiClient.disconnect(); mApiClient.connect(); reconnect(); waitForResponse(); } return mApiClient; } /** * コールバックを登録する. * * @param callbacks 追加するコールバック */ public void addCallbacks(final Callbacks callbacks) { this.mCallbacks.add(callbacks); } /** * 接続完了を通知するコールバックを登録する. * * @param result コールバック */ public void setResult(final Result result) { this.mResult = result; } /** * Chromecastデバイスをセットする. * * @param selectedDevice 選択したChromecastのデバイス */ public void setSelectedDevice(final CastDevice selectedDevice) { this.mSelectedDevice = selectedDevice; } /** * Chromecastデバイスを取得する. * * @return CastDevice */ public CastDevice getSelectedDevice() { return mSelectedDevice; } /** * GooglePlayServiceに接続し、Receiverアプリケーションを起動する. * */ public void connect() { if (mApiClient != null && mIsApplicationDisconnected) { mIsApplicationDisconnected = false; launchApplication(); } if (mApiClient == null) { mIsApplicationDisconnected = false; mCastListener = new Cast.Listener() { @Override public void onApplicationDisconnected(final int statusCode) { if (BuildConfig.DEBUG) { Log.d(TAG, "onApplicationDisconnected$statusCode: " + statusCode); } mIsApplicationDisconnected = true; } @Override public void onApplicationStatusChanged() { if (BuildConfig.DEBUG) { Log.d(TAG, "onApplicationStatusChanged"); } } }; Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(mSelectedDevice, mCastListener); mApiClient = new GoogleApiClient.Builder(mContext) .addApi(Cast.API, apiOptionsBuilder.build()) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); mApiClient.connect(); } } /** * Receiverアプリケーションを終了し、GooglePlayServiceから切断し、再接続する. * */ public void reconnect() { stopApplication(true); } /** * Receiverアプリケーションを終了し、GooglePlayServiceから切断する. * */ public void teardown() { stopApplication(false); } /** * Receiverアプリケーションを起動する. * */ private void launchApplication() { if (mApiClient != null && mApiClient.isConnected()) { Cast.CastApi.launchApplication(mApiClient, mAppId) .setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() { @Override public void onResult(final ApplicationConnectionResult result) { Status status = result.getStatus(); if (status.isSuccess()) { if (BuildConfig.DEBUG) { Log.d("TEST", "launchApplication$onResult: Success"); } for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onAttach(); } } else { if (BuildConfig.DEBUG) { Log.d("TEST", "launchApplication$onResult: Fail"); } teardown(); } if (mResult != null) { mResult.onChromeCastConnected(); } } }); } } /** * Receiverアプリケーションを停止する. * <p> * 停止後、再接続することもできる * </p> */ private void stopApplication(final boolean isReconect) { if (mApiClient != null && mApiClient.isConnected()) { // Cast.CastApi.leaveApplication(mApiClient); Cast.CastApi.stopApplication(mApiClient).setResultCallback(new ResultCallback<Status>() { @Override public void onResult(final Status result) { if (result.getStatus().isSuccess()) { if (BuildConfig.DEBUG) { Log.d("TEST", "stopApplication$onResult: Success"); } for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onDetach(); } mApiClient.disconnect(); mApiClient = null; mSelectedDevice = null; if (isReconect) { launchApplication(); } } else { if (BuildConfig.DEBUG) { Log.d("TEST", "stopApplication$onResult: Fail"); } } } }); } } /** * レスポンスが返ってくるまでの間スレッドを停止する. * タイムアウトは設定していない。 */ private void waitForResponse() { synchronized (mLockObj) { try { mLockObj.wait(5000); } catch (InterruptedException e) { Log.e(TAG, "InterruptedException occurred in waitForResponse."); } } } }