diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b17bd9d51..d83ef107c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -203,6 +203,36 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/DeviceInfo.java
index da8315497..4f68b5979 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/DeviceInfo.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/DeviceInfo.java
@@ -10,11 +10,20 @@ public class DeviceInfo extends AbstractInfo {
super(data);
}
- public String getFirmwareVersion() {
+ public String getHumanFirmwareVersion() {
if (mData.length == 16) {
int last = 15;
return String.format(Locale.US, "%d.%d.%d.%d", mData[last], mData[last - 1], mData[last - 2], mData[last - 3]);
}
return GBApplication.getContext().getString(R.string._unknown_);
}
+
+ public int getFirmwareVersion() {
+ if (mData.length == 16) {
+ int last = 15;
+ return (mData[last] << 24) | (mData[last - 1] << 16) | (mData[last - 2] << 8) | mData[last - 3];
+ }
+ return -1;
+ }
+
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/FwUpgrade.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/FwUpgrade.java
new file mode 100644
index 000000000..1f5c4e4d5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/FwUpgrade.java
@@ -0,0 +1,106 @@
+package nodomain.freeyourgadget.gadgetbridge.miband;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import nodomain.freeyourgadget.gadgetbridge.BluetoothCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.ControlCenter;
+import nodomain.freeyourgadget.gadgetbridge.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+
+/*
+TODO: This could be moved to activities package and merged with pebble/PebbleAppInstallerActivity.java
+ */
+
+public class FwUpgrade extends Activity {
+
+
+ TextView fwUpgradeTextView;
+ Button installButton;
+
+ private MiBandFWHelper mFwReader = null;
+ private GBDevice dev;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ControlCenter.ACTION_QUIT)) {
+ finish();
+ } else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
+ dev = intent.getParcelableExtra("device");
+ if(dev.getType() == DeviceType.MIBAND) {
+ if (dev.isInitialized() && mFwReader != null) {
+ installButton.setEnabled(true);
+ }
+ }
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_fw_upgrade);
+
+ fwUpgradeTextView = (TextView) findViewById(R.id.fwUpgradeTextView);
+ installButton = (Button) findViewById(R.id.installButton);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ControlCenter.ACTION_QUIT);
+ filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
+
+ final Uri uri = getIntent().getData();
+ mFwReader = new MiBandFWHelper(uri, getApplicationContext());
+
+ fwUpgradeTextView.setText(getString(R.string.fw_upgrade_notice, mFwReader.getHumanFirmwareVersion()));
+
+ if (mFwReader.isFirmwareWhitelisted()) {
+ fwUpgradeTextView.append(" " + getString(R.string.miband_firmware_known));
+ }else {
+ fwUpgradeTextView.append(" " + getString(R.string.miband_firmware_unknown_warning) + " " +
+ getString(R.string.miband_firmware_suggest_whitelist, mFwReader.getFirmwareVersion()));
+ }
+
+ installButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent startIntent = new Intent(FwUpgrade.this, BluetoothCommunicationService.class);
+ startIntent.setAction(BluetoothCommunicationService.ACTION_INSTALL);
+ startIntent.putExtra("uri", uri.toString());
+ startService(startIntent);
+ }
+ });
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onDestroy() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+ super.onDestroy();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandFWHelper.java
new file mode 100644
index 000000000..2759f0970
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandFWHelper.java
@@ -0,0 +1,97 @@
+package nodomain.freeyourgadget.gadgetbridge.miband;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.Locale;
+
+public class MiBandFWHelper {
+ private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class);
+
+ private final Uri uri;
+ private final ContentResolver cr;
+ private byte[] fw;
+
+ private final int firmwareVersionBuild = 1056;
+ private final int firmwareVersionRevision = 1057;
+ private final int firmwareVersionMinor = 1058;
+ private final int firmwareVersionMajor = 1059;
+
+ private final int[] whitelistedFirmwareVersion = {
+ 16779534, // 1.0.9.14 tested by developer
+ 16779547 //1.0.9.27 testd by developer
+ };
+
+ public MiBandFWHelper(Uri uri, Context context) {
+ this.uri = uri;
+ cr = context.getContentResolver();
+
+ InputStream fin;
+
+ try {
+ fin = new BufferedInputStream(cr.openInputStream(uri));
+ this.fw = new byte[fin.available()];
+ fin.read(fw);
+ fin.close();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ this.fw = null;
+ }
+
+ if (fw[firmwareVersionMajor] != 1 ) {
+ LOG.error("Firmware major version should be 1, probably this isn't a MiBand firmware.");
+ this.fw = null;
+ }
+
+ }
+
+ public int getFirmwareVersion() {
+ if(fw == null) {
+ return -1;
+ }
+ return (fw[firmwareVersionMajor] << 24) | (fw[firmwareVersionMinor] << 16) | (fw[firmwareVersionRevision] << 8) | fw[firmwareVersionBuild];
+ }
+
+ public String getHumanFirmwareVersion() {
+ if(fw == null) {
+ return "UNK";
+ }
+ return String.format(Locale.US, "%d.%d.%d.%d", fw[firmwareVersionMajor], fw[firmwareVersionMinor], fw[firmwareVersionRevision], fw[firmwareVersionBuild]);
+ }
+
+ public byte[] getFw() {
+ return fw;
+ }
+
+ public boolean isFirmwareWhitelisted() {
+ for (int wlf : whitelistedFirmwareVersion) {
+ if (wlf == getFirmwareVersion()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //thanks http://stackoverflow.com/questions/13209364/convert-c-crc16-to-java-crc16
+ public int getCRC16(byte[] seq) {
+ int crc = 0xFFFF;
+
+ for (int j = 0; j < seq.length ; j++) {
+ crc = ((crc >>> 8) | (crc << 8) )& 0xffff;
+ crc ^= (seq[j] & 0xff);//byte to int, trunc sign
+ crc ^= ((crc & 0xff) >> 4);
+ crc ^= (crc << 12) & 0xffff;
+ crc ^= ((crc & 0xFF) << 5) & 0xffff;
+ }
+ crc &= 0xffff;
+ return crc;
+ }
+
+ }
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java
index e9500f365..be9f288f9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java
@@ -137,6 +137,11 @@ public class MiBandService {
public static final byte COMMAND_SET_TIMER = 0x4;
+ public static final byte COMMAND_SEND_FIRMWARE_INFO = 0x7;
+
+ public static final byte COMMAND_SYNC = 0xb;
+
+
/*
@@ -145,7 +150,6 @@ public class MiBandService {
public static final byte COMMAND_GET_SENSOR_DATA = 0x12t
- public static final byte COMMAND_SEND_FIRMWARE_INFO = 0x7t
public static final int COMMAND_SET_COLOR_THEME = et;
@@ -159,8 +163,6 @@ public class MiBandService {
public static final COMMAND_STOP_SYNC_DATA = 0x11t
- public static final COMMAND_SYNC = 0xbt
-
public static final CONNECTION_LATENCY_LEVEL_HIGH = 0x2t;
public static final CONNECTION_LATENCY_LEVEL_LOW = 0x0t;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
index dc2088bfa..22d8637b8 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
@@ -82,6 +82,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
private ActivityStruct activityStruct;
+ private DeviceInfo mDeviceInfo;
+
public MiBandSupport() {
addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
}
@@ -533,7 +535,19 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
@Override
public void onInstallApp(Uri uri) {
- // not supported
+ 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());
+
+ sendFirmwareInfo(oldFwVersion,newFwVersion, mFwHelper.getFw().length, checksum);
+ sendFirmwareData(mFwHelper.getFw());
+ onReboot();
+
+ return;
}
@Override
@@ -604,8 +618,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
private void handleDeviceInfo(byte[] value, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
- DeviceInfo info = new DeviceInfo(value);
- getDevice().setFirmwareVersion(info.getFirmwareVersion());
+ mDeviceInfo = new DeviceInfo(value);
+ getDevice().setFirmwareVersion(mDeviceInfo.getHumanFirmwareVersion());
getDevice().sendDeviceUpdateIntent(getContext());
}
}
@@ -842,6 +856,102 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
LOG.info("MI Band pairing result: " + value);
}
+ private void sendFirmwareInfo(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) {
+ 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);
+ }
+ }
+
+ private void sendFirmwareData(byte fwbytes[]) {
+ int len = fwbytes.length;
+ final int packetLength = 20;
+ int packets = len / packetLength;
+ byte fwChunk[] = new byte[packetLength];
+
+ int firmwareProgress = 0;
+
+ for (int i = 0; i < packets; i++) {
+ fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength);
+
+ if (!sendFirmwareChunk(fwChunk)) {
+ LOG.error("Firmware chunk write failed");
+ return;
+ }
+
+ firmwareProgress += packetLength;
+
+ if ((i > 0) && (i % 50 == 0)) {
+ if(!sendFirmwareSync()) {
+ LOG.error("Firmware sync failed");
+ return;
+ }
+ }
+ LOG.info("Firmware update progress:" + firmwareProgress + " total lenL:" + len + " progress:" + firmwareProgress / len);
+ }
+
+ if (!(len % packetLength == 0)) {
+ byte lastChunk[] = new byte[len % packetLength];
+ lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len);
+ if (!sendFirmwareChunk(lastChunk)) {
+ LOG.error("Firmware chunk write failed");
+ return;
+ }
+ firmwareProgress += len % packetLength;
+ }
+
+ LOG.info("Firmware update progress:" + firmwareProgress +" total lenL:"+ len + " progress:" + firmwareProgress/len);
+
+ if(!sendFirmwareSync()) {
+ LOG.error("Firmware sync failed");
+ return;
+ }
+
+ }
+
+ private boolean sendFirmwareChunk(byte fwChunk[]) {
+ try {
+ TransactionBuilder builder = performInitialized("send firmware packet");
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), fwChunk);
+ builder.queue(getQueue());
+ } catch (IOException ex) {
+ LOG.error("Unable to send fw packet to MI", ex);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean sendFirmwareSync() {
+ try {
+ TransactionBuilder builder = performInitialized("send firmware sync");
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[] {MiBandService.COMMAND_SYNC});
+ builder.queue(getQueue());
+ } catch (IOException ex) {
+ LOG.error("Unable to send firmware sync to MI", ex);
+ return false;
+ }
+ return true;
+ }
+
+
@Override
protected TransactionBuilder createTransactionBuilder(String taskName) {
return new MiBandTransactionBuilder(taskName);
diff --git a/app/src/main/res/layout/activity_fw_upgrade.xml b/app/src/main/res/layout/activity_fw_upgrade.xml
new file mode 100644
index 000000000..c8c388660
--- /dev/null
+++ b/app/src/main/res/layout/activity_fw_upgrade.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 233112474..1de3435bb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -153,4 +153,9 @@
Alarms sent to device!
No data. Synchronize device?
About to transfer %1$s of data starting from %2$s
+ FwUpgrade
+ You are about to install firmware %s instead of the one currently on your MiBand.
+ This firmware has been tested and is known to be compatible with GadgetBridge.
+ "This firmware is untested and may not be compatible with GadgetBridge. You are not encouraged to flash it to your MiBand. "
+ If you still want to proceed and things continue to work properly afterwards, please tell the GadgetBridge developers to whitelist firmware version: %s