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-07-20 23:11:16 +02:00
import java.util.concurrent.TimeUnit ;
2015-04-19 02:37:29 +02:00
2015-07-19 16:16:58 +02:00
import nodomain.freeyourgadget.gadgetbridge.GB ;
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-07-28 22:38:52 +02:00
import nodomain.freeyourgadget.gadgetbridge.btle.SetProgressAction ;
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-07-27 23:49:53 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.SampleProvider ;
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-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-07-21 22:25:26 +02:00
private static class ActivityStruct {
public byte [ ] activityDataHolder = new byte [ activityDataHolderSize ] ;
//index of the buffer above
public int activityDataHolderProgress = 0 ;
//number of bytes we will get in a single data transfer, used as counter
2015-07-25 21:52:52 +02:00
public int activityDataRemainingBytes = 0 ;
2015-07-21 22:25:26 +02:00
//same as above, but remains untouched for the ack message
2015-07-25 21:52:52 +02:00
public int activityDataUntilNextHeader = 0 ;
2015-07-21 22:25:26 +02:00
//timestamp of the single data transfer, incremented to store each minute's data
2015-07-25 21:52:52 +02:00
public GregorianCalendar activityDataTimestampProgress = null ;
2015-07-21 22:25:26 +02:00
//same as above, but remains untouched for the ack message
public GregorianCalendar activityDataTimestampToAck = null ;
}
2015-07-25 21:52:52 +02:00
2015-07-21 22:25:26 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( MiBandSupport . class ) ;
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-07-21 22:25:26 +02:00
private ActivityStruct activityStruct ;
2015-05-24 19:09:14 +02:00
2015-07-23 17:14:51 +02:00
private DeviceInfo mDeviceInfo ;
2015-07-31 12:30:42 +02:00
private boolean firmwareInfoSent = false ;
private byte [ ] newFirmware ;
private boolean rebootWhenBandReady = false ;
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 )
2015-07-31 13:48:11 +02:00
. setWearLocation ( builder )
2015-07-29 18:49:16 +02:00
. setFitnessGoal ( builder )
2015-05-25 23:14:02 +02:00
. 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-07-29 18:49:16 +02:00
/ * *
* Part of device initialization process . Do not call manually .
*
* @param transaction
* @return
* /
2015-04-19 02:37:29 +02:00
2015-07-29 18:49:16 +02:00
private MiBandSupport setFitnessGoal ( TransactionBuilder transaction ) {
LOG . info ( " Attempting to set Fitness Goal... " ) ;
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) ;
if ( characteristic ! = null ) {
int fitnessGoal = MiBandCoordinator . getFitnessGoal ( getDevice ( ) . getAddress ( ) ) ;
transaction . write ( characteristic , new byte [ ] {
MiBandService . COMMAND_SET_FITNESS_GOAL ,
0 ,
( byte ) ( fitnessGoal & 0xff ) ,
( byte ) ( ( fitnessGoal > > > 8 ) & 0xff )
} ) ;
} else {
LOG . info ( " Unable to set Fitness Goal " ) ;
}
return this ;
}
2015-07-31 13:48:11 +02:00
/ * *
* Part of device initialization process . Do not call manually .
*
* @param transaction
* @return
* /
private MiBandSupport setWearLocation ( TransactionBuilder transaction ) {
LOG . info ( " Attempting to set wear location... " ) ;
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) ;
if ( characteristic ! = null ) {
int location = MiBandCoordinator . getWearLocation ( getDevice ( ) . getAddress ( ) ) ;
transaction . write ( characteristic , new byte [ ] {
MiBandService . COMMAND_SET_WEAR_LOCATION ,
( byte ) location
} ) ;
} else {
LOG . info ( " Unable to set Wear Location " ) ;
}
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-07-20 21:41:18 +02:00
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), getLowLatency());
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-07-20 21:43:23 +02:00
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 ] ;
2015-07-25 21:52:52 +02:00
result [ 0 ] = ( byte ) ( minConnectionInterval & 0xff ) ;
result [ 1 ] = ( byte ) ( 0xff & minConnectionInterval > > 8 ) ;
result [ 2 ] = ( byte ) ( maxConnectionInterval & 0xff ) ;
2015-07-20 21:43:23 +02:00
result [ 3 ] = ( byte ) ( 0xff & maxConnectionInterval > > 8 ) ;
2015-07-25 21:52:52 +02:00
result [ 4 ] = ( byte ) ( latency & 0xff ) ;
result [ 5 ] = ( byte ) ( 0xff & latency > > 8 ) ;
result [ 6 ] = ( byte ) ( timeout & 0xff ) ;
2015-07-20 21:43:23 +02:00
result [ 7 ] = ( byte ) ( 0xff & timeout > > 8 ) ;
result [ 8 ] = 0 ;
result [ 9 ] = 0 ;
2015-07-25 21:52:52 +02:00
result [ 10 ] = ( byte ) ( advertisementInterval & 0xff ) ;
result [ 11 ] = ( byte ) ( 0xff & advertisementInterval > > 8 ) ;
2015-07-20 21:43:23 +02:00
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 ) ;
}
2015-06-06 23:24:00 +02:00
@Override
public void onInstallApp ( Uri uri ) {
2015-07-23 17:14:51 +02:00
MiBandFWHelper mFwHelper = new MiBandFWHelper ( uri , getContext ( ) ) ;
String mMac = getDevice ( ) . getAddress ( ) ;
String [ ] mMacOctets = mMac . split ( " : " ) ;
int newFwVersion = mFwHelper . getFirmwareVersion ( ) ;
int oldFwVersion = mDeviceInfo . getFirmwareVersion ( ) ;
int checksum = ( Integer . decode ( " 0x " + mMacOctets [ 4 ] ) < < 8 | Integer . decode ( " 0x " + mMacOctets [ 5 ] ) ) ^ mFwHelper . getCRC16 ( mFwHelper . getFw ( ) ) ;
2015-07-28 19:15:25 +02:00
if ( sendFirmwareInfo ( oldFwVersion , newFwVersion , mFwHelper . getFw ( ) . length , checksum ) ) {
2015-07-31 12:30:42 +02:00
firmwareInfoSent = true ;
newFirmware = mFwHelper . getFw ( ) ;
//the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
2015-07-28 19:15:25 +02:00
}
2015-07-23 17:14:51 +02:00
return ;
2015-06-06 23:24:00 +02:00
}
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 ) ) {
2015-07-31 12:30:42 +02:00
handleNotificationNotif ( characteristic . getValue ( ) ) ;
2015-06-18 23:07:22 +02:00
}
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-07-31 12:30:42 +02:00
private void logMessageContent ( byte [ ] value ) {
LOG . info ( " RECEIVED DATA WITH LENGTH: " + value . length ) ;
for ( byte b : value ) {
LOG . warn ( " DATA: " + String . format ( " 0x%2x " , b ) ) ;
}
}
private void handleNotificationNotif ( byte [ ] value ) {
if ( value . length ! = 1 ) {
LOG . error ( " Notifications should be 1 byte long. " ) ;
LOG . info ( " RECEIVED DATA WITH LENGTH: " + value . length ) ;
for ( byte b : value ) {
LOG . warn ( " DATA: " + String . format ( " 0x%2x " , b ) ) ;
}
return ;
}
switch ( value [ 0 ] ) {
case MiBandService . NOTIFY_FW_CHECK_SUCCESS :
if ( firmwareInfoSent & & newFirmware ! = null ) {
if ( sendFirmwareData ( newFirmware ) ) {
rebootWhenBandReady = true ;
} else {
//TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do?
GB . toast ( " Problem with the firmware transfer. DO NOT REBOOT YOUR MIBAND!!! " , Toast . LENGTH_LONG , GB . ERROR ) ;
}
firmwareInfoSent = false ;
newFirmware = null ;
}
break ;
case MiBandService . NOTIFY_FW_CHECK_FAILED :
GB . toast ( " Problem with the firmware metadata transfer " , Toast . LENGTH_LONG , GB . ERROR ) ;
firmwareInfoSent = false ;
newFirmware = null ;
break ;
case MiBandService . NOTIFY_FIRMWARE_UPDATE_SUCCESS :
if ( rebootWhenBandReady ) {
2015-07-31 13:12:15 +02:00
GB . updateInstallNotification ( " Firmware installation complete " , false , 100 , getContext ( ) ) ;
2015-07-31 12:30:42 +02:00
onReboot ( ) ;
}
rebootWhenBandReady = false ;
break ;
case MiBandService . NOTIFY_FIRMWARE_UPDATE_FAILED :
//TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do?
GB . toast ( " Problem with the firmware transfer. DO NOT REBOOT YOUR MIBAND!!! " , Toast . LENGTH_LONG , GB . ERROR ) ;
2015-07-31 13:12:15 +02:00
GB . updateInstallNotification ( " Firmware write failed " , false , 0 , getContext ( ) ) ;
2015-07-31 12:30:42 +02:00
rebootWhenBandReady = false ;
break ;
default :
for ( byte b : value ) {
LOG . warn ( " DATA: " + String . format ( " 0x%2x " , b ) ) ;
}
}
}
2015-04-19 22:31:09 +02:00
private void handleDeviceInfo ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS ) {
2015-07-23 17:14:51 +02:00
mDeviceInfo = new DeviceInfo ( value ) ;
getDevice ( ) . setFirmwareVersion ( mDeviceInfo . getHumanFirmwareVersion ( ) ) ;
2015-04-19 22:31:09 +02:00
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-07-21 22:25:26 +02:00
boolean firstChunk = activityStruct = = null ;
if ( firstChunk ) {
activityStruct = new ActivityStruct ( ) ;
}
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-07-21 22:25:26 +02:00
if ( firstChunk & & dataUntilNextHeader ! = 0 ) {
2015-07-20 23:11:16 +02:00
GB . toast ( getContext ( ) . getString ( R . string . user_feedback_miband_activity_data_transfer ,
2015-07-21 22:25:26 +02:00
GB . formatDurationHoursMinutes ( ( totalDataToRead / 3 ) , TimeUnit . MINUTES ) ,
2015-07-20 23:11:16 +02:00
DateFormat . getDateTimeInstance ( ) . format ( timestamp . getTime ( ) ) ) , Toast . LENGTH_LONG , GB . INFO ) ;
}
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-07-21 22:25:26 +02:00
activityStruct . activityDataRemainingBytes = activityStruct . activityDataUntilNextHeader = dataUntilNextHeader ;
activityStruct . activityDataTimestampToAck = ( GregorianCalendar ) timestamp . clone ( ) ;
activityStruct . activityDataTimestampProgress = timestamp ;
2015-06-01 09:42:44 +02:00
} else {
bufferActivityData ( value ) ;
}
2015-07-21 22:25:26 +02:00
LOG . debug ( " activity data: length: " + value . length + " , remaining bytes: " + activityStruct . activityDataRemainingBytes ) ;
2015-07-20 21:41:18 +02:00
2015-07-21 22:25:26 +02:00
if ( activityStruct . activityDataRemainingBytes = = 0 ) {
sendAckDataTransfer ( activityStruct . activityDataTimestampToAck , activityStruct . activityDataUntilNextHeader ) ;
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 ) {
2015-07-21 22:25:26 +02:00
if ( activityStruct . activityDataRemainingBytes > = value . length ) {
2015-06-01 09:42:44 +02:00
//I don't like this clause, but until we figure out why we get different data sometimes this should work
2015-07-21 22:25:26 +02:00
if ( value . length = = 20 | | value . length = = activityStruct . activityDataRemainingBytes ) {
System . arraycopy ( value , 0 , activityStruct . activityDataHolder , activityStruct . activityDataHolderProgress , value . length ) ;
activityStruct . activityDataHolderProgress + = value . length ;
activityStruct . activityDataRemainingBytes - = value . length ;
2015-06-01 09:42:44 +02:00
2015-07-21 22:25:26 +02:00
if ( this . activityDataHolderSize = = activityStruct . activityDataHolderProgress ) {
2015-06-01 09:42:44 +02:00
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-07-21 22:25:26 +02:00
LOG . warn ( " GOT UNEXPECTED ACTIVITY DATA WITH LENGTH: " + value . length + " , EXPECTED LENGTH: " + activityStruct . 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 {
2015-07-21 22:25:26 +02:00
LOG . error ( " error buffering activity data: remaining bytes: " + activityStruct . activityDataRemainingBytes + " , received: " + value . length ) ;
2015-06-01 09:42:44 +02:00
}
}
private void flushActivityDataHolder ( ) {
2015-07-25 00:08:15 +02:00
if ( activityStruct = = null ) {
LOG . debug ( " nothing to flush, struct is already null " ) ;
return ;
}
LOG . debug ( " flushing activity data holder " ) ;
2015-06-01 14:17:35 +02:00
byte category , intensity , steps ;
2015-06-09 21:05:44 +02:00
ActivityDatabaseHandler dbHandler = GBApplication . getActivityDatabaseHandler ( ) ;
2015-07-27 23:49:53 +02:00
2015-06-09 21:05:44 +02:00
try ( SQLiteDatabase db = dbHandler . getWritableDatabase ( ) ) { // explicitly keep the db open while looping over the samples
2015-07-21 22:25:26 +02:00
for ( int i = 0 ; i < activityStruct . activityDataHolderProgress ; i + = 3 ) { //TODO: check if multiple of 3, if not something is wrong
category = activityStruct . activityDataHolder [ i ] ;
intensity = activityStruct . activityDataHolder [ i + 1 ] ;
steps = activityStruct . activityDataHolder [ i + 2 ] ;
2015-06-09 21:05:44 +02:00
dbHandler . addGBActivitySample (
2015-07-21 22:25:26 +02:00
( int ) ( activityStruct . activityDataTimestampProgress . getTimeInMillis ( ) / 1000 ) ,
2015-07-27 23:49:53 +02:00
SampleProvider . PROVIDER_MIBAND ,
2015-06-09 21:05:44 +02:00
intensity ,
steps ,
category ) ;
2015-07-21 22:25:26 +02:00
activityStruct . activityDataTimestampProgress . add ( Calendar . MINUTE , 1 ) ;
2015-06-09 21:05:44 +02:00
}
2015-07-12 00:02:51 +02:00
} finally {
2015-07-21 22:25:26 +02:00
activityStruct . activityDataHolderProgress = 0 ;
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
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
}
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 ( ) ) ;
2015-07-25 00:08:15 +02:00
// flush to the DB after sending the ACK
flushActivityDataHolder ( ) ;
2015-07-19 15:52:27 +02:00
//The last data chunk sent by the miband has always length 0.
//When we ack this chunk, the transfer is done.
2015-07-25 21:52:52 +02:00
if ( getDevice ( ) . isBusy ( ) & & bytesTransferred = = 0 ) {
2015-07-21 22:25:26 +02:00
handleActivityFetchFinish ( ) ;
2015-07-19 15:52:27 +02:00
}
2015-05-24 00:11:14 +02:00
} catch ( IOException ex ) {
LOG . error ( " Unable to send ack to MI " , ex ) ;
}
}
2015-07-21 22:25:26 +02:00
private void handleActivityFetchFinish ( ) {
2015-07-25 00:08:15 +02:00
LOG . info ( " Fetching activity data has finished. " ) ;
2015-07-21 22:25:26 +02:00
activityStruct = null ;
unsetBusy ( ) ;
}
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
2015-07-28 19:15:25 +02:00
private boolean sendFirmwareInfo ( int currentFwVersion , int newFwVersion , int newFwSize , int checksum ) {
2015-07-23 17:14:51 +02:00
byte [ ] fwInfo = new byte [ ] {
MiBandService . COMMAND_SEND_FIRMWARE_INFO ,
( byte ) currentFwVersion ,
( byte ) ( currentFwVersion > > 8 ) ,
( byte ) ( currentFwVersion > > 16 ) ,
( byte ) ( currentFwVersion > > 24 ) ,
( byte ) newFwVersion ,
( byte ) ( newFwVersion > > 8 ) ,
( byte ) ( newFwVersion > > 16 ) ,
( byte ) ( newFwVersion > > 24 ) ,
( byte ) newFwSize ,
( byte ) ( newFwSize > > 8 ) ,
( byte ) checksum ,
( byte ) ( checksum > > 8 )
} ;
try {
TransactionBuilder builder = performInitialized ( " send firmware info " ) ;
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) , fwInfo ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to send fwInfo to MI " , ex ) ;
2015-07-28 19:15:25 +02:00
return false ;
2015-07-23 17:14:51 +02:00
}
2015-07-28 19:15:25 +02:00
return true ;
2015-07-23 17:14:51 +02:00
}
2015-07-28 19:15:25 +02:00
private boolean sendFirmwareData ( byte fwbytes [ ] ) {
2015-07-23 17:14:51 +02:00
int len = fwbytes . length ;
final int packetLength = 20 ;
int packets = len / packetLength ;
byte fwChunk [ ] = new byte [ packetLength ] ;
int firmwareProgress = 0 ;
2015-07-28 19:15:25 +02:00
try {
TransactionBuilder builder = performInitialized ( " send firmware packet " ) ;
for ( int i = 0 ; i < packets ; i + + ) {
fwChunk = Arrays . copyOfRange ( fwbytes , i * packetLength , i * packetLength + packetLength ) ;
2015-07-23 17:14:51 +02:00
2015-07-28 19:15:25 +02:00
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_FIRMWARE_DATA ) , fwChunk ) ;
firmwareProgress + = packetLength ;
2015-07-23 17:14:51 +02:00
2015-07-28 19:15:25 +02:00
if ( ( i > 0 ) & & ( i % 50 = = 0 ) ) {
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) , new byte [ ] { MiBandService . COMMAND_SYNC } ) ;
2015-07-28 22:38:52 +02:00
builder . add ( new SetProgressAction ( " Firmware update in progress " , true , ( firmwareProgress / len ) * 100 , getContext ( ) ) ) ;
2015-07-23 17:14:51 +02:00
}
2015-07-28 19:15:25 +02:00
LOG . info ( " Firmware update progress: " + firmwareProgress + " total len: " + len + " progress: " + ( firmwareProgress / len ) ) ;
2015-07-23 17:14:51 +02:00
}
2015-07-28 19:15:25 +02:00
if ( ! ( len % packetLength = = 0 ) ) {
byte lastChunk [ ] = new byte [ len % packetLength ] ;
lastChunk = Arrays . copyOfRange ( fwbytes , packets * packetLength , len ) ;
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_FIRMWARE_DATA ) , lastChunk ) ;
firmwareProgress + = len % packetLength ;
}
2015-07-23 17:14:51 +02:00
2015-07-28 19:15:25 +02:00
LOG . info ( " Firmware update progress: " + firmwareProgress + " total len: " + len + " progress: " + ( firmwareProgress / len ) ) ;
if ( firmwareProgress > = len ) {
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) , new byte [ ] { MiBandService . COMMAND_SYNC } ) ;
} else {
GB . updateInstallNotification ( " Firmware write failed " , false , 0 , getContext ( ) ) ;
}
2015-07-23 17:14:51 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2015-07-28 19:15:25 +02:00
LOG . error ( " Unable to send fw to MI " , ex ) ;
GB . updateInstallNotification ( " Firmware write failed " , false , 0 , getContext ( ) ) ;
2015-07-23 17:14:51 +02:00
return false ;
}
return true ;
}
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
}