/*
PebbleManager.java
Copyright (c) 2014 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.pebble.util;
import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import com.getpebble.android.kit.BuildConfig;
import com.getpebble.android.kit.PebbleKit;
import com.getpebble.android.kit.util.PebbleDictionary;
import org.deviceconnect.android.profile.util.CanvasProfileUtils;
import org.deviceconnect.message.DConnectMessage;
import org.deviceconnect.profile.BatteryProfileConstants;
import org.deviceconnect.profile.CanvasProfileConstants.Mode;
import org.deviceconnect.profile.DeviceOrientationProfileConstants;
import org.deviceconnect.profile.KeyEventProfileConstants;
import org.deviceconnect.profile.SettingProfileConstants;
import org.deviceconnect.profile.VibrationProfileConstants;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Pebbleとのやり取りを管理するクラス.
* <p>
* Pebbleへコマンドを送信するメソッドは、ほとんどがシングルスレッドで動作するように設計されている。<br/>
* なので、コマンドを並列で処理することはできない。
* </p>
* @author NTT DOCOMO, INC.
*/
public final class PebbleManager {
/** Pebble 側で作成された appinfo.json 内に記述されている UUID. */
private static final UUID MY_UUID = UUID.fromString("ecfbe3b5-65f4-4532-be4e-3d013058d1f5");
/** タイムアウト時間を定義. */
private static final int TIMEOUT = 4 * 1000;
/** profile を示すキー番号. */
public static final int KEY_PROFILE = 1;
/** interface を示すキー番号. */
public static final int KEY_INTERFACE = 2;
/** attribute を示すキー番号. */
public static final int KEY_ATTRIBUTE = 3;
/** action を示すキー番号. */
public static final int KEY_ACTION = 4;
/** Paint. */
private static Paint sPaint = new Paint();
///////// 共通
/** リザルトコード.{@value}. */
public static final int KEY_PARAM_RESULT_CODE = 100;
/** エラーコード.{@value}. */
public static final int KEY_PARAM_ERROR_CODE = 101;
/** リクエストコード.{@value}. */
public static final int KEY_PARAM_REQUEST_CODE = 102;
///////// battery
/** battery charging を表すキー番号. */
public static final int KEY_PARAM_BATTERY_CHARGING = 200;
/** battery level を表すキー番号. */
public static final int KEY_PARAM_BATTERY_LEVEL = 201;
///////// binary
/** バイナリ総受信長を表すキー番号. */
public static final int KEY_PARAM_BINARY_LENGTH = 300;
/** バイナリのパケット順を表すキー番号. */
public static final int KEY_PARAM_BINARY_INDEX = 301;
/** バイナリのデータ本体を表すキー番号. */
public static final int KEY_PARAM_BINARY_BODY = 302;
///////// device orientation
/** device orientation の x を表すキー番号. */
public static final int KEY_PARAM_DEVICE_ORIENTATION_X = 400;
/** device orientation の y を表すキー番号. */
public static final int KEY_PARAM_DEVICE_ORIENTATION_Y = 401;
/** device orientation の z を表すキー番号. */
public static final int KEY_PARAM_DEVICE_ORIENTATION_Z = 402;
/** device orientation のインターバルを表すキー番号. */
public static final int KEY_PARAM_DEVICE_ORIENTATION_INTERVAL = 403;
///////// vibration
/** バイブレーションパターンの長さを表すキー番号.*/
public static final int KEY_PARAM_VIBRATION_LEN = 500;
/** バイブレーションパターンを表すキー番号.*/
public static final int KEY_PARAM_VIBRATION_PATTERN = 501;
///////// setting
/** setting date を表すキー番号.*/
public static final int KEY_PARAM_SETTING_DATE = 600;
///////// key event
/** Key number of KeyID.*/
public static final int KEY_PARAM_KEY_EVENT_ID = 700;
/** Key number of KeyType.*/
public static final int KEY_PARAM_KEY_EVENT_KEY_TYPE = 701;
/** Key number of KeyState. */
public static final int KEY_PARAM_KEY_EVENT_KEY_STATE = 702;
/** Key State: up. */
public static final int KEY_STATE_UP = 1;
/** Key State: down. */
public static final int KEY_STATE_DOWN = 2;
/** Touch State move. */
public static final String STATE_UP = "up";
/** Touch State cancel. */
public static final String STATE_DOWN = "down";
/** get action を表す数値. */
public static final int ACTION_GET = 1;
/** post action を表す数値. */
public static final int ACTION_POST = 2;
/** put action を表す数値. */
public static final int ACTION_PUT = 3;
/** delete action を表す数値. */
public static final int ACTION_DELETE = 4;
/** event action を表す数値. */
public static final int ACTION_EVENT = 5;
/** battery profile を表す数値. */
public static final int PROFILE_BATTERY = 1;
/** device orientation を表す数値. */
public static final int PROFILE_DEVICE_ORIENTATION = 2;
/** vibration profile を表す数値. */
public static final int PROFILE_VIBRATION = 3;
/** setting profile を表す数値. */
public static final int PROFILE_SETTING = 4;
/** system profile を表す数値 system/events 用. */
public static final int PROFILE_SYSTEM = 5;
/** canvas profile を表す数値 canvas 用. */
public static final int PROFILE_CANVAS = 6;
/** Numeric value that represents the key event profile. */
public static final int PROFILE_KEY_EVENT = 7;
/** binary転送 profile を表す数値. */
public static final int PROFILE_BINARY = 255;
/** battery の全状態を表す数値. */
public static final int BATTERY_ATTRIBUTE_ALL = 1;
/** battery の充電状態を表す数値. */
public static final int BATTERY_ATTRIBUTE_CHARING = 2;
/** battery の充電容量を表す数値. */
public static final int BATTERY_ATTRIBUTE_LEVEL = 3;
/** battery の充電容量の変化イベントを表す数値. */
public static final int BATTERY_ATTRIBUTE_ON_BATTERY_CHANGE = 4;
/** battery の受電状態の変化イベントを表す数値. */
public static final int BATTERY_ATTRIBUTE_ON_CHARGING_CHANGE = 5;
/** device orientation を表す数値. */
public static final int DEVICE_ORIENTATION_ATTRIBUTE_ON_DEVICE_ORIENTATION = 1;
/** vibration attribute を表す数値. */
public static final int VIBRATION_ATTRIBUTE_VIBRATE = 1;
/** setting attribute volume を表す数値. */
public static final int SETTING_ATTRIBUTE_VOLUME = 1;
/** setting attribute date を表す数値. */
public static final int SETTING_ATTRIBUTE_DATE = 2;
/** system attribute events を表す数値. */
public static final int SYSTEM_ATTRIBUTE_EVENTS = 1;
/** canvas attribute drawImage を表す数値. */
public static final int CANVAS_ATTRBIUTE_DRAW_IMAGE = 1;
/** key event attribute ondown. */
public static final int KEY_EVENT_ATTRIBUTE_ON_DOWN = 1;
/** key event attribute onup. */
public static final int KEY_EVENT_ATTRIBUTE_ON_UP = 2;
/** key event attribute onkeychange. */
public static final int KEY_EVENT_ATTRIBUTE_ON_KEY_CHANGE = 3;
/** key event action down. */
public static final int KEY_EVENT_ACTION_DOWN = 1;
/** key event action up. */
public static final int KEY_EVENT_ACTION_UP = 2;
/** key event key ID up. */
public static final int KEY_EVENT_KEY_ID_UP = 1;
/** key event key ID select. */
public static final int KEY_EVENT_KEY_ID_SELECT = 2;
/** key event key ID down. */
public static final int KEY_EVENT_KEY_ID_DOWN = 3;
/** key event key ID back. */
public static final int KEY_EVENT_KEY_ID_BACK = 4;
/** key event key type STD_KEY. */
public static final int KEY_EVENT_KEY_TYPE_STD_KEY = 1;
/** key event key type MEDIA. */
public static final int KEY_EVENT_KEY_TYPE_MEDIA = 2;
/** key event key type DPAD_BUTTON. */
public static final int KEY_EVENT_KEY_TYPE_DPAD_BUTTON = 3;
/** key event key type USER. */
public static final int KEY_EVENT_KEY_TYPE_USER = 4;
/** pebble の横ドット数. */
public static final int PEBBLE_SCREEN_WIDTH = 144;
/** pebble の縦ドット数. */
public static final int PEBBLE_SCREEN_HEIGHT = 168;
/** 充電中を表す数値. */
public static final int BATTERY_CHARGING_ON = 1;
/** 充電中でないことを表す数値. */
public static final int BATTERY_CHARGING_OFF = 2;
/** トランザクション(通信時)の数値. */
private static final int TRANSACATION_ID = 255;
/**
* このクラスが属するコンテキスト.
*/
private Context mContext;
/**
* キューイング用スレッド.
*/
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
/**
* イベント用のリスナー管理マップ.
*/
private final Map<Integer, List<OnReceivedEventListener>> mEvtListeners
= new ConcurrentHashMap<Integer, List<OnReceivedEventListener>>();
/**
* 接続状態通知リスナー一覧.
*/
private final List<OnConnectionStatusListener> mConnectStatusListeners
= new ArrayList<OnConnectionStatusListener>();
/**
* ロックオブジェクト.
*/
private final Object mLockObj = new Object();
/**
* Pebbleからの処理結果.
*/
private final Map<Integer, PebbleDictionary> mResponseDataMap = new ConcurrentHashMap<Integer, PebbleDictionary>();
/** PebbleDictionary. */
private PebbleDictionary mRequestDictionary;
/**
* バイナリ送信状態.
*/
private enum BinarySendState {
/** 送信中. */
STATE_NONE,
/** 送信成功. */
STATE_ACK,
/** 送信失敗. */
STATE_NACK
};
/** バイナリ送信状態を保持. */
private BinarySendState mBinarySendState;
/** バイナリ送信をブロックする. */
private final Object mBinaryLockObj = new Object();
/**
* Pebbleからのイベントを受け取るためのハンドラ.
*/
private PebbleKit.PebbleDataReceiver mHandler = new PebbleKit.PebbleDataReceiver(MY_UUID) {
@Override
public void receiveData(final Context context, final int transactionId, final PebbleDictionary data) {
// Pebble に対して、可能な限り素早くACKを返す
PebbleKit.sendAckToPebble(getContext(), transactionId);
try {
executeReceivedData(data);
} catch (Exception e) {
if (BuildConfig.DEBUG) {
Log.e("Pebble", "error", e);
}
}
}
};
/**
* PebbleからのACKイベントを受け取るためのハンドラ.
*/
private PebbleKit.PebbleAckReceiver mAckHandler = new PebbleKit.PebbleAckReceiver(MY_UUID) {
@Override
public void receiveAck(final Context context, final int transactionId) {
if (transactionId == TRANSACATION_ID && mRequestDictionary != null) {
mRequestDictionary = null;
} else {
mBinarySendState = BinarySendState.STATE_ACK;
synchronized (mBinaryLockObj) {
mBinaryLockObj.notifyAll();
}
}
}
};
/**
* PebbleからのNACKイベントを受け取るためのハンドラ.
*/
private PebbleKit.PebbleNackReceiver mNackHandler = new PebbleKit.PebbleNackReceiver(MY_UUID) {
@Override
public void receiveNack(final Context context, final int transactionId) {
final PebbleDictionary dic = mRequestDictionary;
if (transactionId == TRANSACATION_ID && mRequestDictionary != null) {
// Pebbleアプリを起動要求を行った後に1回だけリトライする
sendStartAppOnPebble(new Runnable() {
@Override
public void run() {
PebbleKit.sendDataToPebble(getContext(), MY_UUID, dic);
}
});
mRequestDictionary = null;
} else {
mBinarySendState = BinarySendState.STATE_NACK;
synchronized (mBinaryLockObj) {
mBinaryLockObj.notifyAll();
}
}
}
};
/**
* Pebbleからの接続イベントを受け取るためのハンドラ.
*/
private BroadcastReceiver mConnectHandler = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
String macAddress = intent.getStringExtra("address");
if (macAddress != null) {
for (OnConnectionStatusListener l : mConnectStatusListeners) {
l.onConnect(macAddress);
}
}
}
};
/**
* Pebbleからの切断イベントを受け取るためのハンドラ.
*/
private BroadcastReceiver mDisconnectHandler = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
String macAddress = intent.getStringExtra("address");
if (macAddress != null) {
for (OnConnectionStatusListener l : mConnectStatusListeners) {
l.onDisconnect(macAddress);
}
}
}
};
/**
* コンストラクタ.
*
* @param context コンテキスト
*/
public PebbleManager(final Context context) {
mContext = context;
init();
}
/**
* コンテキストを取得する.
* @return コンテキスト
*/
public Context getContext() {
return mContext;
}
/**
* 初期化を行う.
*/
private void init() {
PebbleKit.registerReceivedDataHandler(getContext(), mHandler);
PebbleKit.registerPebbleConnectedReceiver(getContext(), mConnectHandler);
PebbleKit.registerPebbleDisconnectedReceiver(getContext(), mDisconnectHandler);
PebbleKit.registerReceivedAckHandler(getContext(), mAckHandler);
PebbleKit.registerReceivedNackHandler(getContext(), mNackHandler);
}
/**
* Pebble側のアプリを終了する.
*/
public void destory() {
getContext().unregisterReceiver(mHandler);
getContext().unregisterReceiver(mConnectHandler);
getContext().unregisterReceiver(mDisconnectHandler);
getContext().unregisterReceiver(mAckHandler);
getContext().unregisterReceiver(mNackHandler);
PebbleKit.closeAppOnPebble(getContext(), MY_UUID);
mExecutor.shutdown();
}
/**
* Pebbleにアプリ終了イベントを送信する.
*/
public void sendCloseAppOnPebble() {
PebbleKit.closeAppOnPebble(getContext(), MY_UUID);
}
/**
* イベント受信用のリスナーを設定する.
* <p>
* 指定されたプロファイルのイベントがリスナーに通知される。<br/>
* 設定されていないプロファイルの通知は無視される。
* </p>
* @param profile プロファイル
* @param listener リスナー
*/
public void addEventListener(final int profile, final OnReceivedEventListener listener) {
List<OnReceivedEventListener> listeners = mEvtListeners.get(profile);
if (listeners == null) {
listeners = new ArrayList<OnReceivedEventListener>();
mEvtListeners.put(profile, listeners);
}
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
/**
* イベント受信用のリスナーを削除する.
* @param profile 削除するリスナーのプロファイル
* @param listener イベント受信用のリスナー
*/
public void removeEventListener(final int profile,
final OnReceivedEventListener listener) {
List<OnReceivedEventListener> listeners = mEvtListeners.get(profile);
if (listeners != null) {
listeners.remove(listener);
}
}
/**
* イベント受信用のリスナーを削除する.
* @param profile 削除するリスナーのプロファイル
*/
public void removeEventListener(final int profile) {
mEvtListeners.remove(profile);
}
/**
* 接続状態通知リスナーを追加する.
*
* @param listener リスナー
*/
public void addConnectStatusListener(final OnConnectionStatusListener listener) {
mConnectStatusListeners.add(listener);
}
/**
* 接続状態通知リスナーを削除する.
*
* @param listener リスナー
*/
public void removeConnectStatusListener(final OnConnectionStatusListener listener) {
mConnectStatusListeners.remove(listener);
}
/**
* Pebble側で対応するアプリを起動させる.
*/
public void sendStartAppOnPebble() {
mExecutor.execute(new Runnable() {
@Override
public void run() {
PebbleKit.startAppOnPebble(getContext(), MY_UUID);
}
});
}
/**
* Pebbleアプリに起動依頼を送信後、数秒した後にrunを実行する.
* <p>
* この関数は、別スレッドで動作するので、他の関数とは違う挙動をする。<br/>
* なので、この関数を使用する場合には、考慮する必要が有る。
* </p>
* @param run 起動後に実行する処理
*/
private void sendStartAppOnPebble(final Runnable run) {
final int sleepMilliSec = 2000;
new Thread(new Runnable() {
@Override
public void run() {
PebbleKit.startAppOnPebble(getContext(), MY_UUID);
try {
Thread.sleep(sleepMilliSec);
run.run();
} catch (InterruptedException e) {
return;
}
}
}).start();
}
/**
* Pebbleにコマンドを送信する.
* <p>
* この関数は非同期で動作する。<br/>
* Pebbleからの返答はlistenerに返却される。
* </p>
* @param dic 送信するデータ
* @param listener リスナー
*/
public void sendCommandToPebble(final PebbleDictionary dic, final OnSendCommandListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
final int retryCountMax = 3;
PebbleDictionary result = null;
// リクエストコードを割り振る
final int requestCode = UUID.randomUUID().hashCode();
dic.addInt32(KEY_PARAM_REQUEST_CODE, requestCode);
// リクエストをキャッシュしておく
mRequestDictionary = dic;
for (int retryCount = 0; retryCount < retryCountMax; retryCount++) {
// Pebbleにデータを送信
PebbleKit.sendDataToPebbleWithTransactionId(getContext(), MY_UUID, dic, TRANSACATION_ID);
try {
synchronized (mLockObj) {
mLockObj.wait(TIMEOUT);
}
} catch (InterruptedException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
// Pebbleへの送信結果
result = mResponseDataMap.remove(requestCode);
if (result != null) {
if (mBinarySendState == BinarySendState.STATE_ACK) {
break;
}
}
}
if (listener != null) {
listener.onReceivedData(result);
}
}
});
}
/**
* 指定されたデータをPebbleに送信する.
* @param data 送信するデータ.
* @param listener 送信確認用のリスナー.
*/
public void sendDataToPebble(final byte[] data, final OnSendDataListener listener) {
mExecutor.execute(new Runnable() {
/**
* 分割するデータサイズを定義.
* 分割サイズは、Pebbleアプリ側でも定義してあるので
* 大きくする場合には、Pebbleアプリ側の定義も修正すること。
*/
private static final int BUF_SIZE = 64;
@Override
public void run() {
final int sleepMilliSec = 100;
final int streamRemainLegnth = data.length;
final int count = streamRemainLegnth / BUF_SIZE + 1;
// 最初にデータのサイズを送る
if (!sendLength(streamRemainLegnth)) {
if (listener != null) {
listener.onSend(false);
}
return;
}
// Pebbleにデータ送信
for (int i = 0; i < count; i++) {
mBinarySendState = BinarySendState.STATE_NONE;
if (!sendBody(data, i)) {
if (listener != null) {
listener.onSend(false);
}
return;
}
try {
Thread.sleep(sleepMilliSec);
} catch (InterruptedException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
if (listener != null) {
listener.onSend(true);
}
}
/**
* Pebbleからのレスポンスを待つ.
*
* @return ACKの場合はtrue、NACKの場合はfalseを返却する
*/
private boolean waitAck() {
try {
synchronized (mBinaryLockObj) {
mBinaryLockObj.wait(TIMEOUT);
}
} catch (InterruptedException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
return (mBinarySendState == BinarySendState.STATE_ACK);
}
/**
* データのサイズをPebbleに通知する.
*
* @param streamTotalLength データサイズ
* @return 通知に成功した場合はtrue、それ以外はfalse
*/
private boolean sendLength(final int streamTotalLength) {
final int retryMax = 3;
final int sleepMilliSec = 2000;
PebbleDictionary data = new PebbleDictionary();
data.addInt8(KEY_PROFILE, (byte) PROFILE_BINARY);
data.addInt32(KEY_PARAM_BINARY_LENGTH, streamTotalLength);
for (int retry = 0; retry < retryMax; retry++) {
PebbleKit.sendDataToPebble(mContext, MY_UUID, data);
if (waitAck()) {
return true;
} else {
// nackが返ってきたみたいなので、アプリ起動してから
// リトライを試みる。
PebbleKit.startAppOnPebble(getContext(), MY_UUID);
try {
Thread.sleep(sleepMilliSec);
} catch (InterruptedException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
}
return false;
}
/**
* データの中身を分割して送信する.
*
* @param stream 送信するデータ
* @param index 分割したデータのインデックス
* @return 通知に成功した場合はtrue、それ以外はfalse
*/
public boolean sendBody(final byte[] stream, final int index) {
final int retryMax = 3;
final int sleepMilliSec = 100;
byte[] send = Arrays.copyOfRange(stream, index * BUF_SIZE, (index + 1) * BUF_SIZE);
PebbleDictionary data = new PebbleDictionary();
data.addInt8(KEY_PROFILE, (byte) PROFILE_BINARY);
data.addInt16(KEY_PARAM_BINARY_INDEX, (byte) index);
data.addBytes(KEY_PARAM_BINARY_BODY, send);
for (int retry = 0; retry < retryMax; retry++) {
PebbleKit.sendDataToPebble(mContext, MY_UUID, data);
if (waitAck()) {
return true;
} else {
try {
Thread.sleep(sleepMilliSec);
} catch (InterruptedException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
}
return false;
}
});
}
/**
* PebbleにNotificationを送信する.
* <p>
* senderには、アプリ名を渡す。
* </p>
* @param title タイトル
* @param body メッセージボディ
*/
public void sendNotificationToPebble(final String title, final String body) {
ApplicationInfo info = getContext().getApplicationInfo();
String sender = getContext().getPackageManager().getApplicationLabel(info).toString();
sendNotificationToPebble(title, body, sender);
}
/**
* PebbleにNotificationを送信する.
* <p>
* titleやbodyに日本語が入った場合には文字化けが発生する。
* </p>
* @param title タイトル
* @param body メッセージボディ
* @param sender 送信者名
*/
public void sendNotificationToPebble(final String title, final String body, final String sender) {
final Map<String, String> data = new HashMap<String, String>();
data.put("title", title);
data.put("body", body);
data.put("flags", "" + Notification.FLAG_AUTO_CANCEL);
final JSONObject jsonData = new JSONObject(data);
final String notificationData = new JSONArray().put(jsonData).toString();
final Intent i = new Intent("com.getpebble.action.SEND_NOTIFICATION");
i.putExtra("messageType", "PEBBLE_ALERT");
i.putExtra("sender", sender);
i.putExtra("notificationData", notificationData);
getContext().sendBroadcast(i);
}
/**
* リクエストからPebbleDictionaryを作成する.
* <p>
* PebbleDictionaryの作成に失敗した場合はnullを返却する。
* </p>
*
* @param request リクエスト
* @return PebbleDictionaryのインスタンス
*/
public PebbleDictionary createPebbleDictionary(final Intent request) {
PebbleDictionary dic = new PebbleDictionary();
byte profile = convertProfile(request.getStringExtra(DConnectMessage.EXTRA_PROFILE));
dic.addInt8(KEY_ACTION, convertAction(request.getAction()));
dic.addInt8(KEY_PROFILE, profile);
dic.addInt8(KEY_INTERFACE, convertInterface(profile, request.getStringExtra(DConnectMessage.EXTRA_INTERFACE)));
dic.addInt8(KEY_ATTRIBUTE, convertAttribute(profile, request.getStringExtra(DConnectMessage.EXTRA_ATTRIBUTE)));
return dic;
}
/**
* 受信したデータを解析します.
* @param data 受信したデータ
*/
private void executeReceivedData(final PebbleDictionary data) {
// アクションを取得する
Long action = data.getInteger(KEY_ACTION);
if (action != null && action.intValue() == ACTION_EVENT) {
// イベント処理の場合は、各リスナーに通知を行う
Long profile = data.getInteger(KEY_PROFILE);
if (profile != null) {
int p = profile.intValue();
List<OnReceivedEventListener> listeners = mEvtListeners.get(p);
if (listeners != null) {
synchronized (listeners) {
for (OnReceivedEventListener l : listeners) {
l.onReceivedEvent(data);
}
}
}
}
} else {
// リクエストコードを取得
Long requestCode = data.getInteger(KEY_PARAM_REQUEST_CODE);
if (requestCode != null) {
// 結果をマップに保持しておく
mResponseDataMap.put(requestCode.intValue(), data);
synchronized (mLockObj) {
mLockObj.notifyAll();
}
}
}
}
/**
* ActionをPebble用のアクションに変換する.
* @param action Dconnectのアクション
* @return Pebble用のアクション
*/
private byte convertAction(final String action) {
if (DConnectMessage.METHOD_GET.equals(action)) {
return ACTION_GET;
} else if (DConnectMessage.METHOD_POST.equals(action)) {
return ACTION_POST;
} else if (DConnectMessage.METHOD_PUT.equals(action)) {
return ACTION_PUT;
} else if (DConnectMessage.METHOD_DELETE.equals(action)) {
return ACTION_DELETE;
}
return -1;
}
/**
* プロファイルをPebble用のデータに変換する.
* @param profile プロファイル名
* @return Pebble用のプロファイル
*/
private byte convertProfile(final String profile) {
if (BatteryProfileConstants.PROFILE_NAME.equals(profile)) {
return PROFILE_BATTERY;
} else if (DeviceOrientationProfileConstants.PROFILE_NAME.equals(profile)) {
return PROFILE_DEVICE_ORIENTATION;
} else if (VibrationProfileConstants.PROFILE_NAME.equals(profile)) {
return PROFILE_VIBRATION;
} else if (SettingProfileConstants.PROFILE_NAME.equals(profile)) {
return PROFILE_SETTING;
} else if (KeyEventProfileConstants.PROFILE_NAME.equals(profile)) {
return PROFILE_KEY_EVENT;
}
return -1;
}
/**
* インターフェースをPebble用のデータに変換する.
* @param profile プロファイル名
* @param inter インターフェース名
* @return Pebble用のインターフェース
*/
private byte convertInterface(final int profile, final String inter) {
return -1;
}
/**
* アトリビュートをPebble用のデータに変換する.
* @param profile プロファイル名
* @param attribute アトリビュート名
* @return Pebble用のアトリビュート
*/
private byte convertAttribute(final int profile, final String attribute) {
switch (profile) {
case PROFILE_BATTERY:
return convertBatteryAttribute(attribute);
case PROFILE_DEVICE_ORIENTATION:
return convertDeviceOrientationAttribute(attribute);
case PROFILE_KEY_EVENT:
return convertKeyEventAttribute(attribute);
default:
break;
}
return -1;
}
/**
* バッテリーのアトリビュートをPebble用に変換する.
* @param attribute アトリビュート
* @return Pebble用のアトリビュート
*/
private byte convertBatteryAttribute(final String attribute) {
if (BatteryProfileConstants.ATTRIBUTE_CHARGING.equals(attribute)) {
return BATTERY_ATTRIBUTE_CHARING;
} else if (BatteryProfileConstants.ATTRIBUTE_LEVEL.equals(attribute)) {
return BATTERY_ATTRIBUTE_LEVEL;
} else if (BatteryProfileConstants.ATTRIBUTE_ON_CHARGING_CHANGE.equals(attribute)) {
return BATTERY_ATTRIBUTE_ON_CHARGING_CHANGE;
} else if (BatteryProfileConstants.ATTRIBUTE_ON_BATTERY_CHANGE .equals(attribute)) {
return BATTERY_ATTRIBUTE_ON_BATTERY_CHANGE;
}
return -1;
}
/**
* DeviceOrientationのアトリビュートをPebble用に変換する.
* @param attribute アトリビュート
* @return Pebble用のアトリビュート
*/
private byte convertDeviceOrientationAttribute(final String attribute) {
if (DeviceOrientationProfileConstants.ATTRIBUTE_ON_DEVICE_ORIENTATION.equals(attribute)) {
return DEVICE_ORIENTATION_ATTRIBUTE_ON_DEVICE_ORIENTATION;
}
return -1;
}
/**
* Change Key Event attribute for Pebble.
* @param attribute Attribute.
* @return Attribute foe Pebble.
*/
private byte convertKeyEventAttribute(final String attribute) {
if (KeyEventProfileConstants.ATTRIBUTE_ON_DOWN.equals(attribute)) {
return KEY_EVENT_ATTRIBUTE_ON_DOWN;
} else if (KeyEventProfileConstants.ATTRIBUTE_ON_UP.equals(attribute)) {
return KEY_EVENT_ATTRIBUTE_ON_UP;
}
return -1;
}
/**
* 指定された整数の下位2byteをbyte配列に変換する.
* <p>
* 上位2byteは無視する。<br/>
* <br/>
* PebbleのVibrationの最大振動時間は10000msなので、
* shortの範囲があれば十分。
* </p>
* @param value 変換する整数
* @return byte[]
*/
private static byte[] convertIntToByte(final int value) {
final int shiftValue = 8;
final int mask = 0xff;
byte[] buf = new byte[2];
buf[0] = (byte) ((value >> shiftValue) & mask);
buf[1] = (byte) ((value & mask));
return buf;
}
/**
* Vibrationのパターンをpebbleで使用できるように変換する.
* <p>
* パターンは、100,100,100,100で、ON,OFF,ON,OFFの時間が入っている。
* </p>
* @param pattern パターン
* @return Pebble用のバイブレーションパターン
* @throws NumberFormatException 変換に失敗した場合
*/
public static byte[] convertVibrationPattern(final String pattern) {
if (pattern == null) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String[] p = pattern.split(",");
for (int i = 0; i < p.length; i++) {
int value = Integer.parseInt(p[i]);
byte[] v = convertIntToByte(value);
try {
baos.write(v);
} catch (IOException e) {
break;
}
}
return baos.toByteArray();
}
/**
* バイブレーションのパターンをPebble用に変換する.
*
* @param pattern パターンデータ
* @return Pebble用のバイブレーションパターン
*/
public static byte[] convertVibrationPattern(final long[] pattern) {
if (pattern == null || pattern.length == 0) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int i = 0; i < pattern.length; i++) {
int value = (int) pattern[i];
byte[] v = convertIntToByte(value);
try {
baos.write(v);
} catch (IOException e) {
break;
}
}
return baos.toByteArray();
}
/**
* Pebbleで読み込めるような画像に変換する.
*
* @param data 画像データ
* @param mode 画像描画モード
* @param x 画像配置座標(x)
* @param y 画像配置座標(y)
* @return 変換後のデータ / 描画モードが不正だった場合はnull
*/
public static byte[] convertImage(final byte[] data, final String mode, final double x, final double y) {
final int width = 144;
final int height = 168;
return convertImage(data, width, height, mode, x, y);
}
/**
* Pebbleで読み込めるような画像に変換する.
*
* 指定されたサイズの枠収まるように変換する。
*
* @param data 画像データ
* @param width 横幅
* @param height 縦幅
* @param mode 画像描画モード
* @param x 画像配置座標(x)
* @param y 画像配置座標(y)
* @return 変換後のデータ / 描画モードが不正だった場合はnull
*/
public static byte[] convertImage(final byte[] data, final int width, final int height,
final String mode, final double x, final double y) {
Bitmap b = BitmapFactory.decodeByteArray(data, 0, data.length);
Bitmap b2 = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// 背景を白で塗りつぶす
sPaint.setColor(Color.WHITE);
Canvas canvas = new Canvas(b2);
canvas.drawRect(0, 0, width, height, sPaint);
boolean isDraw = false;
if (mode == null || mode.equals("")) { // 等倍描画モード
CanvasProfileUtils.drawImageForNonScalesMode(b2, b, x, y);
isDraw = true;
} else if (mode.equals(Mode.SCALES.getValue())) { // スケールモード
CanvasProfileUtils.drawImageForScalesMode(b2, b);
isDraw = true;
} else if (mode.equals(Mode.FILLS.getValue())) { // フィルモード
CanvasProfileUtils.drawImageForFillsMode(b2, b);
isDraw = true;
} else {
isDraw = false;
}
if (isDraw) {
byte[] buf = PebbleBitmapUtil.convertImageThresholding(b2);
b.recycle();
b2.recycle();
return buf;
} else {
return null;
}
}
/**
* コマンドを送信した結果を受信するためのリスナー.
*/
public interface OnSendCommandListener {
/**
* コマンドの実行結果を取得する.
* <p>
* Pebbleに送信失敗した場合などは、nullが返却される。
* </p>
* @param dic 実行結果
*/
void onReceivedData(PebbleDictionary dic);
}
/**
* イベントを受信するためのリスナー.
*/
public interface OnReceivedEventListener {
/**
* イベントを受信した.
* @param dic 受信したイベント
*/
void onReceivedEvent(PebbleDictionary dic);
}
/**
* バイナリ送信を行った結果を受信するためのリスナー.
*/
public interface OnSendDataListener {
/**
* バイナリの送信結果を取得する.
* @param successed 送信に成功した場合はtrue、それ以外はfalse
*/
void onSend(boolean successed);
}
/**
* Pebble接続通知リスナー.
*/
public interface OnConnectionStatusListener {
/**
* 接続された.
* @param macAddress 接続されたPebbleのMACアドレス
*/
void onConnect(final String macAddress);
/**
* 切断された.
* @param macAddress 接続の切断されたPebbleのMACアドレス
*/
void onDisconnect(final String macAddress);
}
}