2015-04-14 01:24:03 +02:00
package nodomain.freeyourgadget.gadgetbridge.miband ;
2015-05-01 01:49:43 +02:00
import android.bluetooth.BluetoothGatt ;
import android.bluetooth.BluetoothGattCharacteristic ;
2015-05-17 22:57:37 +02:00
import android.content.SharedPreferences ;
2015-06-09 21:05:44 +02:00
import android.database.sqlite.SQLiteDatabase ;
2015-06-06 23:24:00 +02:00
import android.net.Uri ;
2015-05-17 22:57:37 +02:00
import android.preference.PreferenceManager ;
2015-06-25 14:48:46 +02:00
import android.widget.Toast ;
2015-05-01 01:49:43 +02:00
2015-05-18 20:56:19 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2015-04-19 02:37:29 +02:00
import java.io.IOException ;
2015-06-06 23:59:53 +02:00
import java.text.DateFormat ;
2015-06-25 14:34:21 +02:00
import java.util.ArrayList ;
2015-05-12 20:32:34 +02:00
import java.util.Arrays ;
2015-04-23 14:11:57 +02:00
import java.util.Calendar ;
2015-05-24 00:11:14 +02:00
import java.util.GregorianCalendar ;
2015-04-26 00:53:48 +02:00
import java.util.UUID ;
2015-04-19 02:37:29 +02:00
2015-06-01 10:15:19 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBActivitySample ;
2015-06-24 20:14:08 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBAlarm ;
2015-06-01 10:15:19 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBApplication ;
2015-04-14 01:24:03 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBCommand ;
2015-04-19 15:11:50 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBDevice.State ;
2015-06-06 00:40:16 +02:00
import nodomain.freeyourgadget.gadgetbridge.R ;
2015-06-20 23:22:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.btle.AbortTransactionAction ;
2015-04-19 02:37:29 +02:00
import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport ;
2015-06-20 23:22:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.btle.BtLEAction ;
2015-06-06 00:40:16 +02:00
import nodomain.freeyourgadget.gadgetbridge.btle.SetDeviceBusyAction ;
2015-04-19 02:37:29 +02:00
import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder ;
2015-06-09 21:05:44 +02:00
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler ;
2015-04-14 01:24:03 +02:00
2015-05-18 20:56:19 +02:00
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PAUSE ;
2015-06-23 11:54:33 +02:00
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE ;
2015-05-18 20:56:19 +02:00
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_ORIGINAL_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_GENERIC ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_K9MAIL ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_SMS ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_PAUSE ;
2015-06-20 23:22:22 +02:00
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_PROFILE ;
2015-05-18 20:56:19 +02:00
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.getNotificationPrefIntValue ;
2015-06-20 23:22:22 +02:00
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.getNotificationPrefStringValue ;
2015-05-17 22:57:37 +02:00
2015-04-14 01:24:03 +02:00
public class MiBandSupport extends AbstractBTLEDeviceSupport {
2015-05-12 06:28:11 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( MiBandSupport . class ) ;
2015-06-01 09:42:44 +02:00
2015-06-11 23:34:16 +02:00
//temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 bytes)
2015-07-12 00:02:51 +02:00
private static final int activityDataHolderSize = 3 * 60 * 4 ; // 8h
2015-06-01 09:42:44 +02:00
private byte [ ] activityDataHolder = new byte [ activityDataHolderSize ] ;
//index of the buffer above
private int activityDataHolderProgress = 0 ;
//number of bytes we will get in a single data transfer, used as counter
private int activityDataRemainingBytes = 0 ;
//same as above, but remains untouched for the ack message
private int activityDataUntilNextHeader = 0 ;
//timestamp of the single data transfer, incremented to store each minute's data
private GregorianCalendar activityDataTimestampProgress = null ;
//same as above, but remains untouched for the ack message
private GregorianCalendar activityDataTimestampToAck = null ;
2015-06-20 23:22:22 +02:00
private volatile boolean telephoneRinging ;
2015-06-21 00:34:05 +02:00
private volatile boolean isLocatingDevice ;
2015-05-24 19:09:14 +02:00
2015-04-19 02:37:29 +02:00
public MiBandSupport ( ) {
addSupportedService ( MiBandService . UUID_SERVICE_MIBAND_SERVICE ) ;
2015-04-14 02:03:14 +02:00
}
2015-04-14 01:24:03 +02:00
@Override
2015-04-19 02:37:29 +02:00
protected TransactionBuilder initializeDevice ( TransactionBuilder builder ) {
2015-05-28 00:26:41 +02:00
builder . add ( new SetDeviceStateAction ( getDevice ( ) , State . INITIALIZING , getContext ( ) ) ) ;
2015-05-25 23:14:02 +02:00
pair ( builder )
. sendUserInfo ( builder )
. enableNotifications ( builder , true )
. setCurrentTime ( builder )
2015-05-28 00:26:41 +02:00
. requestBatteryInfo ( builder )
. setInitialized ( builder ) ;
2015-05-24 00:11:14 +02:00
2015-04-19 02:37:29 +02:00
return builder ;
2015-04-14 01:24:03 +02:00
}
2015-05-28 00:26:41 +02:00
/ * *
* 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 .
2015-06-13 00:32:48 +02:00
*
2015-05-28 00:26:41 +02:00
* @param builder
* /
private void setInitialized ( TransactionBuilder builder ) {
builder . add ( new SetDeviceStateAction ( getDevice ( ) , State . INITIALIZED , getContext ( ) ) ) ;
}
2015-05-24 00:11:14 +02:00
// 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 )
2015-05-24 14:39:36 +02:00
. notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_ACTIVITY_DATA ) , enable )
2015-05-24 00:11:14 +02:00
. notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_BATTERY ) , enable )
. notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_SENSOR_DATA ) , enable ) ;
return this ;
}
2015-04-14 01:24:03 +02:00
@Override
2015-04-19 02:37:29 +02:00
public boolean useAutoConnect ( ) {
return true ;
}
2015-04-19 11:28:03 +02:00
2015-05-05 00:48:02 +02:00
@Override
public void pair ( ) {
for ( int i = 0 ; i < 5 ; i + + ) {
if ( connect ( ) ) {
return ;
}
}
}
2015-04-19 02:37:29 +02:00
private byte [ ] getDefaultNotification ( ) {
final int vibrateTimes = 1 ;
final long vibrateDuration = 250l ;
final int flashTimes = 1 ;
final int flashColour = 0xFFFFFFFF ;
final int originalColour = 0xFFFFFFFF ;
final long flashDuration = 250l ;
return getNotification ( vibrateDuration , vibrateTimes , flashTimes , flashColour , originalColour , flashDuration ) ;
}
2015-04-14 01:24:03 +02:00
2015-06-21 00:34:05 +02:00
private void sendDefaultNotification ( TransactionBuilder builder , short repeat , BtLEAction extraAction ) {
2015-04-19 02:37:29 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) ;
2015-06-21 00:34:05 +02:00
LOG . info ( " Sending notification to MiBand: " + characteristic + " ( " + repeat + " times) " ) ;
byte [ ] defaultNotification = getDefaultNotification ( ) ;
for ( short i = 0 ; i < repeat ; i + + ) {
builder . write ( characteristic , defaultNotification ) ;
builder . add ( extraAction ) ;
}
builder . queue ( getQueue ( ) ) ;
2015-04-19 02:37:29 +02:00
}
2015-06-20 23:22:22 +02:00
/ * *
* Sends a custom notification to the Mi Band .
2015-06-30 12:09:29 +02:00
*
2015-06-20 23:22:22 +02:00
* @param vibrationProfile specifies how and how often the Band shall vibrate .
* @param flashTimes
* @param flashColour
* @param originalColour
* @param flashDuration
2015-06-30 12:09:29 +02:00
* @param extraAction an extra action to be executed after every vibration and flash sequence . Allows to abort the repetition , for example .
2015-06-20 23:22:22 +02:00
* @param builder
* /
private void sendCustomNotification ( VibrationProfile vibrationProfile , int flashTimes , int flashColour , int originalColour , long flashDuration , BtLEAction extraAction , TransactionBuilder builder ) {
BluetoothGattCharacteristic controlPoint = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) ;
2015-06-21 00:34:05 +02:00
for ( short i = 0 ; i < vibrationProfile . getRepeat ( ) ; i + + ) {
2015-06-20 23:22:22 +02:00
int [ ] onOffSequence = vibrationProfile . getOnOffSequence ( ) ;
for ( int j = 0 ; j < onOffSequence . length ; j + + ) {
int on = onOffSequence [ j ] ;
on = Math . min ( 500 , on ) ; // longer than 500ms is not possible
builder . write ( controlPoint , startVibrate ) ;
builder . wait ( on ) ;
builder . write ( controlPoint , stopVibrate ) ;
if ( + + j < onOffSequence . length ) {
int off = Math . max ( onOffSequence [ j ] , 25 ) ; // wait at least 25ms
builder . wait ( off ) ;
}
if ( extraAction ! = null ) {
builder . add ( extraAction ) ;
}
}
}
LOG . info ( " Sending notification to MiBand: " + controlPoint ) ;
builder . queue ( getQueue ( ) ) ;
}
2015-05-17 22:57:37 +02:00
private void sendCustomNotification ( int vibrateDuration , int vibrateTimes , int pause , int flashTimes , int flashColour , int originalColour , long flashDuration , TransactionBuilder builder ) {
BluetoothGattCharacteristic controlPoint = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) ;
int vDuration = Math . min ( 500 , vibrateDuration ) ; // longer than 500ms is not possible
for ( int i = 0 ; i < vibrateTimes ; i + + ) {
builder . write ( controlPoint , startVibrate ) ;
builder . wait ( vDuration ) ;
builder . write ( controlPoint , stopVibrate ) ;
if ( pause > 0 ) {
builder . wait ( pause ) ;
}
}
LOG . info ( " Sending notification to MiBand: " + controlPoint ) ;
builder . queue ( getQueue ( ) ) ;
}
2015-06-13 00:32:48 +02:00
private static final byte [ ] startVibrate = new byte [ ] { MiBandService . COMMAND_SEND_NOTIFICATION , 1 } ;
private static final byte [ ] stopVibrate = new byte [ ] { MiBandService . COMMAND_STOP_MOTOR_VIBRATE } ;
private static final byte [ ] reboot = new byte [ ] { MiBandService . COMMAND_REBOOT } ;
private static final byte [ ] fetch = new byte [ ] { MiBandService . COMMAND_FETCH_DATA } ;
2015-05-17 22:57:37 +02:00
2015-04-19 02:37:29 +02:00
private byte [ ] getNotification ( long vibrateDuration , int vibrateTimes , int flashTimes , int flashColour , int originalColour , long flashDuration ) {
2015-05-21 18:19:07 +02:00
byte [ ] vibrate = new byte [ ] { MiBandService . COMMAND_SEND_NOTIFICATION , ( byte ) 1 } ;
2015-04-19 02:37:29 +02:00
byte r = 6 ;
byte g = 0 ;
byte b = 6 ;
boolean display = true ;
// byte[] flashColor = new byte[]{ 14, r, g, b, display ? (byte) 1 : (byte) 0 };
return vibrate ;
}
/ * *
* Part of device initialization process . Do not call manually .
2015-04-19 11:28:03 +02:00
*
2015-04-19 02:37:29 +02:00
* @param builder
* @return
* /
private MiBandSupport sendUserInfo ( TransactionBuilder builder ) {
2015-05-12 06:28:11 +02:00
LOG . debug ( " Writing User Info! " ) ;
2015-04-19 02:37:29 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_USER_INFO ) ;
2015-05-07 23:51:03 +02:00
builder . write ( characteristic , MiBandCoordinator . getAnyUserInfo ( getDevice ( ) . getAddress ( ) ) . getData ( ) ) ;
2015-04-19 02:37:29 +02:00
return this ;
}
2015-05-05 23:25:54 +02:00
private MiBandSupport requestBatteryInfo ( TransactionBuilder builder ) {
2015-05-12 06:28:11 +02:00
LOG . debug ( " Requesting Battery Info! " ) ;
2015-05-05 23:25:54 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_BATTERY ) ;
builder . read ( characteristic ) ;
return this ;
}
2015-04-19 02:37:29 +02:00
/ * *
* Part of device initialization process . Do not call manually .
2015-04-19 11:28:03 +02:00
*
2015-05-07 23:51:03 +02:00
* @param transaction
2015-04-19 02:37:29 +02:00
* @return
* /
private MiBandSupport pair ( TransactionBuilder transaction ) {
2015-05-12 06:28:11 +02:00
LOG . info ( " Attempting to pair MI device... " ) ;
2015-04-19 02:37:29 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_PAIR ) ;
if ( characteristic ! = null ) {
transaction . write ( characteristic , new byte [ ] { 2 } ) ;
} else {
2015-05-12 06:28:11 +02:00
LOG . info ( " Unable to pair MI device -- characteristic not available " ) ;
2015-04-19 02:37:29 +02:00
}
return this ;
}
2015-06-21 00:34:05 +02:00
private void performDefaultNotification ( String task , short repeat , BtLEAction extraAction ) {
2015-04-19 02:37:29 +02:00
try {
TransactionBuilder builder = performInitialized ( task ) ;
2015-06-21 00:34:05 +02:00
sendDefaultNotification ( builder , repeat , extraAction ) ;
2015-04-19 02:37:29 +02:00
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to send notification to MI device " , ex ) ;
2015-04-19 02:37:29 +02:00
}
2015-04-14 01:24:03 +02:00
}
2015-05-17 22:57:37 +02:00
// private void performCustomNotification(String task, int vibrateDuration, int vibrateTimes, int pause, int flashTimes, int flashColour, int originalColour, long flashDuration) {
// try {
// TransactionBuilder builder = performInitialized(task);
// sendCustomNotification(vibrateDuration, vibrateTimes, pause, flashTimes, flashColour, originalColour, flashDuration, builder);
// } catch (IOException ex) {
// LOG.error("Unable to send notification to MI device", ex);
// }
// }
2015-06-20 23:22:22 +02:00
private void performPreferredNotification ( String task , String notificationOrigin , BtLEAction extraAction ) {
2015-05-17 22:57:37 +02:00
try {
TransactionBuilder builder = performInitialized ( task ) ;
SharedPreferences prefs = PreferenceManager . getDefaultSharedPreferences ( getContext ( ) ) ;
int vibrateDuration = getPreferredVibrateDuration ( notificationOrigin , prefs ) ;
int vibratePause = getPreferredVibratePause ( notificationOrigin , prefs ) ;
2015-06-20 23:22:22 +02:00
int vibrateTimes = getPreferredVibrateCount ( notificationOrigin , prefs ) ;
VibrationProfile profile = getPreferredVibrateProfile ( notificationOrigin , prefs , vibrateTimes ) ;
2015-05-17 22:57:37 +02:00
int flashTimes = getPreferredFlashCount ( notificationOrigin , prefs ) ;
int flashColour = getPreferredFlashColour ( notificationOrigin , prefs ) ;
int originalColour = getPreferredOriginalColour ( notificationOrigin , prefs ) ;
int flashDuration = getPreferredFlashDuration ( notificationOrigin , prefs ) ;
2015-06-20 23:22:22 +02:00
sendCustomNotification ( profile , flashTimes , flashColour , originalColour , flashDuration , extraAction , builder ) ;
// sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder);
2015-05-17 22:57:37 +02:00
} catch ( IOException ex ) {
LOG . error ( " Unable to send notification to MI device " , ex ) ;
}
}
private int getPreferredFlashDuration ( String notificationOrigin , SharedPreferences prefs ) {
return getNotificationPrefIntValue ( FLASH_DURATION , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_DURATION ) ;
}
private int getPreferredOriginalColour ( String notificationOrigin , SharedPreferences prefs ) {
return getNotificationPrefIntValue ( FLASH_ORIGINAL_COLOUR , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR ) ;
}
private int getPreferredFlashColour ( String notificationOrigin , SharedPreferences prefs ) {
return getNotificationPrefIntValue ( FLASH_COLOUR , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_COLOUR ) ;
}
private int getPreferredFlashCount ( String notificationOrigin , SharedPreferences prefs ) {
return getNotificationPrefIntValue ( FLASH_COUNT , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_COUNT ) ;
}
private int getPreferredVibratePause ( String notificationOrigin , SharedPreferences prefs ) {
return getNotificationPrefIntValue ( VIBRATION_PAUSE , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_PAUSE ) ;
}
private int getPreferredVibrateCount ( String notificationOrigin , SharedPreferences prefs ) {
return getNotificationPrefIntValue ( VIBRATION_COUNT , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_COUNT ) ;
}
private int getPreferredVibrateDuration ( String notificationOrigin , SharedPreferences prefs ) {
return getNotificationPrefIntValue ( VIBRATION_DURATION , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_DURATION ) ;
}
2015-06-20 23:22:22 +02:00
private VibrationProfile getPreferredVibrateProfile ( String notificationOrigin , SharedPreferences prefs , int repeat ) {
String profileId = getNotificationPrefStringValue ( VIBRATION_PROFILE , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_PROFILE ) ;
2015-06-21 00:34:05 +02:00
return VibrationProfile . getProfile ( profileId , ( byte ) ( repeat & 0xfff ) ) ;
2015-06-20 23:22:22 +02:00
}
2015-06-24 20:14:08 +02:00
@Override
2015-06-25 14:34:21 +02:00
public void onSetAlarms ( ArrayList < GBAlarm > alarms ) {
2015-06-24 20:14:08 +02:00
try {
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) ;
TransactionBuilder builder = performInitialized ( " Set alarm " ) ;
2015-06-25 14:34:21 +02:00
for ( GBAlarm alarm : alarms ) {
queueAlarm ( alarm , builder , characteristic ) ;
}
2015-06-24 20:14:08 +02:00
builder . queue ( getQueue ( ) ) ;
2015-06-25 14:48:46 +02:00
Toast . makeText ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_miband_set_alarms_ok ) , Toast . LENGTH_SHORT ) . show ( ) ;
2015-06-24 20:14:08 +02:00
} catch ( IOException ex ) {
2015-06-25 14:48:46 +02:00
Toast . makeText ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_miband_set_alarms_failed ) , Toast . LENGTH_LONG ) . show ( ) ;
2015-06-24 20:14:08 +02:00
LOG . error ( " Unable to set alarms on MI device " , ex ) ;
}
}
2015-04-14 01:24:03 +02:00
@Override
2015-04-19 02:37:29 +02:00
public void onSMS ( String from , String body ) {
2015-06-20 23:22:22 +02:00
performPreferredNotification ( " sms received " , ORIGIN_SMS , null ) ;
2015-04-19 02:37:29 +02:00
}
2015-04-14 01:24:03 +02:00
2015-04-19 02:37:29 +02:00
@Override
public void onEmail ( String from , String subject , String body ) {
2015-06-20 23:22:22 +02:00
performPreferredNotification ( " email received " , ORIGIN_K9MAIL , null ) ;
2015-04-14 01:24:03 +02:00
}
2015-05-13 21:55:22 +02:00
@Override
public void onGenericNotification ( String title , String details ) {
2015-06-20 23:22:22 +02:00
performPreferredNotification ( " generic notification received " , ORIGIN_GENERIC , null ) ;
2015-05-13 21:55:22 +02:00
}
2015-04-14 01:24:03 +02:00
@Override
public void onSetTime ( long ts ) {
2015-04-27 21:43:57 +02:00
try {
TransactionBuilder builder = performInitialized ( " Set date and time " ) ;
setCurrentTime ( builder ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to set time on MI device " , ex ) ;
2015-04-27 21:43:57 +02:00
}
}
/ * *
* Sets the current time to the Mi device using the given builder .
2015-05-01 01:49:43 +02:00
*
2015-04-27 21:43:57 +02:00
* @param builder
* /
private MiBandSupport setCurrentTime ( TransactionBuilder builder ) {
2015-06-01 09:42:44 +02:00
Calendar now = GregorianCalendar . getInstance ( ) ;
2015-04-26 00:53:48 +02:00
byte [ ] time = new byte [ ] {
( byte ) ( now . get ( Calendar . YEAR ) - 2000 ) ,
( byte ) now . get ( Calendar . MONTH ) ,
( byte ) now . get ( Calendar . DATE ) ,
( byte ) now . get ( Calendar . HOUR_OF_DAY ) ,
( byte ) now . get ( Calendar . MINUTE ) ,
( byte ) now . get ( Calendar . SECOND ) ,
( byte ) 0x0f ,
( byte ) 0x0f ,
( byte ) 0x0f ,
( byte ) 0x0f ,
( byte ) 0x0f ,
( byte ) 0x0f
} ;
2015-04-27 21:43:57 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_DATE_TIME ) ;
if ( characteristic ! = null ) {
builder . write ( characteristic , time ) ;
} else {
2015-05-12 06:28:11 +02:00
LOG . info ( " Unable to set time -- characteristic not available " ) ;
2015-04-23 14:11:57 +02:00
}
2015-04-27 21:43:57 +02:00
return this ;
2015-04-14 01:24:03 +02:00
}
@Override
public void onSetCallState ( String number , String name , GBCommand command ) {
2015-04-19 15:15:53 +02:00
if ( GBCommand . CALL_INCOMING . equals ( command ) ) {
2015-06-20 23:22:22 +02:00
telephoneRinging = true ;
AbortTransactionAction abortAction = new AbortTransactionAction ( ) {
@Override
protected boolean shouldAbort ( ) {
return ! isTelephoneRinging ( ) ;
}
} ;
performPreferredNotification ( " incoming call " , MiBandConst . ORIGIN_INCOMING_CALL , abortAction ) ;
} else if ( GBCommand . CALL_START . equals ( command ) | | GBCommand . CALL_END . equals ( command ) ) {
telephoneRinging = false ;
2015-04-19 15:15:53 +02:00
}
2015-04-14 01:24:03 +02:00
}
2015-06-20 23:22:22 +02:00
private boolean isTelephoneRinging ( ) {
// don't synchronize, this is not really important
return telephoneRinging ;
}
2015-04-14 01:24:03 +02:00
@Override
public void onSetMusicInfo ( String artist , String album , String track ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
@Override
public void onFirmwareVersionReq ( ) {
2015-04-19 22:20:47 +02:00
try {
2015-04-19 22:31:09 +02:00
TransactionBuilder builder = performInitialized ( " Get MI Band device info " ) ;
2015-04-19 22:20:47 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_DEVICE_INFO ) ;
builder . read ( characteristic ) . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to read device info from MI " , ex ) ;
2015-04-19 22:20:47 +02:00
}
2015-04-14 01:24:03 +02:00
}
2015-04-19 22:31:09 +02:00
@Override
public void onBatteryInfoReq ( ) {
try {
TransactionBuilder builder = performInitialized ( " Get MI Band battery info " ) ;
2015-05-05 23:25:54 +02:00
requestBatteryInfo ( builder ) ;
builder . queue ( getQueue ( ) ) ;
2015-04-19 22:31:09 +02:00
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to read battery info from MI " , ex ) ;
2015-04-19 22:31:09 +02:00
}
}
2015-05-17 22:57:37 +02:00
@Override
public void onReboot ( ) {
2015-06-06 00:40:16 +02:00
try {
TransactionBuilder builder = performInitialized ( " Reboot " ) ;
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) , reboot ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to reboot MI " , ex ) ;
}
}
2015-06-21 00:34:05 +02:00
@Override
public void onFindDevice ( boolean start ) {
isLocatingDevice = start ;
if ( start ) {
AbortTransactionAction abortAction = new AbortTransactionAction ( ) {
@Override
protected boolean shouldAbort ( ) {
return ! isLocatingDevice ;
}
} ;
performDefaultNotification ( " locating device " , ( short ) 255 , abortAction ) ;
}
}
2015-06-06 00:40:16 +02:00
@Override
2015-06-06 19:39:04 +02:00
public void onFetchActivityData ( ) {
2015-05-17 22:57:37 +02:00
try {
2015-05-24 15:04:48 +02:00
TransactionBuilder builder = performInitialized ( " fetch activity data " ) ;
2015-06-06 00:40:16 +02:00
builder . add ( new SetDeviceBusyAction ( getDevice ( ) , getContext ( ) . getString ( R . string . busy_task_fetch_activity_data ) , getContext ( ) ) ) ;
2015-05-24 00:11:14 +02:00
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) , fetch ) ;
2015-05-17 22:57:37 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2015-05-24 15:04:48 +02:00
LOG . error ( " Unable to fetch MI activity data " , ex ) ;
2015-05-17 22:57:37 +02:00
}
2015-05-24 15:04:48 +02:00
}
2015-05-17 22:57:37 +02:00
2015-06-06 23:24:00 +02:00
@Override
public void onInstallApp ( Uri uri ) {
// not supported
}
2015-04-14 01:24:03 +02:00
@Override
public void onAppInfoReq ( ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
2015-05-18 22:20:01 +02:00
@Override
public void onAppStart ( UUID uuid ) {
// not supported
}
2015-04-14 01:24:03 +02:00
@Override
2015-05-18 20:56:19 +02:00
public void onAppDelete ( UUID uuid ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
@Override
public void onPhoneVersion ( byte os ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
2015-04-19 02:37:29 +02:00
2015-06-24 00:23:38 +02:00
@Override
public void onScreenshotReq ( ) {
// not supported
}
2015-05-24 00:11:14 +02:00
@Override
public void onCharacteristicChanged ( BluetoothGatt gatt ,
2015-05-24 15:04:48 +02:00
BluetoothGattCharacteristic characteristic ) {
2015-05-24 00:11:14 +02:00
super . onCharacteristicChanged ( gatt , characteristic ) ;
UUID characteristicUUID = characteristic . getUuid ( ) ;
if ( MiBandService . UUID_CHARACTERISTIC_ACTIVITY_DATA . equals ( characteristicUUID ) ) {
2015-05-24 19:09:14 +02:00
handleActivityNotif ( characteristic . getValue ( ) ) ;
2015-06-16 17:03:35 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_BATTERY . equals ( characteristicUUID ) ) {
2015-06-18 23:07:22 +02:00
handleBatteryInfo ( characteristic . getValue ( ) , BluetoothGatt . GATT_SUCCESS ) ;
} else if ( MiBandService . UUID_CHARACTERISTIC_NOTIFICATION . equals ( characteristicUUID ) ) {
// device somehow changed, should we update e.g. battery level?
}
2015-05-24 00:11:14 +02:00
}
2015-05-24 14:39:36 +02:00
2015-04-19 22:20:47 +02:00
@Override
public void onCharacteristicRead ( BluetoothGatt gatt ,
2015-04-20 11:58:59 +02:00
BluetoothGattCharacteristic characteristic , int status ) {
2015-04-19 22:20:47 +02:00
super . onCharacteristicRead ( gatt , characteristic , status ) ;
2015-04-19 22:31:09 +02:00
UUID characteristicUUID = characteristic . getUuid ( ) ;
if ( MiBandService . UUID_CHARACTERISTIC_DEVICE_INFO . equals ( characteristicUUID ) ) {
2015-04-19 22:20:47 +02:00
handleDeviceInfo ( characteristic . getValue ( ) , status ) ;
2015-04-19 22:31:09 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_BATTERY . equals ( characteristicUUID ) ) {
handleBatteryInfo ( characteristic . getValue ( ) , status ) ;
2015-04-19 22:20:47 +02:00
}
}
2015-04-19 02:37:29 +02:00
@Override
public void onCharacteristicWrite ( BluetoothGatt gatt ,
2015-04-19 11:28:03 +02:00
BluetoothGattCharacteristic characteristic , int status ) {
2015-04-19 02:37:29 +02:00
UUID characteristicUUID = characteristic . getUuid ( ) ;
if ( MiBandService . UUID_CHARACTERISTIC_PAIR . equals ( characteristicUUID ) ) {
handlePairResult ( characteristic . getValue ( ) , status ) ;
2015-04-19 15:11:50 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_USER_INFO . equals ( characteristicUUID ) ) {
handleUserInfoResult ( characteristic . getValue ( ) , status ) ;
2015-05-24 00:11:14 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT . equals ( characteristicUUID ) ) {
handleControlPointResult ( characteristic . getValue ( ) , status ) ;
2015-04-19 02:37:29 +02:00
}
}
2015-04-19 22:31:09 +02:00
private void handleDeviceInfo ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS ) {
DeviceInfo info = new DeviceInfo ( value ) ;
getDevice ( ) . setFirmwareVersion ( info . getFirmwareVersion ( ) ) ;
getDevice ( ) . sendDeviceUpdateIntent ( getContext ( ) ) ;
}
}
2015-06-24 20:14:08 +02:00
private void queueAlarm ( GBAlarm alarm , TransactionBuilder builder , BluetoothGattCharacteristic characteristic ) {
Calendar alarmCal = alarm . getAlarmCal ( ) ;
2015-06-30 12:09:29 +02:00
byte [ ] alarmMessage = new byte [ ] {
2015-06-24 20:14:08 +02:00
( byte ) MiBandService . COMMAND_SET_TIMER ,
( byte ) alarm . getIndex ( ) ,
2015-06-30 12:09:29 +02:00
( byte ) ( alarm . isEnabled ( ) ? 1 : 0 ) ,
2015-06-24 20:14:08 +02:00
( byte ) ( alarmCal . get ( Calendar . YEAR ) - 2000 ) ,
( byte ) alarmCal . get ( Calendar . MONTH ) ,
( byte ) alarmCal . get ( Calendar . DATE ) ,
( byte ) alarmCal . get ( Calendar . HOUR_OF_DAY ) ,
( byte ) alarmCal . get ( Calendar . MINUTE ) ,
( byte ) alarmCal . get ( Calendar . SECOND ) ,
( byte ) ( alarm . isSmartWakeup ( ) ? 30 : 0 ) ,
( byte ) alarm . getRepetitionMask ( )
} ;
builder . write ( characteristic , alarmMessage ) ;
}
2015-05-24 19:09:14 +02:00
private void handleActivityNotif ( byte [ ] value ) {
2015-06-13 00:32:48 +02:00
if ( value . length = = 11 ) {
2015-06-01 09:42:44 +02:00
// byte 0 is the data type: 1 means that each minute is represented by a triplet of bytes
int dataType = value [ 0 ] ;
// byte 1 to 6 represent a timestamp
2015-07-12 00:02:51 +02:00
GregorianCalendar timestamp = parseTimestamp ( value , 1 ) ;
2015-06-01 09:42:44 +02:00
// counter of all data held by the band
int totalDataToRead = ( value [ 7 ] & 0xff ) | ( ( value [ 8 ] & 0xff ) < < 8 ) ;
totalDataToRead * = ( dataType = = 1 ) ? 3 : 1 ;
// counter of this data block
int dataUntilNextHeader = ( value [ 9 ] & 0xff ) | ( ( value [ 10 ] & 0xff ) < < 8 ) ;
2015-06-13 00:32:48 +02:00
dataUntilNextHeader * = ( dataType = = 1 ) ? 3 : 1 ;
2015-06-01 09:42:44 +02:00
// there is a total of totalDataToRead that will come in chunks (3 bytes per minute if dataType == 1),
// these chunks are usually 20 bytes long and grouped in blocks
// after dataUntilNextHeader bytes we will get a new packet of 11 bytes that should be parsed
// as we just did
2015-06-13 00:32:48 +02:00
LOG . info ( " total data to read: " + totalDataToRead + " len: " + ( totalDataToRead / 3 ) + " minute(s) " ) ;
LOG . info ( " data to read until next header: " + dataUntilNextHeader + " len: " + ( dataUntilNextHeader / 3 ) + " minute(s) " ) ;
2015-06-01 09:42:44 +02:00
LOG . info ( " TIMESTAMP: " + DateFormat . getDateTimeInstance ( ) . format ( timestamp . getTime ( ) ) . toString ( ) + " magic byte: " + dataUntilNextHeader ) ;
2015-06-04 18:56:35 +02:00
this . activityDataRemainingBytes = this . activityDataUntilNextHeader = dataUntilNextHeader ;
this . activityDataTimestampProgress = this . activityDataTimestampToAck = timestamp ;
2015-06-01 09:42:44 +02:00
} else {
bufferActivityData ( value ) ;
}
if ( this . activityDataRemainingBytes = = 0 ) {
sendAckDataTransfer ( this . activityDataTimestampToAck , this . activityDataUntilNextHeader ) ;
flushActivityDataHolder ( ) ;
2015-05-24 00:11:14 +02:00
}
}
2015-07-12 00:02:51 +02:00
private GregorianCalendar parseTimestamp ( byte [ ] value , int offset ) {
GregorianCalendar timestamp = new GregorianCalendar (
value [ offset ] + 2000 ,
value [ offset + 1 ] ,
value [ offset + 2 ] ,
value [ offset + 3 ] ,
value [ offset + 4 ] ,
value [ offset + 5 ] ) ;
return timestamp ;
}
2015-06-01 09:42:44 +02:00
private void bufferActivityData ( byte [ ] value ) {
if ( this . activityDataRemainingBytes > = value . length ) {
//I don't like this clause, but until we figure out why we get different data sometimes this should work
if ( value . length = = 20 | | value . length = = this . activityDataRemainingBytes ) {
System . arraycopy ( value , 0 , this . activityDataHolder , this . activityDataHolderProgress , value . length ) ;
2015-06-13 00:32:48 +02:00
this . activityDataHolderProgress + = value . length ;
2015-06-01 09:42:44 +02:00
this . activityDataRemainingBytes - = value . length ;
if ( this . activityDataHolderSize = = this . activityDataHolderProgress ) {
flushActivityDataHolder ( ) ;
}
} else {
2015-06-06 00:40:16 +02:00
// the length of the chunk is not what we expect. We need to make sense of this data
2015-06-01 09:42:44 +02:00
LOG . warn ( " GOT UNEXPECTED ACTIVITY DATA WITH LENGTH: " + value . length + " , EXPECTED LENGTH: " + this . activityDataRemainingBytes ) ;
2015-06-13 00:32:48 +02:00
for ( byte b : value ) {
2015-06-01 09:42:44 +02:00
LOG . warn ( " DATA: " + String . format ( " 0x%8x " , b ) ) ;
}
2015-05-24 15:04:48 +02:00
}
2015-07-12 00:02:51 +02:00
} else {
LOG . error ( " error buffering activity data: remaining bytes: " + activityDataRemainingBytes + " , received: " + value . length ) ;
2015-06-01 09:42:44 +02:00
}
}
private void flushActivityDataHolder ( ) {
GregorianCalendar timestamp = this . activityDataTimestampProgress ;
2015-06-01 14:17:35 +02:00
byte category , intensity , steps ;
2015-06-09 21:05:44 +02:00
ActivityDatabaseHandler dbHandler = GBApplication . getActivityDatabaseHandler ( ) ;
try ( SQLiteDatabase db = dbHandler . getWritableDatabase ( ) ) { // explicitly keep the db open while looping over the samples
2015-06-13 00:32:48 +02:00
for ( int i = 0 ; i < this . activityDataHolderProgress ; i + = 3 ) { //TODO: check if multiple of 3, if not something is wrong
2015-06-09 21:05:44 +02:00
category = this . activityDataHolder [ i ] ;
2015-06-13 00:32:48 +02:00
intensity = this . activityDataHolder [ i + 1 ] ;
steps = this . activityDataHolder [ i + 2 ] ;
2015-06-09 21:05:44 +02:00
dbHandler . addGBActivitySample (
( int ) ( timestamp . getTimeInMillis ( ) / 1000 ) ,
GBActivitySample . PROVIDER_MIBAND ,
intensity ,
steps ,
category ) ;
timestamp . add ( Calendar . MINUTE , 1 ) ;
}
2015-07-12 00:02:51 +02:00
} finally {
this . activityDataHolderProgress = 0 ;
this . activityDataTimestampProgress = timestamp ;
2015-06-01 09:42:44 +02:00
}
}
private void handleControlPointResult ( byte [ ] value , int status ) {
if ( status ! = BluetoothGatt . GATT_SUCCESS ) {
LOG . warn ( " Could not write to the control point. " ) ;
}
LOG . info ( " handleControlPoint got status: " + status ) ;
2015-06-06 00:40:16 +02:00
if ( getDevice ( ) . isBusy ( ) ) {
if ( isActivityDataSyncFinished ( value ) ) {
unsetBusy ( ) ;
2015-07-12 00:02:51 +02:00
} else {
if ( value ! = null & & value . length = = 9 & & value [ 0 ] = = 0xa ) {
handleActivityCheckpoint ( value ) ;
}
2015-06-06 00:40:16 +02:00
}
}
2015-06-20 23:22:22 +02:00
if ( value ! = null ) {
for ( byte b : value ) {
LOG . info ( " handleControlPoint GOT DATA: " + String . format ( " 0x%8x " , b ) ) ;
}
} else {
LOG . warn ( " handleControlPoint GOT null " ) ;
2015-05-24 00:11:14 +02:00
}
2015-06-06 00:40:16 +02:00
}
2015-07-12 00:02:51 +02:00
// I have no idea if this is anything we could or should do.
// For some reason, I got these bytes on the control point characteristic
// while in the middle of a activity data transfer:
// { 0xa, 0xf, 0x6, 0x5, 0x14, 0xe, 0x15, 0x70, 0x8 }
// 'a' = activity, then timestamp, then bytes to read
// After this, the communication stops. And this happened again and again
// so that I couldn't transfer the remaning data anymore.
// My idea was that this might be some kind of checkpointing, asking for an ACK
// of the transfer till now.
// Commented out for now until it can be reproduced and tested again.
private void handleActivityCheckpoint ( byte [ ] value ) {
GregorianCalendar timestamp = parseTimestamp ( value , 1 ) ;
sendAckDataTransfer ( timestamp , activityDataHolderProgress ) ;
flushActivityDataHolder ( ) ;
}
2015-06-06 00:40:16 +02:00
private boolean isActivityDataSyncFinished ( byte [ ] value ) {
2015-07-04 22:22:59 +02:00
// byte 0 is the kind of message
// byte 1 to 6 represent a timestamp
// byte 7 to 8 represent the amount of data left (0 = done)
2015-06-06 00:40:16 +02:00
if ( value . length = = 9 ) {
2015-07-04 22:22:59 +02:00
if ( value [ 0 ] = = 0xa & & value [ 7 ] = = 0 & & value [ 8 ] = = 0 ) {
2015-06-06 00:40:16 +02:00
return true ;
}
}
return false ;
}
2015-05-24 00:11:14 +02:00
2015-06-06 00:40:16 +02:00
private void unsetBusy ( ) {
getDevice ( ) . unsetBusyTask ( ) ;
getDevice ( ) . sendDeviceUpdateIntent ( getContext ( ) ) ;
2015-05-24 00:11:14 +02:00
}
2015-06-06 00:40:16 +02:00
2015-06-01 09:42:44 +02:00
private void sendAckDataTransfer ( Calendar time , int bytesTransferred ) {
2015-05-24 00:11:14 +02:00
byte [ ] ack = new byte [ ] {
MiBandService . COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE ,
( byte ) ( time . get ( Calendar . YEAR ) - 2000 ) ,
( byte ) time . get ( Calendar . MONTH ) ,
( byte ) time . get ( Calendar . DATE ) ,
( byte ) time . get ( Calendar . HOUR_OF_DAY ) ,
( byte ) time . get ( Calendar . MINUTE ) ,
( byte ) time . get ( Calendar . SECOND ) ,
2015-06-01 09:42:44 +02:00
( byte ) ( bytesTransferred & 0xff ) ,
( byte ) ( 0xff & ( bytesTransferred > > 8 ) )
2015-05-24 00:11:14 +02:00
} ;
try {
TransactionBuilder builder = performInitialized ( " send acknowledge " ) ;
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) , ack ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to send ack to MI " , ex ) ;
}
}
2015-04-19 22:31:09 +02:00
private void handleBatteryInfo ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS ) {
BatteryInfo info = new BatteryInfo ( value ) ;
getDevice ( ) . setBatteryLevel ( ( short ) info . getLevelInPercent ( ) ) ;
getDevice ( ) . setBatteryState ( info . getStatus ( ) ) ;
getDevice ( ) . sendDeviceUpdateIntent ( getContext ( ) ) ;
}
}
2015-04-19 15:11:50 +02:00
private void handleUserInfoResult ( byte [ ] value , int status ) {
// successfully transfered user info means we're initialized
if ( status = = BluetoothGatt . GATT_SUCCESS ) {
setConnectionState ( State . INITIALIZED ) ;
}
}
private void setConnectionState ( State newState ) {
getDevice ( ) . setState ( newState ) ;
getDevice ( ) . sendDeviceUpdateIntent ( getContext ( ) ) ;
}
2015-04-19 02:37:29 +02:00
private void handlePairResult ( byte [ ] pairResult , int status ) {
if ( status ! = BluetoothGatt . GATT_SUCCESS ) {
2015-05-12 06:28:11 +02:00
LOG . info ( " Pairing MI device failed: " + status ) ;
2015-04-19 02:37:29 +02:00
return ;
}
2015-05-12 20:32:34 +02:00
String value = null ;
2015-04-19 02:37:29 +02:00
if ( pairResult ! = null ) {
if ( pairResult . length = = 1 ) {
try {
2015-05-12 20:32:34 +02:00
if ( pairResult [ 0 ] = = 2 ) {
2015-05-12 06:28:11 +02:00
LOG . info ( " Successfully paired MI device " ) ;
2015-04-19 02:37:29 +02:00
return ;
}
} catch ( Exception ex ) {
2015-05-12 06:28:11 +02:00
LOG . warn ( " Error identifying pairing result " , ex ) ;
2015-04-19 02:37:29 +02:00
return ;
}
}
2015-05-12 20:32:34 +02:00
value = Arrays . toString ( pairResult ) ;
2015-04-19 02:37:29 +02:00
}
2015-05-12 06:28:11 +02:00
LOG . info ( " MI Band pairing result: " + value ) ;
2015-04-19 02:37:29 +02:00
}
2015-05-24 00:11:14 +02:00
@Override
protected TransactionBuilder createTransactionBuilder ( String taskName ) {
return new MiBandTransactionBuilder ( taskName ) ;
}
2015-06-16 17:03:35 +02:00
}