Задача: создать прототип приложения, которое позволяло бы получать частоту сердцебиения с часов на Wear OS и передавать эти данные на смартфон под управлением Android.
Подготовительные работы:
- Создать эмулятор с предустановленными Google Play сервисами
- Установить на эмулятор приложение из Google Play Wear OS (и с не официальных источников https://apkpure.com/wear-os-by-google-smartwatch/com.google.android.wearable.app/download)
- Создать эмулятор для Wear OS (у меня в примере использовался API 30)
- Выполнить сопряжение, используя следующую статью https://developer.android.com/training/wearables/get-started/connect-phone
Результат: будут созданы два приложения, один для часов, другой для смартфона и при этом из package name должен быть одинаковым. А судя из статьи https://stackoverflow.com/questions/48921165/syncing-data-items-between-mobile-device-and-wear ещё и подписи.
- Создаём проем под Android (c одним Активити)
Класс MainActivity должен содержать следующий код:
public class MainActivity extends AppCompatActivity
implements DataClient.OnDataChangedListener {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
....
}
@Override
protected void onResume() {
super.onResume();
Wearable.getDataClient(this).addListener(this);
}
@Override
protected void onPause() {
super.onPause();
Wearable.getDataClient(this).removeListener(this);
}
@Override
public void onDataChanged(@NonNull DataEventBuffer dataEventBuffer) {
for (DataEvent event : dataEventBuffer) {
if (event.getType() == DataEvent.TYPE_DELETED) {
Log.d("SENSORS", "DataItem deleted: " + event.getDataItem().getUri());
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
// Тут, как раз пример получения данных с Wear OS
DataItem item = event.getDataItem();
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
Log.d("SENSORS", "DataItem changed: " + event.getDataItem().getUri() + dataMap.getInt(Sensor.STRING_TYPE_HEART_RATE));
}
}
}
}
В Andriod манифест добавляем разрешение:
<uses-permission android:name="android.permission.BODY_SENSORS" />
Примечание: у меня есть сомнение, что это нужно в приложении для смартфона
В gruble нужно добавить ссылку на:
# play-services-wearable
dependencies {
...
implementation libs.play.services.wearable
...
}
2. В созданный проект добавляем модуль для Wear OS с пустым активити
Создаём активити:
public class MainActivity extends AppCompatActivity
implements View.OnClickListener, SensorEventListener, DataClient.OnDataChangedListener {
public static final String TAG = "HEART_RATE";
protected final ActivityResultLauncher<String[]> mPermissionResultLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), this::onSensorsPermission);
private Sensor mHeartRateSensor;
private SensorManager mSensorManager;
private Button mManagerMonitor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
mManagerMonitor = findViewById(R.id.manager_monitor);
mManagerMonitor.setOnClickListener(this);
Wearable.getDataClient(this).addListener(this);
}
@Override
protected void onStop() {
super.onStop();
Wearable.getDataClient(this).removeListener(this);
}
@Override
public void onClick(View view) {
if(view.getId() == R.id.manager_monitor) {
if(mManagerMonitor.getText() == getString(R.string.start)) {
permissionRequest();
} else {
stopMonitor();
}
}
}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.sensor.getType() == Sensor.TYPE_HEART_RATE) {
String msg = " Value sensor: " + (int)sensorEvent.values[0];
Log.d(TAG, msg);
DataClient dataClient = Wearable.getDataClient(this);
PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/message").setUrgent();
putDataMapReq.getDataMap().putInt(Sensor.STRING_TYPE_HEART_RATE, (int)sensorEvent.values[0]);
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest().setUrgent();
Task<DataItem> putDataTask = dataClient.putDataItem(putDataReq);
putDataTask.addOnSuccessListener(new OnSuccessListener<DataItem>() {
@Override
public void onSuccess(DataItem dataItem) {
Log.d(TAG, "sending :)");
}
});
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
Log.d(TAG, "onAccuracyChanged - accuracy: " + i);
}
private void permissionRequest() {
if (checkSelfPermission(android.Manifest.permission.BODY_SENSORS) != PackageManager.PERMISSION_GRANTED) {
mPermissionResultLauncher.launch(new String[] { android.Manifest.permission.BODY_SENSORS });
}
else{
Log.d(TAG,"ALREADY GRANTED");
initSensors();
startMonitor();
}
}
protected void onSensorsPermission(@NonNull Map<String, Boolean> result) {
boolean areAllGranted = true;
for (Boolean b : result.values()) {
areAllGranted = areAllGranted && b;
}
if (areAllGranted) {
initSensors();
startMonitor();
} else {
Snackbar.make(findViewById(R.id.main), "Недостаточно прав для приложения", Snackbar.LENGTH_LONG).setAction("...", view -> {
startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName())));
}).show();
}
}
private void startMonitor() {
mManagerMonitor.setText(getString(R.string.stop));
if (mSensorManager != null && mHeartRateSensor != null) {
mSensorManager.registerListener(this, mHeartRateSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
}
private void stopMonitor() {
mManagerMonitor.setText(getString(R.string.start));
if (mSensorManager != null)
mSensorManager.unregisterListener(this);
}
private void initSensors() {
mSensorManager = (SensorManager)this.getSystemService(Context.SENSOR_SERVICE);
mHeartRateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);
}
@Override
protected void onDestroy() {
stopMonitor();
super.onDestroy();
}
@Override
public void onDataChanged(@NonNull DataEventBuffer dataEventBuffer) {
for (DataEvent event : dataEventBuffer) {
if (event.getType() == DataEvent.TYPE_DELETED) {
Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
}
}
}
}
Этот кусок кода получает ЧСС и передаёт их на смартфон.
Примеры брал из:
- https://developer.android.com/training/wearables/data/events?hl=ru
- https://stackoverflow.com/questions/48921165/syncing-data-items-between-mobile-device-and-wear
- https://stackoverflow.com/questions/44337896/get-heart-rate-from-android-wear
Манифест выглядит следующим образом:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.type.watch" />
<uses-permission android:name="android.permission.BODY_SENSORS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light">
<activity
android:name=".MainActivity"
android:exported="true"
android:taskAffinity=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
</application>
</manifest>
build.gradle
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.mobwal.budibase.heart'
compileSdk 34
defaultConfig {
applicationId "com.example.myapplication"
minSdk 30
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation libs.play.services.wearable
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
}
Пример проекта тут https://github.com/akrasnov87/heart-rate-monitor