mirror of
https://github.com/danog/termux-api.git
synced 2025-01-22 21:31:10 +01:00
Add SensorAPI (#147)
This commit is contained in:
parent
11b84d9f9c
commit
eab70c318f
@ -29,9 +29,5 @@ Ideas
|
||||
- Method for playing audio files using the system MediaPlayer, so `termux-play myfile.ogg` would play the audio file.
|
||||
- Wifi network search and connect.
|
||||
- real-time sensor data output instead of snap with a second delay.
|
||||
- Gyro sensor.
|
||||
- Proximity sensor.
|
||||
- Accelerometer.
|
||||
- Light sensor.
|
||||
- LED Flash.
|
||||
- Add extra permissions to the app to (un)install apps, stop pricesses etc.
|
||||
|
@ -54,6 +54,7 @@
|
||||
android:exported="false"/>
|
||||
<service android:name="com.termux.api.SpeechToTextAPI$SpeechToTextService"/>
|
||||
<service android:name="com.termux.api.TextToSpeechAPI$TextToSpeechService" />
|
||||
<service android:name=".SensorAPI$SensorReaderService"/>
|
||||
<provider android:authorities="com.termux.sharedfiles"
|
||||
android:readPermission="com.termux.sharedfiles.READ"
|
||||
android:exported="true"
|
||||
|
449
app/src/main/java/com/termux/api/SensorAPI.java
Normal file
449
app/src/main/java/com/termux/api/SensorAPI.java
Normal file
@ -0,0 +1,449 @@
|
||||
package com.termux.api;
|
||||
|
||||
import android.app.Service;
|
||||
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.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.termux.api.util.ResultReturner;
|
||||
import com.termux.api.util.TermuxApiLogger;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
|
||||
/**
|
||||
* API that allows you to listen to all sensors on device
|
||||
*/
|
||||
public class SensorAPI {
|
||||
|
||||
/**
|
||||
* Starts our SensorReader service
|
||||
* @param context
|
||||
* @param intent
|
||||
*/
|
||||
public static void onReceive(final Context context, final Intent intent) {
|
||||
Intent serviceIntent = new Intent(context, SensorReaderService.class);
|
||||
serviceIntent.setAction(intent.getAction());
|
||||
serviceIntent.putExtras(intent.getExtras());
|
||||
context.startService(serviceIntent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All sensor listening functionality exists in this background service
|
||||
*/
|
||||
public static class SensorReaderService extends Service {
|
||||
// indentation for JSON output
|
||||
protected static final int INDENTATION = 2;
|
||||
|
||||
protected static SensorManager sensorManager;
|
||||
protected static JSONObject sensorReadout;
|
||||
protected static SensorOutputWriter outputWriter;
|
||||
|
||||
// prevent concurrent modifications w/ sensor readout
|
||||
protected static Semaphore semaphore;
|
||||
|
||||
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
sensorReadout = new JSONObject();
|
||||
semaphore = new Semaphore(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
String command = intent.getAction();
|
||||
Context context = getApplicationContext();
|
||||
SensorManager sensorManager = getSensorManager(context);
|
||||
|
||||
SensorCommandHandler handler = getSensorCommandHandler(command);
|
||||
SensorCommandResult result = handler.handle(sensorManager, context, intent);
|
||||
|
||||
if (result.type == ResultType.SINGLE) {
|
||||
// post one-time result now, rather than an active stream
|
||||
postSensorCommandResult(context, intent, result);
|
||||
}
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
|
||||
protected static SensorManager getSensorManager(Context context) {
|
||||
if (sensorManager == null) {
|
||||
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
}
|
||||
return sensorManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
cleanup();
|
||||
TermuxApiLogger.info("SensorAPI SensorReaderService onDestroy()");
|
||||
}
|
||||
|
||||
protected void cleanup() {
|
||||
if (outputWriter != null && outputWriter.isRunning()) {
|
||||
outputWriter.interrupt();
|
||||
outputWriter = null;
|
||||
}
|
||||
|
||||
if (sensorManager != null) {
|
||||
sensorManager.unregisterListener(sensorEventListener);
|
||||
sensorManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sensor event listener for reading sensor value updates and storing them
|
||||
* in the sensorReadout JSON object
|
||||
*/
|
||||
protected static SensorEventListener sensorEventListener = new SensorEventListener() {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent sensorEvent) {
|
||||
JSONArray sensorValuesArray = new JSONArray();
|
||||
try {
|
||||
semaphore.acquire();
|
||||
for (int j = 0; j < sensorEvent.values.length; ++j) {
|
||||
sensorValuesArray.put(j, sensorEvent.values[j]);
|
||||
}
|
||||
JSONObject sensorInfo = new JSONObject();
|
||||
sensorInfo.put("values", sensorValuesArray);
|
||||
sensorReadout.put(sensorEvent.sensor.getName(), sensorInfo);
|
||||
semaphore.release();
|
||||
} catch (JSONException e) {
|
||||
TermuxApiLogger.error("onSensorChanged error", e);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// unused
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int i) {
|
||||
}
|
||||
};
|
||||
|
||||
protected static SensorCommandHandler getSensorCommandHandler(final String command) {
|
||||
switch (command) {
|
||||
case "list":
|
||||
return listHandler;
|
||||
case "cleanup":
|
||||
return cleanupHandler;
|
||||
case "sensors":
|
||||
return sensorHandler;
|
||||
default:
|
||||
return new SensorCommandHandler() {
|
||||
@Override
|
||||
public SensorCommandResult handle(SensorManager sensorManager, Context context, Intent intent) {
|
||||
SensorCommandResult result = new SensorCommandResult();
|
||||
result.message = "Unknown command: " + command;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void postSensorCommandResult(final Context context, final Intent intent,
|
||||
final SensorCommandResult result) {
|
||||
|
||||
ResultReturner.returnData(context, intent, new ResultReturner.ResultWriter() {
|
||||
@Override
|
||||
public void writeResult(PrintWriter out) throws Exception {
|
||||
out.append(result.message + "\n");
|
||||
if (result.error != null) {
|
||||
out.append(result.error + "\n");
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* -----
|
||||
* Sensor Command Handlers
|
||||
* -----
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Handler for returning a list of all available sensors
|
||||
*/
|
||||
static SensorCommandHandler listHandler = new SensorCommandHandler() {
|
||||
@Override
|
||||
public SensorCommandResult handle(SensorManager sensorManager, Context context, Intent intent) {
|
||||
SensorCommandResult result = new SensorCommandResult();
|
||||
JSONArray sensorArray = new JSONArray();
|
||||
List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
|
||||
|
||||
try {
|
||||
for (int j = 0; j < sensorList.size(); ++j) {
|
||||
Sensor sensor = sensorList.get(j);
|
||||
sensorArray.put(sensor.getName());
|
||||
}
|
||||
JSONObject output = new JSONObject();
|
||||
output.put("sensors", sensorArray);
|
||||
result.message = output.toString(INDENTATION);
|
||||
} catch (JSONException e) {
|
||||
TermuxApiLogger.error("listHandler JSON error", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for managing cleaning up sensor resources
|
||||
*/
|
||||
static SensorCommandHandler cleanupHandler = new SensorCommandHandler() {
|
||||
@Override
|
||||
public SensorCommandResult handle(SensorManager sensorManager, Context context, Intent intent) {
|
||||
SensorCommandResult result = new SensorCommandResult();
|
||||
|
||||
if (outputWriter != null) {
|
||||
outputWriter.interrupt();
|
||||
outputWriter = null;
|
||||
sensorManager.unregisterListener(sensorEventListener);
|
||||
result.message = "Sensor cleanup successful!";
|
||||
TermuxApiLogger.info("Cleanup()");
|
||||
} else {
|
||||
result.message = "Sensor cleanup unnecessary";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for managing listening to sensors
|
||||
*/
|
||||
static SensorCommandHandler sensorHandler = new SensorCommandHandler() {
|
||||
@Override
|
||||
public SensorCommandResult handle(SensorManager sensorManager, Context context, Intent intent) {
|
||||
SensorCommandResult result = new SensorCommandResult();
|
||||
result.type = ResultType.CONTINUOUS;
|
||||
|
||||
clearSensorValues();
|
||||
|
||||
// sensor list user passed to us
|
||||
String[] requestedSensors = getUserRequestedSensors(intent);
|
||||
List<Sensor> sensorsToListenTo = getSensorsToListenTo(sensorManager, requestedSensors, intent);
|
||||
|
||||
if (sensorsToListenTo.isEmpty()) {
|
||||
result.message = "No valid sensors were registered!";
|
||||
result.type = ResultType.SINGLE;
|
||||
} else {
|
||||
if (outputWriter == null) {
|
||||
outputWriter = createSensorOutputWriter(intent);
|
||||
outputWriter.start();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a string array of all user requested sensor names to listen to
|
||||
* @param intent
|
||||
* @return
|
||||
*/
|
||||
protected static String[] getUserRequestedSensors(Intent intent) {
|
||||
// sensor values passed to us from user
|
||||
String sensorListString = intent.hasExtra("sensors") ? intent.getStringExtra("sensors") : "";
|
||||
return sensorListString.split(",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all sensors to listen to, that were requested and are available
|
||||
* @param sensorManager
|
||||
* @param requestedSensors
|
||||
* @return
|
||||
*/
|
||||
protected static List<Sensor> getSensorsToListenTo(SensorManager sensorManager, String[] requestedSensors, Intent intent) {
|
||||
List<Sensor> availableSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
|
||||
List<Sensor> sensorsToListenTo = new ArrayList<>();
|
||||
|
||||
boolean listenToAll = intent.getBooleanExtra("all", false);
|
||||
|
||||
if (listenToAll) {
|
||||
for (Sensor sensor : availableSensors) {
|
||||
sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_UI);
|
||||
}
|
||||
sensorsToListenTo = availableSensors;
|
||||
TermuxApiLogger.info("Listening to ALL sensors");
|
||||
} else {
|
||||
|
||||
// try to find matching sensors that were sent in request
|
||||
for (String sensorName : requestedSensors) {
|
||||
// ignore case
|
||||
sensorName = sensorName.toUpperCase();
|
||||
|
||||
for (Sensor sensor : availableSensors) {
|
||||
if (sensor.getName().toUpperCase().contains(sensorName)) {
|
||||
sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_UI);
|
||||
sensorsToListenTo.add(sensor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sensorsToListenTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out sensorEventListener as well as our sensorReadout JSON object
|
||||
*/
|
||||
protected static void clearSensorValues() {
|
||||
// prevent duplicate listeners
|
||||
sensorManager.unregisterListener(sensorEventListener);
|
||||
|
||||
// clear out old values
|
||||
sensorReadout = new JSONObject();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates SensorOutputWriter to write sensor values to stdout
|
||||
* @param intent
|
||||
* @return
|
||||
*/
|
||||
protected static SensorOutputWriter createSensorOutputWriter(Intent intent) {
|
||||
String socketAddress = intent.getStringExtra("socket_output");
|
||||
|
||||
outputWriter = new SensorOutputWriter(socketAddress);
|
||||
outputWriter.setOnErrorListener(new SocketWriterErrorListener() {
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
outputWriter = null;
|
||||
TermuxApiLogger.error("SensorOutputWriter error", e);
|
||||
}
|
||||
});
|
||||
int delay = intent.getIntExtra("delay", 1000);
|
||||
TermuxApiLogger.info("Delay set to: " + delay);
|
||||
outputWriter.setDelay(delay);
|
||||
return outputWriter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles continuously writing Sensor info to an OutputStream asynchronously
|
||||
*/
|
||||
static class SensorOutputWriter extends Thread {
|
||||
// delay in milliseconds before posting new sensor reading
|
||||
protected static final int DEFAULT_DELAY = 1000;
|
||||
|
||||
protected String outputSocketAddress;
|
||||
protected boolean isRunning;
|
||||
protected int delay;
|
||||
protected SocketWriterErrorListener errorListener;
|
||||
|
||||
|
||||
public SensorOutputWriter(String outputSocketAddress, int delay) {
|
||||
this.outputSocketAddress = outputSocketAddress;
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public SensorOutputWriter(String outputSocketAddress) {
|
||||
this(outputSocketAddress, DEFAULT_DELAY);
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
public void setOnErrorListener(SocketWriterErrorListener errorListener) {
|
||||
this.errorListener = errorListener;
|
||||
}
|
||||
|
||||
public void setDelay(int delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
isRunning = true;
|
||||
|
||||
try {
|
||||
try (LocalSocket outputSocket = new LocalSocket()) {
|
||||
outputSocket.connect(new LocalSocketAddress(this.outputSocketAddress));
|
||||
|
||||
try (PrintWriter writer = new PrintWriter(outputSocket.getOutputStream())) {
|
||||
|
||||
while (isRunning) {
|
||||
try {
|
||||
Thread.sleep(this.delay);
|
||||
} catch (InterruptedException e) {
|
||||
TermuxApiLogger.info("SensorOutputWriter interrupted: " + e.getMessage());
|
||||
}
|
||||
semaphore.acquire();
|
||||
writer.write(sensorReadout.toString(INDENTATION) + "\n");
|
||||
writer.flush();
|
||||
semaphore.release();
|
||||
}
|
||||
TermuxApiLogger.info("SensorOutputWriter finished");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TermuxApiLogger.error("SensorOutputWriter error", e);
|
||||
|
||||
if (errorListener != null) {
|
||||
errorListener.onError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
super.interrupt();
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface for handling exceptions that could occur in SensorOutputWriter
|
||||
*/
|
||||
interface SocketWriterErrorListener {
|
||||
void onError(Exception e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface for handling sensor commands
|
||||
*/
|
||||
interface SensorCommandHandler {
|
||||
SensorCommandResult handle(SensorManager sensorManager, final Context context, final Intent intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple POJO to store result of executing a sensor command
|
||||
*/
|
||||
static class SensorCommandResult {
|
||||
public String message = "";
|
||||
public ResultType type = ResultType.SINGLE;
|
||||
public String error;
|
||||
}
|
||||
|
||||
enum ResultType {
|
||||
SINGLE,
|
||||
CONTINUOUS
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -71,6 +71,9 @@ public class TermuxApiReceiver extends BroadcastReceiver {
|
||||
case "NotificationRemove":
|
||||
NotificationAPI.onReceiveRemoveNotification(this, context, intent);
|
||||
break;
|
||||
case "Sensor":
|
||||
SensorAPI.onReceive(context, intent);
|
||||
break;
|
||||
case "Share":
|
||||
ShareAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user