/* SimpleBotService.java Copyright (c) 2016 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.app.simplebot; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.os.Build; import android.os.IBinder; import android.os.PowerManager; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.util.Log; import com.x5.template.Chunk; import org.deviceconnect.android.app.simplebot.data.DataManager; import org.deviceconnect.android.app.simplebot.data.ResultData; import org.deviceconnect.android.app.simplebot.data.SettingData; import org.deviceconnect.android.app.simplebot.utils.DConnectHelper; import org.deviceconnect.android.app.simplebot.utils.Utils; import org.deviceconnect.message.DConnectEventMessage; import org.deviceconnect.message.DConnectMessage; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * SimpleBotサービス. * * @author NTT DOCOMO, INC. */ public class SimpleBotService extends Service { /** * サービス停止アクション */ public static final String SERVICE_STOP_ACTION = "org.deviceconnect.android.app.simplebot.service_stop"; /** * デバッグタグ */ private static final String TAG = "SimpleBotService"; /** * Notification ID. */ private static final int ONGOING_NOTIFICATION_ID = 45632; /** * Device Connect Managerと接続を監視するスレッドクラス. */ private final ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor(); /** * 監視スレッドをキャンセルするためのオブジェクト. */ private ScheduledFuture mScheduledFuture; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); if (BuildConfig.DEBUG) { Log.d(TAG, "service started..."); } connect(); startMonitoringDeviceConnectManager(); showNotification(getString(R.string.connecting_service)); registerDozeStateReceiver(); } @Override public void onDestroy() { if (BuildConfig.DEBUG) { Log.d(TAG, "service destroyed..."); } unregisterDozeStateReceiver(); hideNotification(); stopMonitoringDeviceConnectManager(); disconnect(); notifyStopAction(); super.onDestroy(); } /** * サービス停止をBroadcastで通知を行う. */ private void notifyStopAction() { // サービス停止を通知 Intent broadcastIntent = new Intent(); broadcastIntent.setAction(SERVICE_STOP_ACTION); getBaseContext().sendBroadcast(broadcastIntent); } /** * 通知バーにNotificationを表示して、サービスをフォアグランドに登録する. * @param content 通知に表示する文字列 */ private void showNotification(final String content) { Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity( getApplicationContext(), 0, notificationIntent, 0); NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); builder.setContentIntent(pendingIntent); builder.setTicker(getString(R.string.app_name)); builder.setContentTitle(getString(R.string.app_name)); builder.setContentText(content); builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(ONGOING_NOTIFICATION_ID, builder.build()); } /** * 通知バーにNotificationを非表示する. */ private void hideNotification() { stopForeground(true); } /** * Device Connect Managerの生存確認を開始する. */ private void startMonitoringDeviceConnectManager() { if (BuildConfig.DEBUG) { Log.d(TAG, "startMonitoringDeviceConnectManager"); } mScheduledFuture = mExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (BuildConfig.DEBUG) { Log.i(TAG, "Check WebSocket."); } if (!DConnectHelper.INSTANCE.isOpenWebSocket()) { showNotification(getString(R.string.disconnected_service)); connect(); } } }, 15, 15, TimeUnit.SECONDS); } /** * Device Connect Managerの生存確認を停止する. */ private void stopMonitoringDeviceConnectManager() { if (BuildConfig.DEBUG) { Log.d(TAG, "stopMonitoringDeviceConnectManager"); } if (mScheduledFuture != null) { mScheduledFuture.cancel(true); mScheduledFuture = null; } } /** * Device Connect Managerと接続を行う. */ private void connect() { // アクティブじゃない場合は終了 final Context context = getApplicationContext(); final SettingData setting = SettingData.getInstance(context); if (!setting.active) { // サービス終了 stopSelf(); return; } // イベントハンドラー登録 DConnectHelper.INSTANCE.setEventHandler(new DConnectHelper.EventHandler() { @Override public void onEvent(DConnectEventMessage event) { handleEvent(event); } }); // Device Connect Managerの生存確認 Utils.availability(context, new DConnectHelper.FinishCallback<Void>() { @Override public void onFinish(Void aVoid, Exception error) { if (error == null) { // イベント登録 Utils.registerEvent(context, new DConnectHelper.FinishCallback<Void>() { @Override public void onFinish(Void aVoid, Exception error) { if (error != null) { Log.e(TAG, "Error on registerEvent", error); setting.save(); showNotification(getString(R.string.disconnected_service)); } else { showNotification(getString(R.string.connected_service)); } } }); } else { showNotification(getString(R.string.disconnected_service)); } } }); } /** * Device Connect Managerとの接続を切断する. */ private void disconnect() { // イベントハンドラー登録 DConnectHelper.INSTANCE.setEventHandler(null); // イベント解除 Utils.unregisterEvent(getApplicationContext()); } /** * messageHookからのイベントを受領し処理を行う. * * @param event イベント */ private void handleEvent(final DConnectEventMessage event) { if (BuildConfig.DEBUG) { Log.d(TAG, "handleEvent: " + event.toString(4)); } if (!event.keySet().contains("message")) { return; } ResultData.Result result = new ResultData.Result(); DConnectMessage message = event.getMessage("message"); // Direct or Mentionのみ処理する if (!message.keySet().contains("messageType") || !(message.getString("messageType").contains("direct") || message.getString("messageType").contains("mention"))) { return; } // textとchannelIdを取得 if (message.keySet().contains("text")) { result.text = message.getString("text"); result.channel = message.getString("channelId"); result.from = message.getString("from"); } else { return; } // 登録コマンドを取得 DataManager dm = new DataManager(getApplicationContext()); Cursor cursor = dm.getAll(); if (cursor.moveToFirst()) { do { // 各コマンドを判定 result.data = dm.convertData(cursor); if (handleData(result)) { break; } } while (cursor.moveToNext()); } cursor.close(); } /** * 各コマンドを処理する. * * @param result 結果 * @return 処理した場合はtrue */ private boolean handleData(final ResultData.Result result) { final DataManager.Data data = result.data; if (data.serviceId == null) { return false; } final Context context = getApplicationContext(); // 正規表現で検索 Pattern pattern = Pattern.compile(data.keyword); Matcher matcher = pattern.matcher(result.text); if (!matcher.find()) { // 一致せず return false; } // 一致 if (BuildConfig.DEBUG) Log.d(TAG, "match:" + data.keyword); // パラメータ作成 final Map<String, Object> params = Utils.jsonToMap(data.body); if (params != null) { for (String key : params.keySet()) { String val = (String) params.get(key); // groupをパラメータに渡す for (int i = 0; i < matcher.groupCount() + 1; i++) { val = val.replace("$" + i, matcher.group(i)); if (BuildConfig.DEBUG) Log.d(TAG, matcher.group(i)); } params.put(key, val); } if (BuildConfig.DEBUG) Log.d(TAG, params.toString()); } // Slackに受付メッセージ送信する. if ((data.accept != null && data.accept.length() > 0) || (data.acceptUri != null && data.acceptUri.length() > 0)) { Utils.sendMessage(context, result.channel, data.accept, data.acceptUri, new DConnectHelper.FinishCallback<Void>() { @Override public void onFinish(Void aVoid, Exception error) { if (error != null) { Log.e(TAG, "Error on sendMessage", error); } } }); } // リクエスト送信 // コマンドに記載されたリクエストをDevice Connect Managerに送信する // 実行した結果を、メッセージとしてSlackに送信する。 Utils.sendRequest(context, result.data.method, result.data.path, result.data.serviceId, params, new DConnectHelper.FinishCallback<Map<String, Object>>() { @Override public void onFinish(Map<String, Object> stringObjectMap, Exception error) { if (error == null) { if (BuildConfig.DEBUG) Log.d(TAG, stringObjectMap.toString()); sendResponse(result, result.data.success, result.data.successUri, stringObjectMap); } else { if (BuildConfig.DEBUG) Log.e(TAG, "Error on sendRequest", error); sendResponse(result, result.data.error, result.data.errorUri, stringObjectMap); } } }); return true; } /** * Slackに処理結果を送信する. * * @param result 結果 * @param text Text * @param uri リソースURI * @param response Response */ private void sendResponse(final ResultData.Result result, final String text, final String uri, final Map<String, Object> response) { // レスポンスがない if ((text == null || text.length() == 0) && (uri == null || uri.length() == 0)) { // 履歴に保存 ResultData.INSTANCE.add(result); return; } // ChunkでTemplate処理 try { Chunk chunk = new Chunk(); chunk.append(text); chunk.putAll(response); result.response = chunk.toString(); chunk = new Chunk(); chunk.append(uri); chunk.putAll(response); result.responseUri = chunk.toString(); } catch (Exception e) { Log.e(TAG, "Error on Chunk command", e); return; } if (BuildConfig.DEBUG) { Log.d(TAG, result.response); Log.d(TAG, result.responseUri); } // 履歴に保存 ResultData.INSTANCE.add(result); // メッセージ送信 Utils.sendMessage(this, result.channel, result.response, result.responseUri, new DConnectHelper.FinishCallback<Void>() { @Override public void onFinish(Void aVoid, Exception error) { if (error != null) { Log.e(TAG, "Error on sendMessage", error); } } }); } /** * Dozeモードの検知を行うReceiverの登録を行う. */ private void registerDozeStateReceiver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { IntentFilter intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); registerReceiver(mDozeStateReceiver, intentFilter); } } /** * Dozeモードの検知を行うReceiverの解除を行う. */ private void unregisterDozeStateReceiver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { unregisterReceiver(mDozeStateReceiver); } } /** * Dozeモードの検知を行うReceiver. */ private BroadcastReceiver mDozeStateReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PowerManager mgr = context.getSystemService(PowerManager.class); if (!mgr.isIgnoringBatteryOptimizations(context.getPackageName())) { if (mgr.isDeviceIdleMode()) { stopMonitoringDeviceConnectManager(); disconnect(); showNotification(getString(R.string.disconnected_service)); } else { connect(); startMonitoringDeviceConnectManager(); } } } } }; }