diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b17bd9d5..d83ef107 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 da831549..4f68b597 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 00000000..1f5c4e4d --- /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 00000000..2759f097 --- /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 e9500f36..be9f288f 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 dc2088bf..22d8637b 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 00000000..c8c38866 --- /dev/null +++ b/app/src/main/res/layout/activity_fw_upgrade.xml @@ -0,0 +1,21 @@ + + + +