mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-08-24 16:21:16 +02:00
added menu handling and error status
This commit is contained in:
parent
5ca4816b01
commit
c4d63a80e1
@ -505,6 +505,9 @@
|
||||
<activity
|
||||
android:name=".devices.qhybrid.QHybridAppChoserActivity"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".devices.qhybrid.HRConfigActivity"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -312,7 +312,7 @@ public class ConfigActivity extends AbstractGBActivity {
|
||||
});
|
||||
|
||||
device = GBApplication.app().getDeviceManager().getSelectedDevice();
|
||||
if (device == null || device.getType() != DeviceType.FOSSILQHYBRID) {
|
||||
if (device == null || device.getType() != DeviceType.FOSSILQHYBRID || device.getFirmwareVersion().charAt(2) != '0') {
|
||||
setSettingsError(getString(R.string.watch_not_connected));
|
||||
} else {
|
||||
updateSettings();
|
||||
|
@ -0,0 +1,191 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.w3c.dom.Text;
|
||||
|
||||
import java.sql.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||
|
||||
public class HRConfigActivity extends AbstractGBActivity implements View.OnClickListener, DialogInterface.OnClickListener, AdapterView.OnItemClickListener {
|
||||
private SharedPreferences sharedPreferences;
|
||||
private ActionListAdapter actionListAdapter;
|
||||
private ArrayList<MenuAction> menuActions = new ArrayList<>();
|
||||
|
||||
static public final String CONFIG_KEY_Q_ACTIONS = "Q_ACTIONS";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_qhybrid_hr_settings);
|
||||
|
||||
findViewById(R.id.qhybrid_action_add).setOnClickListener(this);
|
||||
|
||||
sharedPreferences = GBApplication.getPrefs().getPreferences();
|
||||
|
||||
ListView actionListView = findViewById(R.id.qhybrid_action_list);
|
||||
actionListAdapter = new ActionListAdapter(menuActions);
|
||||
actionListView.setAdapter(actionListAdapter);
|
||||
actionListView.setOnItemClickListener(this);
|
||||
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.qhybrid_action_add) {
|
||||
final EditText input = new EditText(this);
|
||||
input.setId(0);
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
input.setLayoutParams(lp);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setView(input)
|
||||
.setNegativeButton("cancel", null)
|
||||
.setPositiveButton("ok", this)
|
||||
.setTitle("create action")
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSettings() {
|
||||
JSONArray actionArray = null;
|
||||
try {
|
||||
actionArray = new JSONArray(sharedPreferences.getString(CONFIG_KEY_Q_ACTIONS, "[]"));
|
||||
menuActions.clear();
|
||||
for (int i = 0; i < actionArray.length(); i++)
|
||||
menuActions.add(new MenuAction(actionArray.getString(i)));
|
||||
|
||||
actionListAdapter.notifyDataSetChanged();
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
EditText actionEditText = ((AlertDialog) dialog).findViewById(0);
|
||||
|
||||
String action = actionEditText.getText().toString();
|
||||
try {
|
||||
JSONArray actionArray = new JSONArray(sharedPreferences.getString(CONFIG_KEY_Q_ACTIONS, "[]"));
|
||||
actionArray.put(action);
|
||||
sharedPreferences.edit().putString(CONFIG_KEY_Q_ACTIONS, actionArray.toString()).apply();
|
||||
updateSettings();
|
||||
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
|
||||
final EditText input = new EditText(this);
|
||||
input.setId(0);
|
||||
input.setText(((TextView) view).getText());
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
input.setLayoutParams(lp);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setView(input)
|
||||
.setNegativeButton("delete", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
menuActions.remove(position);
|
||||
putActionItems(menuActions);
|
||||
updateSettings();
|
||||
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
|
||||
}
|
||||
})
|
||||
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
menuActions.get(position).setAction(input.getText().toString());
|
||||
putActionItems(menuActions);
|
||||
updateSettings();
|
||||
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
|
||||
}
|
||||
})
|
||||
.setTitle("edit action")
|
||||
.show();
|
||||
}
|
||||
|
||||
private void putActionItems(List<MenuAction> actions){
|
||||
JSONArray array = new JSONArray();
|
||||
for (MenuAction action : actions) array.put(action.getAction());
|
||||
|
||||
sharedPreferences.edit().putString(CONFIG_KEY_Q_ACTIONS, array.toString()).apply();
|
||||
}
|
||||
|
||||
class MenuAction {
|
||||
private String action;
|
||||
|
||||
public MenuAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
class ActionListAdapter extends ArrayAdapter<MenuAction> {
|
||||
public ActionListAdapter(@NonNull ArrayList<MenuAction> objects) {
|
||||
super(HRConfigActivity.this, 0, objects);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
if (convertView == null) convertView = new TextView(getContext());
|
||||
TextView view = (TextView) convertView;
|
||||
|
||||
view.setText(getItem(position).getAction());
|
||||
// view.setTextColor(Color.WHITE);
|
||||
view.setTextSize(30);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
@ -145,7 +145,9 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return ConfigActivity.class;
|
||||
GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice();
|
||||
boolean isHR = connectedDevice.getFirmwareVersion().charAt(2) == '1';
|
||||
return isHR ? HRConfigActivity.class : ConfigActivity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,4 +94,7 @@ public abstract class WatchAdapter {
|
||||
}
|
||||
return s.substring(0, s.length() - 1) + "\n";
|
||||
}
|
||||
|
||||
public void setCommuteMenuMessage(String message, boolean finished) {
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||
|
||||
private int lastButtonIndex = -1;
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(getClass());
|
||||
Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
|
||||
|
||||
public FossilWatchAdapter(QHybridSupport deviceSupport) {
|
||||
super(deviceSupport);
|
||||
|
@ -1,16 +1,24 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HRConfigActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -18,13 +26,17 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSuppo
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.CurrentStepCountConfigItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfigurationPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.Image;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImagesPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.menu.SetCommuteMenuMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationFilterPutHRRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImagePutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
private byte[] secretKey = new byte[]{(byte) 0x60, (byte) 0x26, (byte) 0xB7, (byte) 0xFD, (byte) 0xB2, (byte) 0x6D, (byte) 0x05, (byte) 0x5E, (byte) 0xDA, (byte) 0xF7, (byte) 0x4B, (byte) 0x49, (byte) 0x98, (byte) 0x78, (byte) 0x02, (byte) 0x38};
|
||||
@ -68,38 +80,64 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
e.printStackTrace();
|
||||
} // icons
|
||||
|
||||
queueWrite(new NotificationFilterPutHRRequest(new NotificationHRConfiguration[]{
|
||||
new NotificationHRConfiguration("com.whatsapp", -1),
|
||||
new NotificationHRConfiguration("asdasdasdasdasd", -1),
|
||||
// new NotificationHRConfiguration("twitter", -1),
|
||||
}, this));
|
||||
// queueWrite(new NotificationFilterPutHRRequest(new NotificationHRConfiguration[]{
|
||||
// new NotificationHRConfiguration("com.whatsapp", -1),
|
||||
// new NotificationHRConfiguration("asdasdasdasdasd", -1),
|
||||
// // new NotificationHRConfiguration("twitter", -1),
|
||||
// }, this));
|
||||
|
||||
queueWrite(new PlayNotificationRequest("com.whatsapp", "WhatsAp", "wHATSaPP", this));
|
||||
queueWrite(new PlayNotificationRequest("twitterrrr", "Twitterr", "tWITTER", this));
|
||||
// queueWrite(new PlayNotificationRequest("com.whatsapp", "WhatsAp", "wHATSaPP", this));
|
||||
// queueWrite(new PlayNotificationRequest("twitterrrr", "Twitterr", "tWITTER", this));
|
||||
|
||||
syncSettings();
|
||||
|
||||
queueWrite(new ButtonConfigurationPutRequest(this));
|
||||
overwriteButtons(null);
|
||||
|
||||
queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED));
|
||||
}
|
||||
|
||||
private void negotiateSymmetricKey(){
|
||||
private void negotiateSymmetricKey() {
|
||||
queueWrite(new VerifyPrivateKeyRequest(
|
||||
this.getSecretKey(),
|
||||
this
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTime() {
|
||||
long millis = System.currentTimeMillis();
|
||||
TimeZone zone = new GregorianCalendar().getTimeZone();
|
||||
|
||||
queueWrite(
|
||||
new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationPutRequest(
|
||||
new ConfigurationPutRequest.TimeConfigItem(
|
||||
(int) (millis / 1000 + getDeviceSupport().getTimeOffset() * 60),
|
||||
(short) (millis % 1000),
|
||||
(short) ((zone.getRawOffset() + (zone.inDaylightTime(new Date()) ? 1 : 0)) / 60000)
|
||||
),
|
||||
this), false
|
||||
);
|
||||
}
|
||||
|
||||
private void setBackgroundImages(Image background, Image[] complications){
|
||||
background.setAngle(0);
|
||||
background.setDistance(0);
|
||||
background.setIndexZ(0);
|
||||
|
||||
queueWrite(new ImagesPutRequest(new Image[]{background}, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
syncSettings();
|
||||
}
|
||||
|
||||
private void syncSettings(){
|
||||
private void syncSettings() {
|
||||
negotiateSymmetricKey();
|
||||
|
||||
queueWrite(new ConfigurationGetRequest(this));
|
||||
|
||||
setTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -109,7 +147,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
|
||||
public boolean playRawNotification(NotificationSpec notificationSpec) {
|
||||
String sender = notificationSpec.sender;
|
||||
if(sender == null) sender = notificationSpec.sourceName;
|
||||
if (sender == null) sender = notificationSpec.sourceName;
|
||||
queueWrite(new PlayNotificationRequest("generic", notificationSpec.sourceName, notificationSpec.body, this));
|
||||
return true;
|
||||
}
|
||||
@ -138,6 +176,24 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
return watchRandomNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overwriteButtons(String jsonConfigString) {
|
||||
try {
|
||||
JSONArray jsonArray = new JSONArray(
|
||||
GBApplication.getPrefs().getString(HRConfigActivity.CONFIG_KEY_Q_ACTIONS, "[]")
|
||||
);
|
||||
String[] menuItems = new String[jsonArray.length()];
|
||||
for(int i = 0; i < jsonArray.length(); i++) menuItems[i] = jsonArray.getString(i);
|
||||
|
||||
queueWrite(new ButtonConfigurationPutRequest(
|
||||
menuItems,
|
||||
this
|
||||
));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||
super.handleBackgroundCharacteristic(characteristic);
|
||||
@ -149,12 +205,29 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
try {
|
||||
JSONObject requestJson = new JSONObject(new String(value, 3, value.length - 3));
|
||||
|
||||
String action = requestJson.getJSONObject("commuteApp._.config.commute_info")
|
||||
String action = requestJson.getJSONObject("req").getJSONObject("commuteApp._.config.commute_info")
|
||||
.getString("dest");
|
||||
|
||||
String startStop = requestJson.getJSONObject("req").getJSONObject("commuteApp._.config.commute_info")
|
||||
.getString("action");
|
||||
|
||||
if(startStop.equals("stop")){
|
||||
// overwriteButtons(null);
|
||||
return;
|
||||
}
|
||||
|
||||
queueWrite(new SetCommuteMenuMessage("Anfrage wird weitergeleitet...", false, this));
|
||||
|
||||
Intent menuIntent = new Intent(QHybridSupport.QHYBRID_EVENT_COMMUTE_MENU);
|
||||
menuIntent.putExtra("EXTRA_ACTION", action);
|
||||
getContext().sendBroadcast(menuIntent);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCommuteMenuMessage(String message, boolean finished) {
|
||||
queueWrite(new SetCommuteMenuMessage(message, finished, this));
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||
|
||||
public class FileCloseRequest extends FossilRequest {
|
||||
private boolean isFinished = false;
|
||||
@ -64,7 +65,7 @@ public class FileCloseRequest extends FossilRequest {
|
||||
|
||||
byte status = buffer.get(3);
|
||||
|
||||
if(status != 0) throw new RuntimeException("wrong response status");
|
||||
if(status != 0) throw new RuntimeException("wrong response status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
|
||||
this.isFinished = true;
|
||||
|
||||
|
@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSuppo
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||
|
||||
public abstract class FileGetRequest extends FossilRequest {
|
||||
private short handle;
|
||||
@ -75,7 +76,7 @@ public abstract class FileGetRequest extends FossilRequest {
|
||||
byte status = buffer.get(3);
|
||||
|
||||
if(status != 0){
|
||||
throw new RuntimeException("FileGet error: " + status);
|
||||
throw new RuntimeException("FileGet error: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
if(this.handle != handle){
|
||||
|
@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSuppo
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||
|
||||
public class FileLookupRequest extends FossilRequest {
|
||||
private short handle = -1;
|
||||
@ -82,7 +83,7 @@ public class FileLookupRequest extends FossilRequest {
|
||||
byte status = buffer.get(3);
|
||||
|
||||
if(status != 0){
|
||||
throw new RuntimeException("file lookup error: " + status);
|
||||
throw new RuntimeException("file lookup error: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
if(this.handle != handle){
|
||||
|
@ -27,6 +27,7 @@ import java.util.zip.CRC32;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
|
||||
|
||||
public class FilePutRequest extends FossilRequest {
|
||||
@ -100,7 +101,7 @@ public class FilePutRequest extends FossilRequest {
|
||||
byte status = value[3];
|
||||
|
||||
if (status != 0) {
|
||||
throw new RuntimeException("upload status: " + status);
|
||||
throw new RuntimeException("upload status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
if (handle != this.handle) {
|
||||
@ -146,7 +147,7 @@ public class FilePutRequest extends FossilRequest {
|
||||
|
||||
if (status != 0) {
|
||||
onFilePut(false);
|
||||
throw new RuntimeException("wrong closing status: " + status);
|
||||
throw new RuntimeException("wrong closing status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
this.state = UploadState.UPLOADED;
|
||||
|
@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||
|
||||
public class FileVerifyRequest extends FossilRequest {
|
||||
private boolean isFinished = false;
|
||||
@ -64,7 +65,7 @@ public class FileVerifyRequest extends FossilRequest {
|
||||
|
||||
byte status = buffer.get(3);
|
||||
|
||||
if(status != 0) throw new RuntimeException("wrong response status");
|
||||
if(status != 0) throw new RuntimeException("wrong response status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
|
||||
this.isFinished = true;
|
||||
|
||||
|
@ -19,6 +19,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||
|
||||
public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
private final FossilHRWatchAdapter adapter;
|
||||
@ -29,7 +30,6 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
this.adapter = adapter;
|
||||
this.key = key;
|
||||
|
||||
adapter.setPhoneRandomNumber(randomPhoneNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -62,6 +62,7 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
System.arraycopy(result, 0, watchRandomNumber, 0, 8);
|
||||
|
||||
adapter.setWatchRandomNumber(watchRandomNumber);
|
||||
adapter.setPhoneRandomNumber(randomPhoneNumber);
|
||||
|
||||
cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
|
||||
@ -81,7 +82,8 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (value[1] == 2) {
|
||||
if (value[2] != 0) throw new RuntimeException("Authentication error: " + value[2]);
|
||||
if (value[2] != 0) throw new RuntimeException("Authentication error: " + ResultCode.fromCode(value[2]) + " (" + value[2] + ")");
|
||||
|
||||
|
||||
this.isFinished = true;
|
||||
}
|
||||
|
@ -11,37 +11,16 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class ButtonConfigurationPutRequest extends JsonPutRequest {
|
||||
public ButtonConfigurationPutRequest(FossilWatchAdapter adapter) {
|
||||
super((short) 0x0500, createObject(), adapter);
|
||||
public ButtonConfigurationPutRequest(String[] menuItems, FossilWatchAdapter adapter) {
|
||||
super((short) 0x0500, createObject(menuItems), adapter);
|
||||
}
|
||||
|
||||
private static JSONObject createObject() {
|
||||
private static JSONObject createObject(String[] menuItems) {
|
||||
try {
|
||||
return new JSONObject()
|
||||
.put("push", new JSONObject()
|
||||
.put("set", new JSONObject()
|
||||
.put("commuteApp._.config.destinations", new JSONArray()
|
||||
.put("LAMP 1")
|
||||
.put("LAMP 3")
|
||||
.put("LAMP 4")
|
||||
.put("LAMP 5")
|
||||
.put("LAMP 6")
|
||||
.put("LAMP 7")
|
||||
.put("LAMP 8")
|
||||
.put("LAMP 9")
|
||||
.put("LAMP 10")
|
||||
.put("LAMP 11")
|
||||
.put("LAMP 12")
|
||||
.put("LAMP 13")
|
||||
.put("LAMP 14")
|
||||
.put("LAMP 8")
|
||||
.put("LAMP 9")
|
||||
.put("LAMP 10")
|
||||
.put("LAMP 11")
|
||||
.put("LAMP 12")
|
||||
.put("LAMP 13")
|
||||
.put("LAMP 14")
|
||||
)
|
||||
.put("commuteApp._.config.destinations", new JSONArray(menuItems))
|
||||
.put("master._.config.buttons", new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("name", "commuteApp")
|
||||
|
@ -0,0 +1,58 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
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.service.devices.qhybrid.requests.fossil_hr.configuration;
|
||||
|
||||
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;
|
||||
|
||||
public class ConfigurationPutRequest extends FileEncryptedPutRequest {
|
||||
private static HashMap<Short, Class<? extends ConfigItem>> itemsById = new HashMap<>();
|
||||
|
||||
public ConfigurationPutRequest(ConfigItem item, FossilHRWatchAdapter adapter) {
|
||||
super((short) 0x0800, createFileContent(new ConfigItem[]{item}), adapter);
|
||||
}
|
||||
|
||||
public ConfigurationPutRequest(ConfigItem[] items, FossilHRWatchAdapter adapter) {
|
||||
super((short) 0x0800, createFileContent(items), adapter);
|
||||
}
|
||||
|
||||
private static byte[] createFileContent(ConfigItem[] items) {
|
||||
int overallSize = 0;
|
||||
for(ConfigItem item : items){
|
||||
overallSize += item.getItemSize() + 3;
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(overallSize);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
for(ConfigItem item : items){
|
||||
buffer.putShort(item.getId());
|
||||
buffer.put((byte) item.getItemSize());
|
||||
buffer.put(item.getContent());
|
||||
}
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public abstract class FileEncryptedGetRequest extends FossilRequest {
|
||||
byte status = buffer.get(3);
|
||||
|
||||
if(status != 0){
|
||||
throw new RuntimeException("FileGet error: " + status);
|
||||
throw new RuntimeException("FileGet error: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
if(this.handle != handle){
|
||||
@ -111,7 +111,7 @@ public abstract class FileEncryptedGetRequest extends FossilRequest {
|
||||
int crcExpected = buffer.getInt(8);
|
||||
|
||||
if((int) crc.getValue() != crcExpected){
|
||||
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||
throw new RuntimeException("crc: " + crc.getValue() + " expected: " + crcExpected);
|
||||
}
|
||||
|
||||
this.handleFileData(this.fileData);
|
||||
|
@ -0,0 +1,271 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
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.service.devices.qhybrid.requests.fossil_hr.file;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
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.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FileEncryptedPutRequest extends FossilRequest {
|
||||
public enum UploadState {INITIALIZED, UPLOADING, CLOSING, UPLOADED}
|
||||
|
||||
public UploadState state;
|
||||
|
||||
private ArrayList<byte[]> packets = new ArrayList<>();
|
||||
|
||||
private short handle;
|
||||
|
||||
private FossilHRWatchAdapter adapter;
|
||||
|
||||
private byte[] file;
|
||||
|
||||
private int fullCRC;
|
||||
|
||||
public FileEncryptedPutRequest(short handle, byte[] file, FossilHRWatchAdapter adapter) {
|
||||
this.handle = handle;
|
||||
this.adapter = adapter;
|
||||
|
||||
int fileLength = file.length + 16;
|
||||
ByteBuffer buffer = this.createBuffer();
|
||||
buffer.putShort(1, handle);
|
||||
buffer.putInt(3, 0);
|
||||
buffer.putInt(7, fileLength);
|
||||
buffer.putInt(11, fileLength);
|
||||
|
||||
this.data = buffer.array();
|
||||
|
||||
this.file = file;
|
||||
|
||||
state = UploadState.INITIALIZED;
|
||||
}
|
||||
|
||||
public short getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] value = characteristic.getValue();
|
||||
if (characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")) {
|
||||
int responseType = value[0] & 0x0F;
|
||||
log("response: " + responseType);
|
||||
switch (responseType) {
|
||||
case 3: {
|
||||
if (value.length != 5 || (value[0] & 0x0F) != 3) {
|
||||
throw new RuntimeException("wrong answer header");
|
||||
}
|
||||
state = UploadState.UPLOADING;
|
||||
|
||||
TransactionBuilder transactionBuilder = new TransactionBuilder("file upload");
|
||||
BluetoothGattCharacteristic uploadCharacteristic = adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0004-957f-7d4a-34a6-74696673696d"));
|
||||
|
||||
this.prepareFilePackets(this.file);
|
||||
|
||||
SecretKeySpec keySpec = new SecretKeySpec(this.adapter.getSecretKey(), "AES");
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
|
||||
byte[] fileIV = new byte[16];
|
||||
|
||||
|
||||
byte[] phoneRandomNumber = adapter.getPhoneRandomNumber();
|
||||
byte[] watchRandomNumber = adapter.getWatchRandomNumber();
|
||||
|
||||
System.arraycopy(phoneRandomNumber, 0, fileIV, 2, 6);
|
||||
System.arraycopy(watchRandomNumber, 0, fileIV, 9, 7);
|
||||
|
||||
fileIV[7]++;
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(fileIV));
|
||||
|
||||
for (byte[] packet : packets) {
|
||||
byte[] result = cipher.doFinal(packet);
|
||||
transactionBuilder.write(uploadCharacteristic, result);
|
||||
}
|
||||
}catch (Exception e){
|
||||
GB.toast("error encrypting file", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
|
||||
transactionBuilder.queue(adapter.getDeviceSupport().getQueue());
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
if (value.length == 4) return;
|
||||
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
short handle = buffer.getShort(1);
|
||||
int crc = buffer.getInt(8);
|
||||
byte status = value[3];
|
||||
|
||||
if (status != 0) {
|
||||
throw new RuntimeException("upload status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
if (handle != this.handle) {
|
||||
throw new RuntimeException("wrong response handle");
|
||||
}
|
||||
|
||||
if (crc != this.fullCRC) {
|
||||
throw new RuntimeException("file upload exception: wrong crc");
|
||||
}
|
||||
|
||||
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(3);
|
||||
buffer2.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer2.put((byte) 4);
|
||||
buffer2.putShort(this.handle);
|
||||
|
||||
new TransactionBuilder("file close")
|
||||
.write(
|
||||
adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d")),
|
||||
buffer2.array()
|
||||
)
|
||||
.queue(adapter.getDeviceSupport().getQueue());
|
||||
|
||||
this.state = UploadState.CLOSING;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
if (value.length == 9) return;
|
||||
if (value.length != 4 || (value[0] & 0x0F) != 4) {
|
||||
throw new RuntimeException("wrong file closing header");
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
short handle = buffer.getShort(1);
|
||||
|
||||
if (handle != this.handle) {
|
||||
onFilePut(false);
|
||||
throw new RuntimeException("wrong file closing handle");
|
||||
}
|
||||
|
||||
byte status = buffer.get(3);
|
||||
|
||||
if (status != 0) {
|
||||
onFilePut(false);
|
||||
throw new RuntimeException("wrong closing status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
this.state = UploadState.UPLOADED;
|
||||
|
||||
onFilePut(true);
|
||||
|
||||
log("uploaded file");
|
||||
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
this.onFilePut(false);
|
||||
throw new RuntimeException("file put timeout");
|
||||
/*timeout = true;
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(3);
|
||||
buffer2.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer2.put((byte) 4);
|
||||
buffer2.putShort(this.handle);
|
||||
|
||||
new TransactionBuilder("file close")
|
||||
.write(
|
||||
adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d")),
|
||||
buffer2.array()
|
||||
)
|
||||
.queue(adapter.getDeviceSupport().getQueue());
|
||||
|
||||
this.state = UploadState.CLOSING;
|
||||
break;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return this.state == UploadState.UPLOADED;
|
||||
}
|
||||
|
||||
private void prepareFilePackets(byte[] file) {
|
||||
int maxPacketSize = adapter.getMTU() - 4;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(file.length + 12 + 4);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buffer.putShort(handle);
|
||||
buffer.put((byte) 2);
|
||||
buffer.put((byte) 0);
|
||||
buffer.putInt(0);
|
||||
buffer.putInt(file.length);
|
||||
|
||||
buffer.put(file);
|
||||
|
||||
CRC32C crc = new CRC32C();
|
||||
|
||||
crc.update(file,0,file.length);
|
||||
buffer.putInt((int) crc.getValue());
|
||||
|
||||
byte[] data = buffer.array();
|
||||
|
||||
CRC32 fullCRC = new CRC32();
|
||||
|
||||
fullCRC.update(data);
|
||||
this.fullCRC = (int) fullCRC.getValue();
|
||||
|
||||
int packetCount = (int) Math.ceil(data.length / (float) maxPacketSize);
|
||||
|
||||
for (int i = 0; i < packetCount; i++) {
|
||||
int currentPacketLength = Math.min(maxPacketSize, data.length - i * maxPacketSize);
|
||||
byte[] packet = new byte[currentPacketLength + 1];
|
||||
packet[0] = (byte) i;
|
||||
System.arraycopy(data, i * maxPacketSize, packet, 1, currentPacketLength);
|
||||
|
||||
packets.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void onFilePut(boolean success) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{0x03};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength() {
|
||||
return 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getRequestUUID() {
|
||||
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||
}
|
||||
}
|
@ -87,7 +87,7 @@ public class FilePutRawRequest extends FossilRequest {
|
||||
byte status = value[3];
|
||||
|
||||
if (status != 0) {
|
||||
throw new RuntimeException("upload status: " + status);
|
||||
throw new RuntimeException("upload status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
if (handle != this.handle) {
|
||||
@ -133,7 +133,7 @@ public class FilePutRawRequest extends FossilRequest {
|
||||
|
||||
if (status != 0) {
|
||||
onFilePut(false);
|
||||
throw new RuntimeException("wrong closing status: " + status);
|
||||
throw new RuntimeException("wrong closing status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}
|
||||
|
||||
this.state = UploadState.UPLOADED;
|
||||
|
@ -0,0 +1,39 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
|
||||
|
||||
public enum ResultCode {
|
||||
SUCCESS(0),
|
||||
INVALID_OPERATION_DATA(1),
|
||||
OPERATION_IN_PROGRESS(2),
|
||||
MISS_PACKET(3),
|
||||
SOCKET_BUSY(4),
|
||||
VERIFICATION_FAIL(5),
|
||||
OVERFLOW(6),
|
||||
SIZE_OVER_LIMIT(7),
|
||||
FIRMWARE_INTERNAL_ERROR(128),
|
||||
FIRMWARE_INTERNAL_ERROR_NOT_OPEN(129),
|
||||
FIRMWARE_INTERNAL_ERROR_ACCESS_ERROR(130),
|
||||
FIRMWARE_INTERNAL_ERROR_NOT_FOUND(131),
|
||||
FIRMWARE_INTERNAL_ERROR_NOT_VALID(132),
|
||||
FIRMWARE_INTERNAL_ERROR_ALREADY_CREATE(133),
|
||||
FIRMWARE_INTERNAL_ERROR_NOT_ENOUGH_MEMORY(134),
|
||||
FIRMWARE_INTERNAL_ERROR_NOT_IMPLEMENTED(135),
|
||||
FIRMWARE_INTERNAL_ERROR_NOT_SUPPORT(136),
|
||||
FIRMWARE_INTERNAL_ERROR_SOCKET_BUSY(137),
|
||||
FIRMWARE_INTERNAL_ERROR_SOCKET_ALREADY_OPEN(138),
|
||||
FIRMWARE_INTERNAL_ERROR_INPUT_DATA_INVALID(139),
|
||||
FIRMWARE_INTERNAL_NOT_AUTHENTICATE(140),
|
||||
FIRMWARE_INTERNAL_SIZE_OVER_LIMIT(141),
|
||||
UNKNOWN(-1);
|
||||
int code;
|
||||
|
||||
ResultCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public static ResultCode fromCode(int code){
|
||||
for (ResultCode resultCode : ResultCode.values()){
|
||||
if(resultCode.code == code) return resultCode;
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
@ -37,4 +37,36 @@ public class Image {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getAngle() {
|
||||
return angle;
|
||||
}
|
||||
|
||||
public void setAngle(int angle) {
|
||||
this.angle = angle;
|
||||
}
|
||||
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setDistance(int distance) {
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public int getIndexZ() {
|
||||
return indexZ;
|
||||
}
|
||||
|
||||
public void setIndexZ(int indexZ) {
|
||||
this.indexZ = indexZ;
|
||||
}
|
||||
|
||||
public String getImageFile() {
|
||||
return imageFile;
|
||||
}
|
||||
|
||||
public void setImageFile(String imageFile) {
|
||||
this.imageFile = imageFile;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
|
||||
|
||||
public class ImagesPutRequest extends JsonPutRequest {
|
||||
public ImagesPutRequest(Image[] images, FossilWatchAdapter adapter) {
|
||||
super((short) 0x0501, prepareObject(images), adapter);
|
||||
super((short) 0x0500, prepareObject(images), adapter);
|
||||
}
|
||||
|
||||
private static JSONObject prepareObject(Image[] images){
|
||||
|
@ -22,6 +22,8 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||
|
||||
public class DownloadFileRequest extends FileRequest {
|
||||
ByteBuffer buffer = null;
|
||||
public byte[] file = null;
|
||||
@ -69,7 +71,7 @@ public class DownloadFileRequest extends FileRequest {
|
||||
this.status = buffer1.get(3);
|
||||
short realHandle = buffer1.getShort(1);
|
||||
if(status != 0){
|
||||
log("wrong status: " + status);
|
||||
log("wrong status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||
}else if(realHandle != fileHandle){
|
||||
log("wrong handle: " + realHandle);
|
||||
completed = true;
|
||||
|
40
app/src/main/res/layout/activity_qhybrid_hr_settings.xml
Normal file
40
app/src/main/res/layout/activity_qhybrid_hr_settings.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:weightSum="1">
|
||||
|
||||
<ListView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_weight="0.4"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/qhybrid_action_list"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="add action"
|
||||
android:id="@+id/qhybrid_action_add"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="top button single press"
|
||||
android:id="@+id/qhybrid_button_top_single_press"
|
||||
android:textSize="20dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="middle button single press"
|
||||
android:id="@+id/qhybrid_button_middle_single_press"
|
||||
android:textSize="20dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="bottom button single press"
|
||||
android:id="@+id/qhybrid_button_bottom_single_press"
|
||||
android:textSize="20dp"/>
|
||||
|
||||
</LinearLayout>
|
Loading…
Reference in New Issue
Block a user