package org.deviceconnect.android.deviceplugin.wear;
import android.content.Context;
import android.os.Bundle;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import org.deviceconnect.android.deviceplugin.wear.profile.WearConst;
import org.deviceconnect.android.logger.AndroidHandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
/**
* Android Wearを管理するクラス.
*
* @author NTT DOCOMO, INC.
*/
public class WearManager implements ConnectionCallbacks, OnConnectionFailedListener {
private final Logger mLogger = Logger.getLogger("dconnect.wear");
/**
* Google Play Service.
*/
private GoogleApiClient mGoogleApiClient;
/**
* コンテキスト.
*/
private final Context mContext;
/**
* スレッド管理用クラス.
*/
private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
/**
* メッセージイベントリスナー一覧.
*/
private final Map<String, OnMessageEventListener> mOnMessageEventListeners
= new HashMap<String, OnMessageEventListener>();
/**
* ノード検知リスナー一覧.
*/
private final List<NodeEventListener> mNodeEventListeners = new ArrayList<NodeEventListener>();
/**
* ノード情報のキャッシュ.
*/
private final Map<String, Node> mNodeCache = new HashMap<String, Node>();
/**
* コンストラクタ.
*
* @param context このクラスが属するコンテキスト
*/
public WearManager(final Context context) {
mContext = context;
if (BuildConfig.DEBUG) {
AndroidHandler handler = new AndroidHandler(mLogger.getName());
handler.setFormatter(new SimpleFormatter());
handler.setLevel(Level.ALL);
mLogger.addHandler(handler);
mLogger.setLevel(Level.ALL);
} else {
mLogger.setLevel(Level.OFF);
}
}
@Override
public void onConnectionFailed(final ConnectionResult result) {
}
@Override
public void onConnected(final Bundle bundle) {
setNodeListener();
setMessageListener();
getNodes(new OnNodeResultListener() {
@Override
public void onResult(final NodeApi.GetConnectedNodesResult result) {
List<Node> nodes = result.getNodes();
if (nodes != null) {
synchronized (mNodeCache) {
for (Node node : nodes) {
if (!mNodeCache.containsKey(node.getId())) {
mNodeCache.put(node.getId(), node);
mLogger.info("getNodes: name = " + node.getDisplayName()
+ ", id = " + node.getId());
notifyOnNodeConnected(node);
}
}
}
}
}
});
}
@Override
public void onConnectionSuspended(final int state) {
}
/**
* このクラスを初期化する.
*/
public void init() {
if (mGoogleApiClient != null) {
return;
}
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
}
/**
* 後始末処理を行う.
*/
public void destroy() {
mExecutorService.shutdown();
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
mNodeEventListeners.clear();
mOnMessageEventListeners.clear();
mNodeCache.clear();
}
/**
* Android Wearとの接続状態を取得する.
*
* @return true接続中、それ以外はfalse
*/
public boolean isConnected() {
if (mGoogleApiClient != null) {
return mGoogleApiClient.isConnected();
}
return false;
}
/**
* メッセージイベントリスナーを追加する.
*
* @param path パス
* @param listener リスナー
*/
public void addMessageEventListener(final String path, final OnMessageEventListener listener) {
mOnMessageEventListeners.put(path, listener);
}
/**
* ノード検知リスナーを追加する.
*/
public void addNodeListener(final NodeEventListener listener) {
synchronized (mNodeEventListeners) {
mNodeEventListeners.add(listener);
}
}
private void setNodeListener() {
Wearable.NodeApi.addListener(mGoogleApiClient, new NodeApi.NodeListener() {
@Override
public void onPeerConnected(final Node node) {
mLogger.info("onPeerConnected: name = " + node.getDisplayName()
+ ", id = " + node.getId());
mNodeCache.put(node.getId(), node);
notifyOnNodeConnected(node);
}
@Override
public void onPeerDisconnected(final Node node) {
mLogger.info("onPeerDisconnected: name = " + node.getDisplayName()
+ ", id = " + node.getId());
mNodeCache.remove(node.getId());
notifyOnNodeDisconnected(node);
}
});
}
private void notifyOnNodeConnected(final Node node) {
synchronized (mNodeEventListeners) {
for (NodeEventListener listener : mNodeEventListeners) {
listener.onNodeConnected(node);
}
}
}
private void notifyOnNodeDisconnected(final Node node) {
synchronized (mNodeEventListeners) {
for (NodeEventListener listener : mNodeEventListeners) {
listener.onNodeDisconnected(node);
}
}
}
/**
* Android Wearのリスナーを設定する.
*/
private void setMessageListener() {
Wearable.MessageApi.addListener(mGoogleApiClient, new MessageApi.MessageListener() {
@Override
public void onMessageReceived(final MessageEvent messageEvent) {
final String data = new String(messageEvent.getData());
final String path = messageEvent.getPath();
final String nodeId = messageEvent.getSourceNodeId();
mLogger.info("onMessageReceived: path = " + path + ":node:" + nodeId);
OnMessageEventListener listener = mOnMessageEventListeners.get(path);
if (listener != null) {
listener.onEvent(nodeId, data);
}
}
});
}
/**
* Wear nodeを取得.
*
* @param listener Wear node取得を通知するリスナー
*/
public void getNodes(final OnNodeResultListener listener) {
sendMessageToWear(new Runnable() {
public void run() {
NodeApi.GetConnectedNodesResult result = Wearable.NodeApi
.getConnectedNodes(mGoogleApiClient).await();
if (listener != null) {
listener.onResult(result);
}
}
});
}
/**
* メッセージをWearに送信する.
*
* @param dest 送信先のWearのnodeId
* @param action メッセージのアクション
* @param message メッセージ
* @param listener メッセージを送信した結果を通知するリスナー
*/
public void sendMessageToWear(final String dest, final String action, final String message,
final OnMessageResultListener listener) {
sendMessageToWear(new Runnable() {
@Override
public void run() {
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
MessageApi.SendMessageResult result = null;
for (Node node : nodes.getNodes()) {
if (node.getId().indexOf(dest) != -1) {
result = Wearable.MessageApi.sendMessage(
mGoogleApiClient, node.getId(), action, message.getBytes()).await();
}
}
if (result != null) {
if (listener != null) {
listener.onResult(result);
}
} else {
if (listener != null) {
listener.onError();
}
}
}
});
}
/**
* PutDataRequestを作成する.
*
* @param nodeId ノードID
* @param requestId リクエストID
* @param data requestに格納する画像
* @param x x座標
* @param y y座標
* @param mode 描画モード
* @return PutDataRequestのインスタンス
*/
private PutDataRequest createPutDataRequest(final String nodeId, final String requestId, final byte[] data,
final int x, final int y, final int mode) {
Asset asset = Asset.createFromBytes(data);
if (asset == null) {
return null;
}
PutDataMapRequest dataMap = PutDataMapRequest.create(WearConst.PATH_CANVAS + "/" + nodeId + "/" + requestId);
dataMap.getDataMap().putAsset(WearConst.PARAM_BITMAP, asset);
dataMap.getDataMap().putInt(WearConst.PARAM_X, x);
dataMap.getDataMap().putInt(WearConst.PARAM_Y, y);
dataMap.getDataMap().putInt(WearConst.PARAM_MODE, mode);
dataMap.getDataMap().putLong(WearConst.TIMESTAMP,
System.currentTimeMillis());
PutDataRequest request = dataMap.asPutDataRequest();
return request;
}
/**
* 画像データを送信する.
*
* @param nodeId ノードID
* @param requestId リクエストID
* @param data 画像データ
* @param x x座標
* @param y y座標
* @param mode 描画モード
* @param listener 送信結果を通知するリスナー
*/
public void sendImageData(final String nodeId, final String requestId,
final byte[] data, final int x, final int y,
final int mode, final OnDataItemResultListener listener) {
// リクエストIDとともに画像送信
sendMessageToWear(new Runnable() {
@Override
public void run() {
final PutDataRequest request = createPutDataRequest(nodeId, requestId, data, x, y, mode);
if (request == null) {
if (listener != null) {
listener.onError();
}
} else {
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi
.putDataItem(mGoogleApiClient, request);
DataApi.DataItemResult result = pendingResult.await();
if (result != null) {
if (listener != null) {
listener.onResult(result);
}
} else {
if (listener != null) {
listener.onError();
}
}
}
}
});
}
/**
* Wearにメッセージを送ります.
*
* @param run 送るメッセージを実行するrunnable
*/
private void sendMessageToWear(final Runnable run) {
mExecutorService.execute(run);
}
public void getLocalNodeId(final OnLocalNodeListener listener) {
sendMessageToWear(new Runnable() {
@Override
public void run() {
PendingResult<NodeApi.GetLocalNodeResult> pendingResult = Wearable.NodeApi.getLocalNode(mGoogleApiClient);
NodeApi.GetLocalNodeResult result = pendingResult.await();
if (result.getStatus().isSuccess()) {
if (listener != null) {
listener.onResult(result);
}
} else {
if (listener != null) {
listener.onError();
}
}
}
});
}
/**
* ノード検知イベントリスナー.
*/
public interface NodeEventListener {
/**
* ノードとの接続イベント.
* @param node ノード
*/
void onNodeConnected(Node node);
/**
* ノードとの接続の切断イベント.
* @param node ノード
*/
void onNodeDisconnected(Node node);
}
/**
* Nodeの検索結果を通知するリスナー.
*/
public interface OnNodeResultListener {
/**
* 結果を通知する.
*
* @param result 検索結果
*/
void onResult(NodeApi.GetConnectedNodesResult result);
}
public interface OnLocalNodeListener {
void onResult(NodeApi.GetLocalNodeResult localNode);
void onError();
}
/**
* メッセージ送信の結果を通知するリスナー.
*/
public interface OnMessageResultListener {
/**
* メッセージ送信結果を通知する.
*
* @param result 結果
*/
void onResult(MessageApi.SendMessageResult result);
/**
* エラーが発生したことを通知する.
*/
void onError();
}
/**
* データ送信の結果を通知するリスナー.
*/
public interface OnDataItemResultListener {
/**
* データ送信の結果を通知する.
*
* @param result 結果
*/
void onResult(DataApi.DataItemResult result);
/**
* エラーが発生したことを通知する.
*/
void onError();
}
/**
* イベント受信を通知するリスナー.
*/
public interface OnMessageEventListener {
/**
* 受信したイベントを通知する.
*
* @param nodeId ノートID
* @param message イベントメッセージ
*/
void onEvent(String nodeId, String message);
}
}