1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-02 11:26:09 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java
José Rebelo 81aef0bf35 Add support for multiple weather locations
Introduce the concept of primary and secondary weathers:

* Primary weather keeps the same behavior as previously across all weather providers, so it's non-breaking. This location is not necessarily the current location, just the primary weather location set by the user.
* The GenericWeatherReceiver now has a new extra WeatherSecondaryJson, that receives a json list with secondary weather locations.

It's guaranteed that the primary weather always exists, so the list of WeatherSpecs provided to devices is never empty. Update all support classes accordingly.
2024-03-29 21:10:40 +00:00

853 lines
40 KiB
Java

/* Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
Pfeiffer, Daniel Dakhno, Dmitriy Bogdanov, Taavi Eomäe
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapterFactory;
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.file.FileHandle;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.DownloadFileRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class QHybridSupport extends QHybridBaseSupport {
public static final String QHYBRID_COMMAND_CONTROL = "qhybrid_command_control";
public static final String QHYBRID_COMMAND_UNCONTROL = "qhybrid_command_uncontrol";
public static final String QHYBRID_COMMAND_SET = "qhybrid_command_set";
public static final String QHYBRID_COMMAND_MOVE = "qhybrid_command_move";
public static final String QHYBRID_COMMAND_SAVE_CALIBRATION = "qhybrid_command_save_calibration";
public static final String QHYBRID_COMMAND_VIBRATE = "qhybrid_command_vibrate";
public static final String QHYBRID_COMMAND_UPDATE = "qhybrid_command_update";
public static final String QHYBRID_COMMAND_UPDATE_TIMEZONE = "qhybrid_command_update_timezone";
public static final String QHYBRID_COMMAND_NOTIFICATION = "qhybrid_command_notification";
public static final String QHYBRID_COMMAND_UPDATE_SETTINGS = "nodomain.freeyourgadget.gadgetbridge.Q_UPDATE_SETTINGS";
public static final String QHYBRID_COMMAND_OVERWRITE_BUTTONS = "nodomain.freeyourgadget.gadgetbridge.Q_OVERWRITE_BUTTONS";
public static final String QHYBRID_COMMAND_UPDATE_WIDGETS = "nodomain.freeyourgadget.gadgetbridge.Q_UPDATE_WIDGETS";
public static final String QHYBRID_COMMAND_SET_MENU_MESSAGE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_MESSAGE";
public static final String QHYBRID_COMMAND_SEND_MENU_ITEMS = "nodomain.freeyourgadget.gadgetbridge.Q_SEND_MENU_ITEMS";
public static final String QHYBRID_COMMAND_SET_WIDGET_CONTENT = "nodomain.freeyourgadget.gadgetbridge.Q_SET_WIDGET_CONTENT";
public static final String QHYBRID_COMMAND_SET_BACKGROUND_IMAGE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_BACKGROUND_IMAGE";
public static final String QHYBRID_COMMAND_UNINSTALL_APP = "nodomain.freeyourgadget.gadgetbridge.Q_UNINSTALL_APP";
public static final String QHYBRID_COMMAND_PUSH_CONFIG = "nodomain.freeyourgadget.gadgetbridge.Q_PUSH_CONFIG";
public static final String QHYBRID_COMMAND_SWITCH_WATCHFACE = "nodomain.freeyourgadget.gadgetbridge.Q_SWITCH_WATCHFACE";
public static final String QHYBRID_COMMAND_DOWNLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOAD_FILE";
public static final String QHYBRID_COMMAND_UPLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOAD_FILE";
public static final String QHYBRID_COMMAND_SET_MENU_STRUCTURE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_STRUCTURE";
public static final String QHYBRID_ACTION_DOWNLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOADED_FILE";
public static final String QHYBRID_ACTION_UPLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOADED_FILE";
private static final String QHYBRID_ACTION_SET_ACTIVITY_HAND = "nodomain.freeyourgadget.gadgetbridge.Q_SET_ACTIVITY_HAND";
public static final String QHYBRID_EVENT_SETTINGS_UPDATED = "nodomain.freeyourgadget.gadgetbridge.Q_SETTINGS_UPDATED";
public static final String QHYBRID_EVENT_FILE_UPLOADED = "nodomain.freeyourgadget.gadgetbridge.Q_FILE_UPLOADED";
public static final String QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED = "nodomain.freeyourgadget.gadgetbridge.Q_NOTIFICATION_CONFIG_CHANGED";
public static final String QHYBRID_EVENT_BUTTON_PRESS = "nodomain.freeyourgadget.gadgetbridge.Q_BUTTON_PRESSED";
public static final String QHYBRID_EVENT_MULTI_BUTTON_PRESS = "nodomain.freeyourgadget.gadgetbridge.Q_MULTI_BUTTON_PRESSED";
public static final String QHYBRID_EVENT_COMMUTE_MENU = "nodomain.freeyourgadget.gadgetbridge.Q_COMMUTE_MENU";
public static final String ITEM_STEP_GOAL = "STEP_GOAL";
public static final String ITEM_STEP_COUNT = "STEP_COUNT";
public static final String ITEM_VIBRATION_STRENGTH = "VIBRATION_STRENGTH";
public static final String ITEM_ACTIVITY_POINT = "ACTIVITY_POINT";
public static final String ITEM_EXTENDED_VIBRATION_SUPPORT = "EXTENDED_VIBRATION";
public static final String ITEM_HAS_ACTIVITY_HAND = "HAS_ACTIVITY_HAND";
public static final String ITEM_USE_ACTIVITY_HAND = "USE_ACTIVITY_HAND";
public static final String ITEM_LAST_HEARTBEAT = "LAST_HEARTBEAT";
public static final String ITEM_TIMEZONE_OFFSET = "TIMEZONE_OFFSET_COUNT";
public static final String ITEM_HEART_RATE_MEASUREMENT_MODE = "HEART_RATE_MEASUREMENT_MODE";
private static final Logger logger = LoggerFactory.getLogger(QHybridSupport.class);
private final BroadcastReceiver commandReceiver;
private final BroadcastReceiver globalCommandReceiver;
private final PackageConfigHelper helper;
public volatile boolean searchDevice = false;
private long timeOffset;
private boolean useActivityHand;
private WatchAdapter watchAdapter;
public QHybridSupport() {
super(logger);
addSupportedService(UUID.fromString("108b5094-4c03-e51c-555e-105d1a1155f0"));
addSupportedService(UUID.fromString("3dda0001-957f-7d4a-34a6-74696673696d"));
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
addSupportedService(GattService.UUID_SERVICE_HEART_RATE);
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
IntentFilter commandFilter = new IntentFilter(QHYBRID_COMMAND_CONTROL);
commandFilter.addAction(QHYBRID_COMMAND_UNCONTROL);
commandFilter.addAction(QHYBRID_COMMAND_SET);
commandFilter.addAction(QHYBRID_COMMAND_MOVE);
commandFilter.addAction(QHYBRID_COMMAND_SAVE_CALIBRATION);
commandFilter.addAction(QHYBRID_COMMAND_VIBRATE);
commandFilter.addAction(QHYBRID_COMMAND_UPDATE);
commandFilter.addAction(QHYBRID_COMMAND_UPDATE_TIMEZONE);
commandFilter.addAction(QHYBRID_COMMAND_NOTIFICATION);
commandFilter.addAction(QHYBRID_COMMAND_UPDATE_SETTINGS);
commandFilter.addAction(QHYBRID_COMMAND_OVERWRITE_BUTTONS);
commandFilter.addAction(QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED);
commandFilter.addAction(QHYBRID_COMMAND_UPDATE_WIDGETS);
commandFilter.addAction(QHYBRID_COMMAND_SEND_MENU_ITEMS);
commandFilter.addAction(QHYBRID_COMMAND_SET_BACKGROUND_IMAGE);
commandFilter.addAction(QHYBRID_COMMAND_UNINSTALL_APP);
commandFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE);
commandFilter.addAction(QHYBRID_COMMAND_DOWNLOAD_FILE);
commandReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
NotificationConfiguration config = extras == null ? null : (NotificationConfiguration) intent.getExtras().get("CONFIG");
if (intent.getAction() == null) {
return;
}
switch (intent.getAction()) {
case QHYBRID_COMMAND_CONTROL: {
log("sending control request");
watchAdapter.requestHandsControl();
MoveHandsRequest.MovementConfiguration movement = new MoveHandsRequest.MovementConfiguration(false);
if (config != null) {
if(config.getHour() != -1) movement.setHourDegrees(config.getHour());
if(config.getMin() != -1) movement.setHourDegrees(config.getMin());
if(config.getSubEye() != -1) movement.setHourDegrees(config.getSubEye());
watchAdapter.setHands(movement);
} else {
movement.setHourDegrees(0);
movement.setMinuteDegrees(0);
movement.setSubDegrees(0);
watchAdapter.setHands(movement);
}
break;
}
case QHYBRID_COMMAND_UNCONTROL: {
watchAdapter.releaseHandsControl();
break;
}
case QHYBRID_COMMAND_SET: {
if(config == null) break;
MoveHandsRequest.MovementConfiguration movement = new MoveHandsRequest.MovementConfiguration(false);
if(config.getHour() != -1) movement.setHourDegrees(config.getHour());
if(config.getMin() != -1) movement.setHourDegrees(config.getMin());
if(config.getSubEye() != -1) movement.setHourDegrees(config.getSubEye());
watchAdapter.setHands(movement);
break;
}
case QHYBRID_COMMAND_MOVE: {
MoveHandsRequest.MovementConfiguration movement = new MoveHandsRequest.MovementConfiguration(true);
if(extras.containsKey("EXTRA_DISTANCE_HOUR")) movement.setHourDegrees(extras.getShort("EXTRA_DISTANCE_HOUR"));
if(extras.containsKey("EXTRA_DISTANCE_MINUTE")) movement.setMinuteDegrees(extras.getShort("EXTRA_DISTANCE_MINUTE"));
if(extras.containsKey("EXTRA_DISTANCE_SUB")) movement.setSubDegrees(extras.getShort("EXTRA_DISTANCE_SUB"));
watchAdapter.setHands(movement);
break;
}
case QHYBRID_COMMAND_SAVE_CALIBRATION: {
watchAdapter.saveCalibration();
break;
}
case QHYBRID_COMMAND_VIBRATE: {
if (config != null) {
watchAdapter.vibrate(config.getVibration());
}
break;
}
case QHYBRID_COMMAND_NOTIFICATION: {
if (config != null) {
watchAdapter.playNotification(config);
}
break;
}
case QHYBRID_COMMAND_UPDATE: {
loadTimeOffset();
onSetTime();
break;
}
case QHYBRID_COMMAND_UPDATE_TIMEZONE:{
loadTimezoneOffset();
break;
}
case QHYBRID_COMMAND_UPDATE_SETTINGS: {
String newSetting = intent.getStringExtra("EXTRA_SETTING");
switch (newSetting) {
case ITEM_VIBRATION_STRENGTH: {
watchAdapter.setVibrationStrength(Short.parseShort(gbDevice.getDeviceInfo(ITEM_VIBRATION_STRENGTH).getDetails()));
break;
}
case ITEM_STEP_GOAL: {
watchAdapter.setStepGoal(Integer.parseInt(gbDevice.getDeviceInfo(ITEM_STEP_GOAL).getDetails()));
break;
}
case ITEM_USE_ACTIVITY_HAND: {
QHybridSupport.this.useActivityHand = gbDevice.getDeviceInfo(ITEM_USE_ACTIVITY_HAND).getDetails().equals("true");
GBApplication.getPrefs().getPreferences().edit().putBoolean("QHYBRID_USE_ACTIVITY_HAND", useActivityHand).apply();
break;
}
}
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(QHYBRID_EVENT_SETTINGS_UPDATED));
break;
}
case QHYBRID_COMMAND_OVERWRITE_BUTTONS: {
String buttonConfig = intent.getStringExtra(FossilWatchAdapter.ITEM_BUTTONS);
watchAdapter.overwriteButtons(buttonConfig);
break;
}
case QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED: {
watchAdapter.syncNotificationSettings();
break;
}
case QHYBRID_COMMAND_UPDATE_WIDGETS: {
watchAdapter.updateWidgets();
break;
}
case QHYBRID_COMMAND_SET_BACKGROUND_IMAGE:{
byte[] pixels = intent.getByteArrayExtra("EXTRA_PIXELS_ENCODED");
watchAdapter.setBackgroundImage(pixels);
break;
}
case QHYBRID_COMMAND_DOWNLOAD_FILE:{
byte majorHandle = intent.getByteExtra("EXTRA_MAJORHANDLE", (byte) 0x00);
byte minorHandle = intent.getByteExtra("EXTRA_MINORHANDLE", (byte) 0x00);
String filename = intent.getStringExtra("EXTRA_NAME");
watchAdapter.downloadFile(majorHandle, minorHandle, filename, intent.getBooleanExtra("EXTRA_ENCRYPTED", false), false);
break;
}
case QHYBRID_COMMAND_UPLOAD_FILE:{
handleFileUploadIntent(intent);
break;
}
case QHYBRID_COMMAND_UNINSTALL_APP:{
watchAdapter.uninstallApp(intent.getStringExtra("EXTRA_APP_NAME"));
break;
}
}
}
};
LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter);
helper = new PackageConfigHelper(GBApplication.getContext());
IntentFilter globalFilter = new IntentFilter();
globalFilter.addAction(QHYBRID_ACTION_SET_ACTIVITY_HAND);
globalFilter.addAction(QHYBRID_COMMAND_SET_MENU_MESSAGE);
globalFilter.addAction(QHYBRID_COMMAND_SET_WIDGET_CONTENT);
globalFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE);
globalFilter.addAction(QHYBRID_COMMAND_PUSH_CONFIG);
globalFilter.addAction(QHYBRID_COMMAND_SWITCH_WATCHFACE);
globalFilter.addAction(QHYBRID_COMMAND_SET_MENU_STRUCTURE);
globalCommandReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (watchAdapter == null) {
return;
}
switch (intent.getAction()) {
case QHYBRID_ACTION_SET_ACTIVITY_HAND: {
try {
String extra = String.valueOf(intent.getExtras().get("EXTRA_PROGRESS"));
float progress = Float.parseFloat(extra);
watchAdapter.setActivityHand(progress);
watchAdapter.playNotification(new NotificationConfiguration(
(short) -1,
(short) -1,
(short) (progress * 180),
PlayNotificationRequest.VibrationType.NO_VIBE
));
} catch (Exception e) {
GB.log("wrong number format", GB.ERROR, e);
logger.debug("trash extra should be number 0.0-1.0");
}
break;
}
case QHYBRID_COMMAND_SET_MENU_MESSAGE: {
String message = String.valueOf(intent.getExtras().get("EXTRA_MESSAGE"));
boolean finished = Boolean.valueOf(String.valueOf(intent.getExtras().get("EXTRA_FINISHED")));
watchAdapter.setCommuteMenuMessage(message, finished);
break;
}
case QHYBRID_COMMAND_SET_WIDGET_CONTENT: {
HashMap<String, String> widgetValues = new HashMap<>();
for(String key : intent.getExtras().keySet()){
if(key.matches("^EXTRA_WIDGET_ID_.*$")){
widgetValues.put(key.substring(16), String.valueOf(intent.getExtras().get(key)));
}
}
boolean render = intent.getBooleanExtra("EXTRA_RENDER", true);
if(widgetValues.size() > 0){
Iterator<String> valuesIterator = widgetValues.keySet().iterator();
valuesIterator.next();
while(valuesIterator.hasNext()){
String id = valuesIterator.next();
watchAdapter.setWidgetContent(id, widgetValues.get(id), false);
}
valuesIterator = widgetValues.keySet().iterator();
String id = valuesIterator.next();
watchAdapter.setWidgetContent(id, widgetValues.get(id), render);
}else {
String id = String.valueOf(intent.getExtras().get("EXTRA_WIDGET_ID"));
String content = String.valueOf(intent.getExtras().get("EXTRA_CONTENT"));
watchAdapter.setWidgetContent(id, content, render);
}
break;
}
case QHYBRID_COMMAND_UPLOAD_FILE:{
if(!dangerousIntentsAllowed()) break;
handleFileUploadIntent(intent);
break;
}
case QHYBRID_COMMAND_PUSH_CONFIG:{
handleConfigSetIntent(intent);
break;
}
case QHYBRID_COMMAND_SWITCH_WATCHFACE:{
handleSwitchWatchfaceIntent(intent);
break;
}
case QHYBRID_COMMAND_SET_MENU_STRUCTURE:{
handleSetMenuStructure(intent);
break;
}
}
}
};
GBApplication.getContext().registerReceiver(globalCommandReceiver, globalFilter);
}
private void handleConfigSetIntent(Intent intent) {
String configJson = intent.getExtras().getString("EXTRA_CONFIG_JSON", "{}");
watchAdapter.pushConfigJson(configJson);
}
private void handleSwitchWatchfaceIntent(Intent intent) {
String watchfaceName = intent.getExtras().getString("WATCHFACE_NAME", "");
if (watchfaceName != "") {
((FossilHRWatchAdapter) watchAdapter).activateWatchface(watchfaceName);
}
}
private void handleSetMenuStructure(Intent intent){
if(intent == null){
logger.error("intent null");
return;
}
String menuStructureJson = intent.getStringExtra("EXTRA_MENU_STRUCTURE_JSON");
if(menuStructureJson == null){
logger.error("Menu structure json null");
return;
}
if(menuStructureJson.isEmpty()){
logger.error("Menu structure json empty");
return;
}
try {
JSONObject menuStructure = new JSONObject(menuStructureJson);
watchAdapter.handleSetMenuStructure(menuStructure);
GB.toast(getContext().getString(R.string.info_menu_structure_set), Toast.LENGTH_SHORT, GB.INFO);
} catch (JSONException e) {
logger.error("Menu structure json empty");
GB.toast(getContext().getString(R.string.error_invalid_menu_structure), Toast.LENGTH_SHORT, GB.ERROR);
}
}
private boolean dangerousIntentsAllowed(){
return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DANGEROUS_EXTERNAL_INTENTS, true);
}
private void handleFileUploadIntent(Intent intent){
boolean generateHeader = intent.getBooleanExtra("EXTRA_GENERATE_FILE_HEADER", false);
String filePath = intent.getStringExtra("EXTRA_PATH");
if(!generateHeader){
watchAdapter.uploadFileIncludesHeader(filePath);
return;
}
Object handleObject = intent.getSerializableExtra("EXTRA_HANDLE");
if(handleObject == null)return;
if(handleObject instanceof String){
handleObject = FileHandle.fromName((String)handleObject);
}
if(!(handleObject instanceof FileHandle)) return;
FileHandle handle = (FileHandle) handleObject;
watchAdapter.uploadFileGenerateHeader(handle, filePath, intent.getBooleanExtra("EXTRA_ENCRYPTED", false));
}
@Override
public void onSetCallState(CallSpec callSpec) {
super.onSetCallState(callSpec);
watchAdapter.onSetCallState(callSpec);
}
@Override
public void dispose() {
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver);
GBApplication.getContext().unregisterReceiver(globalCommandReceiver);
super.dispose();
}
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
super.onSetAlarms(alarms);
if(this.watchAdapter == null){
GB.toast("watch not connected", Toast.LENGTH_LONG, GB.ERROR);
return;
}
this.watchAdapter.onSetAlarms(alarms);
}
@Override
public void onSendConfiguration(String config) {
if (watchAdapter != null) {
watchAdapter.onSendConfiguration(config);
}
}
private void loadTimeOffset() {
timeOffset = getContext().getSharedPreferences(getContext().getPackageName(), Context.MODE_PRIVATE).getInt("QHYBRID_TIME_OFFSET", 0);
}
private void loadTimezoneOffset(){
short offset = (short) getContext().getSharedPreferences(getContext().getPackageName(), Context.MODE_PRIVATE).getInt("QHYBRID_TIMEZONE_OFFSET", 0);
this.watchAdapter.setTimezoneOffsetMinutes(offset);
}
public long getTimeOffset(){
return this.timeOffset;
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
this.useActivityHand = GBApplication.getPrefs().getBoolean("QHYBRID_USE_ACTIVITY_HAND", false);
getDevice().addDeviceInfo(new GenericItem(ITEM_USE_ACTIVITY_HAND, String.valueOf(this.useActivityHand)));
if (GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_USE_CUSTOM_DEVICEICON, true)) {
getDevice().setNotificationIconConnected(R.drawable.ic_notification_qhybrid);
getDevice().setNotificationIconDisconnected(R.drawable.ic_notification_disconnected_qhybrid);
}
for (int i = 2; i <= 7; i++)
builder.notify(getCharacteristic(UUID.fromString("3dda000" + i + "-957f-7d4a-34a6-74696673696d")), true);
builder.notify(getCharacteristic(UUID.fromString("010541ae-efe8-11c0-91c0-105d1a1155f0")), true);
builder.notify(getCharacteristic(UUID.fromString("fef9589f-9c21-4d19-9fc0-105d1a1155f0")), true);
builder.notify(getCharacteristic(UUID.fromString("842d2791-0d20-4ce4-1ada-105d1a1155f0")), true);
builder
.read(getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")))
.read(getCharacteristic(UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb")))
.read(getCharacteristic(UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb")))
// .notify(getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")), true)
;
loadTimeOffset();
return builder;
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
super.onSetMusicInfo(musicSpec);
try {
watchAdapter.setMusicInfo(musicSpec);
}catch (Exception e){
GB.log("setMusicInfo error", GB.ERROR, e);
}
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
super.onSetMusicState(stateSpec);
watchAdapter.setMusicState(stateSpec);
}
@Override
public void onFetchRecordedData(int dataTypes) {
if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
getDevice().sendDeviceUpdateIntent(getContext());
this.watchAdapter.onFetchActivityData();
}
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
log("notif from " + notificationSpec.sourceAppId + " " + notificationSpec.sender + " " + notificationSpec.phoneNumber);
//new Exception().printStackTrace();
if(this.watchAdapter instanceof FossilHRWatchAdapter){
if(((FossilHRWatchAdapter) watchAdapter).playRawNotification(notificationSpec)) return;
}
String packageName = notificationSpec.sourceName;
NotificationConfiguration config = null;
try {
config = helper.getNotificationConfiguration(packageName);
} catch (Exception e) {
GB.toast("error getting notification configuration", Toast.LENGTH_SHORT, GB.ERROR, e);
}
if (config == null) return;
log("handling notification");
if (config.getRespectSilentMode()) {
int mode = ((AudioManager) getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE)).getRingerMode();
if (mode == AudioManager.RINGER_MODE_SILENT) return;
}
boolean enforceActivityHandNotification = config.getHour() == -1 && config.getMin() == -1;
playNotification(config);
showNotificationsByAllActive(enforceActivityHandNotification);
}
private void log(String message){
logger.debug(message);
}
@Override
public String customStringFilter(String inputString) {
// Remove Unicode code points that can't be displayed by the watch like emoji skin tones and variation selectors
// \x{1f3fb}-\x{1f3ff} = Emoji skin tones: https://en.wikipedia.org/wiki/Emoticons_(Unicode_block)
// \ufe00-\ufe0f = Variation selectors: https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)
return inputString.replaceAll("[\\x{1f3fb}-\\x{1f3ff}|\\ufe00-\\ufe0f]", "");
}
@Override
public void onDeleteNotification(int id) {
super.onDeleteNotification(id);
this.watchAdapter.onDeleteNotification(id);
showNotificationsByAllActive(true);
}
private void showNotificationsByAllActive(boolean enforceByNotification) {
if (!this.useActivityHand) return;
double progress = calculateNotificationProgress();
showNotificationCountOnActivityHand(progress);
if (enforceByNotification) {
watchAdapter.playNotification(new NotificationConfiguration(
(short) -1,
(short) -1,
(short) (progress * 180),
PlayNotificationRequest.VibrationType.NO_VIBE
));
}
}
@Override
public void onReset(int flags) {
super.onReset(flags);
if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
this.watchAdapter.factoryReset();
}
}
public double calculateNotificationProgress() {
HashMap<NotificationConfiguration, Boolean> configs = new HashMap<>(0);
try {
for (NotificationConfiguration config : helper.getNotificationConfigurations()) {
configs.put(config, false);
}
} catch (Exception e) {
GB.toast("error getting notification configs", Toast.LENGTH_SHORT, GB.ERROR, e);
}
double notificationProgress = 0;
for (String notificationPackage : NotificationListener.notificationStack) {
for (NotificationConfiguration notificationConfiguration : configs.keySet()) {
if (configs.get(notificationConfiguration)) continue;
if (notificationConfiguration.getPackageName().equals(notificationPackage)) {
notificationProgress += 0.25;
configs.put(notificationConfiguration, true);
}
}
}
return notificationProgress;
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
watchAdapter.onConnectionStateChange(gatt, status, newState);
}
//TODO toggle "Notifications when screen on" options on this check
private void showNotificationCountOnActivityHand(double progress) {
if (useActivityHand) {
watchAdapter.setActivityHand(progress);
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
if(watchAdapter == null) return;
watchAdapter.onMtuChanged(gatt, mtu, status);
}
private void playNotification(NotificationConfiguration config) {
if (config.getMin() == -1 && config.getHour() == -1 && config.getVibration() == PlayNotificationRequest.VibrationType.NO_VIBE)
return;
watchAdapter.playNotification(config);
}
@Override
public void onSetTime() {
watchAdapter.setTime();
}
@Override
public void onFindDevice(boolean start) {
watchAdapter.onFindDevice(start);
}
@Override
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
watchAdapter.onSendWeather(weatherSpecs.get(0));
}
@Override
public void onTestNewFunction() {
watchAdapter.onTestNewFunction();
}
@Override
public void onInstallApp(Uri uri) {
watchAdapter.onInstallApp(uri);
}
private void backupFile(DownloadFileRequest request) {
try {
File file = FileUtils.getExternalFile("qFiles/" + request.timeStamp);
if (file.exists()) {
throw new Exception("file " + file.getPath() + " exists");
}
logger.debug("Writing file " + file.getPath());
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(request.file);
}
logger.debug("file written.");
file = FileUtils.getExternalFile("qFiles/steps");
try (FileOutputStream fos = new FileOutputStream(file, true)) {
fos.write(("file " + request.timeStamp + " cut\n\n").getBytes());
}
//TODO file stuff
// queueWrite(new EraseFileRequest((short) request.fileHandle));
} catch (Exception e) {
GB.log("error", GB.ERROR, e);
if (request.fileHandle > 257) {
// queueWrite(new DownloadFileRequest((short) (request.fileHandle - 1)));
}
}
}
@Override
public void handleGBDeviceEvent(GBDeviceEventBatteryInfo deviceEvent){
super.handleGBDeviceEvent(deviceEvent);
}
@Override
public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
watchAdapter.onCharacteristicWrite(gatt, characteristic, status);
return super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
switch (characteristic.getUuid().toString()) {
case "00002a26-0000-1000-8000-00805f9b34fb": {
String firmwareVersion = characteristic.getStringValue(0);
gbDevice.setFirmwareVersion(firmwareVersion);
Matcher matcher = Pattern
.compile("(?<=[A-Z]{2}[0-9]\\.[0-9]\\.)[0-9]+\\.[0-9]+")
.matcher(firmwareVersion);
if(matcher.find()){
gbDevice.setFirmwareVersion2(matcher.group());
}
this.watchAdapter = new WatchAdapterFactory().createWatchAdapter(firmwareVersion, this);
this.watchAdapter.initialize();
showNotificationsByAllActive(false);
break;
}
case "00002a24-0000-1000-8000-00805f9b34fb": {
String modelNumber = characteristic.getStringValue(0);
gbDevice.setModel(modelNumber);
gbDevice.setName(watchAdapter.getModelName());
try {
gbDevice.addDeviceInfo(new GenericItem(ITEM_EXTENDED_VIBRATION_SUPPORT, String.valueOf(watchAdapter.supportsExtendedVibration())));
gbDevice.addDeviceInfo(new GenericItem(ITEM_HAS_ACTIVITY_HAND, String.valueOf(watchAdapter.supportsActivityHand())));
} catch (UnsupportedOperationException e) {
GB.log("error", GB.ERROR, e);
gbDevice.addDeviceInfo(new GenericItem(ITEM_EXTENDED_VIBRATION_SUPPORT, "false"));
}
break;
}
case "00002a19-0000-1000-8000-00805f9b34fb": {
short level = characteristic.getValue()[0];
gbDevice.setBatteryLevel(level);
gbDevice.setBatteryThresholdPercent((short) 2);
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
batteryInfo.level = gbDevice.getBatteryLevel();
batteryInfo.state = BatteryState.BATTERY_NORMAL;
handleGBDeviceEvent(batteryInfo);
break;
}
}
return true;
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic
characteristic) {
if(watchAdapter == null) return super.onCharacteristicChanged(gatt, characteristic);
return watchAdapter.onCharacteristicChanged(gatt, characteristic);
}
@Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
if(this.watchAdapter instanceof FossilHRWatchAdapter){
((FossilHRWatchAdapter) watchAdapter).setQuickRepliesConfiguration();
}
}
@Override
public void onAppInfoReq() {
if(this.watchAdapter instanceof FossilHRWatchAdapter){
((FossilHRWatchAdapter) watchAdapter).listApplications();
}
}
@Override
public void onAppStart(UUID uuid, boolean start) {
if(this.watchAdapter instanceof FossilHRWatchAdapter) {
String appName = ((FossilHRWatchAdapter) watchAdapter).getInstalledAppNameFromUUID(uuid);
if ((appName != null) && (appName.endsWith("App"))) {
((FossilHRWatchAdapter) watchAdapter).startAppOnWatch(appName);
} else {
((FossilHRWatchAdapter) watchAdapter).activateWatchface(appName);
}
}
}
@Override
public void onAppDownload(UUID uuid) {
((FossilHRWatchAdapter) watchAdapter).downloadAppToCache(uuid);
}
@Override
public void onAppDelete(UUID uuid) {
if(this.watchAdapter instanceof FossilHRWatchAdapter) {
String appName = ((FossilHRWatchAdapter) watchAdapter).getInstalledAppNameFromUUID(uuid);
if (appName != null) {
watchAdapter.uninstallApp(appName);
}
}
}
@Override
public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
((FossilHRWatchAdapter) watchAdapter).onSetNavigationInfo(navigationInfoSpec);
}
}