1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-10 07:07:57 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java
2020-10-30 01:40:24 +01:00

1279 lines
58 KiB
Java

package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferOverflowException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import no.nordicsemi.android.dfu.FileType;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HRConfigActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRActivitySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration;
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigPayload;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityEntry;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityFileParser;
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.button.ButtonConfigurationGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileDeleteRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRawRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupAndGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayCallNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayTextNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.async.ConfirmAppStatusRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfiguration;
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.ConfigurationPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRawRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FirmwareFilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImagesSetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.menu.SetCommuteMenuMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicInfoSetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationFilterPutHRRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.translation.TranslationData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.translation.TranslationsGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.translation.TranslationsPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.Widget;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.WidgetsPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.AnimationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.FactoryResetRequest;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
import nodomain.freeyourgadget.gadgetbridge.util.Version;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_PHONE_REQUEST;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_WATCH_REQUEST;
public class FossilHRWatchAdapter extends FossilWatchAdapter {
private byte[] phoneRandomNumber;
private byte[] watchRandomNumber;
private ArrayList<Widget> widgets = new ArrayList<>();
private NotificationHRConfiguration[] notificationConfigurations;
private MusicSpec currentSpec = null;
int imageNameIndex = 0;
private byte jsonIndex = 0;
private AssetImage backGroundImage = null;
public FossilHRWatchAdapter(QHybridSupport deviceSupport) {
super(deviceSupport);
}
private boolean saveRawActivityFiles = false;
HashMap<String, Bitmap> appIconCache = new HashMap<>();
String lastPostedApp = null;
@Override
public void initialize() {
try {
getSecretKey();
} catch (IllegalAccessException e) {
GB.toast("erro getting key: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
new TransactionBuilder("init fail")
.add(new SetDeviceStateAction(getDeviceSupport().getDevice(), GBDevice.State.AUTHENTICATION_REQUIRED, getContext()))
.queue(getDeviceSupport().getQueue());
return;
}
saveRawActivityFiles = getDeviceSpecificPreferences().getBoolean("save_raw_activity_files", false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
queueWrite(new RequestMtuRequest(512));
}
getDeviceInfos();
}
@Override
protected void initializeWithSupportedFileVersions() {
queueWrite(new SetDeviceStateRequest(GBDevice.State.AUTHENTICATING));
negotiateSymmetricKey();
queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZING));
loadNotificationConfigurations();
queueWrite(new NotificationFilterPutHRRequest(this.notificationConfigurations, this));
setVibrationStrength();
syncSettings();
setTime();
overwriteButtons(null);
loadBackground();
loadWidgets();
// renderWidgets();
// dunno if there is any point in doing this at start since when no watch is connected the QHybridSupport will not receive any intents anyway
queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED));
}
private void setVibrationStrength() {
Prefs prefs = new Prefs(getDeviceSpecificPreferences());
int vibrationStrengh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE, 2);
if (vibrationStrengh > 0) {
vibrationStrengh = (vibrationStrengh + 1) * 25; // Seems 0,50,75,100 are working...
}
setVibrationStrength((short) (vibrationStrengh));
}
@Override
public void setVibrationStrength(short strength) {
negotiateSymmetricKey();
queueWrite(new ConfigurationPutRequest(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.VibrationStrengthConfigItem((byte) strength), this));
}
private void loadNotificationConfigurations() {
this.notificationConfigurations = new NotificationHRConfiguration[]{
new NotificationHRConfiguration("generic", 0),
new NotificationHRConfiguration("call", new byte[]{(byte) 0x80, (byte) 0x00, (byte) 0x59, (byte) 0xB7}, 0)
};
}
private File getBackgroundFile() {
return new File(getContext().getExternalFilesDir(null), "hr_background.bin");
}
private void loadBackground() {
this.backGroundImage = null;
try {
FileInputStream fis = new FileInputStream(getBackgroundFile());
int count = fis.available();
if (count != 14400) {
throw new RuntimeException("wrong background file length");
}
byte[] file = new byte[14400];
fis.read(file);
fis.close();
this.backGroundImage = AssetImageFactory.createAssetImage(file, 0, 0, 0);
} catch (FileNotFoundException e) {
SharedPreferences preferences = getDeviceSpecificPreferences();
if (preferences.getBoolean("force_white_color_scheme", false)) {
Bitmap whiteBitmap = Bitmap.createBitmap(239, 239, Bitmap.Config.ARGB_8888);
new Canvas(whiteBitmap).drawColor(Color.WHITE);
try {
this.backGroundImage = AssetImageFactory.createAssetImage(whiteBitmap, true, 0, 1, 0);
} catch (IOException e2) {
logger.error("Backgroundimage error", e2);
}
}
} catch (IOException | RuntimeException e) {
GB.log("error opening background file", GB.ERROR, e);
GB.toast("error opening background file", Toast.LENGTH_LONG, GB.ERROR);
}
}
@Override
public void setBackgroundImage(byte[] pixels) {
if (pixels == null) {
getBackgroundFile().delete();
loadBackground(); // recreates the white background in force-white mode, else backgroundImage=null
} else {
this.backGroundImage = AssetImageFactory.createAssetImage(pixels, 0, 0, 0);
try {
FileOutputStream fos = new FileOutputStream(getBackgroundFile(), false);
fos.write(pixels);
} catch (IOException e) {
GB.log("error saving background", GB.ERROR, e);
GB.toast("error persistent saving background", Toast.LENGTH_LONG, GB.ERROR);
}
}
renderWidgets();
}
private void loadWidgets() {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDeviceSupport().getDevice().getAddress()));
boolean forceWhiteBackground = prefs.getBoolean("force_white_color_scheme", false);
String fontColor = forceWhiteBackground ? "black" : "default";
this.widgets.clear();
String widgetJson = GBApplication.getPrefs().getPreferences().getString("FOSSIL_HR_WIDGETS", "{}");
String customWidgetJson = GBApplication.getPrefs().getString("QHYBRID_CUSTOM_WIDGETS", "[]");
try {
JSONObject widgetConfig = new JSONObject(widgetJson);
JSONArray customWidgets = new JSONArray(customWidgetJson);
Iterator<String> keyIterator = widgetConfig.keys();
HashMap<String, Integer> positionMap = new HashMap<>(4);
positionMap.put("top", 0);
positionMap.put("right", 90);
positionMap.put("bottom", 180);
positionMap.put("left", 270);
while (keyIterator.hasNext()) {
String position = keyIterator.next();
String identifier = widgetConfig.getString(position);
Widget.WidgetType type = Widget.WidgetType.fromJsonIdentifier(identifier);
Widget widget = null;
if (type != null) {
widget = new Widget(type, positionMap.get(position), 63, fontColor);
} else {
identifier = identifier.substring(7);
for (int i = 0; i < customWidgets.length(); i++) {
JSONObject customWidget = customWidgets.getJSONObject(i);
if (customWidget.getString("name").equals(identifier)) {
boolean drawCircle = false;
if (customWidget.has("drawCircle"))
drawCircle = customWidget.getBoolean("drawCircle");
CustomWidget newWidget = new CustomWidget(
customWidget.getString("name"),
positionMap.get(position),
63,
fontColor
);
JSONArray elements = customWidget.getJSONArray("elements");
for (int i2 = 0; i2 < elements.length(); i2++) {
JSONObject element = elements.getJSONObject(i2);
if (element.getString("type").equals("text")) {
newWidget.addElement(new CustomTextWidgetElement(
element.getString("id"),
element.getString("value"),
element.getInt("x"),
element.getInt("y")
));
} else if (element.getString("type").equals("background")) {
newWidget.addElement(new CustomBackgroundWidgetElement(
element.getString("id"),
element.getString("value")
));
}
}
widget = newWidget;
}
}
}
if (widget == null) continue;
this.widgets.add(widget);
}
} catch (JSONException e) {
e.printStackTrace();
}
uploadWidgets();
}
private void uploadWidgets() {
negotiateSymmetricKey();
ArrayList<Widget> systemWidgets = new ArrayList<>(widgets.size());
for (Widget widget : this.widgets) {
if (!(widget instanceof CustomWidget) && !widget.getWidgetType().isCustom())
systemWidgets.add(widget);
}
queueWrite(new WidgetsPutRequest(systemWidgets.toArray(new Widget[0]), this));
}
private void renderWidgets() {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDeviceSupport().getDevice().getAddress()));
boolean forceWhiteBackground = prefs.getBoolean("force_white_color_scheme", false);
boolean drawCircles = prefs.getBoolean("widget_draw_circles", false);
Bitmap circleBitmap = null;
if (drawCircles) {
circleBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888);
Canvas circleCanvas = new Canvas(circleBitmap);
Paint circlePaint = new Paint();
circlePaint.setAntiAlias(true);
circlePaint.setColor(forceWhiteBackground ? Color.WHITE : Color.BLACK);
circlePaint.setStyle(Paint.Style.FILL);
circlePaint.setStrokeWidth(3);
circleCanvas.drawCircle(38, 38, 35, circlePaint);
circlePaint.setColor(forceWhiteBackground ? Color.BLACK : Color.WHITE);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeWidth(3);
circleCanvas.drawCircle(38, 38, 35, circlePaint);
}
try {
ArrayList<AssetImage> widgetImages = new ArrayList<>();
if (this.backGroundImage != null) {
widgetImages.add(this.backGroundImage);
}
for (int i = 0; i < this.widgets.size(); i++) {
Widget w = widgets.get(i);
if (!(w instanceof CustomWidget)) {
if (w.getWidgetType() == Widget.WidgetType.LAST_NOTIFICATION) {
Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888);
Canvas widgetCanvas = new Canvas(widgetBitmap);
if (drawCircles) {
widgetCanvas.drawBitmap(circleBitmap, 0, 0, null);
}
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
p.setTextSize(10);
p.setColor(Color.WHITE);
if (this.lastPostedApp != null) {
Bitmap icon = appIconCache.get(this.lastPostedApp);
if (icon != null) {
widgetCanvas.drawBitmap(
icon,
(float) (38 - (icon.getWidth() / 2.0)),
(float) (38 - (icon.getHeight() / 2.0)),
null
);
}
}
widgetImages.add(AssetImageFactory.createAssetImage(
widgetBitmap,
true,
w.getAngle(),
w.getDistance(),
1
));
} else if (drawCircles) {
widgetImages.add(AssetImageFactory.createAssetImage(
circleBitmap,
true,
w.getAngle(),
w.getDistance(),
1
));
}
continue;
}
CustomWidget widget = (CustomWidget) w;
Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888);
Canvas widgetCanvas = new Canvas(widgetBitmap);
if (drawCircles) {
widgetCanvas.drawBitmap(circleBitmap, 0, 0, null);
}
for (CustomWidgetElement element : widget.getElements()) {
if (element.getWidgetElementType() == CustomWidgetElement.WidgetElementType.TYPE_BACKGROUND) {
File imageFile = new File(element.getValue());
if (!imageFile.exists() || !imageFile.isFile()) {
logger.debug("Image file " + element.getValue() + " not found");
continue;
}
Bitmap imageBitmap = BitmapFactory.decodeFile(element.getValue());
if (imageBitmap == null) {
logger.debug("image file " + element.getValue() + " could not be decoded");
continue;
}
Bitmap scaledBitmap = Bitmap.createScaledBitmap(imageBitmap, 76, 76, false);
widgetCanvas.drawBitmap(
scaledBitmap,
0,
0,
null);
break;
}
}
for (CustomWidgetElement element : widget.getElements()) {
if (element.getWidgetElementType() == CustomWidgetElement.WidgetElementType.TYPE_TEXT) {
Paint textPaint = new Paint();
textPaint.setStrokeWidth(4);
textPaint.setTextSize(17f);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(forceWhiteBackground ? Color.BLACK : Color.WHITE);
textPaint.setTextAlign(Paint.Align.CENTER);
widgetCanvas.drawText(element.getValue(), element.getX(), element.getY() - (textPaint.descent() + textPaint.ascent()) / 2f, textPaint);
} else if (element.getWidgetElementType() == CustomWidgetElement.WidgetElementType.TYPE_IMAGE) {
Bitmap imageBitmap = BitmapFactory.decodeFile(element.getValue());
widgetCanvas.drawBitmap(imageBitmap, element.getX() - imageBitmap.getWidth() / 2f, element.getY() - imageBitmap.getHeight() / 2f, null);
}
}
widgetImages.add(AssetImageFactory.createAssetImage(
widgetBitmap,
true,
widget.getAngle(),
widget.getDistance(),
1
));
}
AssetImage[] images = widgetImages.toArray(new AssetImage[0]);
ArrayList<AssetImage> pushFiles = new ArrayList<>(4);
imgloop:
for (AssetImage image : images) {
for (AssetImage pushedImage : pushFiles) {
// no need to send same file multiple times, filtering by name since name is hash
if (image.getFileName().equals(pushedImage.getFileName())) continue imgloop;
}
pushFiles.add(image);
}
if (pushFiles.size() > 0) {
queueWrite(new AssetFilePutRequest(
pushFiles.toArray(new AssetImage[0]),
FileHandle.ASSET_BACKGROUND_IMAGES,
this
));
}
// queueWrite(new FileDeleteRequest((short) 0x0503));
queueWrite(new ImagesSetRequest(
images,
this
));
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleFileDownload(FileHandle handle, byte[] file){
Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE);
File outputFile = new File(getContext().getExternalFilesDir("download"), handle.name() + "_" + System.currentTimeMillis() + ".bin");
try {
FileOutputStream fos = new FileOutputStream(outputFile);
fos.write(file);
fos.close();
resultIntent.putExtra("EXTRA_SUCCESS", true);
resultIntent.putExtra("EXTRA_PATH", outputFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
resultIntent.putExtra("EXTRA_SUCCESS", false);
}
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
}
@Override
public void uploadFile(FileHandle handle, String filePath, boolean fileIsEncrypted) {
final Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
byte[] fileData;
try {
FileInputStream fis = new FileInputStream(filePath);
fileData = new byte[fis.available()];
fis.read(fileData);
fis.close();
} catch (IOException e) {
e.printStackTrace();
resultIntent.putExtra("EXTRA_SUCCESS", false);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
return;
}
queueWrite(new FilePutRawRequest(handle, fileData, this){
@Override
public void onFilePut(boolean success) {
resultIntent.putExtra("EXTRA_SUCCESS", success);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
}
});
}
@Override
public void downloadFile(final FileHandle handle, boolean fileIsEncrypted) {
if(fileIsEncrypted){
negotiateSymmetricKey();
queueWrite(new FileEncryptedGetRequest(handle, this) {
@Override
public void handleFileData(byte[] fileData) {
logger.debug("downloaded encrypted file");
handleFileDownload(handle, fileData);
}
});
}else{
queueWrite(new FileGetRawRequest(handle, this) {
@Override
public void handleFileRawData(byte[] fileData) {
logger.debug("downloaded regular file");
handleFileDownload(handle, fileData);
}
});
}
}
@Override
public void setWidgetContent(String widgetID, String content, boolean renderOnWatch) {
boolean update = false;
for (Widget widget : this.widgets) {
if (!(widget instanceof CustomWidget)) continue;
if (((CustomWidget) widget).updateElementValue(widgetID, content)) update = true;
}
if (renderOnWatch && update) renderWidgets();
}
private void negotiateSymmetricKey() {
try {
queueWrite(new VerifyPrivateKeyRequest(
this.getSecretKey(),
this
));
} catch (IllegalAccessException e) {
GB.toast("error getting key: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
getDeviceSupport().getDevice().setState(GBDevice.State.AUTHENTICATION_REQUIRED);
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
getDeviceSupport().getQueue().clear();
}
}
private void toast(final String data){
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(getContext(), data, Toast.LENGTH_LONG).show();
}
});
}
@Override
public void setTime() {
negotiateSymmetricKey();
queueWrite(
new ConfigurationPutRequest(this.generateTimeConfigItemNow() ,this), false
);
}
@Override
public void setMusicInfo(MusicSpec musicSpec) {
if (
currentSpec != null
&& currentSpec.album.equals(musicSpec.album)
&& currentSpec.artist.equals(musicSpec.artist)
&& currentSpec.track.equals(musicSpec.track)
) return;
currentSpec = musicSpec;
try {
queueWrite(new MusicInfoSetRequest(
musicSpec.artist,
musicSpec.album,
musicSpec.track,
this
));
} catch (BufferOverflowException e) {
GB.log("musicInfo: " + musicSpec, GB.ERROR, e);
}
}
@Override
public void setMusicState(MusicStateSpec stateSpec) {
super.setMusicState(stateSpec);
queueWrite(new MusicControlRequest(
stateSpec.state == MusicStateSpec.STATE_PLAYING ? MUSIC_PHONE_REQUEST.MUSIC_REQUEST_SET_PLAYING : MUSIC_PHONE_REQUEST.MUSIC_REQUEST_SET_PAUSED
));
}
@Override
public void updateWidgets() {
loadWidgets();
renderWidgets();
}
@Override
public void onFetchActivityData() {
syncSettings();
negotiateSymmetricKey();
queueWrite(new FileLookupRequest(FileHandle.ACTIVITY_FILE, this) {
@Override
public void handleFileLookup(final short fileHandle) {
queueWrite(new FileEncryptedGetRequest(fileHandle, FossilHRWatchAdapter.this) {
@Override
public void handleFileData(byte[] fileData) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
ActivityFileParser parser = new ActivityFileParser();
ArrayList<ActivityEntry> entries = parser.parseFile(fileData);
HybridHRActivitySampleProvider provider = new HybridHRActivitySampleProvider(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
HybridHRActivitySample[] samples = new HybridHRActivitySample[entries.size()];
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDeviceSupport().getDevice(), dbHandler.getDaoSession()).getId();
for (int i = 0; i < entries.size(); i++) {
samples[i] = entries.get(i).toDAOActivitySample(userId, deviceId);
}
provider.addGBActivitySamples(samples);
if (saveRawActivityFiles) {
writeFile(String.valueOf(System.currentTimeMillis()), fileData);
}
// queueWrite(new FileDeleteRequest(fileHandle));
GB.toast("synced activity data", Toast.LENGTH_SHORT, GB.INFO);
} catch (Exception ex) {
GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
}
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
}
});
}
@Override
public void handleFileLookupError(FILE_LOOKUP_ERROR error) {
if (error == FILE_LOOKUP_ERROR.FILE_EMPTY) {
GB.toast("activity file empty yet", Toast.LENGTH_LONG, GB.ERROR);
} else {
throw new RuntimeException("strange lookup stuff");
}
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
}
});
}
private void writeFile(String fileName, byte[] value) {
File activityDir = new File(getContext().getExternalFilesDir(null), "activity_hr");
activityDir.mkdir();
File f = new File(activityDir, fileName);
try {
f.createNewFile();
FileOutputStream fos = new FileOutputStream(f);
fos.write(value);
fos.close();
GB.toast("saved file data", Toast.LENGTH_SHORT, GB.INFO);
} catch (IOException e) {
GB.log("file error", GB.ERROR, e);
}
}
private void syncSettings() {
negotiateSymmetricKey();
queueWrite(new ConfigurationGetRequest(this));
}
@Override
public void setActivityHand(double progress) {
// super.setActivityHand(progress);
}
public boolean playRawNotification(NotificationSpec notificationSpec) {
String sourceAppId = notificationSpec.sourceAppId;
String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
try {
for (NotificationHRConfiguration configuration : this.notificationConfigurations) {
if (configuration.getPackageName().equals(sourceAppId)) {
queueWrite(new PlayTextNotificationRequest(sourceAppId, senderOrTitle, notificationSpec.body, this));
return true;
}
}
queueWrite(new PlayTextNotificationRequest("generic", senderOrTitle, notificationSpec.body, this));
} catch (Exception e) {
e.printStackTrace();
}
if (sourceAppId != null) {
if (!sourceAppId.equals(this.lastPostedApp)) {
if (appIconCache.get(sourceAppId) == null) {
try {
PackageManager pm = getContext().getPackageManager();
Drawable icon = pm.getApplicationIcon(sourceAppId);
Bitmap iconBitmap = Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888);
icon.setBounds(0, 0, 40, 40);
icon.draw(new Canvas(iconBitmap));
appIconCache.put(sourceAppId, iconBitmap);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
this.lastPostedApp = sourceAppId;
renderWidgets();
}
}
return true;
}
@Override
public void onDeleteNotification(int id) {
super.onDeleteNotification(id);
// only delete app icon when no notification of said app is present
for (String app : NotificationListener.notificationStack) {
if (app.equals(this.lastPostedApp)) return;
}
this.lastPostedApp = null;
renderWidgets();
}
@Override
public void onFindDevice(boolean start) {
if (start) {
new TransactionBuilder("vibrate find")
.write(
getDeviceSupport().getCharacteristic(UUID.fromString("3dda0005-957f-7d4a-34a6-74696673696d")),
new byte[]{(byte) 0x01, (byte) 0x04, (byte) 0x30, (byte) 0x75, (byte) 0x00, (byte) 0x00}
)
.queue(getDeviceSupport().getQueue());
} else {
new TransactionBuilder("vibrate find")
.write(
getDeviceSupport().getCharacteristic(UUID.fromString("3dda0005-957f-7d4a-34a6-74696673696d")),
new byte[]{(byte) 0x02, (byte) 0x05, (byte) 0x04}
)
.queue(getDeviceSupport().getQueue());
}
}
@Override
public void onSetCallState(CallSpec callSpec) {
super.onSetCallState(callSpec);
queueWrite(new PlayCallNotificationRequest(StringUtils.getFirstOf(callSpec.name, callSpec.number), callSpec.command == CallSpec.CALL_INCOMING, this));
}
// this method is based on the one from AppMessageHandlerYWeather.java
private int getIconForConditionCode(int conditionCode, boolean isNight) {
final int CLEAR_DAY = 0;
final int CLEAR_NIGHT = 1;
final int CLOUDY = 2;
final int PARTLY_CLOUDY_DAY = 3;
final int PARTLY_CLOUDY_NIGHT = 4;
final int RAIN = 5;
final int SNOW = 6;
final int SNOW_2 = 7; // same as 6?
final int THUNDERSTORM = 8;
final int CLOUDY_2 = 9; // same as 2?
final int WINDY = 10;
if (conditionCode == 800 || conditionCode == 951) {
return isNight ? CLEAR_NIGHT : CLEAR_DAY;
} else if (conditionCode > 800 && conditionCode < 900) {
return isNight ? PARTLY_CLOUDY_NIGHT : PARTLY_CLOUDY_DAY;
} else if (conditionCode >= 300 && conditionCode < 400) {
return RAIN; // drizzle mapped to rain
} else if (conditionCode >= 500 && conditionCode < 600) {
return RAIN;
} else if (conditionCode >= 700 && conditionCode < 732) {
return CLOUDY;
} else if (conditionCode == 741 || conditionCode == 751 || conditionCode == 761 || conditionCode == 762) {
return CLOUDY; // fog mapped to cloudy
} else if (conditionCode == 771) {
return CLOUDY; // squalls mapped to cloudy
} else if (conditionCode == 781) {
return WINDY; // tornato mapped to windy
} else if (conditionCode >= 200 && conditionCode < 300) {
return THUNDERSTORM;
} else if (conditionCode >= 600 && conditionCode <= 602) {
return SNOW;
} else if (conditionCode >= 611 && conditionCode <= 622) {
return RAIN;
} else if (conditionCode == 906) {
return RAIN; // hail mapped to rain
} else if (conditionCode >= 907 && conditionCode < 957) {
return WINDY;
} else if (conditionCode == 905) {
return WINDY;
} else if (conditionCode == 900) {
return WINDY;
} else if (conditionCode == 901 || conditionCode == 902 || conditionCode == 962) {
return WINDY;
}
return isNight ? CLEAR_NIGHT : CLEAR_DAY;
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
long ts = System.currentTimeMillis();
ts /= 1000;
try {
JSONObject responseObject = new JSONObject()
.put("res", new JSONObject()
.put("id", 0) // seems the id does not matter?
.put("set", new JSONObject()
.put("weatherInfo", new JSONObject()
.put("alive", ts + 60 * 60)
.put("unit", "c") // FIXME: do not hardcode
.put("temp", weatherSpec.currentTemp - 273)
.put("cond_id", getIconForConditionCode(weatherSpec.currentConditionCode, false)) // FIXME do not assume daylight
)
)
);
queueWrite(new JsonPutRequest(responseObject, this));
JSONArray forecastWeekArray = new JSONArray();
final String[] weekdays = {"", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(weatherSpec.timestamp * 1000L);
int i = 0;
for (WeatherSpec.Forecast forecast : weatherSpec.forecasts) {
cal.add(Calendar.DATE, 1);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
forecastWeekArray.put(new JSONObject()
.put("day", weekdays[dayOfWeek])
.put("cond_id", getIconForConditionCode(forecast.conditionCode, false)) // FIXME do not assume daylight
.put("high", forecast.maxTemp - 273)
.put("low", forecast.minTemp - 273)
);
if (++i == 3) break; // max 3
}
JSONArray forecastDayArray = new JSONArray();
final int[] hours = {0, 0, 0};
for (int hour : hours) {
forecastDayArray.put(new JSONObject()
.put("hour", hour)
.put("cond_id", 0)
.put("temp", 0)
);
}
JSONObject forecastResponseObject = new JSONObject()
.put("res", new JSONObject()
.put("id", 0)
.put("set", new JSONObject()
.put("weatherApp._.config.locations", new JSONArray()
.put(new JSONObject()
.put("alive", ts + 60 * 60)
.put("city", weatherSpec.location)
.put("unit", "c") // FIXME: do not hardcode
.put("temp", weatherSpec.currentTemp - 273)
.put("high", weatherSpec.todayMaxTemp - 273)
.put("low", weatherSpec.todayMinTemp - 273)
.put("rain", 0)
.put("cond_id", getIconForConditionCode(weatherSpec.currentConditionCode, false)) // FIXME do not assume daylight
.put("forecast_day", forecastDayArray)
.put("forecast_week", forecastWeekArray)
)
)
)
);
queueWrite(new JsonPutRequest(forecastResponseObject, this));
} catch (JSONException e) {
logger.error("JSON exception: ", e);
}
}
@Override
public void factoryReset() {
queueWrite(new FactoryResetRequest());
}
@Override
public void onTestNewFunction() {
queueWrite(new ButtonConfigurationPutRequest(
new String[]{"test"},
new ButtonConfiguration[]{
new ButtonConfiguration(
"short_press_release", "stopwatchApp"
)
},
this
));
/*queueWrite(new FileLookupAndGetRequest(FileHandle.APP_CODE, this) {
@Override
public void handleFileData(byte[] fileData) {
log("test");
}
@Override
public void handleFileLookupError(FILE_LOOKUP_ERROR error) {
}
});*/
/*queueWrite(new TranslationsGetRequest(this){
@Override
public void handleTranslations(TranslationData translationData) {
translationData.replaceByOriginal("ON", "oi m8");
translationData.replaceByOriginal("OFF", "nah go away");
translationData.replaceByOriginal("Device is about to reset.", "oh frick no no no");
translationData.replaceByOriginal("Release button to stop reset.", "please don't let me die like that");
translationData.replaceByOriginal("Serial number", "Gadgetbridge :)");
translationData.replaceByOriginal("Dial Info", "Widgets");
TranslationsPutRequest request = new TranslationsPutRequest(translationData, FossilHRWatchAdapter.this);
queueWrite(request);
}
});*/
}
@Override
public void onInstallApp(Uri uri) {
UriHelper uriHelper = null;
try {
uriHelper = UriHelper.get(uri, getContext());
} catch (IOException e) {
GB.toast(getContext(), "Could not open firmare: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
}
if (uriHelper != null) {
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
byte[] firmwareBytes = FileUtils.readAll(in, 1024 * 2024); // 2MB
queueWrite(new FirmwareFilePutRequest(firmwareBytes, this));
} catch (Exception e) {
GB.toast(getContext(), "Firmware cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
}
}
}
public byte[] getSecretKey() throws IllegalAccessException {
byte[] authKeyBytes = new byte[16];
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDeviceSupport().getDevice().getAddress());
String authKey = sharedPrefs.getString("authkey", null);
if (authKey != null && !authKey.isEmpty()) {
authKey = authKey.replace(" ", "");
authKey = authKey.replace("0x", "");
if(authKey.length() != 32){
throw new IllegalAccessException("Key should be 16 bytes long as hex string");
}
byte[] srcBytes = GB.hexStringToByteArray(authKey);
System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
}
return authKeyBytes;
}
public void setPhoneRandomNumber(byte[] phoneRandomNumber) {
this.phoneRandomNumber = phoneRandomNumber;
}
public byte[] getPhoneRandomNumber() {
return phoneRandomNumber;
}
public void setWatchRandomNumber(byte[] watchRandomNumber) {
this.watchRandomNumber = watchRandomNumber;
}
public byte[] getWatchRandomNumber() {
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);
SharedPreferences prefs = getDeviceSpecificPreferences();
String singlePressEvent = "short_press_release";
String firmware = getDeviceSupport().getDevice().getFirmwareVersion();
Matcher matcher = Pattern.compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+").matcher(firmware); // DN1.0.2.19r.v5
if(matcher.find()){
firmware = matcher.group(0);
Version version = new Version(firmware);
if(version.compareTo(new Version("1.0.2.19")) == -1) singlePressEvent = "single_click";
}
ButtonConfiguration[] buttonConfigurations = new ButtonConfiguration[]{
new ButtonConfiguration("top_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT, "weatherApp")),
new ButtonConfiguration("top_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG, "weatherApp")),
new ButtonConfiguration("top_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE, "weatherApp")),
new ButtonConfiguration("middle_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_SHORT, "commuteApp")),
// new ButtonConfiguration("middle_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_LONG, "commuteApp")),
new ButtonConfiguration("middle_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_DOUBLE, "commuteApp")),
new ButtonConfiguration("bottom_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_SHORT, "musicApp")),
new ButtonConfiguration("bottom_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_LONG, "musicApp")),
new ButtonConfiguration("bottom_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_DOUBLE, "musicApp")),
};
queueWrite(new ButtonConfigurationPutRequest(
menuItems,
buttonConfigurations,
this
));
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onSendConfiguration(String config) {
switch (config) {
case DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT:
case DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_SHORT:
case DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_SHORT:
case DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG:
case DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_LONG:
case DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_LONG:
case DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE:
case DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_DOUBLE:
case DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_DOUBLE:
overwriteButtons(null);
break;
case DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE:
setVibrationStrength();
break;
case "force_white_color_scheme":
loadBackground();
// not break here
case "widget_draw_circles": {
renderWidgets();
break;
}
case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES: {
saveRawActivityFiles = getDeviceSpecificPreferences().getBoolean("save_raw_activity_files", false);
break;
}
}
}
@Override
public void handleHeartRateCharacteristic(BluetoothGattCharacteristic characteristic) {
super.handleHeartRateCharacteristic(characteristic);
byte[] value = characteristic.getValue();
int heartRate = value[1];
logger.debug("heart rate: " + heartRate);
}
@Override
protected void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic) {
super.handleBackgroundCharacteristic(characteristic);
byte[] value = characteristic.getValue();
byte requestType = value[1];
if (requestType == (byte) 0x04) {
handleCallRequest(value);
} else if (requestType == (byte) 0x05) {
handleMusicRequest(value);
} else if (requestType == (byte) 0x01) {
int eventId = value[2];
logger.info("got event id " + eventId);
try {
String jsonString = new String(value, 3, value.length - 3);
logger.info(jsonString);
JSONObject requestJson = new JSONObject(jsonString);
JSONObject request = requestJson.getJSONObject("req");
int requestId = request.getInt("id");
if (request.has("ringMyPhone")) {
String action = request.getJSONObject("ringMyPhone").getString("action");
logger.info("got ringMyPhone request; " + action);
GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
JSONObject responseObject = new JSONObject()
.put("res", new JSONObject()
.put("id", requestId)
.put("set", new JSONObject()
.put("ringMyPhone", new JSONObject()
)
)
);
if ("on".equals(action)) {
findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
getDeviceSupport().evaluateGBDeviceEvent(findPhoneEvent);
responseObject
.getJSONObject("res")
.getJSONObject("set")
.getJSONObject("ringMyPhone")
.put("result", "on");
queueWrite(new JsonPutRequest(responseObject, this));
} else if ("off".equals(action)) {
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
getDeviceSupport().evaluateGBDeviceEvent(findPhoneEvent);
responseObject
.getJSONObject("res")
.getJSONObject("set")
.getJSONObject("ringMyPhone")
.put("result", "off");
queueWrite(new JsonPutRequest(responseObject, this));
}
} else if (request.has("weatherInfo") || request.has("weatherApp._.config.locations")) {
logger.info("Got weatherInfo request");
WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
if (weatherSpec != null) {
onSendWeather(weatherSpec);
} else {
logger.info("no weather data available - ignoring request");
}
} else if (request.has("commuteApp._.config.commute_info")) {
String action = request.getJSONObject("commuteApp._.config.commute_info")
.getString("dest");
String startStop = request.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);
} else if (request.has("master._.config.app_status")) {
queueWrite(new ConfirmAppStatusRequest(requestId, this));
} else {
logger.warn("Unhandled request from watch: " + requestJson.toString());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
private void handleCallRequest(byte[] value) {
boolean acceptCall = value[7] == (byte) 0x00;
queueWrite(new PlayCallNotificationRequest("", false, this));
GBDeviceEventCallControl callControlEvent = new GBDeviceEventCallControl();
callControlEvent.event = acceptCall ? GBDeviceEventCallControl.Event.START : GBDeviceEventCallControl.Event.REJECT;
getDeviceSupport().evaluateGBDeviceEvent(callControlEvent);
}
private void handleMusicRequest(byte[] value) {
byte command = value[3];
logger.info("got music command: " + command);
MUSIC_WATCH_REQUEST request = MUSIC_WATCH_REQUEST.fromCommandByte(command);
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
// TODO add skipping/seeking
switch (request) {
case MUSIC_REQUEST_PLAY_PAUSE: {
queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PLAY_PAUSE));
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
break;
}
case MUSIC_REQUEST_NEXT: {
queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_NEXT));
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
break;
}
case MUSIC_REQUEST_PREVIOUS: {
queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PREVIOUS));
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
break;
}
case MUSIC_REQUEST_LOUDER: {
queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_LOUDER));
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
break;
}
case MUSIC_REQUEST_QUITER: {
queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_QUITER));
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
break;
}
}
getDeviceSupport().evaluateGBDeviceEvent(deviceEventMusicControl);
}
@Override
public void setCommuteMenuMessage(String message, boolean finished) {
queueWrite(new SetCommuteMenuMessage(message, finished, this));
}
public byte getJsonIndex() {
return jsonIndex++;
}
}