2017-03-10 14:53:19 +01:00
/ * Copyright ( C ) 2015 - 2017 Andreas Shimokawa , Carsten Pfeiffer , Christian
Fischer , Daniele Gobbetti , JohnnySun , Julien Pivotto , Kasha , Sergey Trofimov ,
Steffen Liebergeld
This file is part of Gadgetbridge .
Gadgetbridge is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Gadgetbridge is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>. */
2016-12-14 00:28:35 +01:00
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2 ;
2016-07-25 00:00:22 +02:00
import android.bluetooth.BluetoothGatt ;
import android.bluetooth.BluetoothGattCharacteristic ;
import android.content.BroadcastReceiver ;
import android.content.Context ;
import android.content.Intent ;
import android.content.IntentFilter ;
import android.net.Uri ;
import android.support.v4.content.LocalBroadcastManager ;
2017-03-03 22:32:54 +01:00
import android.text.format.DateFormat ;
2016-07-25 00:00:22 +02:00
import android.widget.Toast ;
2016-11-24 21:58:32 +01:00
import org.apache.commons.lang3.ArrayUtils ;
2016-07-25 00:00:22 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import java.io.IOException ;
import java.util.ArrayList ;
import java.util.Calendar ;
import java.util.GregorianCalendar ;
import java.util.List ;
import java.util.UUID ;
2016-12-01 22:49:58 +01:00
import java.util.concurrent.TimeUnit ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBApplication ;
import nodomain.freeyourgadget.gadgetbridge.R ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler ;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo ;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider ;
2016-11-18 21:14:04 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay ;
2016-11-13 20:47:24 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider ;
2016-08-17 00:53:16 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst ;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator ;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService ;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession ;
import nodomain.freeyourgadget.gadgetbridge.entities.Device ;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample ;
import nodomain.freeyourgadget.gadgetbridge.entities.User ;
2016-10-20 20:08:17 +02:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample ;
2017-03-11 11:29:50 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.Alarm ;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents ;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService ;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec ;
2016-10-11 21:18:43 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType ;
2016-12-31 15:56:05 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport ;
2016-08-17 00:53:16 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction ;
2017-03-02 00:27:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory ;
2017-03-14 00:44:59 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile ;
2016-10-23 23:53:48 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile ;
2017-03-02 00:27:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification ;
2016-12-14 00:28:35 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport ;
2017-02-07 23:49:10 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.actions.StopNotificationAction ;
2016-11-18 21:14:04 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.FetchActivityOperation ;
2016-09-20 23:09:42 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.InitOperation ;
2016-12-11 02:10:07 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.UpdateFirmwareOperation ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.GB ;
2017-03-02 00:27:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.Prefs ;
2017-03-15 00:26:39 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.Version ;
2016-07-25 00:00:22 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PAUSE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_ORIGINAL_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PAUSE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefIntValue ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue ;
public class MiBand2Support extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory . getLogger ( MiBand2Support . class ) ;
private final DeviceInfoProfile < MiBand2Support > deviceInfoProfile ;
2016-10-24 00:23:59 +02:00
private final HeartRateProfile < MiBand2Support > heartRateProfile ;
2016-07-25 00:00:22 +02:00
private final BroadcastReceiver mReceiver = new BroadcastReceiver ( ) {
@Override
public void onReceive ( Context context , Intent intent ) {
String s = intent . getAction ( ) ;
if ( s . equals ( DeviceInfoProfile . ACTION_DEVICE_INFO ) ) {
handleDeviceInfo ( ( nodomain . freeyourgadget . gadgetbridge . service . btle . profiles . deviceinfo . DeviceInfo ) intent . getParcelableExtra ( DeviceInfoProfile . EXTRA_DEVICE_INFO ) ) ;
}
}
} ;
2016-09-20 23:09:42 +02:00
private boolean needsAuth ;
2016-07-25 00:00:22 +02:00
private volatile boolean telephoneRinging ;
private volatile boolean isLocatingDevice ;
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo ( ) ;
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo ( ) ;
2016-11-29 23:22:36 +01:00
private RealtimeSamplesSupport realtimeSamplesSupport ;
2017-02-07 23:49:10 +01:00
private boolean alarmClockRinging ;
2016-07-25 00:00:22 +02:00
public MiBand2Support ( ) {
2016-10-20 21:42:36 +02:00
super ( LOG ) ;
2016-07-25 00:00:22 +02:00
addSupportedService ( GattService . UUID_SERVICE_GENERIC_ACCESS ) ;
addSupportedService ( GattService . UUID_SERVICE_GENERIC_ATTRIBUTE ) ;
addSupportedService ( GattService . UUID_SERVICE_HEART_RATE ) ;
addSupportedService ( GattService . UUID_SERVICE_IMMEDIATE_ALERT ) ;
addSupportedService ( GattService . UUID_SERVICE_DEVICE_INFORMATION ) ;
addSupportedService ( GattService . UUID_SERVICE_ALERT_NOTIFICATION ) ;
addSupportedService ( MiBandService . UUID_SERVICE_MIBAND_SERVICE ) ;
addSupportedService ( MiBandService . UUID_SERVICE_MIBAND2_SERVICE ) ;
2016-12-11 02:10:07 +01:00
addSupportedService ( MiBand2Service . UUID_SERVICE_FIRMWARE_SERVICE ) ;
2016-07-25 00:00:22 +02:00
deviceInfoProfile = new DeviceInfoProfile < > ( this ) ;
addSupportedProfile ( deviceInfoProfile ) ;
2016-11-29 23:22:36 +01:00
heartRateProfile = new HeartRateProfile < > ( this ) ;
2016-10-24 00:23:59 +02:00
addSupportedProfile ( heartRateProfile ) ;
2016-07-25 00:00:22 +02:00
LocalBroadcastManager broadcastManager = LocalBroadcastManager . getInstance ( getContext ( ) ) ;
IntentFilter intentFilter = new IntentFilter ( ) ;
intentFilter . addAction ( DeviceInfoProfile . ACTION_DEVICE_INFO ) ;
2016-09-13 12:15:03 +02:00
intentFilter . addAction ( DeviceService . ACTION_MIBAND2_AUTH ) ;
2016-07-25 00:00:22 +02:00
broadcastManager . registerReceiver ( mReceiver , intentFilter ) ;
}
@Override
public void dispose ( ) {
LocalBroadcastManager broadcastManager = LocalBroadcastManager . getInstance ( getContext ( ) ) ;
broadcastManager . unregisterReceiver ( mReceiver ) ;
super . dispose ( ) ;
}
@Override
protected TransactionBuilder initializeDevice ( TransactionBuilder builder ) {
2016-09-20 23:09:42 +02:00
try {
boolean authenticate = needsAuth ;
needsAuth = false ;
new InitOperation ( authenticate , this , builder ) . perform ( ) ;
} catch ( IOException e ) {
GB . toast ( getContext ( ) , " Initializing Mi Band 2 failed " , Toast . LENGTH_SHORT , GB . ERROR , e ) ;
}
2016-07-25 00:00:22 +02:00
return builder ;
}
2016-12-01 22:49:58 +01:00
public byte [ ] getTimeBytes ( Calendar calendar , TimeUnit precision ) {
byte [ ] bytes ;
if ( precision = = TimeUnit . MINUTES ) {
bytes = BLETypeConversions . shortCalendarToRawBytes ( calendar , true ) ;
} else if ( precision = = TimeUnit . SECONDS ) {
bytes = BLETypeConversions . calendarToRawBytes ( calendar , true ) ;
} else {
throw new IllegalArgumentException ( " Unsupported precision, only MINUTES and SECONDS are supported till now " ) ;
}
2016-11-18 21:14:04 +01:00
byte [ ] tail = new byte [ ] { 0 , BLETypeConversions . mapTimeZone ( calendar . getTimeZone ( ) ) } ; // 0 = adjust reason bitflags? or DST offset?? , timezone
2016-08-17 00:53:16 +02:00
// byte[] tail = new byte[] { 0x2 }; // reason
byte [ ] all = BLETypeConversions . join ( bytes , tail ) ;
2016-11-18 21:14:04 +01:00
return all ;
}
public Calendar fromTimeBytes ( byte [ ] bytes ) {
GregorianCalendar timestamp = BLETypeConversions . rawBytesToCalendar ( bytes , true ) ;
return timestamp ;
}
public MiBand2Support setCurrentTimeWithService ( TransactionBuilder builder ) {
GregorianCalendar now = BLETypeConversions . createCalendar ( ) ;
2016-12-01 22:49:58 +01:00
byte [ ] bytes = getTimeBytes ( now , TimeUnit . SECONDS ) ;
2016-11-18 21:14:04 +01:00
builder . write ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_CURRENT_TIME ) , bytes ) ;
2016-12-01 22:49:58 +01:00
2016-08-17 00:53:16 +02:00
// byte[] localtime = BLETypeConversions.calendarToLocalTimeBytes(now);
// builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_LOCAL_TIME_INFORMATION), localtime);
// builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), new byte[] {0x2, 0x00});
// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x03,0x00,(byte)0x8e,(byte)0xce,0x5a,0x09,(byte)0xb3,(byte)0xd8,0x55,0x57,0x10,0x2a,(byte)0xed,0x7d,0x6b,0x78,(byte)0xc5,(byte)0xd2});
return this ;
}
2016-07-25 00:00:22 +02:00
public MiBand2Support setLowLatency ( TransactionBuilder builder ) {
2017-03-05 21:45:39 +01:00
// TODO: low latency?
2016-07-25 00:00:22 +02:00
return this ;
}
2017-03-05 21:45:39 +01:00
public MiBand2Support setHighLatency ( TransactionBuilder builder ) {
// TODO: high latency?
2016-07-25 00:00:22 +02:00
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
* must be initialized , then .
*
* @param builder
* /
2016-09-20 23:09:42 +02:00
public void setInitialized ( TransactionBuilder builder ) {
2016-07-25 00:00:22 +02:00
builder . add ( new SetDeviceStateAction ( getDevice ( ) , State . INITIALIZED , getContext ( ) ) ) ;
}
// MB2: AVL
// TODO: tear down the notifications on quit
2016-09-20 23:09:42 +02:00
public MiBand2Support enableNotifications ( TransactionBuilder builder , boolean enable ) {
2016-07-25 00:00:22 +02:00
builder . notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_NOTIFICATION ) , enable ) ;
2016-08-17 00:53:16 +02:00
builder . notify ( getCharacteristic ( GattService . UUID_SERVICE_CURRENT_TIME ) , enable ) ;
2016-09-12 19:28:50 +02:00
// Notify CHARACTERISTIC9 to receive random auth code
2016-09-20 23:09:42 +02:00
builder . notify ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_AUTH ) , enable ) ;
2016-07-25 00:00:22 +02:00
return this ;
}
2016-11-04 22:11:07 +01:00
public MiBand2Support enableFurtherNotifications ( TransactionBuilder builder , boolean enable ) {
2016-12-06 22:31:12 +01:00
builder . notify ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) , enable ) ;
2016-12-14 00:50:43 +01:00
builder . notify ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_6_BATTERY_INFO ) , enable ) ;
2017-02-21 21:41:21 +01:00
builder . notify ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_10_BUTTON ) , enable ) ;
2017-03-05 21:45:39 +01:00
BluetoothGattCharacteristic heartrateCharacteristic = getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT ) ;
2016-07-25 00:00:22 +02:00
if ( heartrateCharacteristic ! = null ) {
builder . notify ( heartrateCharacteristic , enable ) ;
}
return this ;
}
@Override
public boolean useAutoConnect ( ) {
return true ;
}
@Override
public void pair ( ) {
2016-09-20 23:09:42 +02:00
needsAuth = true ;
2016-07-25 00:00:22 +02:00
for ( int i = 0 ; i < 5 ; i + + ) {
if ( connect ( ) ) {
return ;
}
}
}
2017-03-02 00:27:54 +01:00
private MiBand2Support sendDefaultNotification ( TransactionBuilder builder , SimpleNotification simpleNotification , short repeat , BtLEAction extraAction ) {
2016-07-25 00:00:22 +02:00
LOG . info ( " Sending notification to MiBand: ( " + repeat + " times) " ) ;
NotificationStrategy strategy = getNotificationStrategy ( ) ;
for ( short i = 0 ; i < repeat ; i + + ) {
2017-03-02 00:27:54 +01:00
strategy . sendDefaultNotification ( builder , simpleNotification , extraAction ) ;
2016-07-25 00:00:22 +02:00
}
return this ;
}
/ * *
* Adds a custom notification to the given transaction builder
* @param vibrationProfile specifies how and how often the Band shall vibrate .
2017-03-02 00:27:54 +01:00
* @param simpleNotification
2016-07-25 00:00:22 +02:00
* @param flashTimes
* @param flashColour
* @param originalColour
* @param flashDuration
* @param extraAction an extra action to be executed after every vibration and flash sequence . Allows to abort the repetition , for example .
* @param builder
* /
2017-03-02 00:27:54 +01:00
private MiBand2Support sendCustomNotification ( VibrationProfile vibrationProfile , SimpleNotification simpleNotification , int flashTimes , int flashColour , int originalColour , long flashDuration , BtLEAction extraAction , TransactionBuilder builder ) {
getNotificationStrategy ( ) . sendCustomNotification ( vibrationProfile , simpleNotification , flashTimes , flashColour , originalColour , flashDuration , extraAction , builder ) ;
2016-07-25 00:00:22 +02:00
LOG . info ( " Sending notification to MiBand " ) ;
return this ;
}
private NotificationStrategy getNotificationStrategy ( ) {
2017-03-15 00:26:39 +01:00
String firmwareVersion = getDevice ( ) . getFirmwareVersion ( ) ;
if ( firmwareVersion ! = null ) {
Version ver = new Version ( firmwareVersion ) ;
if ( MiBandConst . MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS . compareTo ( ver ) > 0 ) {
return new Mi2NotificationStrategy ( this ) ;
}
}
if ( GBApplication . getPrefs ( ) . getBoolean ( MiBandConst . PREF_MI2_ENABLE_TEXT_NOTIFICATIONS , true ) ) {
return new Mi2TextNotificationStrategy ( this ) ;
}
return new Mi2NotificationStrategy ( this ) ;
2016-07-25 00:00:22 +02:00
}
2017-03-05 21:45:39 +01:00
private static final byte [ ] startHeartMeasurementManual = new byte [ ] { 0x15 , MiBandService . COMMAND_SET_HR_MANUAL , 1 } ;
private static final byte [ ] stopHeartMeasurementManual = new byte [ ] { 0x15 , MiBandService . COMMAND_SET_HR_MANUAL , 0 } ;
private static final byte [ ] startHeartMeasurementContinuous = new byte [ ] { 0x15 , MiBandService . COMMAND_SET__HR_CONTINUOUS , 1 } ;
private static final byte [ ] stopHeartMeasurementContinuous = new byte [ ] { 0x15 , MiBandService . COMMAND_SET__HR_CONTINUOUS , 0 } ;
2016-07-25 00:00:22 +02:00
2016-12-14 00:50:43 +01:00
private MiBand2Support requestBatteryInfo ( TransactionBuilder builder ) {
LOG . debug ( " Requesting Battery Info! " ) ;
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_6_BATTERY_INFO ) ;
builder . read ( characteristic ) ;
return this ;
}
2016-09-20 23:09:42 +02:00
public MiBand2Support requestDeviceInfo ( TransactionBuilder builder ) {
2016-07-25 00:00:22 +02:00
LOG . debug ( " Requesting Device Info! " ) ;
deviceInfoProfile . requestDeviceInfo ( builder ) ;
return this ;
}
/ * *
* Part of device initialization process . Do not call manually .
*
* @param transaction
* @return
* /
private MiBand2Support setFitnessGoal ( TransactionBuilder transaction ) {
LOG . info ( " Attempting to set Fitness Goal... " ) ;
2016-11-24 21:58:32 +01:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBand2Service . UUID_UNKNOWN_CHARACTERISTIC8 ) ;
2016-07-25 00:00:22 +02:00
if ( characteristic ! = null ) {
2017-03-11 11:29:50 +01:00
int fitnessGoal = GBApplication . getPrefs ( ) . getInt ( ActivityUser . PREF_USER_STEPS_GOAL , 10000 ) ;
2016-11-24 21:58:32 +01:00
byte [ ] bytes = ArrayUtils . addAll (
MiBand2Service . COMMAND_SET_FITNESS_GOAL_START ,
BLETypeConversions . fromUint16 ( fitnessGoal ) ) ;
bytes = ArrayUtils . addAll ( bytes ,
MiBand2Service . COMMAND_SET_FITNESS_GOAL_END ) ;
transaction . write ( characteristic , bytes ) ;
2016-07-25 00:00:22 +02:00
} else {
LOG . info ( " Unable to set Fitness Goal " ) ;
}
return this ;
}
/ * *
* Part of device initialization process . Do not call manually .
*
2016-11-13 01:42:55 +01:00
* @param builder
2016-07-25 00:00:22 +02:00
* @return
* /
2016-11-13 01:42:55 +01:00
private MiBand2Support setWearLocation ( TransactionBuilder builder ) {
2016-07-25 00:00:22 +02:00
LOG . info ( " Attempting to set wear location... " ) ;
2016-11-13 01:42:55 +01:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBand2Service . UUID_UNKNOWN_CHARACTERISTIC8 ) ;
2016-07-25 00:00:22 +02:00
if ( characteristic ! = null ) {
2016-11-13 20:47:24 +01:00
builder . notify ( characteristic , true ) ;
2016-11-13 01:42:55 +01:00
int location = MiBandCoordinator . getWearLocation ( getDevice ( ) . getAddress ( ) ) ;
switch ( location ) {
case 0 : // left hand
builder . write ( characteristic , MiBand2Service . WEAR_LOCATION_LEFT_WRIST ) ;
break ;
2016-11-24 21:58:32 +01:00
case 1 : // right hand
2016-11-13 01:42:55 +01:00
builder . write ( characteristic , MiBand2Service . WEAR_LOCATION_RIGHT_WRIST ) ;
break ;
}
2016-11-18 21:14:04 +01:00
builder . notify ( characteristic , false ) ; // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
2016-07-25 00:00:22 +02:00
}
return this ;
}
@Override
public void onEnableHeartRateSleepSupport ( boolean enable ) {
try {
TransactionBuilder builder = performInitialized ( " enable heart rate sleep support: " + enable ) ;
setHeartrateSleepSupport ( builder ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
GB . toast ( getContext ( ) , " Error toggling heart rate sleep support: " + e . getLocalizedMessage ( ) , Toast . LENGTH_LONG , GB . ERROR ) ;
}
}
@Override
public void onAddCalendarEvent ( CalendarEventSpec calendarEventSpec ) {
// not supported
}
@Override
public void onDeleteCalendarEvent ( byte type , long id ) {
// not supported
}
/ * *
* Part of device initialization process . Do not call manually .
*
* @param builder
* /
private MiBand2Support setHeartrateSleepSupport ( TransactionBuilder builder ) {
2016-11-13 01:42:55 +01:00
BluetoothGattCharacteristic characteristicHRControlPoint = getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) ;
final boolean enableHrSleepSupport = MiBandCoordinator . getHeartrateSleepSupport ( getDevice ( ) . getAddress ( ) ) ;
if ( characteristicHRControlPoint ! = null ) {
2016-11-13 20:47:24 +01:00
builder . notify ( characteristicHRControlPoint , true ) ;
2016-11-13 01:42:55 +01:00
if ( enableHrSleepSupport ) {
LOG . info ( " Enabling heartrate sleep support... " ) ;
builder . write ( characteristicHRControlPoint , MiBand2Service . COMMAND_ENABLE_HR_SLEEP_MEASUREMENT ) ;
} else {
LOG . info ( " Disabling heartrate sleep support... " ) ;
builder . write ( characteristicHRControlPoint , MiBand2Service . COMMAND_DISABLE_HR_SLEEP_MEASUREMENT ) ;
}
2016-11-18 21:14:04 +01:00
builder . notify ( characteristicHRControlPoint , false ) ; // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
2016-07-25 00:00:22 +02:00
}
return this ;
}
2017-03-02 00:27:54 +01:00
private void performDefaultNotification ( String task , SimpleNotification simpleNotification , short repeat , BtLEAction extraAction ) {
2016-07-25 00:00:22 +02:00
try {
TransactionBuilder builder = performInitialized ( task ) ;
2017-03-02 00:27:54 +01:00
sendDefaultNotification ( builder , simpleNotification , repeat , extraAction ) ;
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to send notification to MI device " , ex ) ;
}
}
2017-03-02 00:27:54 +01:00
private void performPreferredNotification ( String task , String notificationOrigin , SimpleNotification simpleNotification , int alertLevel , BtLEAction extraAction ) {
2016-07-25 00:00:22 +02:00
try {
TransactionBuilder builder = performInitialized ( task ) ;
Prefs prefs = GBApplication . getPrefs ( ) ;
int vibrateDuration = getPreferredVibrateDuration ( notificationOrigin , prefs ) ;
int vibratePause = getPreferredVibratePause ( notificationOrigin , prefs ) ;
short vibrateTimes = getPreferredVibrateCount ( notificationOrigin , prefs ) ;
VibrationProfile profile = getPreferredVibrateProfile ( notificationOrigin , prefs , vibrateTimes ) ;
2016-10-02 23:04:59 +02:00
profile . setAlertLevel ( alertLevel ) ;
2016-07-25 00:00:22 +02:00
int flashTimes = getPreferredFlashCount ( notificationOrigin , prefs ) ;
int flashColour = getPreferredFlashColour ( notificationOrigin , prefs ) ;
int originalColour = getPreferredOriginalColour ( notificationOrigin , prefs ) ;
int flashDuration = getPreferredFlashDuration ( notificationOrigin , prefs ) ;
2017-03-02 00:27:54 +01:00
sendCustomNotification ( profile , simpleNotification , flashTimes , flashColour , originalColour , flashDuration , extraAction , builder ) ;
2017-03-14 00:44:59 +01:00
2016-07-25 00:00:22 +02:00
// sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder);
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to send notification to MI device " , ex ) ;
}
}
private int getPreferredFlashDuration ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_DURATION , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_DURATION ) ;
}
private int getPreferredOriginalColour ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_ORIGINAL_COLOUR , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR ) ;
}
private int getPreferredFlashColour ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_COLOUR , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_COLOUR ) ;
}
private int getPreferredFlashCount ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_COUNT , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_COUNT ) ;
}
private int getPreferredVibratePause ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( VIBRATION_PAUSE , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_PAUSE ) ;
}
private short getPreferredVibrateCount ( String notificationOrigin , Prefs prefs ) {
return ( short ) Math . min ( Short . MAX_VALUE , getNotificationPrefIntValue ( VIBRATION_COUNT , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_COUNT ) ) ;
}
private int getPreferredVibrateDuration ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( VIBRATION_DURATION , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_DURATION ) ;
}
private VibrationProfile getPreferredVibrateProfile ( String notificationOrigin , Prefs prefs , short repeat ) {
String profileId = getNotificationPrefStringValue ( VIBRATION_PROFILE , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_PROFILE ) ;
return VibrationProfile . getProfile ( profileId , repeat ) ;
}
@Override
public void onSetAlarms ( ArrayList < ? extends Alarm > alarms ) {
try {
2016-12-06 22:31:12 +01:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) ;
2016-07-25 00:00:22 +02:00
TransactionBuilder builder = performInitialized ( " Set alarm " ) ;
boolean anyAlarmEnabled = false ;
for ( Alarm alarm : alarms ) {
anyAlarmEnabled | = alarm . isEnabled ( ) ;
queueAlarm ( alarm , builder , characteristic ) ;
}
builder . queue ( getQueue ( ) ) ;
if ( anyAlarmEnabled ) {
GB . toast ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_miband_set_alarms_ok ) , Toast . LENGTH_SHORT , GB . INFO ) ;
} else {
GB . toast ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_all_alarms_disabled ) , Toast . LENGTH_SHORT , GB . INFO ) ;
}
} catch ( IOException ex ) {
GB . toast ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_miband_set_alarms_failed ) , Toast . LENGTH_LONG , GB . ERROR , ex ) ;
}
}
@Override
public void onNotification ( NotificationSpec notificationSpec ) {
2017-02-07 23:49:10 +01:00
if ( notificationSpec . type = = NotificationType . GENERIC_ALARM_CLOCK ) {
onAlarmClock ( notificationSpec ) ;
return ;
}
2016-10-11 21:18:43 +02:00
int alertLevel = MiBand2Service . ALERT_LEVEL_MESSAGE ;
if ( notificationSpec . type = = NotificationType . UNKNOWN ) {
alertLevel = MiBand2Service . ALERT_LEVEL_VIBRATE_ONLY ;
2016-07-25 00:00:22 +02:00
}
2017-03-02 00:27:54 +01:00
String message = NotificationUtils . getPreferredTextFor ( notificationSpec , 40 , 40 , getContext ( ) ) . trim ( ) ;
2016-10-11 21:18:43 +02:00
String origin = notificationSpec . type . getGenericType ( ) ;
2017-03-02 00:27:54 +01:00
SimpleNotification simpleNotification = new SimpleNotification ( message , BLETypeConversions . toAlertCategory ( notificationSpec . type ) ) ;
performPreferredNotification ( origin + " received " , origin , simpleNotification , alertLevel , null ) ;
2016-07-25 00:00:22 +02:00
}
2017-02-07 23:49:10 +01:00
private void onAlarmClock ( NotificationSpec notificationSpec ) {
alarmClockRinging = true ;
AbortTransactionAction abortAction = new StopNotificationAction ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_ALERT_LEVEL ) ) {
@Override
protected boolean shouldAbort ( ) {
return ! isAlarmClockRinging ( ) ;
}
} ;
2017-03-02 00:27:54 +01:00
String message = NotificationUtils . getPreferredTextFor ( notificationSpec , 40 , 40 , getContext ( ) ) ;
SimpleNotification simpleNotification = new SimpleNotification ( message , AlertCategory . HighPriorityAlert ) ;
performPreferredNotification ( " alarm clock ringing " , MiBandConst . ORIGIN_ALARM_CLOCK , simpleNotification , MiBand2Service . ALERT_LEVEL_VIBRATE_ONLY , abortAction ) ;
2017-02-07 23:49:10 +01:00
}
2017-01-09 16:33:00 +01:00
@Override
public void onDeleteNotification ( int id ) {
2017-02-07 23:49:10 +01:00
alarmClockRinging = false ; // we should have the notificationtype at least to check
2017-01-09 16:33:00 +01:00
}
2016-07-25 00:00:22 +02:00
@Override
public void onSetTime ( ) {
try {
TransactionBuilder builder = performInitialized ( " Set date and time " ) ;
2016-12-01 22:49:58 +01:00
setCurrentTimeWithService ( builder ) ;
//TODO: once we have a common strategy for sending events (e.g. EventHandler), remove this call from here. Meanwhile it does no harm.
sendCalendarEvents ( builder ) ;
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to set time on MI device " , ex ) ;
}
}
@Override
public void onSetCallState ( CallSpec callSpec ) {
if ( callSpec . command = = CallSpec . CALL_INCOMING ) {
telephoneRinging = true ;
2017-02-07 23:49:10 +01:00
AbortTransactionAction abortAction = new StopNotificationAction ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_ALERT_LEVEL ) ) {
2016-07-25 00:00:22 +02:00
@Override
protected boolean shouldAbort ( ) {
return ! isTelephoneRinging ( ) ;
}
} ;
2017-03-02 00:27:54 +01:00
String message = NotificationUtils . getPreferredTextFor ( callSpec ) ;
SimpleNotification simpleNotification = new SimpleNotification ( message , AlertCategory . IncomingCall ) ;
performPreferredNotification ( " incoming call " , MiBandConst . ORIGIN_INCOMING_CALL , simpleNotification , MiBand2Service . ALERT_LEVEL_PHONE_CALL , abortAction ) ;
2016-07-25 00:00:22 +02:00
} else if ( ( callSpec . command = = CallSpec . CALL_START ) | | ( callSpec . command = = CallSpec . CALL_END ) ) {
telephoneRinging = false ;
2017-03-14 00:44:59 +01:00
stopCurrentNotification ( ) ;
}
}
private void stopCurrentNotification ( ) {
try {
TransactionBuilder builder = performInitialized ( " stop notification " ) ;
getNotificationStrategy ( ) . stopCurrentNotification ( builder ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Error stopping notification " ) ;
2016-07-25 00:00:22 +02:00
}
}
@Override
public void onSetCannedMessages ( CannedMessagesSpec cannedMessagesSpec ) {
}
2017-02-07 23:49:10 +01:00
private boolean isAlarmClockRinging ( ) {
// don't synchronize, this is not really important
return alarmClockRinging ;
}
2016-07-25 00:00:22 +02:00
private boolean isTelephoneRinging ( ) {
// don't synchronize, this is not really important
return telephoneRinging ;
}
@Override
public void onSetMusicState ( MusicStateSpec stateSpec ) {
// not supported
}
@Override
public void onSetMusicInfo ( MusicSpec musicSpec ) {
// not supported
}
@Override
public void onReboot ( ) {
try {
TransactionBuilder builder = performInitialized ( " Reboot " ) ;
2017-03-05 21:27:47 +01:00
builder . write ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_FIRMWARE ) , new byte [ ] { MiBand2Service . COMMAND_FIRMWARE_REBOOT } ) ;
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to reboot MI " , ex ) ;
}
}
@Override
public void onHeartRateTest ( ) {
2016-10-30 23:04:21 +01:00
try {
TransactionBuilder builder = performInitialized ( " HeartRateTest " ) ;
2017-03-05 21:45:39 +01:00
builder . write ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) , stopHeartMeasurementContinuous ) ;
builder . write ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) , stopHeartMeasurementManual ) ;
builder . write ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) , startHeartMeasurementManual ) ;
2016-10-30 23:04:21 +01:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to read HearRate with MI2 " , ex ) ;
2016-07-25 00:00:22 +02:00
}
}
@Override
public void onEnableRealtimeHeartRateMeasurement ( boolean enable ) {
2016-11-29 23:22:36 +01:00
try {
TransactionBuilder builder = performInitialized ( " Enable realtime heart rateM measurement " ) ;
if ( enable ) {
2017-03-05 21:45:39 +01:00
builder . write ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) , stopHeartMeasurementManual ) ;
builder . write ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) , startHeartMeasurementContinuous ) ;
2016-11-29 23:22:36 +01:00
} else {
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) , stopHeartMeasurementContinuous ) ;
}
builder . queue ( getQueue ( ) ) ;
enableRealtimeSamplesTimer ( enable ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to enable realtime heart rate measurement in MI1S " , ex ) ;
}
2016-07-25 00:00:22 +02:00
}
@Override
public void onFindDevice ( boolean start ) {
isLocatingDevice = start ;
if ( start ) {
AbortTransactionAction abortAction = new AbortTransactionAction ( ) {
@Override
protected boolean shouldAbort ( ) {
return ! isLocatingDevice ;
}
} ;
2017-03-14 00:44:59 +01:00
SimpleNotification simpleNotification = new SimpleNotification ( getContext ( ) . getString ( R . string . find_device_you_found_it ) , AlertCategory . HighPriorityAlert ) ;
2017-03-02 00:27:54 +01:00
performDefaultNotification ( " locating device " , simpleNotification , ( short ) 255 , abortAction ) ;
2016-07-25 00:00:22 +02:00
}
}
2016-09-20 20:28:52 +02:00
@Override
public void onSetConstantVibration ( int intensity ) {
}
2016-07-25 00:00:22 +02:00
@Override
public void onFetchActivityData ( ) {
2016-11-18 21:14:04 +01:00
try {
new FetchActivityOperation ( this ) . perform ( ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to fetch MI activity data " , ex ) ;
}
2016-07-25 00:00:22 +02:00
}
@Override
public void onEnableRealtimeSteps ( boolean enable ) {
}
private byte [ ] getHighLatency ( ) {
int minConnectionInterval = 460 ;
int maxConnectionInterval = 500 ;
int latency = 0 ;
int timeout = 500 ;
int advertisementInterval = 0 ;
return getLatency ( minConnectionInterval , maxConnectionInterval , latency , timeout , advertisementInterval ) ;
}
private byte [ ] getLatency ( int minConnectionInterval , int maxConnectionInterval , int latency , int timeout , int advertisementInterval ) {
byte result [ ] = new byte [ 12 ] ;
result [ 0 ] = ( byte ) ( minConnectionInterval & 0xff ) ;
result [ 1 ] = ( byte ) ( 0xff & minConnectionInterval > > 8 ) ;
result [ 2 ] = ( byte ) ( maxConnectionInterval & 0xff ) ;
result [ 3 ] = ( byte ) ( 0xff & maxConnectionInterval > > 8 ) ;
result [ 4 ] = ( byte ) ( latency & 0xff ) ;
result [ 5 ] = ( byte ) ( 0xff & latency > > 8 ) ;
result [ 6 ] = ( byte ) ( timeout & 0xff ) ;
result [ 7 ] = ( byte ) ( 0xff & timeout > > 8 ) ;
result [ 8 ] = 0 ;
result [ 9 ] = 0 ;
result [ 10 ] = ( byte ) ( advertisementInterval & 0xff ) ;
result [ 11 ] = ( byte ) ( 0xff & advertisementInterval > > 8 ) ;
return result ;
}
private byte [ ] getLowLatency ( ) {
int minConnectionInterval = 39 ;
int maxConnectionInterval = 49 ;
int latency = 0 ;
int timeout = 500 ;
int advertisementInterval = 0 ;
return getLatency ( minConnectionInterval , maxConnectionInterval , latency , timeout , advertisementInterval ) ;
}
@Override
public void onInstallApp ( Uri uri ) {
2016-12-11 02:10:07 +01:00
try {
new UpdateFirmwareOperation ( uri , this ) . perform ( ) ;
} catch ( IOException ex ) {
GB . toast ( getContext ( ) , " Firmware cannot be installed: " + ex . getMessage ( ) , Toast . LENGTH_LONG , GB . ERROR , ex ) ;
}
2016-07-25 00:00:22 +02:00
}
@Override
public void onAppInfoReq ( ) {
// not supported
}
@Override
public void onAppStart ( UUID uuid , boolean start ) {
// not supported
}
@Override
public void onAppDelete ( UUID uuid ) {
// not supported
}
@Override
public void onAppConfiguration ( UUID uuid , String config ) {
// not supported
}
@Override
public void onAppReorder ( UUID [ ] uuids ) {
// not supported
}
@Override
public void onScreenshotReq ( ) {
// not supported
}
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicChanged ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic ) {
2016-07-25 00:00:22 +02:00
super . onCharacteristicChanged ( gatt , characteristic ) ;
UUID characteristicUUID = characteristic . getUuid ( ) ;
2016-12-14 00:50:43 +01:00
if ( MiBand2Service . UUID_CHARACTERISTIC_6_BATTERY_INFO . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleBatteryInfo ( characteristic . getValue ( ) , BluetoothGatt . GATT_SUCCESS ) ;
2016-07-28 23:04:37 +02:00
return true ;
2016-07-25 00:00:22 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_REALTIME_STEPS . equals ( characteristicUUID ) ) {
handleRealtimeSteps ( characteristic . getValue ( ) ) ;
2016-07-28 23:04:37 +02:00
return true ;
2017-03-05 21:45:39 +01:00
} else if ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleHeartrate ( characteristic . getValue ( ) ) ;
2016-07-28 23:04:37 +02:00
return true ;
2016-08-17 00:53:16 +02:00
// } else if (MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0.equals(characteristicUUID)) {
// handleUnknownCharacteristic(characteristic.getValue());
// return true;
2016-09-20 23:09:42 +02:00
} else if ( MiBand2Service . UUID_CHARACTERISTIC_AUTH . equals ( characteristicUUID ) ) {
LOG . info ( " AUTHENTICATION?? " + characteristicUUID ) ;
2016-09-13 12:15:03 +02:00
logMessageContent ( characteristic . getValue ( ) ) ;
2016-09-20 23:09:42 +02:00
return true ;
2017-02-21 21:41:21 +01:00
} else if ( MiBand2Service . UUID_CHARACTERISTIC_10_BUTTON . equals ( characteristicUUID ) ) {
handleButtonPressed ( characteristic . getValue ( ) ) ;
return true ;
2016-07-25 00:00:22 +02:00
} else {
LOG . info ( " Unhandled characteristic changed: " + characteristicUUID ) ;
logMessageContent ( characteristic . getValue ( ) ) ;
}
2016-07-28 23:04:37 +02:00
return false ;
2016-07-25 00:00:22 +02:00
}
2017-02-21 21:41:21 +01:00
private void handleButtonPressed ( byte [ ] value ) {
LOG . info ( " Button pressed: " + value ) ;
logMessageContent ( value ) ;
}
2016-08-17 00:53:16 +02:00
private void handleUnknownCharacteristic ( byte [ ] value ) {
}
2016-07-25 00:00:22 +02:00
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicRead ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic , int status ) {
2016-07-25 00:00:22 +02:00
super . onCharacteristicRead ( gatt , characteristic , status ) ;
UUID characteristicUUID = characteristic . getUuid ( ) ;
2016-10-30 23:04:21 +01:00
if ( GattCharacteristic . UUID_CHARACTERISTIC_GAP_DEVICE_NAME . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleDeviceName ( characteristic . getValue ( ) , status ) ;
2016-07-28 23:04:37 +02:00
return true ;
2016-12-14 00:50:43 +01:00
} else if ( MiBand2Service . UUID_CHARACTERISTIC_6_BATTERY_INFO . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleBatteryInfo ( characteristic . getValue ( ) , status ) ;
2016-07-28 23:04:37 +02:00
return true ;
2017-03-05 21:45:39 +01:00
} else if ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
logHeartrate ( characteristic . getValue ( ) , status ) ;
2016-07-28 23:04:37 +02:00
return true ;
2017-02-21 21:41:21 +01:00
} else if ( MiBand2Service . UUID_CHARACTERISTIC_10_BUTTON . equals ( characteristicUUID ) ) {
handleButtonPressed ( characteristic . getValue ( ) ) ;
return true ;
2016-07-25 00:00:22 +02:00
} else {
LOG . info ( " Unhandled characteristic read: " + characteristicUUID ) ;
logMessageContent ( characteristic . getValue ( ) ) ;
}
2016-07-28 23:04:37 +02:00
return false ;
2016-07-25 00:00:22 +02:00
}
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicWrite ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic , int status ) {
2016-07-25 00:00:22 +02:00
UUID characteristicUUID = characteristic . getUuid ( ) ;
2017-03-05 21:45:39 +01:00
if ( MiBand2Service . UUID_CHARACTERISTIC_AUTH . equals ( characteristicUUID ) ) {
2016-09-13 12:15:03 +02:00
LOG . info ( " KEY AES SEND " ) ;
logMessageContent ( characteristic . getValue ( ) ) ;
return true ;
2016-07-25 00:00:22 +02:00
}
2016-07-28 23:04:37 +02:00
return false ;
2016-07-25 00:00:22 +02:00
}
public void logHeartrate ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS & & value ! = null ) {
LOG . info ( " Got heartrate: " ) ;
2016-11-29 23:22:36 +01:00
if ( value . length = = 2 & & value [ 0 ] = = 0 ) {
2016-07-25 00:00:22 +02:00
int hrValue = ( value [ 1 ] & 0xff ) ;
GB . toast ( getContext ( ) , " Heart Rate measured: " + hrValue , Toast . LENGTH_LONG , GB . INFO ) ;
}
return ;
}
logMessageContent ( value ) ;
}
private void handleHeartrate ( byte [ ] value ) {
2016-11-29 23:22:36 +01:00
if ( value . length = = 2 & & value [ 0 ] = = 0 ) {
2016-07-25 00:00:22 +02:00
int hrValue = ( value [ 1 ] & 0xff ) ;
if ( LOG . isDebugEnabled ( ) ) {
LOG . debug ( " heart rate: " + hrValue ) ;
}
2016-11-29 23:22:36 +01:00
RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport ( ) ;
realtimeSamplesSupport . setHeartrateBpm ( hrValue ) ;
if ( ! realtimeSamplesSupport . isRunning ( ) ) {
// single shot measurement, manually invoke storage and result publishing
realtimeSamplesSupport . triggerCurrentSample ( ) ;
}
2016-07-25 00:00:22 +02:00
}
}
private void handleRealtimeSteps ( byte [ ] value ) {
int steps = 0xff & value [ 0 ] | ( 0xff & value [ 1 ] ) < < 8 ;
if ( LOG . isDebugEnabled ( ) ) {
LOG . debug ( " realtime steps: " + steps ) ;
}
2016-11-29 23:22:36 +01:00
getRealtimeSamplesSupport ( ) . setSteps ( steps ) ;
}
private void enableRealtimeSamplesTimer ( boolean enable ) {
if ( enable ) {
getRealtimeSamplesSupport ( ) . start ( ) ;
} else {
if ( realtimeSamplesSupport ! = null ) {
realtimeSamplesSupport . stop ( ) ;
}
}
}
public MiBandActivitySample createActivitySample ( Device device , User user , int timestampInSeconds , SampleProvider provider ) {
MiBandActivitySample sample = new MiBandActivitySample ( ) ;
sample . setDevice ( device ) ;
sample . setUser ( user ) ;
sample . setTimestamp ( timestampInSeconds ) ;
sample . setProvider ( provider ) ;
return sample ;
}
private RealtimeSamplesSupport getRealtimeSamplesSupport ( ) {
if ( realtimeSamplesSupport = = null ) {
realtimeSamplesSupport = new RealtimeSamplesSupport ( 1000 , 1000 ) {
@Override
public void doCurrentSample ( ) {
try ( DBHandler handler = GBApplication . acquireDB ( ) ) {
DaoSession session = handler . getDaoSession ( ) ;
Device device = DBHelper . getDevice ( getDevice ( ) , session ) ;
User user = DBHelper . getUser ( session ) ;
int ts = ( int ) ( System . currentTimeMillis ( ) / 1000 ) ;
MiBand2SampleProvider provider = new MiBand2SampleProvider ( gbDevice , session ) ;
MiBandActivitySample sample = createActivitySample ( device , user , ts , provider ) ;
sample . setHeartRate ( getHeartrateBpm ( ) ) ;
sample . setSteps ( getSteps ( ) ) ;
sample . setRawIntensity ( ActivitySample . NOT_MEASURED ) ;
sample . setRawKind ( MiBand2SampleProvider . TYPE_ACTIVITY ) ; // to make it visible in the charts TODO: add a MANUAL kind for that?
2016-12-26 11:33:01 +01:00
provider . addGBActivitySample ( sample ) ;
// set the steps only afterwards, since realtime steps are also recorded
// in the regular samples and we must not count them twice
// Note: we know that the DAO sample is never committed again, so we simply
// change the value here in memory.
sample . setSteps ( getSteps ( ) ) ;
if ( LOG . isDebugEnabled ( ) ) {
LOG . debug ( " realtime sample: " + sample ) ;
}
2016-12-26 00:23:02 +01:00
Intent intent = new Intent ( DeviceService . ACTION_REALTIME_SAMPLES )
. putExtra ( DeviceService . EXTRA_REALTIME_SAMPLE , sample ) ;
LocalBroadcastManager . getInstance ( getContext ( ) ) . sendBroadcast ( intent ) ;
2016-11-29 23:22:36 +01:00
} catch ( Exception e ) {
LOG . warn ( " Unable to acquire db for saving realtime samples " , e ) ;
}
}
} ;
}
return realtimeSamplesSupport ;
2016-07-25 00:00:22 +02:00
}
private void handleDeviceName ( byte [ ] value , int status ) {
// if (status == BluetoothGatt.GATT_SUCCESS) {
// versionCmd.hwVersion = new String(value);
// handleGBDeviceEvent(versionCmd);
// }
}
/ * *
* Convert an alarm from the GB internal structure to a Mi Band message and put on the specified
* builder queue as a write message for the passed characteristic
*
* @param alarm
* @param builder
* @param characteristic
* /
private void queueAlarm ( Alarm alarm , TransactionBuilder builder , BluetoothGattCharacteristic characteristic ) {
2016-10-21 00:49:42 +02:00
Calendar calendar = alarm . getAlarmCal ( ) ;
2016-10-23 23:05:54 +02:00
int maxAlarms = 5 ; // arbitrary at the moment...
if ( alarm . getIndex ( ) > = maxAlarms ) {
2016-10-23 22:37:18 +02:00
if ( alarm . isEnabled ( ) ) {
2016-10-23 23:05:54 +02:00
GB . toast ( getContext ( ) , " Only 5 alarms are currently supported. " , Toast . LENGTH_LONG , GB . WARN ) ;
2016-10-23 22:37:18 +02:00
}
2016-10-21 01:01:30 +02:00
return ;
}
2016-10-28 23:18:10 +02:00
int base = 0 ;
if ( alarm . isEnabled ( ) ) {
base = 128 ;
}
2016-10-28 22:25:38 +02:00
int daysMask = alarm . getRepetitionMask ( ) ;
2016-10-28 23:18:10 +02:00
if ( ! alarm . isRepetitive ( ) ) {
daysMask = 128 ;
}
2016-10-21 00:49:42 +02:00
byte [ ] alarmMessage = new byte [ ] {
2016-10-23 23:05:54 +02:00
( byte ) 0x2 , // TODO what is this?
2016-10-28 22:25:38 +02:00
( byte ) ( base + alarm . getIndex ( ) ) , // 128 is the base, alarm slot is added
2016-10-21 00:49:42 +02:00
( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ,
( byte ) calendar . get ( Calendar . MINUTE ) ,
( byte ) daysMask ,
2016-07-25 00:00:22 +02:00
} ;
2016-10-21 01:01:30 +02:00
builder . write ( characteristic , alarmMessage ) ;
2016-10-23 22:37:18 +02:00
// TODO: react on 0x10, 0x02, 0x01 on notification (success)
2016-07-25 00:00:22 +02:00
}
private void handleDeviceInfo ( nodomain . freeyourgadget . gadgetbridge . service . btle . profiles . deviceinfo . DeviceInfo info ) {
// if (getDeviceInfo().supportsHeartrate()) {
// getDevice().addDeviceInfo(new GenericItem(
// getContext().getString(R.string.DEVINFO_HR_VER),
// info.getSoftwareRevision()));
// }
LOG . warn ( " Device info: " + info ) ;
versionCmd . hwVersion = info . getHardwareRevision ( ) ;
2016-09-20 23:09:42 +02:00
// versionCmd.fwVersion = info.getFirmwareRevision(); // always null
versionCmd . fwVersion = info . getSoftwareRevision ( ) ;
2017-03-15 00:26:39 +01:00
if ( versionCmd . fwVersion ! = null & & versionCmd . fwVersion . length ( ) > 0 & & versionCmd . fwVersion . charAt ( 0 ) = = 'V' ) {
versionCmd . fwVersion = versionCmd . fwVersion . substring ( 1 ) ;
}
2016-07-25 00:00:22 +02:00
handleGBDeviceEvent ( versionCmd ) ;
}
private void handleBatteryInfo ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS ) {
BatteryInfo info = new BatteryInfo ( value ) ;
batteryCmd . level = ( ( short ) info . getLevelInPercent ( ) ) ;
batteryCmd . state = info . getState ( ) ;
batteryCmd . lastChargeTime = info . getLastChargeTime ( ) ;
batteryCmd . numCharges = info . getNumCharges ( ) ;
handleGBDeviceEvent ( batteryCmd ) ;
}
}
/ * *
* Fetch the events from the android device calendars and set the alarms on the miband .
2016-12-01 22:49:58 +01:00
* @param builder
2016-07-25 00:00:22 +02:00
* /
2016-12-01 22:49:58 +01:00
private MiBand2Support sendCalendarEvents ( TransactionBuilder builder ) {
2016-12-06 22:31:12 +01:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) ;
2016-07-25 00:00:22 +02:00
2016-12-01 22:49:58 +01:00
Prefs prefs = GBApplication . getPrefs ( ) ;
int availableSlots = prefs . getInt ( MiBandConst . PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR , 0 ) ;
2016-07-25 00:00:22 +02:00
2016-12-01 22:49:58 +01:00
if ( availableSlots > 0 ) {
CalendarEvents upcomingEvents = new CalendarEvents ( ) ;
List < CalendarEvents . CalendarEvent > mEvents = upcomingEvents . getCalendarEventList ( getContext ( ) ) ;
2016-07-25 00:00:22 +02:00
2016-12-01 22:49:58 +01:00
int iteration = 0 ;
for ( CalendarEvents . CalendarEvent mEvt : mEvents ) {
if ( iteration > = availableSlots | | iteration > 2 ) {
break ;
2016-07-25 00:00:22 +02:00
}
2016-12-01 22:49:58 +01:00
int slotToUse = 2 - iteration ;
Calendar calendar = Calendar . getInstance ( ) ;
calendar . setTimeInMillis ( mEvt . getBegin ( ) ) ;
Alarm alarm = GBAlarm . createSingleShot ( slotToUse , false , calendar ) ;
queueAlarm ( alarm , builder , characteristic ) ;
iteration + + ;
2016-07-25 00:00:22 +02:00
}
2016-12-01 22:49:58 +01:00
builder . queue ( getQueue ( ) ) ;
2016-07-25 00:00:22 +02:00
}
2016-12-01 22:49:58 +01:00
return this ;
2016-07-25 00:00:22 +02:00
}
2016-11-13 20:47:24 +01:00
@Override
public void onSendConfiguration ( String config ) {
2017-03-03 22:32:54 +01:00
TransactionBuilder builder ;
2016-11-13 20:47:24 +01:00
try {
builder = performInitialized ( " Sending configuration for option: " + config ) ;
switch ( config ) {
case MiBandConst . PREF_MI2_DATEFORMAT :
setDateDisplay ( builder ) ;
break ;
2016-11-13 21:33:43 +01:00
case MiBandConst . PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT :
setActivateDisplayOnLiftWrist ( builder ) ;
break ;
2017-03-11 11:29:50 +01:00
case ActivityUser . PREF_USER_STEPS_GOAL :
2016-11-24 21:58:32 +01:00
setFitnessGoal ( builder ) ;
break ;
2016-11-13 20:47:24 +01:00
}
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
GB . toast ( " Error setting configuration " , Toast . LENGTH_LONG , GB . ERROR , e ) ;
}
}
2016-10-11 23:06:59 +02:00
@Override
public void onTestNewFunction ( ) {
2017-02-21 21:41:21 +01:00
try {
2017-03-14 00:44:59 +01:00
TransactionBuilder builder = performInitialized ( " incoming call from peter " ) ;
NewAlert alert = new NewAlert ( AlertCategory . Custom , 1 , new String ( new byte [ ] { 0x19 } ) ) ;
AlertNotificationProfile < MiBand2Support > profile = new AlertNotificationProfile < > ( this ) ;
profile . newAlert ( builder , alert , OverflowStrategy . MAKE_MULTIPLE ) ;
builder . queue ( getQueue ( ) ) ;
2017-02-21 21:41:21 +01:00
} catch ( IOException e ) {
}
2016-10-11 23:06:59 +02:00
}
2016-11-13 01:42:55 +01:00
2016-12-31 15:56:05 +01:00
@Override
public void onSendWeather ( WeatherSpec weatherSpec ) {
}
2016-11-13 20:47:24 +01:00
private MiBand2Support setDateDisplay ( TransactionBuilder builder ) {
DateTimeDisplay dateTimeDisplay = MiBand2Coordinator . getDateDisplay ( getContext ( ) ) ;
LOG . info ( " Setting date display to " + dateTimeDisplay ) ;
switch ( dateTimeDisplay ) {
2016-11-13 01:42:55 +01:00
case TIME :
2016-12-06 22:31:12 +01:00
builder . write ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand2Service . DATEFORMAT_TIME ) ;
2016-11-13 01:42:55 +01:00
break ;
case DATE_TIME :
2016-12-06 22:31:12 +01:00
builder . write ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand2Service . DATEFORMAT_DATE_TIME ) ;
2016-11-13 01:42:55 +01:00
break ;
}
return this ;
}
2017-03-03 22:14:28 +01:00
private MiBand2Support setTimeFormat ( TransactionBuilder builder ) {
2017-03-03 22:32:54 +01:00
boolean is24Format = DateFormat . is24HourFormat ( getContext ( ) ) ;
LOG . info ( " Setting 24h time format to " + is24Format ) ;
if ( is24Format ) {
builder . write ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand2Service . DATEFORMAT_TIME_24_HOURS ) ;
} else {
builder . write ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand2Service . DATEFORMAT_TIME_12_HOURS ) ;
2017-03-03 22:14:28 +01:00
}
return this ;
}
2016-11-13 21:33:43 +01:00
private MiBand2Support setActivateDisplayOnLiftWrist ( TransactionBuilder builder ) {
boolean enable = MiBand2Coordinator . getActivateDisplayOnLiftWrist ( ) ;
LOG . info ( " Setting activate display on lift wrist to " + enable ) ;
if ( enable ) {
2016-12-06 22:31:12 +01:00
builder . write ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand2Service . COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST ) ;
2016-11-13 21:33:43 +01:00
} else {
2016-12-06 22:31:12 +01:00
builder . write ( getCharacteristic ( MiBand2Service . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand2Service . COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST ) ;
2016-11-13 21:33:43 +01:00
}
return this ;
}
2016-11-13 01:42:55 +01:00
public void phase2Initialize ( TransactionBuilder builder ) {
LOG . info ( " phase2Initialize... " ) ;
enableFurtherNotifications ( builder , true ) ;
2016-12-14 00:50:43 +01:00
requestBatteryInfo ( builder ) ;
2016-11-13 20:47:24 +01:00
setDateDisplay ( builder ) ;
2017-03-03 22:14:28 +01:00
setTimeFormat ( builder ) ;
2016-11-13 01:42:55 +01:00
setWearLocation ( builder ) ;
2016-11-24 21:58:32 +01:00
setFitnessGoal ( builder ) ;
2016-11-13 21:33:43 +01:00
setActivateDisplayOnLiftWrist ( builder ) ;
2016-11-13 01:42:55 +01:00
setHeartrateSleepSupport ( builder ) ;
}
2016-07-25 00:00:22 +02:00
}