/* EventBroker.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.Intent; import android.os.Bundle; import org.deviceconnect.android.event.Event; import org.deviceconnect.android.event.EventManager; import org.deviceconnect.android.manager.DConnectLocalOAuth; import org.deviceconnect.android.manager.DConnectMessageService; import org.deviceconnect.android.manager.DevicePlugin; import org.deviceconnect.android.manager.DevicePluginManager; import org.deviceconnect.android.manager.request.DiscoveryDeviceRequest; import org.deviceconnect.android.profile.DConnectProfile; import org.deviceconnect.android.profile.ServiceDiscoveryProfile; import org.deviceconnect.message.DConnectMessage; import org.deviceconnect.message.intent.message.IntentDConnectMessage; import org.deviceconnect.profile.ServiceDiscoveryProfileConstants; import java.io.IOException; import java.util.List; import java.util.logging.Logger; /** * イベントブローカー. * * @author NTT DOCOMO, INC. */ public class EventBroker { private final Logger mLogger = Logger.getLogger("dconnect.manager"); private final EventSessionTable mTable; private final DConnectMessageService mContext; private DConnectLocalOAuth mLocalOAuth; private DevicePluginManager mPluginManager; private RegistrationListener mListener; public EventBroker(final DConnectMessageService context, final EventSessionTable table, final DConnectLocalOAuth localOAuth, final DevicePluginManager pluginManager) { mTable = table; mContext = context; mLocalOAuth = localOAuth; mPluginManager = pluginManager; } public void setRegistrationListener(final RegistrationListener listener) { mListener = listener; } public void removeEventSession(final String receiverId) { mTable.removeForReceiverId(receiverId); } public void onRequest(final Intent request, final DevicePlugin dest) { String serviceId = DConnectProfile.getServiceID(request); String origin = request.getStringExtra(IntentDConnectMessage.EXTRA_ORIGIN); if (serviceId == null) { return; } String accessToken = getAccessToken(origin, serviceId); if (accessToken != null) { request.putExtra(DConnectMessage.EXTRA_ACCESS_TOKEN, accessToken); } else { request.removeExtra(DConnectMessage.EXTRA_ACCESS_TOKEN); } if (isRegistrationRequest(request)) { onRegistrationRequest(request, dest); } else if (isUnregistrationRequest(request)) { onUnregistrationRequest(request, dest); } } private void onRegistrationRequest(final Intent request, final DevicePlugin dest) { EventProtocol protocol = EventProtocol.getInstance(mContext, request); if (protocol == null) { mLogger.warning("Failed to identify a event receiver."); return; } protocol.addSession(mTable, request, dest); if (mListener != null) { mListener.onPutEventSession(request, dest); } } private void onUnregistrationRequest(final Intent request, final DevicePlugin dest) { EventProtocol protocol = EventProtocol.getInstance(mContext, request); if (protocol == null) { mLogger.warning("Failed to identify a event receiver."); return; } protocol.removeSession(mTable, request, dest); if (mListener != null) { mListener.onDeleteEventSession(request, dest); } } private String getAccessToken(final String origin, final String serviceId) { DConnectLocalOAuth.OAuthData oauth = mLocalOAuth.getOAuthData(origin, serviceId); if (oauth != null) { return mLocalOAuth.getAccessToken(oauth.getId()); } return null; } public void onEvent(final Intent event) { if (isServiceChangeEvent(event)) { onServiceChangeEvent(event); return; } String pluginAccessToken = event.getStringExtra(DConnectMessage.EXTRA_ACCESS_TOKEN); String serviceId = DConnectProfile.getServiceID(event); String profileName = DConnectProfile.getProfile(event); String interfaceName = DConnectProfile.getInterface(event); String attributeName = DConnectProfile.getAttribute(event); EventSession targetSession = null; if (pluginAccessToken != null) { for (EventSession session : mTable.getAll()) { if (isSameNameCaseSensitive(pluginAccessToken, session.getAccessToken()) && isSameNameCaseSensitive(serviceId, session.getServiceId()) && isSameName(profileName, session.getProfileName()) && isSameName(interfaceName, session.getInterfaceName()) && isSameName(attributeName, session.getAttributeName())) { targetSession = session; break; } } } else { // 旧バージョンのイベントAPIとの互換性保持 String sessionKey = DConnectProfile.getSessionKey(event); if (sessionKey != null) { sessionKey = trimReceiverName(sessionKey); String pluginId = EventProtocol.convertSessionKey2PluginId(sessionKey); String receiverId = EventProtocol.convertSessionKey2Key(sessionKey); for (EventSession session : mTable.getAll()) { if (isSameNameCaseSensitive(pluginId, session.getPluginId()) && isSameNameCaseSensitive(receiverId, session.getReceiverId()) && isSameNameCaseSensitive(serviceId, session.getServiceId()) && isSameName(profileName, session.getProfileName()) && isSameName(interfaceName, session.getInterfaceName()) && isSameName(attributeName, session.getAttributeName())) { targetSession = session; break; } } } } if (targetSession != null) { try { DevicePlugin plugin = mPluginManager.getDevicePlugin(targetSession.getPluginId()); if (plugin != null) { event.putExtra(IntentDConnectMessage.EXTRA_SESSION_KEY, targetSession.getReceiverId()); event.putExtra(DConnectMessage.EXTRA_SERVICE_ID, mPluginManager.appendServiceId(plugin, serviceId)); targetSession.sendEvent(event); } else { mLogger.warning("onEvent: Plugin is not found: id = " + targetSession.getPluginId()); } } catch (IOException e) { error("Failed to send event."); } } } private String trimReceiverName(final String sessionKey) { int index = sessionKey.lastIndexOf(DConnectMessageService.SEPARATOR_SESSION); if (index == -1) { // HTTP経由でイベントを登録した場合 return sessionKey; } // Intent経由でイベントを登録した場合 return sessionKey.substring(0, index); } private boolean isServiceChangeEvent(final Intent event) { String profileName = DConnectProfile.getProfile(event); String attributeName = DConnectProfile.getAttribute(event); // MEMO パスの大文字小文字を無視 return ServiceDiscoveryProfile.PROFILE_NAME.equalsIgnoreCase(profileName) && ServiceDiscoveryProfile.ATTRIBUTE_ON_SERVICE_CHANGE.equalsIgnoreCase(attributeName); } private void onServiceChangeEvent(final Intent event) { DevicePlugin plugin = findPluginForServiceChange(event); if (plugin == null) { warn("onServiceChangeEvent: plugin is not found"); return; } // network service discoveryの場合には、networkServiceのオブジェクトの中にデータが含まれる Bundle service = event.getParcelableExtra(ServiceDiscoveryProfile.PARAM_NETWORK_SERVICE); String id = service.getString(ServiceDiscoveryProfile.PARAM_ID); // サービスIDを変更 replaceServiceId(event, plugin); // 送信先のセッションを取得 List<Event> evts = EventManager.INSTANCE.getEventList( ServiceDiscoveryProfile.PROFILE_NAME, ServiceDiscoveryProfile.ATTRIBUTE_ON_SERVICE_CHANGE); for (int i = 0; i < evts.size(); i++) { Event evt = evts.get(i); mContext.sendEvent(evt.getReceiverName(), event); } } private DevicePlugin findPluginForServiceChange(final Intent event) { String pluginAccessToken = DConnectProfile.getAccessToken(event); if (pluginAccessToken != null) { return mPluginManager.getDevicePlugin(pluginAccessToken); } else { String sessionKey = DConnectProfile.getSessionKey(event); if (sessionKey != null) { String pluginId = EventProtocol.convertSessionKey2PluginId(sessionKey); return mPluginManager.getDevicePlugin(pluginId); } } return null; } /** * デバイスプラグインのクライアントを作成する. * @param plugin クライアントを作成するデバイスプラグイン * @param serviceId サービスID * @param event 送信するイベント */ private void createClientOfDevicePlugin(final DevicePlugin plugin, final String serviceId, final Intent event) { Intent intent = new Intent(IntentDConnectMessage.ACTION_GET); intent.setComponent(plugin.getComponentName()); intent.putExtra(DConnectMessage.EXTRA_PROFILE, ServiceDiscoveryProfileConstants.PROFILE_NAME); intent.putExtra(DConnectMessage.EXTRA_SERVICE_ID, serviceId); intent.putExtra(IntentDConnectMessage.EXTRA_ORIGIN, mContext.getPackageName()); DiscoveryDeviceRequest request = new DiscoveryDeviceRequest(); request.setContext(mContext); request.setLocalOAuth(mLocalOAuth); request.setUseAccessToken(true); request.setRequireOrigin(true); request.setDestination(plugin); request.setRequest(intent); request.setEvent(event); request.setDevicePluginManager(mPluginManager); mContext.addRequest(request); } /** * イベント用メッセージのサービスIDを置換する. * <br> * * デバイスプラグインから送られてくるサービスIDは、デバイスプラグインの中でIDになっている。 * dConnect ManagerでデバイスプラグインのIDをサービスIDに付加することでDNSっぽい動きを実現する。 * * @param event イベントメッセージ用Intent * @param plugin 送信元のデバイスプラグイン */ private void replaceServiceId(final Intent event, final DevicePlugin plugin) { String serviceId = event.getStringExtra(IntentDConnectMessage.EXTRA_SERVICE_ID); event.putExtra(IntentDConnectMessage.EXTRA_SERVICE_ID, mPluginManager.appendServiceId(plugin, serviceId)); } private boolean isSameName(final String a, final String b) { if (a == null && b == null) { return true; } if (a != null) { return a.equalsIgnoreCase(b); } else { return b.equalsIgnoreCase(a); } } private boolean isSameNameCaseSensitive(final String a, final String b) { if (a == null && b == null) { return true; } if (a != null) { return a.equals(b); } else { return b.equals(a); } } private boolean isRegistrationRequest(final Intent request) { String action = request.getAction(); return IntentDConnectMessage.ACTION_PUT.equals(action); } private boolean isUnregistrationRequest(final Intent request) { String action = request.getAction(); return IntentDConnectMessage.ACTION_DELETE.equals(action); } private void warn(final String message) { mLogger.warning(message); } private void error(final String message) { mLogger.severe(message); } public interface RegistrationListener { void onPutEventSession(final Intent request, final DevicePlugin plugin); void onDeleteEventSession(final Intent request, final DevicePlugin plugin); } }