1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-23 18:36:50 +01:00

Fossil Hybrid HR: Experimenal firmware update support

TODO:
- progress bar
- set device state to firmware update

There is currently absolutely no feedback during install, just wait and pray
This commit is contained in:
Andreas Shimokawa 2020-04-10 00:48:46 +02:00
parent 630c58c8f4
commit 1e93a8c1cc
13 changed files with 168 additions and 19 deletions

View File

@ -1,7 +1,9 @@
### Changelog
#### Version 0.43.2
* Fossil Q Hybrid: Allow choosing and cropping image to be set as watch background
#### NEXT
* Fossil Hybrid HR: Allow choosing and cropping image to be set as watch background
* Fossil Hybrid HR: Option to draw circles around widgets
* Fossil Hybrid HR: Experimenal firmware update support
* Steps/Sleep averages: skip days with zero data
#### Version 0.43.1

View File

@ -0,0 +1,100 @@
/* Copyright (C) 2020 Andreas Shimokawa
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
import android.content.Context;
import android.net.Uri;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
public class FossilHRInstallHandler implements InstallHandler {
private final Context mContext;
private boolean mIsValid;
FossilHRInstallHandler(Uri uri, Context context) {
mContext = context;
UriHelper uriHelper = null;
try {
uriHelper = UriHelper.get(uri, mContext);
} catch (IOException e) {
mIsValid = false;
return;
}
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
byte[] bytes = new byte[16];
in.read(bytes);
ByteBuffer buf = ByteBuffer.wrap(bytes);
buf.order(ByteOrder.LITTLE_ENDIAN);
int header0 = buf.getInt();
int size = buf.getInt();
int header2 = buf.getInt();
int header3 = buf.getInt();
if (header0 != 1 || header2 != 0x00012000 || header3 != 0x00012000) {
mIsValid = false;
return;
}
} catch (Exception e) {
mIsValid = false;
return;
}
mIsValid = true;
}
@Override
public void validateInstallation(InstallActivity installActivity, GBDevice device) {
if (device.isBusy()) {
installActivity.setInfoText(device.getBusyTask());
installActivity.setInstallEnabled(false);
return;
}
if (device.getType() != DeviceType.FOSSILQHYBRID || !device.isConnected()) {
installActivity.setInfoText("Element cannot be installed");
installActivity.setInstallEnabled(false);
return;
}
GenericItem installItem = new GenericItem();
installItem.setIcon(R.drawable.ic_firmware);
installItem.setName("Fossil Hybrid HR Firmware");
installItem.setDetails("Unknown version");
installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
installActivity.setInstallEnabled(true);
installActivity.setInstallItem(installItem);
}
@Override
public void onStartInstall(GBDevice device) {
}
@Override
public boolean isValid() {
return mIsValid;
}
}

View File

@ -100,6 +100,10 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
if (isHybridHR()) {
FossilHRInstallHandler installHandler = new FossilHRInstallHandler(uri, context);
return installHandler.isValid() ? installHandler : null;
}
return null;
}

View File

@ -541,6 +541,11 @@ public class QHybridSupport extends QHybridBaseSupport {
watchAdapter.onTestNewFunction();
}
@Override
public void onInstallApp(Uri uri) {
watchAdapter.onInstallApp(uri);
}
private void backupFile(DownloadFileRequest request) {
try {
File file = FileUtils.getExternalFile("qFiles/" + request.timeStamp);

View File

@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.net.Uri;
import java.util.ArrayList;
@ -63,6 +64,7 @@ public abstract class WatchAdapter {
public abstract void syncNotificationSettings();
public abstract void onTestNewFunction();
public abstract void setTimezoneOffsetMinutes(short offset);
public abstract void onInstallApp(Uri uri);
public abstract boolean supportsFindDevice();
public abstract boolean supportsExtendedVibration();

View File

@ -20,6 +20,7 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.widget.Toast;
@ -382,6 +383,11 @@ public class FossilWatchAdapter extends WatchAdapter {
});
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public boolean supportsFindDevice() {
return false;

View File

@ -1,7 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
@ -9,6 +8,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Build;
import android.widget.Toast;
@ -16,11 +16,13 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferOverflowException;
import java.util.ArrayList;
import java.util.Calendar;
@ -31,7 +33,6 @@ import java.util.TimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.GBActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
@ -58,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FirmwareFilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImagesSetRequest;
@ -72,9 +74,11 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.Widget;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.WidgetsPutRequest;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_PHONE_REQUEST;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_WATCH_REQUEST;
@ -693,6 +697,25 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
}
}
*/
@Override
public void onInstallApp(Uri uri) {
UriHelper uriHelper = null;
try {
uriHelper = UriHelper.get(uri, getContext());
} catch (IOException e) {
GB.toast(getContext(), "Could not open firmare: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
}
if (uriHelper != null) {
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
byte[] firmwareBytes = FileUtils.readAll(in, 1024 * 2024); // 2MB
queueWrite(new FirmwareFilePutRequest(firmwareBytes, this));
} catch (Exception e) {
GB.toast(getContext(), "Firmware cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
}
}
}
public byte[] getSecretKey() {
byte[] authKeyBytes = new byte[16];

View File

@ -19,7 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.mis
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.util.Log;
import android.net.Uri;
import android.util.SparseArray;
import android.widget.Toast;
@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ActivityPointGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.AnimationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.BatteryLevelRequest;
@ -66,7 +67,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.mis
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.OTAEraseRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ReleaseHandsControlRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.RequestHandControlRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetCurrentStepCountRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetStepGoalRequest;
@ -418,6 +418,11 @@ public class MisfitWatchAdapter extends WatchAdapter {
GB.toast("old firmware does't support timezones", Toast.LENGTH_LONG, GB.ERROR);
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public boolean supportsFindDevice() {
return supportsExtendedVibration();

View File

@ -63,7 +63,7 @@ public abstract class PlayNotificationRequest extends FilePutRequest {
String nullTerminatedMessage = StringUtils.terminateNull(message);
byte[] messageBytes = nullTerminatedMessage.getBytes(charsetUTF8);
if (messageBytes.length > 490) {
messageBytes = Arrays.copyOf(messageBytes, 490);
messageBytes = Arrays.copyOf(messageBytes, 475);
}
short mainBufferLength = (short) (lengthBufferLength + uidLength + appBundleCRCLength + titleBytes.length + senderBytes.length + messageBytes.length);

View File

@ -18,15 +18,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fo
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedPutRequest;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.ConfigItem;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedPutRequest;
public class ConfigurationPutRequest extends FileEncryptedPutRequest {
public ConfigurationPutRequest(ConfigItem item, FossilHRWatchAdapter adapter) {

View File

@ -12,16 +12,16 @@ public class AssetFilePutRequest extends FilePutRequest {
public AssetFilePutRequest(AssetFile[] files, byte subHandle, FossilWatchAdapter adapter) throws IOException {
super((short) (0x0700 | subHandle), prepareFileData(files), adapter);
}
public AssetFilePutRequest(AssetFile file, byte subHandle, FossilWatchAdapter adapter) throws IOException {
public AssetFilePutRequest(AssetFile file, byte subHandle, FossilWatchAdapter adapter) {
super((short) (0x0700 | subHandle), prepareFileData(file), adapter);
}
private static byte[] prepareFileData(AssetFile[] files) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for(int i = 0; i < files.length; i++){
for (AssetFile file : files) {
stream.write(
prepareFileData(files[i])
prepareFileData(file)
);
}
@ -29,7 +29,7 @@ public class AssetFilePutRequest extends FilePutRequest {
}
private static byte[] prepareFileData(AssetFile file){
int size = file.getFileName().length() + file.getFileData().length + 1 /**null byte **/;
int size = file.getFileName().length() + file.getFileData().length + 1; // null byte
ByteBuffer buffer = ByteBuffer.allocate(size + 2);
buffer.order(ByteOrder.LITTLE_ENDIAN);

View File

@ -0,0 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
public class FirmwareFilePutRequest extends FilePutRawRequest {
public FirmwareFilePutRequest(byte[] firmwareBytes, FossilHRWatchAdapter adapter) {
super((short) 0x00FF, firmwareBytes, adapter);
}
}

View File

@ -4,9 +4,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FilePutRawRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
public class WidgetsPutRequest extends JsonPutRequest {