/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.formulaeditor; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.SystemClock; import android.util.Log; import com.parrot.freeflight.service.DroneControlService; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.bluetooth.base.BluetoothDevice; import org.catrobat.catroid.bluetooth.base.BluetoothDeviceService; import org.catrobat.catroid.common.CatroidService; import org.catrobat.catroid.common.ServiceProvider; import org.catrobat.catroid.devices.arduino.phiro.Phiro; import org.catrobat.catroid.devices.mindstorms.ev3.LegoEV3; import org.catrobat.catroid.devices.mindstorms.nxt.LegoNXT; import org.catrobat.catroid.drone.DroneServiceWrapper; import org.catrobat.catroid.facedetection.FaceDetectionHandler; import org.catrobat.catroid.nfc.NfcHandler; import org.catrobat.catroid.utils.TouchUtil; import java.util.Calendar; public final class SensorHandler implements SensorEventListener, SensorCustomEventListener, LocationListener, GpsStatus.Listener { public static final float RADIAN_TO_DEGREE_CONST = 180f / (float) Math.PI; private static final String TAG = SensorHandler.class.getSimpleName(); private static SensorHandler instance = null; private static BluetoothDeviceService btService = ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE); private SensorManagerInterface sensorManager = null; private Sensor linearAccelerationSensor = null; private Sensor accelerometerSensor = null; private Sensor magneticFieldSensor = null; private Sensor rotationVectorSensor = null; private LocationManager locationManager = null; private float[] rotationMatrix = new float[16]; private float[] rotationVector = new float[3]; private float[] accelerationXYZ = new float[3]; private float signAccelerationZ = 0f; private float[] gravity = new float[] { 0f, 0f, 0f }; private boolean useLinearAccelerationFallback = false; private boolean useRotationVectorFallback = false; private float linearAccelerationX = 0f; private float linearAccelerationY = 0f; private float linearAccelerationZ = 0f; private float loudness = 0f; private float faceDetected = 0f; private float faceSize = 0f; private float facePositionX = 0f; private float facePositionY = 0f; private double latitude = 0d; private double longitude = 0d; private float locationAccuracy = 0f; private double altitude = 0d; private boolean compassAvailable = true; private boolean accelerationAvailable = true; private boolean inclinationAvailable = true; private boolean isGpsConnected = false; private Location lastLocationGps; private long lastLocationGpsMillis; private SensorHandler(Context context) { sensorManager = new SensorManager( (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE)); linearAccelerationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); if (linearAccelerationSensor == null) { useLinearAccelerationFallback = true; } rotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); if (rotationVectorSensor == null) { useRotationVectorFallback = true; } if (useRotationVectorFallback) { accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); magneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); if (accelerometerSensor == null) { accelerationAvailable = false; inclinationAvailable = false; } if (magneticFieldSensor == null) { compassAvailable = false; } } else if (useLinearAccelerationFallback) { accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (accelerometerSensor == null) { accelerationAvailable = false; } } locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); Log.d(TAG, "*** LINEAR_ACCELERATION SENSOR: " + linearAccelerationSensor); Log.d(TAG, "*** ACCELEROMETER SENSOR: " + accelerometerSensor); Log.d(TAG, "*** ROTATION_VECTOR SENSOR: " + rotationVectorSensor); Log.d(TAG, "*** MAGNETIC_FIELD SENSOR: " + magneticFieldSensor); Log.d(TAG, "*** LOCATION_MANAGER: " + locationManager); } public boolean compassAvailable() { return this.compassAvailable; } public static boolean gpsAvailable() { return gpsSensorAvailable() | networkGpsAvailable(); } private static boolean gpsSensorAvailable() { return instance.locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); } private static boolean networkGpsAvailable() { return instance.locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); } private static double startWeekWithMonday() { int weekdayOfAndroidCalendar = Calendar.getInstance().get(Calendar.DAY_OF_WEEK); int convertedWeekday; if (weekdayOfAndroidCalendar == Calendar.SUNDAY) { convertedWeekday = Calendar.SATURDAY; } else { convertedWeekday = weekdayOfAndroidCalendar - 1; } return convertedWeekday; } public boolean accelerationAvailable() { return this.accelerationAvailable; } public boolean inclinationAvailable() { return this.inclinationAvailable; } public static SensorHandler getInstance(Context context) { if (instance == null) { instance = new SensorHandler(context); } return instance; } public static void startSensorListener(Context context) { if (instance == null) { instance = new SensorHandler(context); } instance.sensorManager.unregisterListener((SensorEventListener) instance); instance.sensorManager.unregisterListener((SensorCustomEventListener) instance); instance.locationManager.removeUpdates(instance); instance.locationManager.removeGpsStatusListener(instance); instance.registerListener(instance); instance.sensorManager.registerListener(instance, Sensors.LOUDNESS); FaceDetectionHandler.registerOnFaceDetectedListener(instance); FaceDetectionHandler.registerOnFaceDetectionStatusListener(instance); instance.locationManager.addGpsStatusListener(instance); if (gpsSensorAvailable()) { instance.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, instance); } if (networkGpsAvailable()) { instance.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, instance); } } public static void registerListener(SensorEventListener listener) { if (instance == null) { return; } if (!instance.useLinearAccelerationFallback) { instance.sensorManager.registerListener(listener, instance.linearAccelerationSensor, android.hardware.SensorManager.SENSOR_DELAY_GAME); } if (!instance.useRotationVectorFallback) { instance.sensorManager.registerListener(listener, instance.rotationVectorSensor, android.hardware.SensorManager.SENSOR_DELAY_GAME); } if (instance.useLinearAccelerationFallback || instance.useRotationVectorFallback) { instance.sensorManager.registerListener(listener, instance.accelerometerSensor, android.hardware.SensorManager.SENSOR_DELAY_GAME); } if (instance.useRotationVectorFallback && instance.magneticFieldSensor != null) { instance.sensorManager.registerListener(listener, instance.magneticFieldSensor, android.hardware.SensorManager.SENSOR_DELAY_GAME); } } public static void unregisterListener(SensorEventListener listener) { if (instance == null) { return; } instance.sensorManager.unregisterListener(listener); } public static void stopSensorListeners() { if (instance == null) { return; } instance.sensorManager.unregisterListener((SensorEventListener) instance); instance.sensorManager.unregisterListener((SensorCustomEventListener) instance); instance.locationManager.removeUpdates(instance); instance.locationManager.removeGpsStatusListener(instance); FaceDetectionHandler.unregisterOnFaceDetectedListener(instance); FaceDetectionHandler.unregisterOnFaceDetectionStatusListener(instance); } public static Object getSensorValue(Sensors sensor) { if (instance.sensorManager == null) { return 0d; } DroneControlService dcs = DroneServiceWrapper.getInstance().getDroneService(); Double sensorValue; float[] rotationMatrixOut = new float[16]; switch (sensor) { case X_ACCELERATION: if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { return (double) (-instance.linearAccelerationY); } else { return (double) instance.linearAccelerationX; } case Y_ACCELERATION: if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { return (double) instance.linearAccelerationX; } else { return (double) instance.linearAccelerationY; } case Z_ACCELERATION: return (double) instance.linearAccelerationZ; case COMPASS_DIRECTION: float[] orientations = new float[3]; if (!instance.useRotationVectorFallback) { android.hardware.SensorManager.getRotationMatrixFromVector(instance.rotationMatrix, instance.rotationVector); } if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { android.hardware.SensorManager.remapCoordinateSystem(instance.rotationMatrix, android.hardware.SensorManager .AXIS_Y, android.hardware.SensorManager.AXIS_MINUS_X, rotationMatrixOut); android.hardware.SensorManager.getOrientation(rotationMatrixOut, orientations); } else { android.hardware.SensorManager.getOrientation(instance.rotationMatrix, orientations); } sensorValue = (double) orientations[0]; return sensorValue * RADIAN_TO_DEGREE_CONST * -1f; case LATITUDE: return instance.latitude; case LONGITUDE: return instance.longitude; case LOCATION_ACCURACY: return (double) instance.locationAccuracy; case ALTITUDE: return instance.altitude; case X_INCLINATION: if (instance.useRotationVectorFallback) { float rawInclinationX; if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { rawInclinationX = RADIAN_TO_DEGREE_CONST * (float) (Math.acos(instance .accelerationXYZ[1])); } else { rawInclinationX = RADIAN_TO_DEGREE_CONST * (float) (Math.acos(instance.accelerationXYZ[0])); } float correctedInclinationX = 0; if (rawInclinationX >= 90 && rawInclinationX <= 180) { if (instance.signAccelerationZ > 0) { correctedInclinationX = -(rawInclinationX - 90); } else { correctedInclinationX = -(180 + (90 - rawInclinationX)); } } else if (rawInclinationX >= 0 && rawInclinationX < 90) { if (instance.signAccelerationZ > 0) { correctedInclinationX = (90 - rawInclinationX); } else { correctedInclinationX = (90 + rawInclinationX); } } if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { correctedInclinationX = -correctedInclinationX; } return (double) correctedInclinationX; } else { orientations = new float[3]; android.hardware.SensorManager.getRotationMatrixFromVector(instance.rotationMatrix, instance.rotationVector); if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { android.hardware.SensorManager.remapCoordinateSystem(instance.rotationMatrix, android.hardware.SensorManager .AXIS_Y, android.hardware.SensorManager.AXIS_MINUS_X, rotationMatrixOut); android.hardware.SensorManager.getOrientation(rotationMatrixOut, orientations); } else { android.hardware.SensorManager.getOrientation(instance.rotationMatrix, orientations); } sensorValue = (double) orientations[2]; return sensorValue * RADIAN_TO_DEGREE_CONST * -1f; } case Y_INCLINATION: if (instance.useRotationVectorFallback) { float rawInclinationY; if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { rawInclinationY = RADIAN_TO_DEGREE_CONST * (float) (Math.acos(instance.accelerationXYZ[0])); } else { rawInclinationY = RADIAN_TO_DEGREE_CONST * (float) (Math.acos(instance.accelerationXYZ[1])); } float correctedInclinationY = 0; if (rawInclinationY >= 90 && rawInclinationY <= 180) { if (instance.signAccelerationZ > 0) { correctedInclinationY = -(rawInclinationY - 90); } else { correctedInclinationY = -(180 + (90 - rawInclinationY)); } } else if (rawInclinationY >= 0 && rawInclinationY < 90) { if (instance.signAccelerationZ > 0) { correctedInclinationY = (90 - rawInclinationY); } else { correctedInclinationY = (90 + rawInclinationY); } } return (double) correctedInclinationY; } else { orientations = new float[3]; android.hardware.SensorManager.getRotationMatrixFromVector(instance.rotationMatrix, instance.rotationVector); if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { android.hardware.SensorManager.remapCoordinateSystem(instance.rotationMatrix, android.hardware.SensorManager .AXIS_Y, android.hardware.SensorManager.AXIS_MINUS_X, rotationMatrixOut); android.hardware.SensorManager.getOrientation(rotationMatrixOut, orientations); } else { android.hardware.SensorManager.getOrientation(instance.rotationMatrix, orientations); } float xInclinationUsedToExtendRangeOfRoll = orientations[2] * RADIAN_TO_DEGREE_CONST * -1f; sensorValue = (double) orientations[1]; if (Math.abs(xInclinationUsedToExtendRangeOfRoll) <= 90f) { return sensorValue * RADIAN_TO_DEGREE_CONST * -1f; } else { float uncorrectedYInclination = sensorValue.floatValue() * RADIAN_TO_DEGREE_CONST * -1f; if (uncorrectedYInclination > 0f) { return (double) 180f - uncorrectedYInclination; } else { return (double) -180f - uncorrectedYInclination; } } } case FACE_DETECTED: return (double) instance.faceDetected; case FACE_SIZE: return (double) instance.faceSize; case FACE_X_POSITION: if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { return (double) (-instance.facePositionY); } else { return (double) instance.facePositionX; } case FACE_Y_POSITION: if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { return (double) instance.facePositionX; } else { return (double) instance.facePositionY; } case LOUDNESS: return Double.valueOf(instance.loudness); case DATE_YEAR: return Double.valueOf(Calendar.getInstance().get(Calendar.YEAR)); case DATE_MONTH: return Double.valueOf(Calendar.getInstance().get(Calendar.MONTH) + 1); case DATE_DAY: return Double.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH)); case DATE_WEEKDAY: return startWeekWithMonday(); case TIME_HOUR: return Double.valueOf(Calendar.getInstance().get(Calendar.HOUR_OF_DAY)); case TIME_MINUTE: return Double.valueOf(Calendar.getInstance().get(Calendar.MINUTE)); case TIME_SECOND: return Double.valueOf(Calendar.getInstance().get(Calendar.SECOND)); case NXT_SENSOR_1: case NXT_SENSOR_2: case NXT_SENSOR_3: case NXT_SENSOR_4: LegoNXT nxt = btService.getDevice(BluetoothDevice.LEGO_NXT); if (nxt != null) { return Double.valueOf(nxt.getSensorValue(sensor)); } break; case EV3_SENSOR_1: case EV3_SENSOR_2: case EV3_SENSOR_3: case EV3_SENSOR_4: LegoEV3 ev3 = btService.getDevice(BluetoothDevice.LEGO_EV3); if (ev3 != null) { return Double.valueOf(ev3.getSensorValue(sensor)); } break; case PHIRO_BOTTOM_LEFT: case PHIRO_BOTTOM_RIGHT: case PHIRO_FRONT_LEFT: case PHIRO_FRONT_RIGHT: case PHIRO_SIDE_LEFT: case PHIRO_SIDE_RIGHT: Phiro phiro = btService.getDevice(BluetoothDevice.PHIRO); if (phiro != null) { return Double.valueOf(phiro.getSensorValue(sensor)); } break; case LAST_FINGER_INDEX: return Double.valueOf(TouchUtil.getLastTouchIndex()); case FINGER_TOUCHED: return TouchUtil.isFingerTouching(TouchUtil.getLastTouchIndex()) ? 1d : 0d; case FINGER_X: return Double.valueOf(TouchUtil.getX(TouchUtil.getLastTouchIndex())); case FINGER_Y: return Double.valueOf(TouchUtil.getY(TouchUtil.getLastTouchIndex())); case DRONE_BATTERY_STATUS: return (double) dcs.getDroneNavData().batteryStatus; case DRONE_EMERGENCY_STATE: return (double) dcs.getDroneNavData().emergencyState; case DRONE_USB_REMAINING_TIME: return (double) dcs.getDroneNavData().usbRemainingTime; case DRONE_NUM_FRAMES: return (double) dcs.getDroneNavData().numFrames; case DRONE_RECORDING: if (dcs.getDroneNavData().recording) { return 1d; } else { return 0d; } case DRONE_FLYING: if (dcs.getDroneNavData().flying) { return 1.0; } else { return 0.0; } case DRONE_INITIALIZED: if (dcs.getDroneNavData().initialized) { return 1.0; } else { return 0.0; } case DRONE_USB_ACTIVE: if (dcs.getDroneNavData().usbActive) { return 1.0; } else { return 0.0; } case DRONE_CAMERA_READY: if (dcs.getDroneNavData().cameraReady) { return 1.0; } else { return 0.0; } case DRONE_RECORD_READY: if (dcs.getDroneNavData().recordReady) { return 1.0; } else { return 0.0; } case NFC_TAG_MESSAGE: return String.valueOf(NfcHandler.getLastNfcTagMessage()); case NFC_TAG_ID: return String.valueOf(NfcHandler.getLastNfcTagId()); } return 0d; } public static void clearFaceDetectionValues() { if (instance != null) { instance.faceDetected = 0f; instance.faceSize = 0f; instance.facePositionX = 0f; instance.facePositionY = 0f; } } @Override public void onAccuracyChanged(Sensor arg0, int arg1) { } @Override public void onSensorChanged(SensorEvent event) { switch (event.sensor.getType()) { case Sensor.TYPE_MAGNETIC_FIELD: float[] tempMagneticFieldXYZ = event.values.clone(); float[] tempInclinationMatrix = new float[9]; android.hardware.SensorManager.getRotationMatrix(rotationMatrix, tempInclinationMatrix, accelerationXYZ, tempMagneticFieldXYZ); //http://goo.gl/wo6QK5 break; case Sensor.TYPE_ACCELEROMETER: accelerationXYZ = event.values.clone(); if (useLinearAccelerationFallback) { determinePseudoLinearAcceleration(accelerationXYZ.clone()); } double normOfG = Math.sqrt(accelerationXYZ[0] * accelerationXYZ[0] + accelerationXYZ[1] * accelerationXYZ[1] + accelerationXYZ[2] * accelerationXYZ[2]); accelerationXYZ[0] = (float) (accelerationXYZ[0] / normOfG); accelerationXYZ[1] = (float) (accelerationXYZ[1] / normOfG); accelerationXYZ[2] = (float) (accelerationXYZ[2] / normOfG); signAccelerationZ = Math.signum(event.values[2]); break; case Sensor.TYPE_LINEAR_ACCELERATION: linearAccelerationX = event.values[0]; linearAccelerationY = event.values[1]; linearAccelerationZ = event.values[2]; break; case Sensor.TYPE_ROTATION_VECTOR: rotationVector[0] = event.values[0]; rotationVector[1] = event.values[1]; rotationVector[2] = event.values[2]; break; default: Log.v(TAG, "Unhandled sensor type: " + event.sensor.getType()); } } private void determinePseudoLinearAcceleration(float[] input) { float alpha = 0.8f; gravity[0] = alpha * gravity[0] + ((1 - alpha) * input[0]); gravity[1] = alpha * gravity[1] + ((1 - alpha) * input[1]); gravity[2] = alpha * gravity[2] + ((1 - alpha) * input[2]); linearAccelerationX = -1f * (input[0] - gravity[0]); linearAccelerationY = -1f * (input[1] - gravity[1]); linearAccelerationZ = -1f * (input[2] - gravity[2]); } @Override public void onCustomSensorChanged(SensorCustomEvent event) { switch (event.sensor) { case LOUDNESS: instance.loudness = event.values[0]; break; case FACE_DETECTED: instance.faceDetected = event.values[0]; break; case FACE_SIZE: instance.faceSize = event.values[0]; break; case FACE_X_POSITION: instance.facePositionX = event.values[0]; break; case FACE_Y_POSITION: instance.facePositionY = event.values[0]; break; default: Log.v(TAG, "Unhandled sensor: " + event.sensor); } } @Override public void onLocationChanged(Location location) { if (location == null) { return; } if (location.getProvider().equals(LocationManager.GPS_PROVIDER) || !isGpsConnected) { latitude = location.getLatitude(); longitude = location.getLongitude(); locationAccuracy = location.getAccuracy(); altitude = location.hasAltitude() ? location.getAltitude() : 0; } if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) { lastLocationGpsMillis = SystemClock.elapsedRealtime(); lastLocationGps = location; } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } @Override public void onGpsStatusChanged(int event) { switch (event) { case GpsStatus.GPS_EVENT_SATELLITE_STATUS: if (lastLocationGps != null) { isGpsConnected = (SystemClock.elapsedRealtime() - lastLocationGpsMillis) < 3000; } break; case GpsStatus.GPS_EVENT_FIRST_FIX: isGpsConnected = true; break; } } }