Интеграция Android Wear OS c мобильным устройством на Android

Задача: создать прототип приложения, которое позволяло бы получать частоту сердцебиения с часов на Wear OS и передавать эти данные на смартфон под управлением Android.

Подготовительные работы:

  1. Создать эмулятор с предустановленными Google Play сервисами
  2. Установить на эмулятор приложение из Google Play Wear OS (и с не официальных источников https://apkpure.com/wear-os-by-google-smartwatch/com.google.android.wearable.app/download)
  3. Создать эмулятор для Wear OS (у меня в примере использовался API 30)
  4. Выполнить сопряжение, используя следующую статью 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 ещё и подписи.

  1. Создаём проем под 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

Print Friendly, PDF & Email

Добавить комментарий