Initial support for upgrading firmware of the MiBand.

This release seems to be working quite well with respect to the firmware upgrading itself. The user facing part needs more work.

In order to update the firmware one has to:
- open a file ending with .fw
- switch from the firmware upgrade activity to the main one
- connect to the miband
- return to the firmware upgrade activity
- press the "install" button (that became active when the device connection was established)

Caveats:
There are almost no check wrt. the integrity of the firmware files.
master
Daniele Gobbetti 2015-07-23 17:14:51 +02:00
parent 268e658e6f
commit f16a96e9b2
8 changed files with 387 additions and 7 deletions

View File

@ -203,6 +203,36 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms" />
</activity>
<activity
android:name=".miband.FwUpgrade"
android:label="@string/title_activity_fw_upgrade">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="nodomain.freeyourgadget.gadgetbridge.ControlCenter" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <!-- needed for android 5 -->
<data android:host="*" />
<data android:scheme="file" />
<!-- as seen on openkeychain repo: https://github.com/open-keychain/open-keychain/blob/master/OpenKeychain/src/main/AndroidManifest.xml -->
<data android:pathPattern=".*\\.fw" />
<data android:pathPattern=".*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -80,6 +80,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
private ActivityStruct activityStruct;
private DeviceInfo mDeviceInfo;
public MiBandSupport() {
addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
}
@ -531,7 +533,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
@ -602,8 +616,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());
}
}
@ -830,6 +844,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);

View File

@ -0,0 +1,21 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="nodomain.freeyourgadget.gadgetbridge.miband.FwUpgrade">
<TextView
android:id="@+id/fwUpgradeTextView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/appinstaller_install"
android:id="@+id/installButton"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:enabled="false" />
</RelativeLayout>

View File

@ -153,4 +153,9 @@
<string name="user_feedback_miband_set_alarms_ok">Alarms sent to device!</string>
<string name="chart_no_data_synchronize">No data. Synchronize device?</string>
<string name="user_feedback_miband_activity_data_transfer">About to transfer %1$s of data starting from %2$s</string>
<string name="title_activity_fw_upgrade">FwUpgrade</string>
<string name="fw_upgrade_notice">You are about to install firmware %s instead of the one currently on your MiBand.</string>
<string name="miband_firmware_known">This firmware has been tested and is known to be compatible with GadgetBridge.</string>
<string name="miband_firmware_unknown_warning">"This firmware is untested and may not be compatible with GadgetBridge. You are not encouraged to flash it to your MiBand. "</string>
<string name="miband_firmware_suggest_whitelist">If you still want to proceed and things continue to work properly afterwards, please tell the GadgetBridge developers to whitelist firmware version: %s</string>
</resources>