/*
HostDeviceOrientationProfile.java
Copyright (c) 2014 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.host.profile;
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import org.deviceconnect.android.deviceplugin.host.HostDeviceService;
import org.deviceconnect.android.event.Event;
import org.deviceconnect.android.event.EventError;
import org.deviceconnect.android.event.EventManager;
import org.deviceconnect.android.message.MessageUtils;
import org.deviceconnect.android.profile.DConnectProfile;
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;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* DeviceOrientation Profile.
* @author NTT DOCOMO, INC.
*/
public class HostDeviceOrientationProfile extends DeviceOrientationProfile implements SensorEventListener {
/** SensorManager. */
private SensorManager mSensorManager;
/** ServiceID. */
private String mServiceId;
/** X軸方向の重力付き加速度. (単位: m/s^2). */
private double mAccellX;
/** Y軸方向の重力付き加速度. (単位: m/s^2). */
private double mAccellY;
/** Z軸方向の重力付き加速度. (単位: m/s^2). */
private double mAccellZ;
/** 加速度データが準備できているかどうかのフラグ */
private AtomicBoolean mIsAccellReady = new AtomicBoolean(false);
/** X軸方向の重力加速度成分. (単位: m/s^2). */
private double mGravityX = 0;
/** Y軸方向の重力加速度成分. (単位: m/s^2). */
private double mGravityY = 0;
/** Z軸方向の重力加速度成分. (単位: m/s^2). */
private double mGravityZ = 0;
/** 重力加速度データが準備できているかどうかのフラグ */
private AtomicBoolean mIsGravityReady = new AtomicBoolean(false);
/** X軸周りの角速度. (単位: degree/s). */
private double mGyroX;
/** Y軸周りの角速度. (単位: degree/s). */
private double mGyroY;
/** Z軸周りの角速度. (単位: degree/s). */
private double mGyroZ;
/** 角速度データが準備できているかどうかのフラグ */
private AtomicBoolean mIsGyroReady = new AtomicBoolean(false);
/** 前回の加速度の計測時間を保持する. */
private long mAccelLastTime;
/** センサー情報処理間隔設定用. */
private long mSensorInterval;
/** イベント送信間隔計測用. */
private long mLastEventSendTime = 0;
/** Device Orientationのデフォルト送信間隔を定義. */
private static final long DEVICE_ORIENTATION_INTERVAL_TIME = 200;
/** Device Orientationのキャッシュを残す時間を定義する. */
private static final long DEVICE_ORIENTATION_CACHE_TIME = 100;
private final DConnectApi mGetOnDeviceOrientationApi = new GetApi() {
@Override
public String getAttribute() {
return ATTRIBUTE_ON_DEVICE_ORIENTATION;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
return getDeviceOrientationEvent(response);
}
};
private final DConnectApi mPutOnDeviceOrientationApi = new PutApi() {
@Override
public String getAttribute() {
return ATTRIBUTE_ON_DEVICE_ORIENTATION;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
String serviceId = getServiceID(request);
try {
String interval = request.getStringExtra(PARAM_INTERVAL);
mSensorInterval = Long.parseLong(interval);
} catch (NumberFormatException e) {
mSensorInterval = DEVICE_ORIENTATION_INTERVAL_TIME;
}
// イベントの登録
EventError error = EventManager.INSTANCE.addEvent(request);
if (error == EventError.NONE) {
registerDeviceOrientationEvent(response, serviceId);
} else {
MessageUtils.setUnknownError(response, "Can not register event.");
}
return true;
}
};
private final DConnectApi mDeleteOnDeviceOrientationApi = new DeleteApi() {
@Override
public String getAttribute() {
return ATTRIBUTE_ON_DEVICE_ORIENTATION;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
// イベントの解除
EventError error = EventManager.INSTANCE.removeEvent(request);
if (error == EventError.NONE) {
unregisterDeviceOrientationEvent(response);
} else {
MessageUtils.setUnknownError(response, "Can not unregister event.");
}
return true;
}
};
public HostDeviceOrientationProfile() {
addApi(mGetOnDeviceOrientationApi);
addApi(mPutOnDeviceOrientationApi);
addApi(mDeleteOnDeviceOrientationApi);
}
/**
* センサー管理クラスを取得する.
* @return センサー管理クラス
*/
private SensorManager getSensorManager() {
return (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
}
/**
* イベント登録が空か確認する.
* @return 空の場合はtrue、それ以外はfalse
*/
private boolean isEmptyEventList() {
List<Event> events = EventManager.INSTANCE.getEventList(mServiceId,
DeviceOrientationProfile.PROFILE_NAME, null,
DeviceOrientationProfile.ATTRIBUTE_ON_DEVICE_ORIENTATION);
return events == null || events.size() == 0;
}
/**
* Device Orientationのデータを取得する.
* @param response データを格納するレスポンス
* @return trueの場合には即座に値を返却する、falseの場合には返さない
*/
private boolean getDeviceOrientationEvent(final Intent response) {
long t = System.currentTimeMillis() - mAccelLastTime;
if (t > DEVICE_ORIENTATION_CACHE_TIME) {
List<Sensor> sensors;
final SensorEventListener l = new SensorEventListener() {
@Override
public void onSensorChanged(final SensorEvent event) {
processSensorData(event);
if (mIsAccellReady.get() && mIsGravityReady.get() && mIsGyroReady.get()) {
mAccelLastTime = System.currentTimeMillis();
Bundle orientation = createOrientation();
setResult(response, DConnectMessage.RESULT_OK);
setOrientation(response, orientation);
HostDeviceService service = (HostDeviceService) getContext();
service.sendResponse(response);
if (isEmptyEventList()) {
mSensorManager.unregisterListener(this);
}
}
}
@Override
public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
// No operation
}
};
mSensorManager = getSensorManager();
sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
if (sensors.size() > 0) {
Sensor sensor = sensors.get(0);
mSensorManager.registerListener(l, sensor,
SensorManager.SENSOR_DELAY_NORMAL);
} else {
MessageUtils.setNotSupportAttributeError(response);
return true;
}
sensors = mSensorManager
.getSensorList(Sensor.TYPE_GRAVITY);
if (sensors.size() > 0) {
Sensor sensor = sensors.get(0);
mSensorManager.registerListener(l, sensor,
SensorManager.SENSOR_DELAY_NORMAL);
} else {
MessageUtils.setNotSupportAttributeError(response);
return true;
}
sensors = mSensorManager.getSensorList(Sensor.TYPE_GYROSCOPE);
if (sensors.size() > 0) {
Sensor sensor = sensors.get(0);
mSensorManager.registerListener(l, sensor,
SensorManager.SENSOR_DELAY_NORMAL);
} else {
MessageUtils.setNotSupportAttributeError(response);
return true;
}
invalidateLatestData();
return false;
} else {
Bundle orientation = createOrientation();
setResult(response, DConnectMessage.RESULT_OK);
setOrientation(response, orientation);
return true;
}
}
/**
* Device Orientation Profile<br>
* イベントの登録.
*
* @param response
* レスポンス
* @param serviceId
* サービスID
*/
private void registerDeviceOrientationEvent(final Intent response, final String serviceId) {
mServiceId = serviceId;
mSensorManager = getSensorManager();
mAccelLastTime = System.currentTimeMillis();
List<Sensor> sensors;
sensors = mSensorManager
.getSensorList(Sensor.TYPE_ACCELEROMETER);
if (sensors.size() > 0) {
Sensor sensor = sensors.get(0);
mSensorManager.registerListener(this, sensor, (int)mSensorInterval * 1000);
} else {
MessageUtils.setNotSupportAttributeError(response);
return;
}
sensors = mSensorManager
.getSensorList(Sensor.TYPE_GRAVITY);
if (sensors.size() > 0) {
Sensor sensor = sensors.get(0);
mSensorManager.registerListener(this, sensor, (int)mSensorInterval * 1000);
} else {
MessageUtils.setNotSupportAttributeError(response);
return;
}
sensors = mSensorManager.getSensorList(Sensor.TYPE_GYROSCOPE);
if (sensors.size() > 0) {
Sensor sensor = sensors.get(0);
mSensorManager.registerListener(this, sensor, (int)mSensorInterval * 1000);
} else {
MessageUtils.setNotSupportAttributeError(response);
return;
}
DConnectProfile.setResult(response, DConnectMessage.RESULT_OK);
response.putExtra(DConnectMessage.EXTRA_VALUE,
"Register OnDeviceOrientation event");
}
/**
* Device Orientation Profile イベントの解除.
* @param response レスポンス
*/
private void unregisterDeviceOrientationEvent(final Intent response) {
mSensorManager.unregisterListener(this);
response.putExtra(DConnectMessage.EXTRA_RESULT,
DConnectMessage.RESULT_OK);
response.putExtra(DConnectMessage.EXTRA_VALUE,
"Unregister OnDeviceOrientation event");
}
/**
* Orientationのデータを作成する.
* @return Orientationのデータ
*/
private Bundle createOrientation() {
long interval = System.currentTimeMillis() - mLastEventSendTime;
if (interval < 0) {
interval = 0;
}
Bundle orientation = new Bundle();
Bundle a1 = new Bundle();
DeviceOrientationProfile.setX(a1, mAccellX - mGravityX);
DeviceOrientationProfile.setY(a1, mAccellY - mGravityY);
DeviceOrientationProfile.setZ(a1, mAccellZ - mGravityZ);
Bundle a2 = new Bundle();
DeviceOrientationProfile.setX(a2, mAccellX);
DeviceOrientationProfile.setY(a2, mAccellY);
DeviceOrientationProfile.setZ(a2, mAccellZ);
Bundle r = new Bundle();
DeviceOrientationProfile.setAlpha(r, mGyroX);
DeviceOrientationProfile.setBeta(r, mGyroY);
DeviceOrientationProfile.setGamma(r, mGyroZ);
DeviceOrientationProfile.setAcceleration(orientation, a1);
DeviceOrientationProfile.setAccelerationIncludingGravity(orientation, a2);
DeviceOrientationProfile.setRotationRate(orientation, r);
DeviceOrientationProfile.setInterval(orientation, interval);
return orientation;
}
private void processSensorData(final SensorEvent sensorEvent) {
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mAccellX = sensorEvent.values[0];
mAccellY = sensorEvent.values[1];
mAccellZ = sensorEvent.values[2];
mIsAccellReady.compareAndSet(false, true);
} else if (sensorEvent.sensor.getType() == Sensor.TYPE_GRAVITY) {
mGravityX = sensorEvent.values[0];
mGravityY = sensorEvent.values[1];
mGravityZ = sensorEvent.values[2];
mIsGravityReady.compareAndSet(false, true);
} else if (sensorEvent.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
mGyroX = Math.toDegrees(sensorEvent.values[0]);
mGyroY = Math.toDegrees(sensorEvent.values[1]);
mGyroZ = Math.toDegrees(sensorEvent.values[2]);
mIsGyroReady.compareAndSet(false, true);
}
}
/**
* キャッシュされたセンサーデータを無効扱いにし、最新センサーデータが全て揃うまでデータ収集を行わせる。
*/
private void invalidateLatestData() {
mIsAccellReady.compareAndSet(true, false);
mIsGravityReady.compareAndSet(true, false);
mIsGyroReady.compareAndSet(true, false);
}
@Override
public void onSensorChanged(final SensorEvent sensorEvent) {
processSensorData(sensorEvent);
if (mIsAccellReady.get() && mIsGravityReady.get() && mIsGyroReady.get()) {
Bundle orientation = createOrientation();
mAccelLastTime = System.currentTimeMillis();
if (isEmptyEventList()) {
mSensorManager.unregisterListener(this);
return;
}
long interval = System.currentTimeMillis() - mLastEventSendTime;
if (interval > mSensorInterval) {
List<Event> events = EventManager.INSTANCE.getEventList(mServiceId,
DeviceOrientationProfile.PROFILE_NAME, null,
DeviceOrientationProfile.ATTRIBUTE_ON_DEVICE_ORIENTATION);
for (int i = 0; i < events.size(); i++) {
Event event = events.get(i);
Intent intent = EventManager.createEventMessage(event);
intent.putExtra(DeviceOrientationProfile.PARAM_ORIENTATION, orientation);
sendEvent(intent, event.getAccessToken());
}
mLastEventSendTime = System.currentTimeMillis();
}
}
}
@Override
public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
// No operation
}
}