/*
AWSIotRemoteManager.java
Copyright (c) 2016 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.awsiot.remote;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import org.deviceconnect.android.deviceplugin.awsiot.AWSIotDeviceManager;
import org.deviceconnect.android.deviceplugin.awsiot.cores.core.AWSIotController;
import org.deviceconnect.android.deviceplugin.awsiot.cores.core.AWSIotDeviceApplication;
import org.deviceconnect.android.deviceplugin.awsiot.cores.core.RDCMListManager;
import org.deviceconnect.android.deviceplugin.awsiot.cores.core.RemoteDeviceConnectManager;
import org.deviceconnect.android.deviceplugin.awsiot.cores.p2p.WebClient;
import org.deviceconnect.android.deviceplugin.awsiot.cores.util.AWSIotUtil;
import org.deviceconnect.android.event.Event;
import org.deviceconnect.android.event.EventManager;
import org.deviceconnect.android.message.DConnectMessageService;
import org.deviceconnect.android.message.MessageUtils;
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.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class AWSIotRemoteManager {
private static final boolean DEBUG = BuildConfig.DEBUG;
private static final String TAG = "AWS-Remote";
private Context mContext;
private AWSIotWebServerManager mAWSIotWebServerManager;
private AWSIotWebClientManager mAWSIotWebClientManager;
private AWSIotDeviceManager mAWSIotDeviceManager;
private AWSIotRequestManager mRequestManager;
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private AWSIotController mIot;
private RDCMListManager mRDCMListManager;
public AWSIotRemoteManager(final Context context, final AWSIotController controller) {
mContext = context;
mIot = controller;
mAWSIotDeviceManager = new AWSIotDeviceManager();
AWSIotDeviceApplication app = (AWSIotDeviceApplication) mContext.getApplicationContext();
mRDCMListManager = app.getRDCMListManager();
mRDCMListManager.setOnEventListener(mUpdateListener);
}
public AWSIotController getAWSIotController() {
return mIot;
}
public void connect() {
if (DEBUG) {
Log.i(TAG, "AWSIotRemoteManager#connect");
}
if (mAWSIotWebServerManager == null) {
mAWSIotWebServerManager = new AWSIotWebServerManager(mContext, this);
}
if (mAWSIotWebClientManager == null) {
mAWSIotWebClientManager = new AWSIotWebClientManager(mContext, this);
}
if (mRequestManager == null) {
mRequestManager = new AWSIotRequestManager();
}
}
public void disconnect() {
if (DEBUG) {
Log.i(TAG, "AWSIotRemoteManager#disconnect");
}
if (mAWSIotWebServerManager != null) {
mAWSIotWebServerManager.destroy();
mAWSIotWebServerManager = null;
}
if (mAWSIotWebClientManager != null) {
mAWSIotWebClientManager.destroy();
mAWSIotWebClientManager = null;
}
if (mRequestManager != null) {
mRequestManager.destroy();
mRequestManager = null;
}
}
public boolean sendRequest(final Intent request, final Intent response) {
if (!mIot.isConnected()) {
MessageUtils.setIllegalDeviceStateError(response, "Not connected to the AWS IoT.");
return true;
}
RemoteDeviceConnectManager remote = mAWSIotDeviceManager.findManagerById(DConnectProfile.getServiceID(request));
if (remote == null) {
MessageUtils.setNotFoundServiceError(response);
return true;
}
if (DEBUG) {
Log.i(TAG, "@@@@@@@@@ AWSIotRemoteManager#sendRequest: " + DConnectProfile.getProfile(request));
}
String action = request.getAction();
if (IntentDConnectMessage.ACTION_PUT.equals(action)) {
EventManager.INSTANCE.addEvent(request);
} else if (IntentDConnectMessage.ACTION_DELETE.equals(action)) {
EventManager.INSTANCE.removeEvent(request);
}
String message = AWSIotRemoteUtil.intentToJson(request, new AWSIotRemoteUtil.ConversionIntentCallback() {
@Override
public String convertServiceId(final String id) {
return mAWSIotDeviceManager.getServiceId(id);
}
@Override
public String convertUri(final String uri) {
// TODO 他のスキーマがある場合には追加
if (uri.startsWith("content://")) {
return "http://localhost" + WebClient.PATH_CONTENT_PROVIDER + "?" + uri;
} else {
return uri;
}
}
});
int requestCode = AWSIotUtil.generateRequestCode();
if (!publish(remote, AWSIotUtil.createRequest(requestCode, message))) {
MessageUtils.setIllegalDeviceStateError(response, "Not publish to the mqtt.");
return true;
}
AWSIotRequest aws = new AWSIotRequest() {
@Override
public void onReceivedMessage(final RemoteDeviceConnectManager remote, final JSONObject responseObj) throws JSONException {
if (!mRequestManager.pop(mRequestCode)) {
return;
}
if (responseObj == null) {
MessageUtils.setUnknownError(mResponse);
} else {
Bundle b = new Bundle();
AWSIotRemoteUtil.jsonToIntent(responseObj, b, new AWSIotRemoteUtil.ConversionJsonCallback() {
@Override
public String convertServiceId(final String id) {
return id;
}
@Override
public String convertName(final String name) {
return name;
}
@Override
public String convertUri(final String uri) {
Uri u = Uri.parse(uri);
return createWebServer(remote, u.getAuthority(), u.getPath() + "?" + u.getEncodedQuery());
}
});
mResponse.putExtras(b);
}
sendResponse(mResponse);
}
};
aws.mRequest = request;
aws.mResponse = response;
aws.mRequestCode = requestCode;
aws.mRequestCount = 1;
mRequestManager.put(requestCode, aws);
return false;
}
public boolean sendServiceDiscovery(final Intent request, final Intent response) {
if (DEBUG) {
Log.i(TAG, "@@@@@@@@@ AWSIotRemoteManager#sendServiceDiscovery");
}
if (!mIot.isConnected()) {
MessageUtils.setIllegalDeviceStateError(response, "Not connected to the AWS IoT.");
return true;
}
if (existAWSFlag(request)) {
MessageUtils.setUnknownError(response);
return true;
}
List<RemoteDeviceConnectManager> managers = mRDCMListManager.getRDCMList();
if (managers == null) {
MessageUtils.setUnknownError(response, "There is no managers.");
return true;
}
String message = AWSIotRemoteUtil.intentToJson(request, null);
int count = 0;
int requestCode = AWSIotUtil.generateRequestCode();
for (RemoteDeviceConnectManager remote : managers) {
if (isOnlineManager(remote)) {
if (publish(remote, AWSIotUtil.createRequest(requestCode, message))) {
count++;
}
}
}
if (count == 0) {
MessageUtils.setUnknownError(response, "There is no managers.");
return true;
}
AWSIotRequest aws = new AWSIotRequest() {
@Override
public void onReceivedMessage(final RemoteDeviceConnectManager remote, final JSONObject responseObj) throws JSONException {
if (responseObj != null && responseObj.has(ServiceDiscoveryProfile.PARAM_SERVICES)) {
JSONArray array = responseObj.getJSONArray("services");
for (int i = 0; i < array.length(); i++) {
Bundle service = new Bundle();
JSONObject obj = array.getJSONObject(i);
AWSIotRemoteUtil.jsonToIntent(obj, service, new AWSIotRemoteUtil.ConversionJsonCallback() {
@Override
public String convertServiceId(final String id) {
return mAWSIotDeviceManager.generateServiceId(remote, id);
}
@Override
public String convertName(final String name) {
return remote.getName() + " " + name;
}
@Override
public String convertUri(final String uri) {
Uri u = Uri.parse(uri);
return createWebServer(remote, u.getAuthority(), u.getPath() + "?" + u.getEncodedQuery());
}
});
mServices.add(service);
}
}
if (!mRequestManager.pop(mRequestCode)) {
return;
}
send();
}
@Override
public void onTimeout() {
send();
}
private void send() {
DConnectProfile.setResult(mResponse, DConnectMessage.RESULT_OK);
ServiceDiscoveryProfile.setServices(mResponse, mServices);
sendResponse(mResponse);
}
};
aws.mRequest = request;
aws.mResponse = response;
aws.mRequestCode = requestCode;
aws.mRequestCount = count;
mRequestManager.put(requestCode, aws, 6);
return false;
}
public boolean publish(final RemoteDeviceConnectManager remote, final String message) {
return mIot.publish(remote.getRequestTopic(), message);
}
private String createWebServer(final RemoteDeviceConnectManager remote, final String address, final String path) {
return mAWSIotWebServerManager.createWebServer(remote, address, path);
}
private void sendResponse(final Intent intent) {
((DConnectMessageService) mContext).sendResponse(intent);
}
private void sendEvent(final Intent event, final String accessToken) {
((DConnectMessageService) mContext).sendEvent(event, accessToken);
}
private boolean existAWSFlag(final Intent intent) {
Object o = intent.getExtras().get(AWSIotUtil.PARAM_SELF_FLAG);
if (o instanceof String) {
return "true".equals(o);
} else if (o instanceof Boolean) {
return (Boolean) o;
} else {
return false;
}
}
private RemoteDeviceConnectManager parseTopic(final String topic) {
List<RemoteDeviceConnectManager> managers = mRDCMListManager.getRDCMList();
if (managers != null) {
for (RemoteDeviceConnectManager remote : managers) {
if (remote.getResponseTopic().equals(topic)) {
return remote;
}
if (remote.getEventTopic().equals(topic)) {
return remote;
}
}
}
return null;
}
private void parseMQTT(final RemoteDeviceConnectManager remote, final String message) {
try {
JSONObject json = new JSONObject(message);
JSONObject response = json.optJSONObject(AWSIotUtil.KEY_RESPONSE);
if (response != null) {
onReceivedDeviceConnectResponse(remote, message);
}
JSONObject p2p = json.optJSONObject(AWSIotUtil.KEY_P2P_REMOTE);
if (p2p != null) {
mAWSIotWebServerManager.onReceivedSignaling(remote, p2p.toString());
}
JSONObject p2pa = json.optJSONObject(AWSIotUtil.KEY_P2P_LOCAL);
if (p2pa != null) {
mAWSIotWebClientManager.onReceivedSignaling(remote, p2pa.toString());
}
} catch (Exception e) {
if (DEBUG) {
Log.w(TAG, "", e);
}
}
}
private void onReceivedDeviceConnectResponse(final RemoteDeviceConnectManager remote, final String message) {
try {
JSONObject jsonObject = new JSONObject(message);
AWSIotRequest request = mRequestManager.get(jsonObject.getInt("requestCode"));
if (request == null) {
return;
}
request.onReceivedMessage(remote, jsonObject.optJSONObject("response"));
} catch (JSONException e) {
if (DEBUG) {
Log.e(TAG, "onReceivedDeviceConnectResponse", e);
}
}
}
private void onReceivedDeviceConnectEvent(final RemoteDeviceConnectManager remote, final String message) {
if (DEBUG) {
Log.d(TAG, "onReceivedDeviceConnectEvent: " + remote);
Log.d(TAG, "message=" + message);
}
try {
JSONObject jsonObject = new JSONObject(message);
String profile = jsonObject.optString("profile");
String inter = jsonObject.optString("interface");
String attribute = jsonObject.optString("attribute");
String serviceId = mAWSIotDeviceManager.generateServiceId(remote, jsonObject.optString("serviceId"));
List<Event> events = EventManager.INSTANCE.getEventList(serviceId, profile, inter, attribute);
for (Event event : events) {
Intent intent = EventManager.createEventMessage(event);
String accessToken = intent.getStringExtra("accessToken");
// TODO json->intent 変換をちゃんと検討すること。
Bundle b = new Bundle();
AWSIotRemoteUtil.jsonToIntent(jsonObject, b, new AWSIotRemoteUtil.ConversionJsonCallback() {
@Override
public String convertServiceId(final String id) {
return mAWSIotDeviceManager.generateServiceId(remote, id);
}
@Override
public String convertName(final String name) {
return remote.getName() + " " + name;
}
@Override
public String convertUri(final String uri) {
return uri;
}
});
intent.putExtras(b);
if (accessToken != null) {
intent.putExtra("accessToken", accessToken);
}
sendEvent(intent, event.getAccessToken());
}
} catch (JSONException e) {
if (DEBUG) {
Log.e(TAG, "onReceivedDeviceConnectEvent", e);
}
}
}
private RDCMListManager.OnEventListener mUpdateListener = new RDCMListManager.OnEventListener() {
@Override
public void onRDCMListUpdateSubscribe(final RemoteDeviceConnectManager manager) {
if (isOnlineManager(manager)) {
mIot.subscribe(manager.getResponseTopic(), mMessageCallback);
mIot.subscribe(manager.getEventTopic(), mMessageCallback);
} else {
mIot.unsubscribe(manager.getResponseTopic());
mIot.unsubscribe(manager.getEventTopic());
}
}
};
private boolean isOnlineManager(final RemoteDeviceConnectManager manager) {
long checkTime = System.currentTimeMillis() - 600000;
return (manager.isSubscribe() && manager.isOnline() && (manager.getTimeStamp() - checkTime > 0));
}
private final AWSIotController.MessageCallback mMessageCallback = new AWSIotController.MessageCallback() {
@Override
public void onReceivedMessage(final String topic, final String message, final Exception err) {
if (err != null) {
Log.e(TAG, "", err);
return;
}
final RemoteDeviceConnectManager remote = parseTopic(topic);
if (remote == null) {
if (DEBUG) {
Log.e(TAG, "Not found the RemoteDeviceConnectManager. topic=" + topic);
}
return;
}
if (DEBUG) {
Log.i(TAG, "onReceivedMessage: " + topic + " " + message);
}
mExecutorService.submit(new Runnable() {
@Override
public void run() {
if (topic.endsWith(RemoteDeviceConnectManager.EVENT)) {
onReceivedDeviceConnectEvent(remote, message);
} else if (topic.endsWith(RemoteDeviceConnectManager.RESPONSE)) {
parseMQTT(remote, message);
} else {
if (DEBUG) {
Log.w(TAG, "Unknown topic. topic=" + topic);
}
}
}
});
}
};
private class AWSIotRequestManager {
private final Map<Integer, AWSIotRequest> mMap = new HashMap<>();
private ScheduledExecutorService mExecutorService = Executors.newScheduledThreadPool(8);
public void destroy() {
mExecutorService.shutdown();
}
public void put(final int requestCode, final AWSIotRequest request, final int timeout, final TimeUnit timeUnit) {
mMap.put(requestCode, request);
request.mFuture = mExecutorService.schedule(request, timeout, timeUnit);
}
public void put(final int requestCode, final AWSIotRequest request, final int timeout) {
put(requestCode, request, timeout, TimeUnit.SECONDS);
}
public void put(final int requestCode, final AWSIotRequest request) {
put(requestCode, request, 30, TimeUnit.SECONDS);
}
public AWSIotRequest get(int key) {
return mMap.get(key);
}
public void remove(int key) {
mMap.remove(key);
}
public boolean pop(int key) {
AWSIotRequest request = mMap.get(key);
if (request == null) {
return false;
}
request.mRequestCount--;
if (request.mRequestCount > 0) {
return false;
}
mMap.remove(key);
request.cancel();
return true;
}
}
private class AWSIotRequest implements Runnable {
protected int mRequestCode;
protected int mRequestCount;
protected Intent mRequest;
protected Intent mResponse;
protected ScheduledFuture mFuture;
protected List<Bundle> mServices = new ArrayList<>();
@Override
public void run() {
if (DEBUG) {
Log.w(TAG, "timeout " + mRequestCode + " " + DConnectProfile.getProfile(mRequest));
}
mRequestManager.remove(mRequestCode);
onTimeout();
}
public void cancel() {
mFuture.cancel(true);
}
public void onReceivedMessage(RemoteDeviceConnectManager remote, JSONObject jsonObject) throws JSONException {
// do nothing.
}
public void onTimeout() {
// do nothing.
}
}
}