/* KeepAlive.java Copyright (c) 2016 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.manager.event; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import org.deviceconnect.android.manager.DConnectBroadcastReceiver; import org.deviceconnect.android.manager.DConnectService; import org.deviceconnect.android.manager.DevicePlugin; import org.deviceconnect.message.intent.message.IntentDConnectMessage; import java.util.Iterator; import java.util.LinkedList; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Logger; /** * Keep Alive Manager. * @author NTT DOCOMO, INC. */ public class KeepAliveManager { /** ロガー. */ private final Logger mLogger = Logger.getLogger("dconnect.manager"); /** コンテキスト. */ private final Context mContext; /** イベントセッション管理テーブル. */ private final EventSessionTable mEventSessionTable; /** イベント Keep Alive 管理テーブル. */ private final LinkedList<KeepAlive> mManagementList = new LinkedList<>(); /** 機能有効フラグ 初期起動時は有効. */ private Boolean mEnableKeepAlive = true; /** タイマーハンドラー. */ private Timer mTimer; /** 定期処理中フラグ. */ private Boolean mRunningPeriodicProcess = false; /** * コンストラクター. * * @param context コンテキスト * @param table イベントセッション管理テーブル */ public KeepAliveManager(final Context context, final EventSessionTable table) { mContext = context; mEventSessionTable = table; } /** * KeepAlive機能有効化チェック. * @return 有効時はtrue、無効時はfalse. */ public Boolean isEnableKeepAlive() { return mEnableKeepAlive; } /** * KeepAlive機能無効. * @return 正常終了はtrue、それ以外はfalse. */ public synchronized Boolean disableKeepAlive() { setKeepAliveFunction(false); stopPeriodicProcess(); if (!mManagementList.isEmpty()) { for (int i = 0; i < mManagementList.size(); i++) { KeepAlive data = mManagementList.get(i); sendKeepAlive(data.getPlugin(), "STOP"); } } return true; } /** * KeepAlive機能有効. * @return 正常終了はtrue、それ以外はfalse. */ public synchronized Boolean enableKeepAlive() { setKeepAliveFunction(true); if (!mManagementList.isEmpty()) { if (!mManagementList.isEmpty()) { for (int i = 0; i < mManagementList.size(); i++) { KeepAlive data = mManagementList.get(i); sendKeepAlive(data.getPlugin(), "START"); } } startPeriodicProcess(); } return true; } /** * KeepAlive機能設定. * @param flag true設定で有効、false設定で無効. */ public void setKeepAliveFunction(final Boolean flag) { mEnableKeepAlive = flag; } /** * 該当デバイスプラグインを管理テーブルに設定する. * @param plugin デバイスプラグイン. */ public void setManagementTable(DevicePlugin plugin) { KeepAlive data = getKeepAlive(plugin); if (data == null) { mManagementList.add(new KeepAlive(plugin)); sendKeepAlive(plugin, "START"); } else { data.additionEventCounter(); } if (isEnableKeepAlive() && !mRunningPeriodicProcess) { startPeriodicProcess(); } } /** * 該当するデバイスプラグインを持つKeepAlive要素を削除する. * @param plugin デバイスプラグイン. */ public synchronized void removeManagementTable(final DevicePlugin plugin) { if (!(mManagementList.isEmpty())) { for (int i = 0; i < mManagementList.size(); i++) { KeepAlive data = mManagementList.get(i); if (data.getServiceId().equals(plugin.getPluginId())) { data.subtractionEventCounter(); if (data.getEventCounter() <= 0) { sendKeepAlive(plugin, "STOP"); mManagementList.remove(i); } break; } } if (isEnableKeepAlive() && mManagementList.isEmpty() && mRunningPeriodicProcess) { stopPeriodicProcess(); } } else if (isEnableKeepAlive() && mRunningPeriodicProcess) { stopPeriodicProcess(); } } /** * 該当するデバイスプラグインのKeepAlive要素を取得する. * @param plugin デバイスプラグイン. * @return KeepAliveデータ. */ public synchronized KeepAlive getKeepAlive(final DevicePlugin plugin) { if (!(mManagementList.isEmpty())) { /** 要素数分ループ. */ for (KeepAlive data : mManagementList) { if (data.getServiceId().equals(plugin.getPluginId())) { return data; } } } /** 要素0または該当なしならnull. */ return null; } /** * 該当するプラグインIDのKeepAlive要素を取得する. * @param serviceId プラグインID. * @return KeepAliveデータ. */ public synchronized KeepAlive getKeepAlive(final String serviceId) { if (!(mManagementList.isEmpty())) { /** 要素数分ループ. */ for (KeepAlive data : mManagementList) { if (data.getServiceId().equals(serviceId)) { return data; } } } /** 要素0または該当なしならnull. */ return null; } /** * KeepAlive送信. * @param plugin デバイスプラグイン. * @param status ステータス. */ private void sendKeepAlive(final DevicePlugin plugin, final String status) { Intent request = new Intent(); request.setComponent(plugin.getComponentName()); request.setAction(IntentDConnectMessage.ACTION_KEEPALIVE); request.putExtra(IntentDConnectMessage.EXTRA_KEEPALIVE_STATUS, status); request.putExtra(IntentDConnectMessage.EXTRA_RECEIVER, new ComponentName(mContext, DConnectBroadcastReceiver.class)); request.putExtra(IntentDConnectMessage.EXTRA_SERVICE_ID, plugin.getPluginId()); mContext.sendBroadcast(request); } /** * DisconnectWebSocket送信. * @param receiverId イベントレシーバーID. */ private void sendDisconnectWebSocket(final String receiverId) { Intent request = new Intent(); request.setComponent(new ComponentName(mContext, DConnectBroadcastReceiver.class)); request.setAction(IntentDConnectMessage.ACTION_KEEPALIVE); request.putExtra(IntentDConnectMessage.EXTRA_KEEPALIVE_STATUS, "DISCONNECT"); request.putExtra(DConnectService.EXTRA_EVENT_RECEIVER_ID, receiverId); mContext.sendBroadcast(request); } /** * 定期処理 */ private synchronized void periodicProcess() { if (isEnableKeepAlive()) { mLogger.info("periodicProcess: plugins = " + mManagementList.size()); Iterator<KeepAlive> iterator = mManagementList.iterator(); while (iterator.hasNext()) { KeepAlive data = iterator.next(); if (data.getResponseFlag()) { mLogger.info("Plugin " + data.getPlugin().getPackageName() + " is alive."); data.resetResponseFlag(); sendKeepAlive(data.getPlugin(), "CHECK"); } else { mLogger.info("Plugin " + data.getPlugin().getPackageName() + " is dead."); DevicePlugin plugin = data.getPlugin(); data.subtractionEventCounter(); if (data.getEventCounter() <= 0) { sendKeepAlive(plugin, "STOP"); iterator.remove(); } // 該当プラグインIDに紐付くWebSocketの切断処理 for (EventSession session : mEventSessionTable.findEventSessionsForPlugin(plugin)) { mLogger.info("Disconnecting with receiver: id = " + session.getReceiverId()); sendDisconnectWebSocket(session.getReceiverId()); mEventSessionTable.remove(session); } } } if (isEnableKeepAlive() && mManagementList.isEmpty() && mRunningPeriodicProcess) { stopPeriodicProcess(); } } } /** * 定期処理開始. */ private void startPeriodicProcess() { if (mTimer == null) { mTimer = new Timer(true); /* 定期処理間隔(mSec). */ int PROCESS_INTERVAL = 30000; mTimer.schedule(new TimerTask() { @Override public void run() { periodicProcess(); } }, PROCESS_INTERVAL, PROCESS_INTERVAL); mRunningPeriodicProcess = true; } } /** 定期処理停止. */ private void stopPeriodicProcess() { mRunningPeriodicProcess = false; if (mTimer != null) { mTimer.cancel(); mTimer = null; } } }