diff --git a/README.md b/README.md index 822106811..654cc493e 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e7718ee64..9f82da618 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,6 +54,7 @@ android:exported="false"/> + 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 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 getSensorsToListenTo(SensorManager sensorManager, String[] requestedSensors, Intent intent) { + List availableSensors = sensorManager.getSensorList(Sensor.TYPE_ALL); + List 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 + } + +} + diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index 304a30d0c..3fcac72ee 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -71,6 +71,9 @@ public void onReceive(Context context, Intent intent) { case "NotificationRemove": NotificationAPI.onReceiveRemoveNotification(this, context, intent); break; + case "Sensor": + SensorAPI.onReceive(context, intent); + break; case "Share": ShareAPI.onReceive(this, context, intent); break;