/*
SlackMessageHookDeviceService.java
Copyright (c) 2016 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.slackmessagehook;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.util.Log;
import org.deviceconnect.android.deviceplugin.slackmessagehook.profile.SlackMessageHookProfile;
import org.deviceconnect.android.deviceplugin.slackmessagehook.profile.SlackMessageHookSystemProfile;
import org.deviceconnect.android.deviceplugin.slackmessagehook.setting.fragment.Utils;
import org.deviceconnect.android.deviceplugin.slackmessagehook.slack.SlackManager;
import org.deviceconnect.android.event.Event;
import org.deviceconnect.android.event.EventManager;
import org.deviceconnect.android.event.cache.MemoryCacheController;
import org.deviceconnect.android.message.DConnectMessageService;
import org.deviceconnect.android.profile.SystemProfile;
import org.deviceconnect.android.service.DConnectService;
import org.deviceconnect.profile.ServiceDiscoveryProfileConstants;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 本デバイスプラグインのプロファイルをDeviceConnectに登録するサービス.
* @author NTT DOCOMO, INC.
*/
public class SlackMessageHookDeviceService extends DConnectMessageService implements SlackManager.SlackEventListener {
/** サービスID */
public static final String SERVICE_ID = "SlackMessageHook";
/** タイムスタンプのフォーマット. */
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHMMSS.sssZ", Locale.getDefault());
/** メッセージ履歴保持時間(秒) */
private static final int MESSAGE_HOLD_LIMIT = 60;
/** メッセージ履歴 */
private List<Bundle> mEventHistory = new ArrayList<>();
/** ユーザーリスト */
private final HashMap<String, SlackManager.ListInfo> mUserMap = new HashMap<>();
@Override
public void onCreate() {
super.onCreate();
// Eventの設定.
EventManager.INSTANCE.setController(new MemoryCacheController());
SlackManager.INSTANCE.addSlackEventListener(this);
// Profile追加
DConnectService service = new DConnectService(SERVICE_ID);
service.setName(SERVICE_ID);
service.setNetworkType(ServiceDiscoveryProfileConstants.NetworkType.WIFI);
service.addProfile(new SlackMessageHookProfile());
getServiceProvider().addService(service);
// 接続
if (Utils.getOnlineStatus(getContext())) {
final String token = Utils.getAccessToken(this);
SlackManager.INSTANCE.setApiToken(token, true, null);
}
registerDozeStateReceiver();
}
@Override
public void onDestroy() {
unregisterDozeStateReceiver();
SlackManager.INSTANCE.removeSlackEventListener(this);
super.onDestroy();
}
@Override
protected void onManagerUninstalled() {
EventManager.INSTANCE.removeAll();
}
@Override
protected void onManagerTerminated() {
EventManager.INSTANCE.removeAll();
}
@Override
protected void onManagerEventTransmitDisconnected(final String origin) {
EventManager.INSTANCE.removeEvents(origin);
}
@Override
protected void onDevicePluginReset() {
EventManager.INSTANCE.removeAll();
if (Utils.getOnlineStatus(getContext())) {
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
if (SlackManager.INSTANCE.isDisconnecting()) {
SlackManager.INSTANCE.disconnect(new SlackManager.FinishCallback<Void>() {
@Override
public void onFinish(final Void aVoid, final Exception error) {
SlackManager.INSTANCE.connect();
}
});
} else {
SlackManager.INSTANCE.connect();
}
}
});
}
}
@Override
protected SystemProfile getSystemProfile() {
return new SlackMessageHookSystemProfile();
}
@Override
public void OnConnect() {
// オンライン
DConnectService service = getServiceProvider().getService(SERVICE_ID);
if (service != null) {
service.setOnline(true);
}
// ユーザーリスト取得
if (SlackManager.INSTANCE.isConnected()) {
fetchUserList();
}
}
@Override
public void OnConnectLost() {
// オフライン
DConnectService service = getServiceProvider().getService(SERVICE_ID);
if (service != null) {
service.setOnline(false);
}
}
@Override
public void OnReceiveSlackMessage(SlackManager.HistoryInfo info) {
sendMessageEvent(info.text, info.channel, info.user, info.ts, info.file, info.mimetype);
}
/**
* 受信メッセージをEventとして送信
* @param text テキスト
* @param channel チャンネル
* @param user 送信者
* @param ts 時間
* @param url リソースURL
* @param mimeType MimeType
*/
private void sendMessageEvent(String text, String channel, String user, Double ts, String url, String mimeType) {
String serviceId = SERVICE_ID;
String profile = SlackMessageHookProfile.PROFILE_NAME;
String attribute = SlackMessageHookProfile.ATTRIBUTE_ONMESSAGE;
List<Event> events = EventManager.INSTANCE.getEventList(serviceId, profile, null, attribute);
synchronized (mUserMap) {
// 情報セット
Bundle message = new Bundle();
message.putString("messengerType", "slack");
message.putString("channelId", channel);
// TimeStampは少数以下切り捨て
double time = ts;
message.putLong("timeStamp", (long)time * 1000);
message.putString("timeStampString", timeStampToText((long) time * 1000));
// 送信者情報を変換
SlackManager.ListInfo info = mUserMap.get(user);
if (info != null) {
message.putString("from", info.name);
} else {
message.putString("from", user);
// 値が無い場合はユーザーリスト再取得
fetchUserList();
}
// メンション処理
if (text != null) {
boolean isMentioned = false;
Pattern p = Pattern.compile("<@(\\w*)>");
Matcher m = p.matcher(text);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String uid = m.group(1);
info = mUserMap.get(uid);
if (info != null) {
m.appendReplacement(sb, "@" + info.name);
} else {
m.appendReplacement(sb, m.group());
// 値が無い場合はユーザーリスト再取得
fetchUserList();
}
if (!isMentioned) {
isMentioned = SlackManager.INSTANCE.getBotInfo().id.equals(uid);
}
}
m.appendTail(sb);
message.putString("text", sb.toString());
// メッセージタイプ
String messageType = null;
// Dから始まるChannelIDはDirectMessage
if (channel.startsWith("D")) {
messageType = "direct";
} else {
messageType = "normal";
}
if (isMentioned) {
messageType += ",mention";
}
message.putString("messageType", messageType);
}
// リソース
if (url != null) {
message.putString("resource", url);
}
if (mimeType != null) {
message.putString("mimeType", mimeType);
}
// GetMessageのために一定時間イベントを保持する
mEventHistory.add(message);
// 一定時間経過したメッセージを削除する
truncateHistory();
// Eventに値をおくる.
for (Event event : events) {
Intent intent = EventManager.createEventMessage(event);
intent.putExtra("message", message);
sendEvent(intent, event.getAccessToken());
}
}
}
/**
* ユーザーリスト取得
*/
private void fetchUserList() {
SlackManager.INSTANCE.getUserList(new SlackManager.FinishCallback<ArrayList<SlackManager.ListInfo>>() {
@Override
public void onFinish(ArrayList<SlackManager.ListInfo> listInfos, Exception error) {
if (error == null) {
synchronized (mUserMap) {
// UserをHashMapへ
mUserMap.clear();
for (SlackManager.ListInfo info : listInfos) {
mUserMap.put(info.id, info);
}
}
} else {
Log.e("slack", "err", error);
}
}
});
}
/**
* メッセージ履歴を取得する
* @return 履歴
*/
public List<Bundle> getHistory() {
// 一定時間経過したメッセージを削除してから返す
truncateHistory();
return mEventHistory;
}
/**
* 一定時間経過したメッセージを削除する
*/
private void truncateHistory() {
Iterator itr = mEventHistory.iterator();
long limit = System.currentTimeMillis() / 1000L - MESSAGE_HOLD_LIMIT;
while(itr.hasNext()){
Bundle bundle = (Bundle)itr.next();
long timeStamp = bundle.getLong("timeStamp");
if (timeStamp < limit) {
itr.remove();
} else {
break;
}
}
}
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);
}
}
private void unregisterDozeStateReceiver() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
unregisterReceiver(mDozeStateReceiver);
}
}
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()) {
SlackManager.INSTANCE.disconnect();
} else {
SlackManager.INSTANCE.connect();
}
}
}
}
};
private static String timeStampToText(final long timeStamp) {
return SIMPLE_DATE_FORMAT.format(new Date(timeStamp));
}
}