/*
LinkingDeviceOrientationProfile.java
Copyright (c) 2016 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.linking.linking.profile;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import org.deviceconnect.android.deviceplugin.linking.BuildConfig;
import org.deviceconnect.android.deviceplugin.linking.LinkingApplication;
import org.deviceconnect.android.deviceplugin.linking.LinkingDestroy;
import org.deviceconnect.android.deviceplugin.linking.LinkingDevicePluginService;
import org.deviceconnect.android.deviceplugin.linking.linking.LinkingDevice;
import org.deviceconnect.android.deviceplugin.linking.linking.LinkingDeviceManager;
import org.deviceconnect.android.deviceplugin.linking.linking.LinkingSensorData;
import org.deviceconnect.android.deviceplugin.linking.linking.service.LinkingDeviceService;
import org.deviceconnect.android.event.Event;
import org.deviceconnect.android.event.EventDispatcher;
import org.deviceconnect.android.event.EventDispatcherFactory;
import org.deviceconnect.android.event.EventDispatcherManager;
import org.deviceconnect.android.event.EventError;
import org.deviceconnect.android.event.EventManager;
import org.deviceconnect.android.message.DConnectMessageService;
import org.deviceconnect.android.message.MessageUtils;
import org.deviceconnect.android.profile.DeviceOrientationProfile;
import org.deviceconnect.android.profile.api.DConnectApi;
import org.deviceconnect.android.profile.api.DeleteApi;
import org.deviceconnect.android.profile.api.GetApi;
import org.deviceconnect.android.profile.api.PutApi;
import org.deviceconnect.message.DConnectMessage;
import java.util.List;
public class LinkingDeviceOrientationProfile extends DeviceOrientationProfile implements LinkingDestroy {
private static final String TAG = "LinkingPlugIn";
private static final String PARAM_COMPASS = "compass";
private EventDispatcherManager mDispatcherManager;
private SensorHolder mSensorHolder;
public LinkingDeviceOrientationProfile() {
mDispatcherManager = new EventDispatcherManager();
addApi(mGetOnDeviceOrientation);
addApi(mPutOnDeviceOrientation);
addApi(mDeleteOnDeviceOrientation);
}
private final LinkingDeviceManager.OnSensorListener mListener = new LinkingDeviceManager.OnSensorListener() {
@Override
public void onChangeSensor(final LinkingDevice device, final LinkingSensorData sensor) {
notifyOrientation(device, sensor);
}
};
private final DConnectApi mGetOnDeviceOrientation = new GetApi() {
@Override
public String getAttribute() {
return ATTRIBUTE_ON_DEVICE_ORIENTATION;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
LinkingDevice device = getDevice(response);
if (device == null) {
return true;
}
final LinkingDeviceManager deviceManager = getLinkingDeviceManager();
getLinkingDeviceManager().enableListenSensor(device, new OnSensorListenerImpl(device) {
@Override
public void onCleanup() {
deviceManager.disableListenSensor(mDevice, this);
}
@Override
public void onTimeout() {
if (mCleanupFlag) {
return;
}
MessageUtils.setTimeoutError(response);
sendResponse(response);
}
@Override
public synchronized void onChangeSensor(final LinkingDevice device, final LinkingSensorData sensor) {
if (mCleanupFlag || !mDevice.equals(device)) {
return;
}
updateOrientation(mSensorHolder, sensor, 0);
if (mSensorHolder.isFlag()) {
mSensorHolder.clearFlag();
setResult(response, DConnectMessage.RESULT_OK);
setOrientation(response, mSensorHolder.getOrientation());
sendResponse(response);
cleanup();
}
}
});
return false;
}
};
private final DConnectApi mPutOnDeviceOrientation = new PutApi() {
@Override
public String getAttribute() {
return ATTRIBUTE_ON_DEVICE_ORIENTATION;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
LinkingDevice device = getDevice(response);
if (device == null) {
return true;
}
EventError error = EventManager.INSTANCE.addEvent(request);
if (error == EventError.NONE) {
getLinkingDeviceManager().enableListenSensor(device, mListener);
if (mSensorHolder == null) {
mSensorHolder = createSensorHolder(device, getInterval(request));
}
addEventDispatcher(request);
setResult(response, DConnectMessage.RESULT_OK);
} else if (error == EventError.INVALID_PARAMETER) {
MessageUtils.setInvalidRequestParameterError(response);
} else {
MessageUtils.setUnknownError(response);
}
return true;
}
};
private final DConnectApi mDeleteOnDeviceOrientation = new DeleteApi() {
@Override
public String getAttribute() {
return ATTRIBUTE_ON_DEVICE_ORIENTATION;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
LinkingDevice device = getDevice(response);
if (device == null) {
return true;
}
removeEventDispatcher(request);
EventError error = EventManager.INSTANCE.removeEvent(request);
if (error == EventError.NONE) {
if (isEmptyEventList(getServiceID(request))) {
getLinkingDeviceManager().disableListenSensor(device, mListener);
mSensorHolder = null;
}
setResult(response, DConnectMessage.RESULT_OK);
} else if (error == EventError.INVALID_PARAMETER) {
MessageUtils.setInvalidRequestParameterError(response);
} else {
MessageUtils.setUnknownError(response);
}
return true;
}
};
@Override
public void onDestroy() {
if (BuildConfig.DEBUG) {
Log.i(TAG, "LinkingDeviceOrientationProfile#destroy: " + getService().getId());
}
getLinkingDeviceManager().disableListenSensor(getDevice(), mListener);
mDispatcherManager.removeAllEventDispatcher();
}
private boolean isEmptyEventList(final String serviceId) {
List<Event> events = EventManager.INSTANCE.getEventList(serviceId,
PROFILE_NAME, null, ATTRIBUTE_ON_DEVICE_ORIENTATION);
return events.isEmpty();
}
private void addEventDispatcher(final Intent request) {
Event event = EventManager.INSTANCE.getEvent(request);
EventDispatcher dispatcher = EventDispatcherFactory.createEventDispatcher(
(DConnectMessageService)getContext(), request);
mDispatcherManager.addEventDispatcher(event, dispatcher);
}
private void removeEventDispatcher(final Intent request) {
Event event = EventManager.INSTANCE.getEvent(request);
mDispatcherManager.removeEventDispatcher(event);
}
private void updateOrientation(final SensorHolder holder, final LinkingSensorData data, final long interval) {
switch (data.getType()) {
case GYRO:
setGyroValuesToBundle(holder.getOrientation(), data);
break;
case ACCELERATION:
setAccelerationValuesToBundle(holder.getOrientation(), data);
break;
case COMPASS:
setCompassValuesToBundle(holder.getOrientation(), data);
break;
default:
throw new IllegalArgumentException("unknown type");
}
setInterval(holder.getOrientation(), interval);
holder.setFlag(data);
}
private void setGyroValuesToBundle(final Bundle bundle, final LinkingSensorData data) {
Bundle gyro = new Bundle();
setBeta(gyro, data.getX());
setGamma(gyro, data.getY());
setAlpha(gyro, data.getZ());
setRotationRate(bundle, gyro);
}
private void setAccelerationValuesToBundle(final Bundle bundle, final LinkingSensorData data) {
Bundle acceleration = new Bundle();
setX(acceleration, data.getX() * 10);
setY(acceleration, data.getY() * 10);
setZ(acceleration, data.getZ() * 10);
setAccelerationIncludingGravity(bundle, acceleration);
}
private void setCompassValuesToBundle(final Bundle bundle, final LinkingSensorData data) {
Bundle compass = new Bundle();
setX(compass, data.getX());
compass.putDouble("beta", data.getX());
compass.putDouble("gamma", data.getY());
compass.putDouble("alpha", data.getZ());
bundle.putBundle(PARAM_COMPASS, compass);
}
private void notifyOrientation(final LinkingDevice device, final LinkingSensorData sensor) {
if (mSensorHolder == null) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "holder is not exist.");
}
return;
}
updateOrientation(mSensorHolder, sensor, 0);
if (mSensorHolder.isFlag()) {
mSensorHolder.clearFlag();
setInterval(mSensorHolder.getOrientation(), mSensorHolder.getInterval());
String serviceId = device.getBdAddress();
List<Event> events = EventManager.INSTANCE.getEventList(serviceId,
PROFILE_NAME, null, ATTRIBUTE_ON_DEVICE_ORIENTATION);
if (events != null && events.size() > 0) {
for (Event event : events) {
Intent intent = EventManager.createEventMessage(event);
setOrientation(intent, mSensorHolder.getOrientation());
mDispatcherManager.sendEvent(event, intent);
}
}
}
}
private LinkingDevice getDevice() {
return ((LinkingDeviceService) getService()).getLinkingDevice();
}
private LinkingDevice getDevice(final Intent response) {
LinkingDevice device = getDevice();
if (!device.isConnected()) {
MessageUtils.setIllegalDeviceStateError(response, "device not connected");
return null;
}
if (!device.isSupportGyro() && !device.isSupportAcceleration() && !device.isSupportCompass()) {
MessageUtils.setIllegalDeviceStateError(response, "device has not Sensor");
return null;
}
return device;
}
private LinkingDeviceManager getLinkingDeviceManager() {
LinkingApplication app = getLinkingApplication();
return app.getLinkingDeviceManager();
}
private LinkingApplication getLinkingApplication() {
LinkingDevicePluginService service = (LinkingDevicePluginService) getContext();
return (LinkingApplication) service.getApplication();
}
private SensorHolder createSensorHolder(final LinkingDevice device, final int interval) {
SensorHolder holder = new SensorHolder();
holder.setSupportGyro(device.isSupportGyro());
holder.setSupportAcceleration(device.isSupportAcceleration());
holder.setSupportCompass(device.isSupportCompass());
holder.setTime(System.currentTimeMillis());
holder.setInterval(interval);
return holder;
}
private static int getInterval(final Intent request) {
try {
String interval = request.getStringExtra("interval");
return Integer.parseInt(interval);
} catch (NumberFormatException e) {
return -1;
}
}
private abstract class OnSensorListenerImpl extends TimeoutSchedule implements LinkingDeviceManager.OnSensorListener {
protected SensorHolder mSensorHolder;
OnSensorListenerImpl(final LinkingDevice device) {
super(device);
mSensorHolder = createSensorHolder(device, 0);
}
}
private class SensorHolder {
private long mTime = System.currentTimeMillis();
private Bundle mOrientation = new Bundle();
private int mFlag;
private int mInterval;
private boolean mSupportGyro;
private boolean mSupportAcceleration;
private boolean mSupportCompass;
public Bundle getOrientation() {
return mOrientation;
}
public void setTime(long time) {
mTime = time;
}
public void setInterval(int interval) {
mInterval = interval;
}
public long getInterval() {
if (mInterval <= 0) {
long interval = System.currentTimeMillis() - mTime;
mTime = System.currentTimeMillis();
return interval;
} else {
return mInterval;
}
}
public void setSupportGyro(boolean supportGyro) {
mSupportGyro = supportGyro;
}
public void setSupportAcceleration(boolean supportAcceleration) {
mSupportAcceleration = supportAcceleration;
}
public void setSupportCompass(boolean supportCompass) {
mSupportCompass = supportCompass;
}
public void clearFlag() {
mFlag = 0;
}
public boolean isFlag() {
int f = 0;
if (mSupportGyro) {
f |= LinkingDevice.GYRO;
}
if (mSupportAcceleration) {
f |= LinkingDevice.ACCELERATION;
}
if (mSupportCompass) {
f |= LinkingDevice.COMPASS;
}
return mFlag == f;
}
private void setFlag(final LinkingSensorData sensor) {
switch (sensor.getType()) {
case GYRO:
mFlag |= LinkingDevice.GYRO;
break;
case ACCELERATION:
mFlag |= LinkingDevice.ACCELERATION;
break;
case COMPASS:
mFlag |= LinkingDevice.COMPASS;
break;
default:
throw new IllegalArgumentException("unknown type");
}
}
}
}