From c86365ee2eff03e0f30c180b34896fe2f77c69b6 Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Sat, 13 Feb 2016 00:09:35 +0100 Subject: [PATCH] Some more Mi Band pairing improvements #180 - listen to notifications early -- the band then actually tells us that authentication is required - check for this after sending user info - add authentication states to GBDevice - workaround for event problems in pairing activity (delivered although already unregistered) - BtLEQueue now deals with gatt events coming *before* connectGatt() actually returned (namely the connection event) --- CHANGELOG.md | 2 +- .../devices/miband/MiBandPairingActivity.java | 11 +++- .../gadgetbridge/impl/GBDevice.java | 6 ++ .../gadgetbridge/service/btle/BtLEQueue.java | 6 +- .../CheckAuthenticationNeededAction.java | 27 +++++++++ .../service/devices/miband/MiBandSupport.java | 59 ++++++++++++++++--- app/src/main/res/values/strings.xml | 2 + 7 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java diff --git a/CHANGELOG.md b/CHANGELOG.md index bf9cd1359..f07328eb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ###Changelog ####Version (next) -* Pebble: Support Pebble Health: steps/activity data are stored correctly. Sleep time is considered as light sleep. Deep sleep is discarded. The pebble will send data where it deems appropriate, there is no action to perform on the watch for this to happen. +* Pebble: Support Pebble Health: steps/activity data are stored correctly. Sleep time is considered as light sleep. Deep sleep is discarded. The pebble will send data where it deems appropriate, there is no action to perform on the watch for this to happen.* Mi Band: improvements to pairing ####Version 0.7.4 * Refactored the settings activity: User details are now generic instead of miband specific. Old settings are preserved. diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java index c33a1909e..9a2b9fac3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java @@ -159,6 +159,12 @@ public class MiBandPairingActivity extends Activity { } private void pairingFinished(boolean pairedSuccessfully) { + LOG.debug("pairingFinished: " + pairedSuccessfully); + if (!isPairing) { + // already gone? + return; + } + isPairing = false; LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver); unregisterReceiver(mBondingReceiver); @@ -188,12 +194,13 @@ public class MiBandPairingActivity extends Activity { bondingMacAddress = device.getAddress(); if (bondState == BluetoothDevice.BOND_BONDING) { - LOG.info("Bonding in progress: " + device.getAddress()); + GB.toast(this, "Bonding in progress: " + bondingMacAddress, Toast.LENGTH_LONG, GB.INFO); return; } + GB.toast(this, "Creating bond with" + bondingMacAddress, Toast.LENGTH_LONG, GB.INFO); if (!device.createBond()) { - GB.toast(this, "Unable to pair with " + device.getAddress(), Toast.LENGTH_LONG, GB.ERROR); + GB.toast(this, "Unable to pair with " + bondingMacAddress, Toast.LENGTH_LONG, GB.ERROR); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java index 382230042..9e832494e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java @@ -203,6 +203,10 @@ public class GBDevice implements Parcelable { return GBApplication.getContext().getString(R.string.connected); case INITIALIZING: return GBApplication.getContext().getString(R.string.initializing); + case AUTHENTICATION_REQUIRED: + return GBApplication.getContext().getString(R.string.authentication_required); + case AUTHENTICATING: + return GBApplication.getContext().getString(R.string.authenticating); case INITIALIZED: return GBApplication.getContext().getString(R.string.initialized); } @@ -333,6 +337,8 @@ public class GBDevice implements Parcelable { CONNECTING, CONNECTED, INITIALIZING, + AUTHENTICATION_REQUIRED, // some kind of pairing is required by the device + AUTHENTICATING, // some kind of pairing is requested by the device /** * Means that the device is connected AND all the necessary initialization steps * have been performed. At the very least, this means that basic information like diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index b5f3125df..8104cb466 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -271,8 +271,8 @@ public final class BtLEQueue { } private boolean checkCorrectGattInstance(BluetoothGatt gatt, String where) { - if (gatt != mBluetoothGatt) { - LOG.info("Ignoring event from wrong BluetoothGatt instance: " + where); + if (gatt != mBluetoothGatt && mBluetoothGatt != null) { + LOG.info("Ignoring event from wrong BluetoothGatt instance: " + where + "; " + gatt); return false; } return true; @@ -319,7 +319,7 @@ public final class BtLEQueue { setDeviceConnectionState(State.CONNECTED); // Attempts to discover services after successful connection. LOG.info("Attempting to start service discovery:" + - mBluetoothGatt.discoverServices()); + gatt.discoverServices()); break; case BluetoothProfile.STATE_DISCONNECTED: LOG.info("Disconnected from GATT server."); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java new file mode 100644 index 000000000..6f25b1634 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java @@ -0,0 +1,27 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.bluetooth.BluetoothGatt; + +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction; + +public class CheckAuthenticationNeededAction extends PlainAction { + private final GBDevice mDevice; + + public CheckAuthenticationNeededAction(GBDevice device) { + super(); + mDevice = device; + } + + @Override + public boolean run(BluetoothGatt gatt) { + // the state is set in MiBandSupport.handleNotificationNotif() + switch (mDevice.getState()) { + case AUTHENTICATION_REQUIRED: // fall through + case AUTHENTICATING: + return false; // abort the whole thing + default: + return true; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index a78b37f86..43d6c75db 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -31,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; @@ -94,12 +95,14 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { @Override protected TransactionBuilder initializeDevice(TransactionBuilder builder) { builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZING, getContext())); - pair(builder) + enableNotifications(builder, true) + .pair(builder) .requestDeviceInfo(builder) .sendUserInfo(builder) + .checkAuthenticationNeeded(builder, getDevice()) .setWearLocation(builder) .setFitnessGoal(builder) - .enableNotifications(builder, true) + .enableFurtherNotifications(builder, true) .setCurrentTime(builder) .requestBatteryInfo(builder) .setInitialized(builder); @@ -107,6 +110,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return builder; } + private MiBandSupport checkAuthenticationNeeded(TransactionBuilder builder, GBDevice device) { + builder.add(new CheckAuthenticationNeededAction(device)); + return this; + } + /** * Last action of initialization sequence. Sets the device to initialized. * It is only invoked if all other actions were successfully run, so the device @@ -120,8 +128,12 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { // TODO: tear down the notifications on quit private MiBandSupport enableNotifications(TransactionBuilder builder, boolean enable) { - builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable) - .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable) + builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable); + return this; + } + + private MiBandSupport enableFurtherNotifications(TransactionBuilder builder, boolean enable) { + builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable) .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA), enable) .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_BATTERY), enable) .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable); @@ -685,6 +697,26 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return; } switch (value[0]) { + case MiBandService.NOTIFY_AUTHENTICATION_FAILED: + // we get first FAILED, then NOTIFY_STATUS_MOTOR_AUTH (0x13) + // which means, we need to authenticate by tapping + getDevice().setState(State.AUTHENTICATION_REQUIRED); + getDevice().sendDeviceUpdateIntent(getContext()); + GB.toast(getContext(), "Band needs pairing", Toast.LENGTH_LONG, GB.ERROR); + break; + case MiBandService.NOTIFY_AUTHENTICATION_SUCCESS: // fall through -- not sure which one we get + case MiBandService.NOTIFY_STATUS_MOTOR_AUTH_SUCCESS: + LOG.info("Band successfully authenticated"); + // maybe we can perform the rest of the initialization from here + doInitialize(); + break; + + case MiBandService.NOTIFY_STATUS_MOTOR_AUTH: + LOG.info("Band needs authentication (MOTOR_AUTH)"); + getDevice().setState(State.AUTHENTICATING); + getDevice().sendDeviceUpdateIntent(getContext()); + break; + default: for (byte b : value) { LOG.warn("DATA: " + String.format("0x%2x", b)); @@ -692,6 +724,15 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } } + private void doInitialize() { + try { + TransactionBuilder builder = performInitialized("just initializing after authentication"); + builder.queue(getQueue()); + } catch (IOException ex) { + LOG.error("Unable to initialize device after authentication", ex); + } + } + private void handleDeviceInfo(byte[] value, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { mDeviceInfo = new DeviceInfo(value); @@ -763,10 +804,12 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } private void handleUserInfoResult(byte[] value, int status) { - // successfully transfered user info means we're initialized - if (status == BluetoothGatt.GATT_SUCCESS) { - setConnectionState(State.INITIALIZED); - } + // successfully transferred user info means we're initialized +// commented out, because we have SetDeviceStateAction which sets initialized +// state on every successful initialization. +// if (status == BluetoothGatt.GATT_SUCCESS) { +// setConnectionState(State.INITIALIZED); +// } } private void setConnectionState(State newState) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 81a7d2db2..cb75de4d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -221,5 +221,7 @@ Weight in kg Activate Deactivate + authenticating + authentication required