GeoTecINIT / WearOSSensors

Reliable and concurrent access to smartwatch's sensors
Apache License 2.0
18 stars 4 forks source link
library sensors wear-os

WearOS Sensors

Maven Central

The WearOS Sensors library is an Android WearOS library that allows to collect data from the IMU sensors (i.e., accelerometer and gyroscope), the magnetometer, the heart rate, and the GPS of an Android WearOS smartwatch (if the corresponding sensor is available in the device).

This library can be used to build WearOS applications that are the counterpart of smartphone applications built with the nativescript-wearos-sensors plugin (for the NativeScript framework). The smartphone application counterpart can request to start/stop the data collection on the smartwatch, and then receive the collected data from the smartwatch.

[!IMPORTANT] An application built with this library is completely useless if there is not a counterpart application built with the nativescript-wearos-sensors plugin installed in the paired smartphone. In other words, the smartwatch can not work by itself alone. It requires for a smartphone to work.

The data collection can be started both from the smartwatch and from the paired smartphone. In addition, the library offers a way to communicate with the smartphone by sending messages.

The WearOS Sensors library uses and extends the functionality of the Android Background Sensors library, and therefore, it is safe to carry out the data collection in the background (i.e., when the app is not in the foreground, or the smartwatch is idle).

Installation

To install the library you have to add the dependency in your build.gradle:

dependencies {
    implementation 'io.github.geotecinit:wear-os-sensors:1.2.0'
}

Requirements

The library has the following requirements:

[!IMPORTANT] Both applications (smartwatch and smartphone apps) must have the same application id. If that's not the case, the applications will not be able to interact.

[!TIP] Don't forget to check the requirements of nativescript-wearos-sensors too.

Tested WearOS versions and devices

The library has been tested in the following WearOS versions:

Usage

The library offers two main features:

Location You **must** add the following permission to the manifest: ```xml ``` In addition, if your app runs on a WearOS 3+ smartwatch (Android 13 - API 33), add the following permission: ```xml ```

Sensor data collection

The library allows to collect data from the accelerometer, gyroscope, magnetometer, heart rate and GPS sensors of the smartwatch device. For the data collection, the application in the smartwatch acts as a companion application, where the main application is the on in the smartphone. This means that the smartphone is who instructs the smartwatch to start/stop the data collection, even when is the smartwatch who wants to start/stop the data collection.

Before going deeper with the data collection, we have to talk about permissions.

Permissions

In order to access to the data of the heart rate and the GPS sensors, the user has to grant some permissions. The library handles this situation (i.e., when permissions are required), and launches a notification to warn the user that some permissions need to be granted. We can differentiate two main steps in the process:

  1. Check if permissions are required:
    • If the data collection is started using the paired smartphone, the check is automatic. You have to do nothing!
    • If the data collection is started using the smartwatch, you need to do the check using the PermissionsManager.launchPermissionsRequestIfNeeded() method.
  2. Request the required permissions: the library handles mostly of the process by you, but you still have to do execute some steps:
    • Create an Activity: it will be used by the library to request the permissions. This will allow you to define a UI where some information can be given prior to the permissions request.
    • In your activity:
    • Inject the Activity to the library using the PermissionsManager.setPermissionsActivity() method.

Here you can see an example of an activity for requesting permissions:

public class YourRequestPermissionsActivity extends FragmentActivity {

    private PermissionsRequestHandler handler;

    protected void onCreate(Bundle savedInstanceState) {
        // You can show a message explaining why you are going to request permissions
        // and then...

        handler = new PermissionsRequestHandler(this);
        handler.onPermissionsResult(this::updateUI);
        handler.handleRequest();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        handler.handleResult(requestCode, permissions, grantResults);
    }

    private void updateUI(boolean success) {
        // Update your UI...
    }
}

If your app runs in WearOS 4+, the POST_NOTIFICATIONS permission will be required. To do so, we also provide the PermissionsManager.launchRequiredPermissionsRequest().

Finally, here is a sample on how to setup your activity for requesting permissions:

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        // ...

        // Inject the permissions Activity
        PermissionsManager.setPermissionsActivity(this, YourRequestPermissionsActivity.class);

        // Request Android 13+ required permissions
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            PermissionsManager.launchRequiredPermissionsRequest(this);
        }
    }
}

Start/stop data collection from smartphone

The library fully handles this for you. You can start and stop the data collection from the smartphone and receive the collected data. You have to do nothing!

Start/stop data collection from smartwatch

When starting the data collection from the smartwatch, you can chose where the collected data will be delivered: to the smartwatch itself or to the paired smartphone.

Obtain the data in the smartwatch

You can use the ServiceManager provided by Background Sensors to start and stop the data collection and receive the data in the device:

public class MainActivity extends Activity {
    // ...
    private CommandClient commandClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        sensorManager = new SensorManager(context);
        serviceManager = new ServiceManager(this, WearSensorRecordingService.class);
    }

    public void setupUI() {
        List<Sensor> availableSensors = sensorManager.availableSensors(WearSensor.values());
    }

    public void onStartSingleCommandTap(WearSensor sensor) {
        CollectionConfiguration config = new CollectionConfiguration(
                selectedSensor,
                android.hardware.SensorManager.SENSOR_DELAY_GAME,
                selectedSensor == WearSensor.HEART_RATE || selectedSensor == WearSensor.LOCATION ? 1 : 50
        );
        serviceManager.startCollection(config, records -> {
            // ...
        });
    }

    public void onStopSingleCommandTap(Sensor sensor) {
        serviceManager.stopCollection(sensor, records -> {
            // ...
        });
    }

    // ...
}

