mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-25 19:36:50 +01:00
added menu handling and error status
This commit is contained in:
parent
5ca4816b01
commit
c4d63a80e1
@ -505,6 +505,9 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".devices.qhybrid.QHybridAppChoserActivity"
|
android:name=".devices.qhybrid.QHybridAppChoserActivity"
|
||||||
android:exported="true" />
|
android:exported="true" />
|
||||||
|
<activity
|
||||||
|
android:name=".devices.qhybrid.HRConfigActivity"
|
||||||
|
android:exported="true" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -312,7 +312,7 @@ public class ConfigActivity extends AbstractGBActivity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
device = GBApplication.app().getDeviceManager().getSelectedDevice();
|
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));
|
setSettingsError(getString(R.string.watch_not_connected));
|
||||||
} else {
|
} else {
|
||||||
updateSettings();
|
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
|
@Override
|
||||||
public Class<? extends Activity> getAppsManagementActivity() {
|
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
|
@Override
|
||||||
|
@ -94,4 +94,7 @@ public abstract class WatchAdapter {
|
|||||||
}
|
}
|
||||||
return s.substring(0, s.length() - 1) + "\n";
|
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;
|
private int lastButtonIndex = -1;
|
||||||
|
|
||||||
Logger logger = LoggerFactory.getLogger(getClass());
|
Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
|
||||||
|
|
||||||
public FossilWatchAdapter(QHybridSupport deviceSupport) {
|
public FossilWatchAdapter(QHybridSupport deviceSupport) {
|
||||||
super(deviceSupport);
|
super(deviceSupport);
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGatt;
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
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.devices.qhybrid.NotificationHRConfiguration;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
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.adapter.fossil.FossilWatchAdapter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
|
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.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.notification.PlayNotificationRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
|
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.buttons.ButtonConfigurationPutRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest;
|
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.NotificationFilterPutHRRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImagePutRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImagePutRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|
||||||
|
|
||||||
public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
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};
|
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();
|
e.printStackTrace();
|
||||||
} // icons
|
} // icons
|
||||||
|
|
||||||
queueWrite(new NotificationFilterPutHRRequest(new NotificationHRConfiguration[]{
|
// queueWrite(new NotificationFilterPutHRRequest(new NotificationHRConfiguration[]{
|
||||||
new NotificationHRConfiguration("com.whatsapp", -1),
|
// new NotificationHRConfiguration("com.whatsapp", -1),
|
||||||
new NotificationHRConfiguration("asdasdasdasdasd", -1),
|
// new NotificationHRConfiguration("asdasdasdasdasd", -1),
|
||||||
// new NotificationHRConfiguration("twitter", -1),
|
// // new NotificationHRConfiguration("twitter", -1),
|
||||||
}, this));
|
// }, this));
|
||||||
|
|
||||||
queueWrite(new PlayNotificationRequest("com.whatsapp", "WhatsAp", "wHATSaPP", this));
|
// queueWrite(new PlayNotificationRequest("com.whatsapp", "WhatsAp", "wHATSaPP", this));
|
||||||
queueWrite(new PlayNotificationRequest("twitterrrr", "Twitterr", "tWITTER", this));
|
// queueWrite(new PlayNotificationRequest("twitterrrr", "Twitterr", "tWITTER", this));
|
||||||
|
|
||||||
syncSettings();
|
syncSettings();
|
||||||
|
|
||||||
queueWrite(new ButtonConfigurationPutRequest(this));
|
overwriteButtons(null);
|
||||||
|
|
||||||
queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED));
|
queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void negotiateSymmetricKey(){
|
private void negotiateSymmetricKey() {
|
||||||
queueWrite(new VerifyPrivateKeyRequest(
|
queueWrite(new VerifyPrivateKeyRequest(
|
||||||
this.getSecretKey(),
|
this.getSecretKey(),
|
||||||
this
|
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
|
@Override
|
||||||
public void onFetchActivityData() {
|
public void onFetchActivityData() {
|
||||||
syncSettings();
|
syncSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncSettings(){
|
private void syncSettings() {
|
||||||
negotiateSymmetricKey();
|
negotiateSymmetricKey();
|
||||||
|
|
||||||
queueWrite(new ConfigurationGetRequest(this));
|
queueWrite(new ConfigurationGetRequest(this));
|
||||||
|
|
||||||
|
setTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -109,7 +147,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||||||
|
|
||||||
public boolean playRawNotification(NotificationSpec notificationSpec) {
|
public boolean playRawNotification(NotificationSpec notificationSpec) {
|
||||||
String sender = notificationSpec.sender;
|
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));
|
queueWrite(new PlayNotificationRequest("generic", notificationSpec.sourceName, notificationSpec.body, this));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -138,6 +176,24 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||||||
return watchRandomNumber;
|
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
|
@Override
|
||||||
protected void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic) {
|
protected void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||||
super.handleBackgroundCharacteristic(characteristic);
|
super.handleBackgroundCharacteristic(characteristic);
|
||||||
@ -149,12 +205,29 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||||||
try {
|
try {
|
||||||
JSONObject requestJson = new JSONObject(new String(value, 3, value.length - 3));
|
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");
|
.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) {
|
} catch (JSONException e) {
|
||||||
e.printStackTrace();
|
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.Request;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
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 {
|
public class FileCloseRequest extends FossilRequest {
|
||||||
private boolean isFinished = false;
|
private boolean isFinished = false;
|
||||||
@ -64,7 +65,7 @@ public class FileCloseRequest extends FossilRequest {
|
|||||||
|
|
||||||
byte status = buffer.get(3);
|
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;
|
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.adapter.fossil.FossilWatchAdapter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
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.FossilRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||||
|
|
||||||
public abstract class FileGetRequest extends FossilRequest {
|
public abstract class FileGetRequest extends FossilRequest {
|
||||||
private short handle;
|
private short handle;
|
||||||
@ -75,7 +76,7 @@ public abstract class FileGetRequest extends FossilRequest {
|
|||||||
byte status = buffer.get(3);
|
byte status = buffer.get(3);
|
||||||
|
|
||||||
if(status != 0){
|
if(status != 0){
|
||||||
throw new RuntimeException("FileGet error: " + status);
|
throw new RuntimeException("FileGet error: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.handle != handle){
|
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.adapter.fossil.FossilWatchAdapter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
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.FossilRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||||
|
|
||||||
public class FileLookupRequest extends FossilRequest {
|
public class FileLookupRequest extends FossilRequest {
|
||||||
private short handle = -1;
|
private short handle = -1;
|
||||||
@ -82,7 +83,7 @@ public class FileLookupRequest extends FossilRequest {
|
|||||||
byte status = buffer.get(3);
|
byte status = buffer.get(3);
|
||||||
|
|
||||||
if(status != 0){
|
if(status != 0){
|
||||||
throw new RuntimeException("file lookup error: " + status);
|
throw new RuntimeException("file lookup error: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.handle != handle){
|
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.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
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.FossilRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
|
import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
|
||||||
|
|
||||||
public class FilePutRequest extends FossilRequest {
|
public class FilePutRequest extends FossilRequest {
|
||||||
@ -100,7 +101,7 @@ public class FilePutRequest extends FossilRequest {
|
|||||||
byte status = value[3];
|
byte status = value[3];
|
||||||
|
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
throw new RuntimeException("upload status: " + status);
|
throw new RuntimeException("upload status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle != this.handle) {
|
if (handle != this.handle) {
|
||||||
@ -146,7 +147,7 @@ public class FilePutRequest extends FossilRequest {
|
|||||||
|
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
onFilePut(false);
|
onFilePut(false);
|
||||||
throw new RuntimeException("wrong closing status: " + status);
|
throw new RuntimeException("wrong closing status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = UploadState.UPLOADED;
|
this.state = UploadState.UPLOADED;
|
||||||
|
@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
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 {
|
public class FileVerifyRequest extends FossilRequest {
|
||||||
private boolean isFinished = false;
|
private boolean isFinished = false;
|
||||||
@ -64,7 +65,7 @@ public class FileVerifyRequest extends FossilRequest {
|
|||||||
|
|
||||||
byte status = buffer.get(3);
|
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;
|
this.isFinished = true;
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
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.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
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 {
|
public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||||
private final FossilHRWatchAdapter adapter;
|
private final FossilHRWatchAdapter adapter;
|
||||||
@ -29,7 +30,6 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
|||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
|
||||||
adapter.setPhoneRandomNumber(randomPhoneNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -62,6 +62,7 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
|||||||
System.arraycopy(result, 0, watchRandomNumber, 0, 8);
|
System.arraycopy(result, 0, watchRandomNumber, 0, 8);
|
||||||
|
|
||||||
adapter.setWatchRandomNumber(watchRandomNumber);
|
adapter.setWatchRandomNumber(watchRandomNumber);
|
||||||
|
adapter.setPhoneRandomNumber(randomPhoneNumber);
|
||||||
|
|
||||||
cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
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}));
|
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);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else if (value[1] == 2) {
|
} 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;
|
this.isFinished = true;
|
||||||
}
|
}
|
||||||
|
@ -11,37 +11,16 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class ButtonConfigurationPutRequest extends JsonPutRequest {
|
public class ButtonConfigurationPutRequest extends JsonPutRequest {
|
||||||
public ButtonConfigurationPutRequest(FossilWatchAdapter adapter) {
|
public ButtonConfigurationPutRequest(String[] menuItems, FossilWatchAdapter adapter) {
|
||||||
super((short) 0x0500, createObject(), adapter);
|
super((short) 0x0500, createObject(menuItems), adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JSONObject createObject() {
|
private static JSONObject createObject(String[] menuItems) {
|
||||||
try {
|
try {
|
||||||
return new JSONObject()
|
return new JSONObject()
|
||||||
.put("push", new JSONObject()
|
.put("push", new JSONObject()
|
||||||
.put("set", new JSONObject()
|
.put("set", new JSONObject()
|
||||||
.put("commuteApp._.config.destinations", new JSONArray()
|
.put("commuteApp._.config.destinations", new JSONArray(menuItems))
|
||||||
.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("master._.config.buttons", new JSONArray()
|
.put("master._.config.buttons", new JSONArray()
|
||||||
.put(new JSONObject()
|
.put(new JSONObject()
|
||||||
.put("name", "commuteApp")
|
.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);
|
byte status = buffer.get(3);
|
||||||
|
|
||||||
if(status != 0){
|
if(status != 0){
|
||||||
throw new RuntimeException("FileGet error: " + status);
|
throw new RuntimeException("FileGet error: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.handle != handle){
|
if(this.handle != handle){
|
||||||
@ -111,7 +111,7 @@ public abstract class FileEncryptedGetRequest extends FossilRequest {
|
|||||||
int crcExpected = buffer.getInt(8);
|
int crcExpected = buffer.getInt(8);
|
||||||
|
|
||||||
if((int) crc.getValue() != crcExpected){
|
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);
|
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];
|
byte status = value[3];
|
||||||
|
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
throw new RuntimeException("upload status: " + status);
|
throw new RuntimeException("upload status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle != this.handle) {
|
if (handle != this.handle) {
|
||||||
@ -133,7 +133,7 @@ public class FilePutRawRequest extends FossilRequest {
|
|||||||
|
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
onFilePut(false);
|
onFilePut(false);
|
||||||
throw new RuntimeException("wrong closing status: " + status);
|
throw new RuntimeException("wrong closing status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = UploadState.UPLOADED;
|
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;
|
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 class ImagesPutRequest extends JsonPutRequest {
|
||||||
public ImagesPutRequest(Image[] images, FossilWatchAdapter adapter) {
|
public ImagesPutRequest(Image[] images, FossilWatchAdapter adapter) {
|
||||||
super((short) 0x0501, prepareObject(images), adapter);
|
super((short) 0x0500, prepareObject(images), adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JSONObject prepareObject(Image[] images){
|
private static JSONObject prepareObject(Image[] images){
|
||||||
|
@ -22,6 +22,8 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
|
||||||
|
|
||||||
public class DownloadFileRequest extends FileRequest {
|
public class DownloadFileRequest extends FileRequest {
|
||||||
ByteBuffer buffer = null;
|
ByteBuffer buffer = null;
|
||||||
public byte[] file = null;
|
public byte[] file = null;
|
||||||
@ -69,7 +71,7 @@ public class DownloadFileRequest extends FileRequest {
|
|||||||
this.status = buffer1.get(3);
|
this.status = buffer1.get(3);
|
||||||
short realHandle = buffer1.getShort(1);
|
short realHandle = buffer1.getShort(1);
|
||||||
if(status != 0){
|
if(status != 0){
|
||||||
log("wrong status: " + status);
|
log("wrong status: " + ResultCode.fromCode(status) + " (" + status + ")");
|
||||||
}else if(realHandle != fileHandle){
|
}else if(realHandle != fileHandle){
|
||||||
log("wrong handle: " + realHandle);
|
log("wrong handle: " + realHandle);
|
||||||
completed = true;
|
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