[!TIP] Please, refer to the Background Sensors documentation.

Obtain the data in the smartphone

To start or stop the data collection, the smartphone needs to be updated regarding the change in the data collection status. So, if we want to start/stop the data collection from the smartwatch, we have to notify that intention to the smartphone. Then, the smartphone will update its internal status and once everything is set up, it will confirm the smartwatch that the data collection can be started/stopped, so the smartwatch can act in consequence.

In order to start/stop the data collection from the smartwatch you can use the CommandClient. Also, the sensors are defined in WearSensor and you can get the sensors that are available using the SensorManager. Here you can see a sample usage:

public class MainActivity extends Activity {
    // ...
    private CommandClient commandClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        commandClient = new CommandClient(this);
    }

    public void setupUI() {
        List<Sensor> availableSensors = sensorManager.availableSensors(WearSensor.values());
    }

    public void onStartSingleCommandTap(Sensor sensor) {
        // If the sensor requires permissions and have not been granted...
        boolean requested = PermissionsManager.launchPermissionsRequestIfNeeded(this, sensor.getRequiredPermissions());
        if (requested) return;

        CollectionConfiguration config = new CollectionConfiguration(
                selectedSensor,
                android.hardware.SensorManager.SENSOR_DELAY_GAME,
                selectedSensor == WearSensor.HEART_RATE || selectedSensor == WearSensor.LOCATION ? 1 : 50
        );
        commandClient.sendStartCommand(config);
    }

    public void onStopSingleCommandTap(Sensor sensor) {
        commandClient.sendStopCommand(selectedSensor);
    }

    // ...
}

[!TIP] Here we are using Sensor and CollectionConfiguration from Background Sensors. Check its documentation for more information.

Messaging

When having a system composed by several devices (two, in our case), it is important to have a way to communicate. We provide the PlainMessageClient, which allows to send and receive string-based messages. There are two types of received messages: the ones which require a response and the ones which don't. For now, sending messages with required response is only available from the smartphone side.

Here you can see an example on how to use the messaging feature:

public class MainActivity extends Activity {
    // ...
    private PlainMessageClient plainMessageClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        plainMessageClient = new PlainMessageClient(this);

        // Register a listener for the messages
        plainMessageClient.registerListener(message -> {
            Log.d("MainActivity", "received " + message);

            // We received a message with response required so...
            if (message.responseRequired()){
                Log.d("MainActivity", "response required! sending response...");
                // We send a response
                PlainMessage response = new PlainMessage("PONG!", message.getPlainMessage());
                plainMessageClient.send(response);
            }
        });
    }

      public void onSendPlainMessageTap(View view) {
            PlainMessage message = new PlainMessage("Hi! This is a test message");
            plainMessageClient.send(message);
      }
  }

[!TIP] You can find a full sample of all these features in the MainActivity and RequestPermissionsActivity activities of the demo application.

API

WearSensor

Value Description
ACCELEROMETER Represents the accelerometer sensor.
GYROSCOPE Represents the gyroscope sensor.
MAGNETOMETER Represents the magnetometer sensor.
HEART_RATE Represents the heart rate monitor.
LOCATION Represents the GPS.

Each sensor provide the getRequiredPermissions() method to obtain the permissions that need to be requested for the specified sensor. Use it along PermissionsManager.launchPermissionsRequestIfNeeded().

SensorManager

Refer to the Background Sensors documentation.

ServiceManager

Refer to the Background Sensors documentation.

PermissionsManager

Static Method Return type Description
setPermissionsActivity(Context context, Class<?> permissionsActivity) void Sets up the class that will be used for requesting permissions. You should call this method in your MainActivity class once your app has started.

PermissionsRequestHandler

Method Return type Description
handleRequest() void Starts the workflow to request permissions. Call inside your permissions activity.
handleResult(int requestCode, String[] permissions, int[] grantResults) void Call inside the overrided onRequestPermissionsResult of your permissions activity.
onPermissionsResult(PermissionsResult permissionsResult) void Inject a PermissionsResult callback. The callback will be called with True if all required permissions were granted, False otherwise.

CommandClient

Method Return type Description
sendStartCommand(CollectionConfiguration configuration) void Sends a command to the smartphone to start the collection in the smartwatch with the specified configuration.
sendStopCommand(Sensor sensor) void Sends a command to the smartphone to stop the collection of the specified sensor in the smartwatch.

CollectionConfiguration

Refer to the Background Sensors documentation.

Sensor

Refer to the Background Sensors documentation.

PlainMessageClient

Method Return type Description
registerListener(PlainMessageListener listener) void Registers the listener for the messaging feature.
unregisterListener() void Unregisters the listener for the messaging feature.
send(PlainMessage plainMessage) void Sends a message to the smartphone.

PlainMessage

Field Type Description
message String Content of the message.
inResponseTo PlainMessage If the message is a response to other one, the reference to that message. null otherwise.

License

Apache License 2.0

See LICENSE.

Author

Miguel Matey Sanz

Acknowledgements

The development of this library has been possible thanks to the Spanish Ministry of Universities (grant FPU19/05352).