Merge branch 'dakhnod-fossil-q-hybrid'
@ -54,6 +54,7 @@ vendor's servers.
|
|||||||
* XWatch (Affordable Chinese Casio-like smartwatches)
|
* XWatch (Affordable Chinese Casio-like smartwatches)
|
||||||
* Vibratissimo (experimental)
|
* Vibratissimo (experimental)
|
||||||
* ZeTime [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime)
|
* ZeTime [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime)
|
||||||
|
* Fossil Q Hybrid (not officially supported by Fossil)
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
@ -85,12 +85,21 @@ dependencies {
|
|||||||
implementation "net.e175.klaus:solarpositioning:0.0.9"
|
implementation "net.e175.klaus:solarpositioning:0.0.9"
|
||||||
// use pristine greendao instead of our custom version, since our custom jitpack-packaged
|
// use pristine greendao instead of our custom version, since our custom jitpack-packaged
|
||||||
// version contains way too much and our custom patches are in the generator only.
|
// version contains way too much and our custom patches are in the generator only.
|
||||||
|
|
||||||
|
implementation 'com.twofortyfouram:android-plugin-client-sdk-for-locale:[4.0.3, 5.0['
|
||||||
|
implementation 'com.twofortyfouram:android-plugin-host-sdk-for-locale:[2.0.3,3.0['
|
||||||
|
implementation 'com.twofortyfouram:android-plugin-api-for-locale:[1.0.2,2.0['
|
||||||
|
|
||||||
implementation "org.greenrobot:greendao:2.2.1"
|
implementation "org.greenrobot:greendao:2.2.1"
|
||||||
implementation "org.apache.commons:commons-lang3:3.7"
|
implementation "org.apache.commons:commons-lang3:3.7"
|
||||||
implementation "org.cyanogenmod:platform.sdk:6.0"
|
implementation "org.cyanogenmod:platform.sdk:6.0"
|
||||||
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
||||||
// implementation project(":DaoCore")
|
// implementation project(":DaoCore")
|
||||||
implementation 'com.github.wax911:android-emojify:0.1.7'
|
implementation 'com.github.wax911:android-emojify:0.1.7'
|
||||||
|
|
||||||
|
implementation 'com.android.support:support-v4:28.1.0'
|
||||||
|
|
||||||
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
||||||
@ -499,7 +499,32 @@
|
|||||||
<data android:scheme="gadgetbridge" />
|
<data android:scheme="gadgetbridge" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".devices.qhybrid.ConfigActivity"
|
||||||
|
android:exported="true" />
|
||||||
|
<activity
|
||||||
|
android:name=".devices.qhybrid.QHybridAppChoserActivity"
|
||||||
|
android:exported="true" />
|
||||||
|
<activity
|
||||||
|
android:name=".devices.qhybrid.TaskerPluginActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:icon="@drawable/ic_device_pebble"
|
||||||
|
android:label="@string/tasker_notification">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".devices.qhybrid.TaskerPluginReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -80,7 +80,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
|||||||
private ScanCallback newLeScanCallback = null;
|
private ScanCallback newLeScanCallback = null;
|
||||||
|
|
||||||
// Disabled for testing, it seems worse for a few people
|
// Disabled for testing, it seems worse for a few people
|
||||||
private final boolean disableNewBLEScanning = true;
|
private final boolean disableNewBLEScanning = false;
|
||||||
|
|
||||||
private final Handler handler = new Handler();
|
private final Handler handler = new Handler();
|
||||||
|
|
||||||
@ -394,7 +394,11 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
|||||||
LOG.warn("Not starting discovery, because already scanning.");
|
LOG.warn("Not starting discovery, because already scanning.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startDiscovery(Scanning.SCANNING_BT);
|
if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) {
|
||||||
|
startDiscovery(Scanning.SCANNING_NEW_BTLE);
|
||||||
|
} else {
|
||||||
|
startDiscovery(Scanning.SCANNING_BTLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startDiscovery(Scanning what) {
|
private void startDiscovery(Scanning what) {
|
||||||
|
@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||||
@ -116,6 +117,15 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pref = findPreference("pref_key_qhybrid");
|
||||||
|
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
startActivity(new Intent(SettingsActivity.this, ConfigActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
pref = findPreference("pref_key_zetime");
|
pref = findPreference("pref_key_zetime");
|
||||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
@ -435,8 +445,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
|||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||||
}
|
}
|
||||||
}
|
} catch (Exception fdfsdfds) {
|
||||||
catch (Exception fdfsdfds) {
|
|
||||||
LOG.warn("fuck");
|
LOG.warn("fuck");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,641 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.NumberPicker;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo;
|
||||||
|
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.requests.fossil.FossilRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class ConfigActivity extends AbstractGBActivity {
|
||||||
|
PackageAdapter adapter;
|
||||||
|
ArrayList<NotificationConfiguration> list;
|
||||||
|
PackageConfigHelper helper;
|
||||||
|
|
||||||
|
final int REQUEST_CODE_ADD_APP = 0;
|
||||||
|
|
||||||
|
private boolean hasControl = false;
|
||||||
|
|
||||||
|
SharedPreferences prefs;
|
||||||
|
|
||||||
|
TextView timeOffsetView, timezoneOffsetView;
|
||||||
|
|
||||||
|
GBDevice device;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_qhybrid_settings);
|
||||||
|
|
||||||
|
findViewById(R.id.buttonOverwriteButtons).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
prefs = getSharedPreferences(getPackageName(), MODE_PRIVATE);
|
||||||
|
timeOffsetView = findViewById(R.id.qhybridTimeOffset);
|
||||||
|
timeOffsetView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
int timeOffset = prefs.getInt("QHYBRID_TIME_OFFSET", 0);
|
||||||
|
LinearLayout layout2 = new LinearLayout(ConfigActivity.this);
|
||||||
|
layout2.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|
||||||
|
final NumberPicker hourPicker = new NumberPicker(ConfigActivity.this);
|
||||||
|
hourPicker.setMinValue(0);
|
||||||
|
hourPicker.setMaxValue(23);
|
||||||
|
hourPicker.setValue(timeOffset / 60);
|
||||||
|
|
||||||
|
final NumberPicker minPicker = new NumberPicker(ConfigActivity.this);
|
||||||
|
minPicker.setMinValue(0);
|
||||||
|
minPicker.setMaxValue(59);
|
||||||
|
minPicker.setValue(timeOffset % 60);
|
||||||
|
|
||||||
|
layout2.addView(hourPicker);
|
||||||
|
TextView tw = new TextView(ConfigActivity.this);
|
||||||
|
tw.setText(":");
|
||||||
|
layout2.addView(tw);
|
||||||
|
layout2.addView(minPicker);
|
||||||
|
|
||||||
|
layout2.setGravity(Gravity.CENTER);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(ConfigActivity.this)
|
||||||
|
.setTitle(getString(R.string.qhybrid_offset_time_by))
|
||||||
|
.setView(layout2)
|
||||||
|
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
prefs.edit().putInt("QHYBRID_TIME_OFFSET", hourPicker.getValue() * 60 + minPicker.getValue()).apply();
|
||||||
|
updateTimeOffset();
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE));
|
||||||
|
GB.toast(getString(R.string.qhybrid_changes_delay_prompt), Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton("cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateTimeOffset();
|
||||||
|
|
||||||
|
|
||||||
|
timezoneOffsetView = findViewById(R.id.timezoneOffset);
|
||||||
|
timezoneOffsetView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
int timeOffset = prefs.getInt("QHYBRID_TIMEZONE_OFFSET", 0);
|
||||||
|
LinearLayout layout2 = new LinearLayout(ConfigActivity.this);
|
||||||
|
layout2.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|
||||||
|
final NumberPicker hourPicker = new NumberPicker(ConfigActivity.this);
|
||||||
|
hourPicker.setMinValue(0);
|
||||||
|
hourPicker.setMaxValue(23);
|
||||||
|
hourPicker.setValue(timeOffset / 60);
|
||||||
|
|
||||||
|
final NumberPicker minPicker = new NumberPicker(ConfigActivity.this);
|
||||||
|
minPicker.setMinValue(0);
|
||||||
|
minPicker.setMaxValue(59);
|
||||||
|
minPicker.setValue(timeOffset % 60);
|
||||||
|
|
||||||
|
layout2.addView(hourPicker);
|
||||||
|
TextView tw = new TextView(ConfigActivity.this);
|
||||||
|
tw.setText(":");
|
||||||
|
layout2.addView(tw);
|
||||||
|
layout2.addView(minPicker);
|
||||||
|
|
||||||
|
layout2.setGravity(Gravity.CENTER);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(ConfigActivity.this)
|
||||||
|
.setTitle(getString(R.string.qhybrid_offset_timezone))
|
||||||
|
.setView(layout2)
|
||||||
|
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
prefs.edit().putInt("QHYBRID_TIMEZONE_OFFSET", hourPicker.getValue() * 60 + minPicker.getValue()).apply();
|
||||||
|
updateTimezoneOffset();
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_TIMEZONE));
|
||||||
|
GB.toast(getString(R.string.qhybrid_changes_delay_prompt), Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton("cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateTimezoneOffset();
|
||||||
|
|
||||||
|
setTitle(R.string.preferences_qhybrid_settings);
|
||||||
|
|
||||||
|
ListView appList = findViewById(R.id.qhybrid_appList);
|
||||||
|
|
||||||
|
try {
|
||||||
|
helper = new PackageConfigHelper(getApplicationContext());
|
||||||
|
list = helper.getNotificationConfigurations();
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error getting configurations", GB.ERROR, e);
|
||||||
|
GB.toast("error getting configurations", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
list = new ArrayList<>();
|
||||||
|
}
|
||||||
|
// null is added to indicate the plus button added handled in PackageAdapter#getView
|
||||||
|
list.add(null);
|
||||||
|
appList.setAdapter(adapter = new PackageAdapter(this, R.layout.qhybrid_package_settings_item, list));
|
||||||
|
appList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onItemLongClick(final AdapterView<?> adapterView, View view, final int i, long l) {
|
||||||
|
PopupMenu menu = new PopupMenu(ConfigActivity.this, view);
|
||||||
|
menu.getMenu().add(0, 0, 0, "edit");
|
||||||
|
menu.getMenu().add(0, 1, 1, "delete");
|
||||||
|
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||||
|
switch (menuItem.getItemId()) {
|
||||||
|
case 0: {
|
||||||
|
TimePicker picker = new TimePicker(ConfigActivity.this, (NotificationConfiguration) adapterView.getItemAtPosition(i));
|
||||||
|
picker.finishListener = new TimePicker.OnFinishListener() {
|
||||||
|
@Override
|
||||||
|
public void onFinish(boolean success, NotificationConfiguration config) {
|
||||||
|
setControl(false, null);
|
||||||
|
if (success) {
|
||||||
|
try {
|
||||||
|
helper.saveNotificationConfiguration(config);
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED));
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error saving configuration", GB.ERROR, e);
|
||||||
|
|
||||||
|
GB.toast("error saving notification", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
}
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
picker.handsListener = new TimePicker.OnHandsSetListener() {
|
||||||
|
@Override
|
||||||
|
public void onHandsSet(NotificationConfiguration config) {
|
||||||
|
setHands(config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
picker.vibrationListener = new TimePicker.OnVibrationSetListener() {
|
||||||
|
@Override
|
||||||
|
public void onVibrationSet(NotificationConfiguration config) {
|
||||||
|
vibrate(config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setControl(true, picker.getSettings());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
try {
|
||||||
|
helper.deleteNotificationConfiguration((NotificationConfiguration) adapterView.getItemAtPosition(i));
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED));
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error deleting configuration", GB.ERROR, e);
|
||||||
|
GB.toast("error deleting setting", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
}
|
||||||
|
refreshList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
menu.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||||
|
Intent notificationIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION);
|
||||||
|
notificationIntent.putExtra("CONFIG", (NotificationConfiguration) adapterView.getItemAtPosition(i));
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(notificationIntent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SeekBar vibeBar = findViewById(R.id.vibrationStrengthBar);
|
||||||
|
vibeBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
int start;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
start = seekBar.getProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
int progress;
|
||||||
|
if ((progress = seekBar.getProgress()) == start) return;
|
||||||
|
String[] values = {"25", "50", "100"};
|
||||||
|
device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_VIBRATION_STRENGTH, values[progress]));
|
||||||
|
Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_SETTINGS);
|
||||||
|
intent.putExtra("EXTRA_SETTING", QHybridSupport.ITEM_VIBRATION_STRENGTH);
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
device = GBApplication.app().getDeviceManager().getSelectedDevice();
|
||||||
|
if (device == null || device.getType() != DeviceType.FOSSILQHYBRID) {
|
||||||
|
setSettingsError(getString(R.string.watch_not_connected));
|
||||||
|
} else {
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTimeOffset() {
|
||||||
|
int timeOffset = prefs.getInt("QHYBRID_TIME_OFFSET", 0);
|
||||||
|
DecimalFormat format = new DecimalFormat("00");
|
||||||
|
timeOffsetView.setText(
|
||||||
|
format.format(timeOffset / 60) + ":" +
|
||||||
|
format.format(timeOffset % 60)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateTimezoneOffset() {
|
||||||
|
int timeOffset = prefs.getInt("QHYBRID_TIMEZONE_OFFSET", 0);
|
||||||
|
DecimalFormat format = new DecimalFormat("00");
|
||||||
|
timezoneOffsetView.setText(
|
||||||
|
format.format(timeOffset / 60) + ":" +
|
||||||
|
format.format(timeOffset % 60)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setSettingsEnabled(boolean enables) {
|
||||||
|
findViewById(R.id.settingsLayout).setAlpha(enables ? 1f : 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSettings() {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
EditText et = findViewById(R.id.stepGoalEt);
|
||||||
|
et.setOnEditorActionListener(null);
|
||||||
|
final String text = device.getDeviceInfo(QHybridSupport.ITEM_STEP_GOAL).getDetails();
|
||||||
|
et.setText(text);
|
||||||
|
et.setSelection(text.length());
|
||||||
|
et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
|
||||||
|
if (i == EditorInfo.IME_ACTION_DONE || i == EditorInfo.IME_ACTION_NEXT) {
|
||||||
|
String t = textView.getText().toString();
|
||||||
|
if (!t.isEmpty()) {
|
||||||
|
device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_GOAL, t));
|
||||||
|
Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_SETTINGS);
|
||||||
|
intent.putExtra("EXTRA_SETTING", QHybridSupport.ITEM_STEP_GOAL);
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(intent);
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
((InputMethodManager) getApplicationContext().getSystemService(Activity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("true".equals(device.getDeviceInfo(QHybridSupport.ITEM_EXTENDED_VIBRATION_SUPPORT).getDetails())) {
|
||||||
|
final int strengthProgress = (int) (Math.log(Double.parseDouble(device.getDeviceInfo(QHybridSupport.ITEM_VIBRATION_STRENGTH).getDetails()) / 25) / Math.log(2));
|
||||||
|
|
||||||
|
setSettingsEnabled(true);
|
||||||
|
SeekBar seekBar = findViewById(R.id.vibrationStrengthBar);
|
||||||
|
seekBar.setProgress(strengthProgress);
|
||||||
|
} else {
|
||||||
|
findViewById(R.id.vibrationStrengthBar).setEnabled(false);
|
||||||
|
findViewById(R.id.vibrationStrengthLayout).setAlpha(0.5f);
|
||||||
|
}
|
||||||
|
CheckBox activityHandCheckbox = findViewById(R.id.checkBoxUserActivityHand);
|
||||||
|
if (device.getDeviceInfo(QHybridSupport.ITEM_HAS_ACTIVITY_HAND).getDetails().equals("true")) {
|
||||||
|
if (device.getDeviceInfo(QHybridSupport.ITEM_USE_ACTIVITY_HAND).getDetails().equals("true")) {
|
||||||
|
activityHandCheckbox.setChecked(true);
|
||||||
|
}
|
||||||
|
activityHandCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean checked) {
|
||||||
|
if (!device.getDeviceInfo(QHybridSupport.ITEM_STEP_GOAL).getDetails().equals("1000000")) {
|
||||||
|
new AlertDialog.Builder(ConfigActivity.this)
|
||||||
|
.setMessage(getString(R.string.qhybrid_prompt_million_steps))
|
||||||
|
.setPositiveButton("ok", null)
|
||||||
|
.show();
|
||||||
|
buttonView.setChecked(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_USE_ACTIVITY_HAND, String.valueOf(checked)));
|
||||||
|
Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_SETTINGS);
|
||||||
|
intent.putExtra("EXTRA_SETTING", QHybridSupport.ITEM_USE_ACTIVITY_HAND);
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// activityHandCheckbox.setEnabled(false);
|
||||||
|
activityHandCheckbox.setAlpha(0.2f);
|
||||||
|
activityHandCheckbox.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
GB.toast("nah.", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
((CheckBox) v).setChecked(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final String buttonJson = device.getDeviceInfo(FossilWatchAdapter.ITEM_BUTTONS).getDetails();
|
||||||
|
try {
|
||||||
|
JSONArray buttonConfig_;
|
||||||
|
if (buttonJson == null || buttonJson.isEmpty()) {
|
||||||
|
buttonConfig_ = new JSONArray(new String[]{"", "", ""});
|
||||||
|
}else{
|
||||||
|
buttonConfig_ = new JSONArray(buttonJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
final JSONArray buttonConfig = buttonConfig_;
|
||||||
|
|
||||||
|
LinearLayout buttonLayout = findViewById(R.id.buttonConfigLayout);
|
||||||
|
buttonLayout.removeAllViews();
|
||||||
|
findViewById(R.id.buttonOverwriteButtons).setVisibility(View.GONE);
|
||||||
|
final ConfigPayload[] payloads = ConfigPayload.values();
|
||||||
|
final String[] names = new String[payloads.length];
|
||||||
|
for (int i = 0; i < payloads.length; i++)
|
||||||
|
names[i] = payloads[i].getDescription();
|
||||||
|
for (int i = 0; i < buttonConfig.length(); i++) {
|
||||||
|
final int currentIndex = i;
|
||||||
|
String configName = buttonConfig.getString(i);
|
||||||
|
TextView buttonTextView = new TextView(ConfigActivity.this);
|
||||||
|
buttonTextView.setTextColor(Color.WHITE);
|
||||||
|
buttonTextView.setTextSize(20);
|
||||||
|
try {
|
||||||
|
ConfigPayload payload = ConfigPayload.valueOf(configName);
|
||||||
|
buttonTextView.setText("Button " + (i + 1) + ": " + payload.getDescription());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
buttonTextView.setText("Button " + (i + 1) + ": Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonTextView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(ConfigActivity.this)
|
||||||
|
.setItems(names, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.cancel();
|
||||||
|
ConfigPayload selected = payloads[which];
|
||||||
|
|
||||||
|
try {
|
||||||
|
buttonConfig.put(currentIndex, selected.toString());
|
||||||
|
device.addDeviceInfo(new GenericItem(FossilWatchAdapter.ITEM_BUTTONS, buttonConfig.toString()));
|
||||||
|
updateSettings();
|
||||||
|
Intent buttonIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS);
|
||||||
|
buttonIntent.putExtra(FossilWatchAdapter.ITEM_BUTTONS, buttonConfig.toString());
|
||||||
|
LocalBroadcastManager.getInstance(ConfigActivity.this).sendBroadcast(buttonIntent);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setTitle("Button " + (currentIndex + 1))
|
||||||
|
.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonLayout.addView(buttonTextView);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
GB.log("error parsing button config", GB.ERROR, e);
|
||||||
|
GB.toast("error parsing button config", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setControl(boolean control, NotificationConfiguration config) {
|
||||||
|
if (hasControl == control) return;
|
||||||
|
Intent intent = new Intent(control ? QHybridSupport.QHYBRID_COMMAND_CONTROL : QHybridSupport.QHYBRID_COMMAND_UNCONTROL);
|
||||||
|
intent.putExtra("CONFIG", config);
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
|
this.hasControl = control;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHands(NotificationConfiguration config) {
|
||||||
|
sendControl(config, QHybridSupport.QHYBRID_COMMAND_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void vibrate(NotificationConfiguration config) {
|
||||||
|
sendControl(config, QHybridSupport.QHYBRID_COMMAND_VIBRATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendControl(NotificationConfiguration config, String request) {
|
||||||
|
Intent intent = new Intent(request);
|
||||||
|
intent.putExtra("CONFIG", config);
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshList() {
|
||||||
|
list.clear();
|
||||||
|
try {
|
||||||
|
list.addAll(helper.getNotificationConfigurations());
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error getting configurations", GB.ERROR, e);
|
||||||
|
GB.toast("error getting configurations", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
}
|
||||||
|
// null is added to indicate the plus button added handled in PackageAdapter#getView
|
||||||
|
list.add(null);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
refreshList();
|
||||||
|
registerReceiver(buttonReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS));
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(settingsReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_SETTINGS_UPDATED));
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(fileReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_FILE_UPLOADED));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
unregisterReceiver(buttonReceiver);
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(settingsReceiver);
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(fileReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setSettingsError(final String error) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setSettingsEnabled(false);
|
||||||
|
((TextView) findViewById(R.id.settingsErrorText)).setVisibility(View.VISIBLE);
|
||||||
|
((TextView) findViewById(R.id.settingsErrorText)).setText(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class PackageAdapter extends ArrayAdapter<NotificationConfiguration> {
|
||||||
|
PackageManager manager;
|
||||||
|
|
||||||
|
PackageAdapter(@NonNull Context context, int resource, @NonNull List<NotificationConfiguration> objects) {
|
||||||
|
super(context, resource, objects);
|
||||||
|
manager = context.getPackageManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
|
||||||
|
if (!(view instanceof RelativeLayout))
|
||||||
|
view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.qhybrid_package_settings_item, null);
|
||||||
|
NotificationConfiguration settings = getItem(position);
|
||||||
|
|
||||||
|
if (settings == null) {
|
||||||
|
Button addButton = new Button(ConfigActivity.this);
|
||||||
|
addButton.setText("+");
|
||||||
|
addButton.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
addButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
startActivityForResult(new Intent(ConfigActivity.this, QHybridAppChoserActivity.class), REQUEST_CODE_ADD_APP);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return addButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
((ImageView) view.findViewById(R.id.packageIcon)).setImageDrawable(manager.getApplicationIcon(settings.getPackageName()));
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
}
|
||||||
|
final int width = 100;
|
||||||
|
((TextView) view.findViewById(R.id.packageName)).setText(settings.getAppName());
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas c = new Canvas(bitmap);
|
||||||
|
|
||||||
|
Paint black = new Paint();
|
||||||
|
black.setColor(Color.BLACK);
|
||||||
|
black.setStyle(Paint.Style.STROKE);
|
||||||
|
black.setStrokeWidth(5);
|
||||||
|
|
||||||
|
c.drawCircle(width / 2, width / 2, width / 2 - 3, black);
|
||||||
|
|
||||||
|
int center = width / 2;
|
||||||
|
if (settings.getHour() != -1) {
|
||||||
|
c.drawLine(
|
||||||
|
center,
|
||||||
|
center,
|
||||||
|
(float) (center + Math.sin(Math.toRadians(settings.getHour())) * (width / 4)),
|
||||||
|
(float) (center - Math.cos(Math.toRadians(settings.getHour())) * (width / 4)),
|
||||||
|
black
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (settings.getMin() != -1) {
|
||||||
|
c.drawLine(
|
||||||
|
center,
|
||||||
|
center,
|
||||||
|
(float) (center + Math.sin(Math.toRadians(settings.getMin())) * (width / 3)),
|
||||||
|
(float) (center - Math.cos(Math.toRadians(settings.getMin())) * (width / 3)),
|
||||||
|
black
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
((ImageView) view.findViewById(R.id.packageClock)).setImageBitmap(bitmap);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BroadcastReceiver fileReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
boolean error = intent.getBooleanExtra("EXTRA_ERROR", false);
|
||||||
|
if (error) {
|
||||||
|
GB.toast(getString(R.string.qhybrid_buttons_overwrite_error), Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GB.toast(getString(R.string.qhybrid_buttons_overwrite_success), Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BroadcastReceiver buttonReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
GB.toast("Button " + intent.getIntExtra("BUTTON", -1) + " pressed", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BroadcastReceiver settingsReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
GB.toast("Setting updated", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
|
||||||
|
public class NotificationConfiguration implements Serializable {
|
||||||
|
private short min, hour, subEye = -1;
|
||||||
|
private String packageName, appName;
|
||||||
|
private PlayNotificationRequest.VibrationType vibration;
|
||||||
|
private boolean respectSilentMode;
|
||||||
|
private long id = -1;
|
||||||
|
|
||||||
|
NotificationConfiguration(short min, short hour, String packageName, String appName, boolean respectSilentMode, PlayNotificationRequest.VibrationType vibration) {
|
||||||
|
this.min = min;
|
||||||
|
this.hour = hour;
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.appName = appName;
|
||||||
|
this.respectSilentMode = respectSilentMode;
|
||||||
|
this.vibration = vibration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationConfiguration(short min, short hour, short subEye, PlayNotificationRequest.VibrationType vibration) {
|
||||||
|
this.min = min;
|
||||||
|
this.hour = hour;
|
||||||
|
this.subEye = subEye;
|
||||||
|
this.vibration = vibration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationConfiguration(short min, short hour, String packageName, String appName, boolean respectSilentMode, PlayNotificationRequest.VibrationType vibration, long id) {
|
||||||
|
this.min = min;
|
||||||
|
this.hour = hour;
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.appName = appName;
|
||||||
|
this.respectSilentMode = respectSilentMode;
|
||||||
|
this.vibration = vibration;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
NotificationConfiguration(String packageName, String appName) {
|
||||||
|
this.min = -1;
|
||||||
|
this.hour = -1;
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.appName = appName;
|
||||||
|
this.respectSilentMode = false;
|
||||||
|
this.vibration = PlayNotificationRequest.VibrationType.SINGLE_NORMAL;
|
||||||
|
this.id = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayNotificationRequest.VibrationType getVibration() {
|
||||||
|
return vibration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVibration(PlayNotificationRequest.VibrationType vibration) {
|
||||||
|
this.vibration = vibration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getRespectSilentMode() {
|
||||||
|
Log.d("Config", "respect: " + respectSilentMode);
|
||||||
|
return respectSilentMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRespectSilentMode(boolean respectSilentMode) {
|
||||||
|
this.respectSilentMode = respectSilentMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMin(short min) {
|
||||||
|
this.min = min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHour(short hour) {
|
||||||
|
this.hour = hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageName(String packageName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppName(String appName) {
|
||||||
|
this.appName = appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getMin() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getHour() {
|
||||||
|
return hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getSubEye() {
|
||||||
|
return subEye;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubEye(short subEye) {
|
||||||
|
this.subEye = subEye;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackageName() {
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppName() {
|
||||||
|
return appName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class PackageConfigHelper {
|
||||||
|
public static final String DB_NAME = "qhybridNotifications.db";
|
||||||
|
public static final String DB_ID = "id";
|
||||||
|
public static final String DB_TABLE = "notifications";
|
||||||
|
public static final String DB_PACKAGE = "package";
|
||||||
|
public static final String DB_APPNAME = "appName";
|
||||||
|
public static final String DB_VIBRATION = "vibrationTtype";
|
||||||
|
public static final String DB_MINUTE = "minDegress";
|
||||||
|
public static final String DB_HOUR = "hourDegrees";
|
||||||
|
public static final String DB_RESPECT_SILENT = "respectSilent";
|
||||||
|
|
||||||
|
|
||||||
|
public PackageConfigHelper(Context context) throws GBException {
|
||||||
|
initDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveNotificationConfiguration(NotificationConfiguration settings) throws GBException {
|
||||||
|
ContentValues values = new ContentValues(6);
|
||||||
|
values.put(DB_PACKAGE, settings.getPackageName());
|
||||||
|
values.put(DB_APPNAME, settings.getAppName());
|
||||||
|
values.put(DB_HOUR, settings.getHour());
|
||||||
|
values.put(DB_MINUTE, settings.getMin());
|
||||||
|
values.put(DB_VIBRATION, settings.getVibration().getValue());
|
||||||
|
values.put(DB_RESPECT_SILENT, settings.getRespectSilentMode());
|
||||||
|
|
||||||
|
SQLiteDatabase database = GBApplication.acquireDB().getDatabase();
|
||||||
|
|
||||||
|
if(settings.getId() == -1) {
|
||||||
|
settings.setId(database.insert(DB_TABLE, null, values));
|
||||||
|
}else{
|
||||||
|
database.update(DB_TABLE, values, DB_ID + "=?", new String[]{String.valueOf(settings.getId())});
|
||||||
|
}
|
||||||
|
|
||||||
|
GBApplication.releaseDB();
|
||||||
|
//LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<NotificationConfiguration> getNotificationConfigurations() throws GBException {
|
||||||
|
SQLiteDatabase database = GBApplication.acquireDB().getDatabase();
|
||||||
|
|
||||||
|
Cursor cursor = database.query(DB_TABLE, new String[]{"*"}, null, null, null, null, null);
|
||||||
|
|
||||||
|
GBApplication.releaseDB();
|
||||||
|
|
||||||
|
int size = cursor.getCount();
|
||||||
|
ArrayList<NotificationConfiguration> list = new ArrayList<>(size);
|
||||||
|
if(size > 0){
|
||||||
|
int appNamePos = cursor.getColumnIndex(DB_APPNAME);
|
||||||
|
int packageNamePos = cursor.getColumnIndex(DB_PACKAGE);
|
||||||
|
int hourPos = cursor.getColumnIndex(DB_HOUR);
|
||||||
|
int minPos = cursor.getColumnIndex(DB_MINUTE);
|
||||||
|
int silentPos = cursor.getColumnIndex(DB_RESPECT_SILENT);
|
||||||
|
int vibrationPos = cursor.getColumnIndex(DB_VIBRATION);
|
||||||
|
int idPos = cursor.getColumnIndex(DB_ID);
|
||||||
|
cursor.moveToFirst();
|
||||||
|
do {
|
||||||
|
list.add(new NotificationConfiguration(
|
||||||
|
(short)cursor.getInt(minPos),
|
||||||
|
(short)cursor.getInt(hourPos),
|
||||||
|
cursor.getString(packageNamePos),
|
||||||
|
cursor.getString(appNamePos),
|
||||||
|
cursor.getInt(silentPos) == 1,
|
||||||
|
PlayNotificationRequest.VibrationType.fromValue((byte)cursor.getInt(vibrationPos)),
|
||||||
|
cursor.getInt(idPos)
|
||||||
|
));
|
||||||
|
Log.d("Settings", "setting #" + cursor.getPosition() + ": " + cursor.getInt(silentPos));
|
||||||
|
}while (cursor.moveToNext());
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationConfiguration getNotificationConfiguration(String appName) throws GBException {
|
||||||
|
if(appName == null) return null;
|
||||||
|
|
||||||
|
SQLiteDatabase database = GBApplication.acquireDB().getDatabase();
|
||||||
|
|
||||||
|
Cursor c = database.query(DB_TABLE, new String[]{"*"}, DB_APPNAME + "=?", new String[]{appName}, null, null, null);
|
||||||
|
|
||||||
|
GBApplication.releaseDB();
|
||||||
|
|
||||||
|
if(c.getCount() == 0){
|
||||||
|
c.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
c.moveToFirst();
|
||||||
|
NotificationConfiguration settings = new NotificationConfiguration(
|
||||||
|
(short)c.getInt(c.getColumnIndex(DB_MINUTE)),
|
||||||
|
(short)c.getInt(c.getColumnIndex(DB_HOUR)),
|
||||||
|
c.getString(c.getColumnIndex(DB_PACKAGE)),
|
||||||
|
c.getString(c.getColumnIndex(DB_APPNAME)),
|
||||||
|
c.getInt(c.getColumnIndex(DB_RESPECT_SILENT)) == 1,
|
||||||
|
PlayNotificationRequest.VibrationType.fromValue((byte)c.getInt(c.getColumnIndex(DB_VIBRATION))),
|
||||||
|
c.getInt(c.getColumnIndex(DB_ID))
|
||||||
|
);
|
||||||
|
c.close();
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initDB() throws GBException {
|
||||||
|
SQLiteDatabase database = GBApplication.acquireDB().getDatabase();
|
||||||
|
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS notifications(" +
|
||||||
|
DB_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
|
||||||
|
DB_PACKAGE + " TEXT, " +
|
||||||
|
DB_VIBRATION + " INTEGER, " +
|
||||||
|
DB_MINUTE + " INTEGER DEFAULT -1, " +
|
||||||
|
DB_APPNAME + " TEXT," +
|
||||||
|
DB_RESPECT_SILENT + " INTEGER," +
|
||||||
|
DB_HOUR + " INTEGER DEFAULT -1);");
|
||||||
|
|
||||||
|
GBApplication.releaseDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteNotificationConfiguration(NotificationConfiguration packageSettings) throws GBException {
|
||||||
|
Log.d("DB", "deleting id " + packageSettings.getId());
|
||||||
|
if(packageSettings.getId() == -1) return;
|
||||||
|
|
||||||
|
SQLiteDatabase database = GBApplication.acquireDB().getDatabase();
|
||||||
|
|
||||||
|
database.delete(DB_TABLE, DB_ID + "=?", new String[]{String.valueOf(packageSettings.getId())});
|
||||||
|
|
||||||
|
GBApplication.releaseDB();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
|
||||||
|
public class QHybridAppChoserActivity extends AbstractGBActivity {
|
||||||
|
boolean hasControl = false;
|
||||||
|
|
||||||
|
PackageConfigHelper helper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_qhybrid_app_choser);
|
||||||
|
|
||||||
|
try {
|
||||||
|
helper = new PackageConfigHelper(getApplicationContext());
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("database error", GB.ERROR, e);
|
||||||
|
GB.toast("error getting database helper", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ListView appList = findViewById(R.id.qhybrid_appChooserList);
|
||||||
|
final PackageManager manager = getPackageManager();
|
||||||
|
final List<PackageInfo> packages = manager.getInstalledPackages(0);
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final IdentityHashMap<PackageInfo, String> nameMap = new IdentityHashMap(packages.size());
|
||||||
|
for(PackageInfo info : packages){
|
||||||
|
CharSequence label = manager.getApplicationLabel(info.applicationInfo);
|
||||||
|
if(label == null) label = info.packageName;
|
||||||
|
nameMap.put(info, label.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(packages, new Comparator<PackageInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(PackageInfo packageInfo, PackageInfo t1) {
|
||||||
|
return nameMap.get(packageInfo)
|
||||||
|
.compareToIgnoreCase(
|
||||||
|
nameMap.get(t1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
appList.setAdapter(new ConfigArrayAdapter(QHybridAppChoserActivity.this, R.layout.qhybrid_app_view, packages, manager));
|
||||||
|
findViewById(R.id.qhybrid_packageChooserLoading).setVisibility(GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
appList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||||
|
showPackageDialog(packages.get(i));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setControl(boolean control) {
|
||||||
|
if (hasControl == control) return;
|
||||||
|
Intent intent = new Intent(control ? QHybridSupport.QHYBRID_COMMAND_CONTROL : QHybridSupport.QHYBRID_COMMAND_UNCONTROL);
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
|
this.hasControl = control;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHands(NotificationConfiguration config){
|
||||||
|
sendControl(config, QHybridSupport.QHYBRID_COMMAND_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void vibrate(NotificationConfiguration config){
|
||||||
|
sendControl(config, QHybridSupport.QHYBRID_COMMAND_VIBRATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendControl(NotificationConfiguration config, String request){
|
||||||
|
Intent intent = new Intent(request);
|
||||||
|
intent.putExtra("CONFIG", config);
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private void showPackageDialog(PackageInfo info) {
|
||||||
|
TimePicker picker = new TimePicker(this, info);
|
||||||
|
|
||||||
|
picker.finishListener = new TimePicker.OnFinishListener() {
|
||||||
|
@Override
|
||||||
|
public void onFinish(boolean success, NotificationConfiguration config) {
|
||||||
|
setControl(false);
|
||||||
|
if(success){
|
||||||
|
try {
|
||||||
|
helper.saveNotificationConfiguration(config);
|
||||||
|
LocalBroadcastManager.getInstance(QHybridAppChoserActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED));
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error saving config", GB.ERROR, e);
|
||||||
|
GB.toast("error saving configuration", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
picker.handsListener = new TimePicker.OnHandsSetListener() {
|
||||||
|
@Override
|
||||||
|
public void onHandsSet(NotificationConfiguration config) {
|
||||||
|
setHands(config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
picker.vibrationListener = new TimePicker.OnVibrationSetListener() {
|
||||||
|
@Override
|
||||||
|
public void onVibrationSet(NotificationConfiguration config) {
|
||||||
|
vibrate(config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setControl(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
setControl(false);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigArrayAdapter extends ArrayAdapter<PackageInfo> {
|
||||||
|
PackageManager manager;
|
||||||
|
|
||||||
|
public ConfigArrayAdapter(@NonNull Context context, int resource, @NonNull List<PackageInfo> objects, PackageManager manager) {
|
||||||
|
super(context, resource, objects);
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
|
||||||
|
if (view == null)
|
||||||
|
view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.qhybrid_app_view, null);
|
||||||
|
|
||||||
|
ApplicationInfo info = getItem(position).applicationInfo;
|
||||||
|
((ImageView) view.findViewById(R.id.qhybrid_appChooserItemIcon)).setImageDrawable(manager.getApplicationIcon(info));
|
||||||
|
((TextView) view.findViewById(R.id.qhybrid_appChooserItemText)).setText(manager.getApplicationLabel(info));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.bluetooth.le.ScanCallback;
|
||||||
|
import android.bluetooth.le.ScanFilter;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.ParcelUuid;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||||
|
|
||||||
|
public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||||
|
for(ParcelUuid uuid : candidate.getServiceUuids()){
|
||||||
|
if(uuid.getUuid().toString().equals("3dda0001-957f-7d4a-34a6-74696673696d")){
|
||||||
|
return DeviceType.FOSSILQHYBRID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DeviceType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<? extends ScanFilter> createBLEScanFilters() {
|
||||||
|
return Collections.singletonList(new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("3dda0001-957f-7d4a-34a6-74696673696d")).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.FOSSILQHYBRID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getPairingActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityDataFetching() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityTracking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsScreenshots() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsAlarmConfiguration() {
|
||||||
|
GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice();
|
||||||
|
if(connectedDevice == null || connectedDevice.getType() != DeviceType.FOSSILQHYBRID || connectedDevice.getState() != GBDevice.State.INITIALIZED){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAlarmSlotCount() {
|
||||||
|
return this.supportsAlarmConfiguration() ? 5 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsSmartWakeup(GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturer() {
|
||||||
|
return "Fossil";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsAppsManagement() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getAppsManagementActivity() {
|
||||||
|
return ConfigActivity.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsCalendarEvents() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRealtimeData() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsWeather() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFindDevice() {
|
||||||
|
GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice();
|
||||||
|
if(connectedDevice == null || connectedDevice.getType() != DeviceType.FOSSILQHYBRID){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ItemWithDetails vibration = connectedDevice.getDeviceInfo(QHybridSupport.ITEM_EXTENDED_VIBRATION_SUPPORT);
|
||||||
|
if(vibration == null){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return vibration.getDetails().equals("true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.RadioGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.twofortyfouram.locale.sdk.client.ui.activity.AbstractPluginActivity;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
|
||||||
|
public class TaskerPluginActivity extends AbstractPluginActivity {
|
||||||
|
public static final String key_hours = "qhybrid_hours";
|
||||||
|
public static final String key_minute = "qhybrid_minutes";
|
||||||
|
public static final String key_vibration = "qhybrid_vibration";
|
||||||
|
|
||||||
|
RadioGroup group;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_tasker_plugin);
|
||||||
|
|
||||||
|
group = findViewById(R.id.qhybrid_tasker_vibration);
|
||||||
|
for(PlayNotificationRequest.VibrationType type : PlayNotificationRequest.VibrationType.values()){
|
||||||
|
RadioButton button = new RadioButton(this);
|
||||||
|
button.setText(type.name() + " (" + type.name() + ")");
|
||||||
|
button.setId(type.getValue());
|
||||||
|
group.addView(button);
|
||||||
|
}
|
||||||
|
group.check(PlayNotificationRequest.VibrationType.NO_VIBE.getValue());
|
||||||
|
RadioButton custom = new RadioButton(this);
|
||||||
|
custom.setText("variable %vibration");
|
||||||
|
custom.setId(10);
|
||||||
|
group.addView(custom);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if(intent.hasExtra(key_hours)){
|
||||||
|
((TextView) findViewById(R.id.qhybrid_hour_degrees)).setText(intent.getStringExtra(key_hours));
|
||||||
|
}
|
||||||
|
if(intent.hasExtra(key_minute)){
|
||||||
|
((TextView) findViewById(R.id.qhybrid_minute_degrees)).setText(intent.getStringExtra(key_minute));
|
||||||
|
}
|
||||||
|
if(intent.hasExtra(key_vibration)){
|
||||||
|
String vibe = intent.getStringExtra(key_vibration);
|
||||||
|
if(vibe.equals("%vibration")){
|
||||||
|
group.check(10);
|
||||||
|
}else {
|
||||||
|
group.check(Integer.parseInt(vibe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBundleValid(@NonNull Bundle bundle) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPostCreateWithPreviousResult(@NonNull Bundle bundle, @NonNull String s) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Bundle getResultBundle() {
|
||||||
|
int vibration = group.getCheckedRadioButtonId();
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(key_hours, ((EditText) findViewById(R.id.qhybrid_hour_degrees)).getText().toString());
|
||||||
|
bundle.putString(key_minute, ((EditText) findViewById(R.id.qhybrid_minute_degrees)).getText().toString());
|
||||||
|
|
||||||
|
if(vibration == 10){
|
||||||
|
bundle.putString(key_vibration, "%vibration");
|
||||||
|
}else{
|
||||||
|
bundle.putString(key_vibration, String.valueOf(vibration));
|
||||||
|
}
|
||||||
|
TaskerPlugin.Setting.setVariableReplaceKeys(bundle, new String[]{key_hours, key_minute, key_vibration});
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String getResultBlurb(@NonNull Bundle bundle) {
|
||||||
|
return "nope";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
|
||||||
|
public class TaskerPluginReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String min = intent.getStringExtra(TaskerPluginActivity.key_minute);
|
||||||
|
String hour = intent.getStringExtra(TaskerPluginActivity.key_hours);
|
||||||
|
String vibration = intent.getStringExtra(TaskerPluginActivity.key_vibration);
|
||||||
|
|
||||||
|
int minDegrees = (int)Float.parseFloat(min);
|
||||||
|
int hourDegrees = (int)Float.parseFloat(hour);
|
||||||
|
|
||||||
|
NotificationConfiguration config = new NotificationConfiguration(
|
||||||
|
(short)minDegrees,
|
||||||
|
(short)hourDegrees,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
PlayNotificationRequest.VibrationType.fromValue(Byte.parseByte(vibration))
|
||||||
|
);
|
||||||
|
|
||||||
|
Intent send = new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION);
|
||||||
|
send.putExtra("CONFIG", config);
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(send);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,278 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.RadioGroup;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
|
||||||
|
public class TimePicker extends AlertDialog.Builder {
|
||||||
|
ImageView pickerView;
|
||||||
|
Canvas pickerCanvas;
|
||||||
|
Bitmap pickerBitmap;
|
||||||
|
|
||||||
|
NotificationConfiguration settings;
|
||||||
|
|
||||||
|
int height, width, radius;
|
||||||
|
int radius1, radius2, radius3;
|
||||||
|
int controlledHand = 0;
|
||||||
|
int handRadius;
|
||||||
|
|
||||||
|
AlertDialog dialog;
|
||||||
|
|
||||||
|
OnFinishListener finishListener;
|
||||||
|
OnHandsSetListener handsListener;
|
||||||
|
OnVibrationSetListener vibrationListener;
|
||||||
|
|
||||||
|
protected TimePicker(@NonNull Context context, PackageInfo info) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
settings = new NotificationConfiguration(info.packageName, context.getApplicationContext().getPackageManager().getApplicationLabel(info.applicationInfo).toString());
|
||||||
|
initGraphics(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TimePicker(Context context, NotificationConfiguration config){
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
settings = config;
|
||||||
|
initGraphics(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initGraphics(Context context){
|
||||||
|
int w = (int) (((WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth() * 0.8);
|
||||||
|
height = w;
|
||||||
|
width = w;
|
||||||
|
radius = (int) (w * 0.06);
|
||||||
|
radius1 = 0;
|
||||||
|
radius2 = (int) (radius * 2.3);
|
||||||
|
radius3 = (int)(radius2 * 2.15);
|
||||||
|
int offset = (int) (w * 0.1);
|
||||||
|
radius1 += offset;
|
||||||
|
radius2 += offset;
|
||||||
|
radius3 += offset;
|
||||||
|
|
||||||
|
pickerView = new ImageView(context);
|
||||||
|
pickerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
|
||||||
|
pickerCanvas = new Canvas(pickerBitmap);
|
||||||
|
|
||||||
|
drawClock();
|
||||||
|
|
||||||
|
LinearLayout layout = new LinearLayout(context);
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
layout.addView(pickerView);
|
||||||
|
|
||||||
|
CheckBox box = new CheckBox(context);
|
||||||
|
box.setText("Respect silent mode");
|
||||||
|
box.setChecked(settings.getRespectSilentMode());
|
||||||
|
box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||||
|
settings.setRespectSilentMode(b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layout.addView(box);
|
||||||
|
|
||||||
|
RadioGroup group = new RadioGroup(context);
|
||||||
|
for(PlayNotificationRequest.VibrationType vibe: PlayNotificationRequest.VibrationType.values()){
|
||||||
|
RadioButton button = new RadioButton(context);
|
||||||
|
button.setText(vibe.toString());
|
||||||
|
button.setId(vibe.getValue());
|
||||||
|
group.addView(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.check(settings.getVibration().getValue());
|
||||||
|
group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(RadioGroup radioGroup, int i) {
|
||||||
|
settings.setVibration(PlayNotificationRequest.VibrationType.fromValue((byte)i));
|
||||||
|
if(TimePicker.this.vibrationListener != null) TimePicker.this.vibrationListener.onVibrationSet(settings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ScrollView scrollView = new ScrollView(context);
|
||||||
|
scrollView.addView(group);
|
||||||
|
layout.addView(scrollView);
|
||||||
|
|
||||||
|
setView(layout);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setNegativeButton("cancel", null);
|
||||||
|
setPositiveButton("ok", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
if(finishListener == null) return;
|
||||||
|
finishListener.onFinish(true, settings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialogInterface) {
|
||||||
|
if(finishListener == null) return;
|
||||||
|
finishListener.onFinish(false, settings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialogInterface) {
|
||||||
|
if(finishListener == null) return;
|
||||||
|
finishListener.onFinish(false, settings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog = show();
|
||||||
|
|
||||||
|
pickerView.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||||
|
handleTouch(dialog, motionEvent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationConfiguration getSettings() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTouch(AlertDialog dialog, MotionEvent event) {
|
||||||
|
int centerX = width / 2;
|
||||||
|
int centerY = height / 2;
|
||||||
|
int difX = centerX - (int) event.getX();
|
||||||
|
int difY = (int) event.getY() - centerY;
|
||||||
|
int dist = (int) Math.sqrt(Math.abs(difX) * Math.abs(difX) + Math.abs(difY) * Math.abs(difY));
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN: {
|
||||||
|
int radiusHalf = radius;
|
||||||
|
if (dist < (radius1 + radiusHalf) && dist > (radius1 - radiusHalf)) {
|
||||||
|
Log.d("Settings", "hit sub");
|
||||||
|
handRadius = (int) (height / 2f - radius1);
|
||||||
|
controlledHand = 3;
|
||||||
|
} else if (dist < (radius2 + radiusHalf) && dist > (radius2 - radiusHalf)) {
|
||||||
|
Log.d("Settings", "hit hour");
|
||||||
|
controlledHand = 1;
|
||||||
|
handRadius = (int) (height / 2f - radius2);
|
||||||
|
} else if (dist < (radius3 + radiusHalf) && dist > (radius3 - radiusHalf)) {
|
||||||
|
Log.d("Settings", "hit minute");
|
||||||
|
controlledHand = 2;
|
||||||
|
handRadius = (int) (height / 2f - radius3);
|
||||||
|
} else {
|
||||||
|
Log.d("Settings", "hit nothing");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_MOVE: {
|
||||||
|
if (controlledHand == 0) return;
|
||||||
|
double degree = difY == 0 ? (difX < 0 ? 90 : 270) : Math.toDegrees(Math.atan((float) difX / (float) difY));
|
||||||
|
if (difY > 0) degree = 180 + degree;
|
||||||
|
if (degree < 0) degree = 360 + degree;
|
||||||
|
switch (controlledHand) {
|
||||||
|
case 1: {
|
||||||
|
settings.setHour((short) (((int)(degree + 15) / 30) * 30 % 360));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
settings.setMin((short) (((int)(degree + 15) / 30) * 30 % 360));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_UP: {
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setClickable(true);
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setAlpha(1f);
|
||||||
|
if(handsListener != null) handsListener.onHandsSet(settings);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawClock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void drawClock() {
|
||||||
|
//pickerCanvas.drawColor(Color.WHITE);
|
||||||
|
Paint white = new Paint();
|
||||||
|
white.setColor(Color.WHITE);
|
||||||
|
white.setStyle(Paint.Style.FILL);
|
||||||
|
pickerCanvas.drawCircle(width / 2, width / 2, width / 2, white);
|
||||||
|
|
||||||
|
Paint paint = new Paint();
|
||||||
|
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||||
|
paint.setColor(Color.BLUE);
|
||||||
|
Paint text = new Paint();
|
||||||
|
text.setStyle(Paint.Style.FILL);
|
||||||
|
text.setTextSize(radius * 1.5f);
|
||||||
|
text.setColor(Color.BLACK);
|
||||||
|
text.setTextAlign(Paint.Align.CENTER);
|
||||||
|
int textShiftY = (int) ((text.descent() + text.ascent()) / 2);
|
||||||
|
|
||||||
|
Paint linePaint = new Paint();
|
||||||
|
linePaint.setStrokeWidth(10);
|
||||||
|
linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||||
|
linePaint.setColor(Color.BLACK);
|
||||||
|
|
||||||
|
if (settings.getMin() != -1) {
|
||||||
|
paint.setAlpha(255);
|
||||||
|
float x = (float) (width / 2f + Math.sin(Math.toRadians(settings.getMin())) * (float) radius3);
|
||||||
|
float y = (float) (height / 2f - Math.cos(Math.toRadians(settings.getMin())) * (float) radius3);
|
||||||
|
linePaint.setAlpha(255);
|
||||||
|
pickerCanvas.drawLine(width / 2, height / 2, x, y, linePaint);
|
||||||
|
pickerCanvas.drawCircle(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
radius,
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
pickerCanvas.drawText(String.valueOf(settings.getMin() / 6), x, y - textShiftY, text);
|
||||||
|
}
|
||||||
|
if (settings.getHour() != -1) {
|
||||||
|
paint.setAlpha(255);
|
||||||
|
float x = (float) (width / 2f + Math.sin(Math.toRadians(settings.getHour())) * (float) radius2);
|
||||||
|
float y = (float) (height / 2f - Math.cos(Math.toRadians(settings.getHour())) * (float) radius2);
|
||||||
|
linePaint.setAlpha(255);
|
||||||
|
pickerCanvas.drawLine(width / 2, height / 2, x, y, linePaint);
|
||||||
|
pickerCanvas.drawCircle(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
radius,
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
pickerCanvas.drawText(settings.getHour() == 0 ? "12" : String.valueOf(settings.getHour() / 30), x, y - textShiftY, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Paint paint2 = new Paint();
|
||||||
|
paint2.setColor(Color.BLACK);
|
||||||
|
paint2.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||||
|
pickerCanvas.drawCircle(width / 2, height / 2, 5, paint2);
|
||||||
|
pickerView.setImageBitmap(pickerBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnFinishListener{
|
||||||
|
public void onFinish(boolean success, NotificationConfiguration config);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnHandsSetListener{
|
||||||
|
public void onHandsSet(NotificationConfiguration config);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnVibrationSetListener{
|
||||||
|
public void onVibrationSet(NotificationConfiguration config);
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.media.MediaMetadata;
|
import android.media.MediaMetadata;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
@ -43,6 +44,7 @@ import android.support.v4.media.session.MediaSessionCompat;
|
|||||||
import android.support.v4.media.session.PlaybackStateCompat;
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.RemoteInput;
|
import androidx.core.app.RemoteInput;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
@ -106,6 +108,8 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
private HashMap<String, Long> notificationBurstPrevention = new HashMap<>();
|
private HashMap<String, Long> notificationBurstPrevention = new HashMap<>();
|
||||||
private HashMap<String, Long> notificationOldRepeatPrevention = new HashMap<>();
|
private HashMap<String, Long> notificationOldRepeatPrevention = new HashMap<>();
|
||||||
|
|
||||||
|
public static ArrayList<String> notificationStack = new ArrayList<>();
|
||||||
|
|
||||||
private long activeCallPostTime;
|
private long activeCallPostTime;
|
||||||
|
|
||||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||||
@ -223,6 +227,7 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||||
|
notificationStack.clear();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,6 +246,9 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
|
||||||
|
notificationStack.remove(sbn.getPackageName());
|
||||||
|
notificationStack.add(sbn.getPackageName());
|
||||||
|
|
||||||
if (GBApplication.isRunningLollipopOrLater()) {
|
if (GBApplication.isRunningLollipopOrLater()) {
|
||||||
if ("call".equals(sbn.getNotification().category) && prefs.getBoolean("notification_support_voip_calls", false)) {
|
if ("call".equals(sbn.getNotification().category) && prefs.getBoolean("notification_support_voip_calls", false)) {
|
||||||
handleCallNotification(sbn);
|
handleCallNotification(sbn);
|
||||||
@ -384,7 +392,6 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
}else {
|
}else {
|
||||||
LOG.info("This app might show old/duplicate notifications. notification.when is 0 for " + source);
|
LOG.info("This app might show old/duplicate notifications. notification.when is 0 for " + source);
|
||||||
}
|
}
|
||||||
|
|
||||||
GBApplication.deviceService().onNotification(notificationSpec);
|
GBApplication.deviceService().onNotification(notificationSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,29 +633,30 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
@Override
|
@Override
|
||||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||||
LOG.info("Notification removed: " + sbn.getPackageName());
|
LOG.info("Notification removed: " + sbn.getPackageName() + ": " + sbn.getNotification().category);
|
||||||
if (GBApplication.isRunningLollipopOrLater()) {
|
|
||||||
LOG.info("Notification removed: " + sbn.getPackageName() + ", category: " + sbn.getNotification().category);
|
notificationStack.remove(sbn.getPackageName());
|
||||||
|
|
||||||
if(Notification.CATEGORY_CALL.equals(sbn.getNotification().category) && activeCallPostTime == sbn.getPostTime()) {
|
if(Notification.CATEGORY_CALL.equals(sbn.getNotification().category) && activeCallPostTime == sbn.getPostTime()) {
|
||||||
activeCallPostTime = 0;
|
activeCallPostTime = 0;
|
||||||
CallSpec callSpec = new CallSpec();
|
CallSpec callSpec = new CallSpec();
|
||||||
callSpec.command = CallSpec.CALL_END;
|
callSpec.command = CallSpec.CALL_END;
|
||||||
GBApplication.deviceService().onSetCallState(callSpec);
|
GBApplication.deviceService().onSetCallState(callSpec);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// FIXME: DISABLED for now
|
// FIXME: DISABLED for now
|
||||||
/*
|
|
||||||
if (shouldIgnore(sbn))
|
if (shouldIgnore(sbn))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
if (prefs.getBoolean("autoremove_notifications", false)) {
|
if (prefs.getBoolean("autoremove_notifications", true)) {
|
||||||
LOG.info("notification removed, will ask device to delete it");
|
LOG.info("notification removed, will ask device to delete it");
|
||||||
GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime());
|
GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime());
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ public enum DeviceType {
|
|||||||
NO1F1(50, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_no1_f1),
|
NO1F1(50, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_no1_f1),
|
||||||
TECLASTH30(60, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_teclast_h30),
|
TECLASTH30(60, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_teclast_h30),
|
||||||
XWATCH(70, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_xwatch),
|
XWATCH(70, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_xwatch),
|
||||||
|
FOSSILQHYBRID(80, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_qhybrid),
|
||||||
ZETIME(80, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_mykronoz_zetime),
|
ZETIME(80, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_mykronoz_zetime),
|
||||||
ID115(90, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_id115),
|
ID115(90, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_id115),
|
||||||
WATCH9(100, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watch9),
|
WATCH9(100, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watch9),
|
||||||
|
@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd02.MijiaL
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
|
||||||
@ -183,6 +184,9 @@ public class DeviceSupportFactory {
|
|||||||
case XWATCH:
|
case XWATCH:
|
||||||
deviceSupport = new ServiceDeviceSupport(new XWatchSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
deviceSupport = new ServiceDeviceSupport(new XWatchSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
break;
|
break;
|
||||||
|
case FOSSILQHYBRID:
|
||||||
|
deviceSupport = new ServiceDeviceSupport(new QHybridSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
|
break;
|
||||||
case ZETIME:
|
case ZETIME:
|
||||||
deviceSupport = new ServiceDeviceSupport(new ZeTimeDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
deviceSupport = new ServiceDeviceSupport(new ZeTimeDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
break;
|
break;
|
||||||
|
@ -351,6 +351,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSetFmFrequency(float frequency) {
|
public void onSetFmFrequency(float frequency) {
|
||||||
|
|
||||||
|
@ -209,4 +209,9 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
|
|||||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||||
mSupport.onReadRemoteRssi(gatt, rssi, status);
|
mSupport.onReadRemoteRssi(gatt, rssi, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
mSupport.onMtuChanged(gatt, mtu, status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,4 +60,8 @@ public abstract class AbstractGattCallback implements GattCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -532,6 +532,19 @@ public final class BtLEQueue {
|
|||||||
checkWaitingCharacteristic(characteristic, status);
|
checkWaitingCharacteristic(characteristic, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
super.onMtuChanged(gatt, mtu, status);
|
||||||
|
|
||||||
|
if(getCallbackToUse() != null){
|
||||||
|
getCallbackToUse().onMtuChanged(gatt, mtu, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCharacteristicRead(BluetoothGatt gatt,
|
public void onCharacteristicRead(BluetoothGatt gatt,
|
||||||
BluetoothGattCharacteristic characteristic,
|
BluetoothGattCharacteristic characteristic,
|
||||||
|
@ -104,6 +104,8 @@ public interface GattCallback {
|
|||||||
*/
|
*/
|
||||||
void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status);
|
void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status);
|
||||||
|
|
||||||
|
void onMtuChanged(BluetoothGatt gatt, int mtu, int status);
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * @see BluetoothGattCallback#onMtuChanged(BluetoothGatt, int, int)
|
// * @see BluetoothGattCallback#onMtuChanged(BluetoothGatt, int, int)
|
||||||
// * @param gatt
|
// * @param gatt
|
||||||
|
@ -18,13 +18,17 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ReadAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ReadAction;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.RequestMtuAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
|
||||||
|
|
||||||
@ -56,6 +60,13 @@ public class TransactionBuilder {
|
|||||||
return add(action);
|
return add(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public TransactionBuilder requestMtu(int mtu){
|
||||||
|
return add(
|
||||||
|
new RequestMtuAction(mtu)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public TransactionBuilder notify(BluetoothGattCharacteristic characteristic, boolean enable) {
|
public TransactionBuilder notify(BluetoothGattCharacteristic characteristic, boolean enable) {
|
||||||
if (characteristic == null) {
|
if (characteristic == null) {
|
||||||
LOG.warn("Unable to notify characteristic: null");
|
LOG.warn("Unable to notify characteristic: null");
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
||||||
|
|
||||||
|
public class RequestMtuAction extends BtLEAction {
|
||||||
|
private int mtu;
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public RequestMtuAction(int mtu) {
|
||||||
|
super(null);
|
||||||
|
this.mtu = mtu;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean expectsResult() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public boolean run(BluetoothGatt gatt) {
|
||||||
|
return gatt.requestMtu(this.mtu);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;//
|
||||||
|
// Source code recreated from a .class file by IntelliJ IDEA
|
||||||
|
// (powered by Fernflower decompiler)
|
||||||
|
//
|
||||||
|
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
public final class CRC32C implements Checksum {
|
||||||
|
private static final int[] crcTable = new int[]{0, (int) 4067132163L, (int) 3778769143L, 324072436, (int) 3348797215L, 904991772, 648144872, (int) 3570033899L, (int) 2329499855L, 2024987596, 1809983544, (int) 2575936315L, 1296289744, (int) 3207089363L, (int) 2893594407L, 1578318884, 274646895, (int) 3795141740L, (int) 4049975192L, 51262619, (int) 3619967088L, 632279923, 922689671, (int) 3298075524L, (int) 2592579488L, 1760304291, 2075979607, (int) 2312596564L, 1562183871, (int) 2943781820L, (int) 3156637768L, 1313733451, 549293790, (int) 3537243613L, (int) 3246849577L, 871202090, (int) 3878099393L, 357341890, 102525238, (int) 4101499445L, (int) 2858735121L, 1477399826, 1264559846, (int) 3107202533L, 1845379342, (int) 2677391885L, (int) 2361733625L, 2125378298, 820201905, (int) 3263744690L, (int) 3520608582L, 598981189, (int) 4151959214L, 85089709, 373468761, (int) 3827903834L, (int) 3124367742L, 1213305469, 1526817161, (int) 2842354314L, 2107672161, (int) 2412447074L, (int) 2627466902L, 1861252501, 1098587580, (int) 3004210879L, (int) 2688576843L, 1378610760, (int) 2262928035L, 1955203488, 1742404180, (int) 2511436119L, (int) 3416409459L, 969524848, 714683780, (int) 3639785095L, 205050476, (int) 4266873199L, (int) 3976438427L, 526918040, 1361435347, (int) 2739821008L, (int) 2954799652L, 1114974503, (int) 2529119692L, 1691668175, 2005155131, (int) 2247081528L, (int) 3690758684L, 697762079, 986182379, (int) 3366744552L, 476452099, (int) 3993867776L, (int) 4250756596L, 255256311, 1640403810, (int) 2477592673L, (int) 2164122517L, 1922457750, (int) 2791048317L, 1412925310, 1197962378, (int) 3037525897L, (int) 3944729517L, 427051182, 170179418, (int) 4165941337L, 746937522, (int) 3740196785L, (int) 3451792453L, 1070968646, 1905808397, (int) 2213795598L, (int) 2426610938L, 1657317369, (int) 3053634322L, 1147748369, 1463399397, (int) 2773627110L, (int) 4215344322L, 153784257, 444234805, (int) 3893493558L, 1021025245, (int) 3467647198L, (int) 3722505002L, 797665321, (int) 2197175160L, 1889384571, 1674398607, (int) 2443626636L, 1164749927, (int) 3070701412L, (int) 2757221520L, 1446797203, 137323447, (int) 4198817972L, (int) 3910406976L, 461344835, (int) 3484808360L, 1037989803, 781091935, (int) 3705997148L, (int) 2460548119L, 1623424788, 1939049696, (int) 2180517859L, 1429367560, (int) 2807687179L, (int) 3020495871L, 1180866812, 410100952, (int) 3927582683L, (int) 4182430767L, 186734380, (int) 3756733383L, 763408580, 1053836080, (int) 3434856499L, (int) 2722870694L, 1344288421, 1131464017, (int) 2971354706L, 1708204729, (int) 2545590714L, (int) 2229949006L, 1988219213, 680717673, (int) 3673779818L, (int) 3383336350L, 1002577565, (int) 4010310262L, 493091189, 238226049, (int) 4233660802L, (int) 2987750089L, 1082061258, 1395524158, (int) 2705686845L, 1972364758, (int) 2279892693L, (int) 2494862625L, 1725896226, 952904198, (int) 3399985413L, (int) 3656866545L, 731699698, (int) 4283874585L, 222117402, 510512622, (int) 3959836397L, (int) 3280807620L, 837199303, 582374963, (int) 3504198960L, 68661723, (int) 4135334616L, (int) 3844915500L, 390545967, 1230274059, (int) 3141532936L, (int) 2825850620L, 1510247935, (int) 2395924756L, 2091215383, 1878366691, (int) 2644384480L, (int) 3553878443L, 565732008, 854102364, (int) 3229815391L, 340358836, (int) 3861050807L, (int) 4117890627L, 119113024, 1493875044, (int) 2875275879L, (int) 3090270611L, 1247431312, (int) 2660249211L, 1828433272, 2141937292, (int) 2378227087L, (int) 3811616794L, 291187481, 34330861, (int) 4032846830L, 615137029, (int) 3603020806L, (int) 3314634738L, 939183345, 1776939221, (int) 2609017814L, (int) 2295496738L, 2058945313, (int) 2926798794L, 1545135305, 1330124605, (int) 3173225534L, (int) 4084100981L, 17165430, 307568514, (int) 3762199681L, 888469610, (int) 3332340585L, (int) 3587147933L, 665062302, 2042050490, (int) 2346497209L, (int) 2559330125L, 1793573966, (int) 3190661285L, 1279665062, 1595330642, (int) 2910671697L};
|
||||||
|
private int crc = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
public long getValue() {
|
||||||
|
return ~this.crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.crc = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(int data) {
|
||||||
|
crc = crcTable[(data & 255 ^ crc) & 255] ^ crc >>> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(byte[] data, int offset, int length) {
|
||||||
|
for (int index = offset; index < length + offset; index++) {
|
||||||
|
this.crc = crcTable[(this.crc ^ data[index]) & 255] ^ this.crc >>> 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(byte[] data){
|
||||||
|
update(data, 0, data.length);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,179 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||||
|
|
||||||
|
class QHybridBaseSupport extends AbstractBTLEDeviceSupport {
|
||||||
|
QHybridBaseSupport(Logger logger) {
|
||||||
|
super(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useAutoConnect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeleteNotification(int id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetTime() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetCallState(CallSpec callSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetMusicState(MusicStateSpec stateSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableRealtimeSteps(boolean enable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInstallApp(Uri uri) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppInfoReq() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppStart(UUID uuid, boolean start) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppDelete(UUID uuid) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppReorder(UUID[] uuids) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFetchRecordedData(int dataTypes) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReset(int flags) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeartRateTest() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindDevice(boolean start) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetConstantVibration(int integer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScreenshotReq() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableHeartRateSleepSupport(boolean enable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetHeartRateMeasurementInterval(int seconds) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeleteCalendarEvent(byte type, long id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendConfiguration(String config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReadConfiguration(String config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTestNewFunction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,561 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
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.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
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.GenericItem;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
|
||||||
|
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.requests.misfit.DownloadFileRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
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_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";
|
||||||
|
|
||||||
|
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 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 = "STEPTIMEZONE_OFFSET_COUNT";
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(QHybridSupport.class);
|
||||||
|
|
||||||
|
private PackageConfigHelper helper;
|
||||||
|
|
||||||
|
private volatile boolean searchDevice = false;
|
||||||
|
|
||||||
|
private long timeOffset;
|
||||||
|
|
||||||
|
private boolean useActivityHand;
|
||||||
|
|
||||||
|
private WatchAdapter watchAdapter;
|
||||||
|
|
||||||
|
public QHybridSupport() {
|
||||||
|
super(logger);
|
||||||
|
addSupportedService(UUID.fromString("3dda0001-957f-7d4a-34a6-74696673696d"));
|
||||||
|
addSupportedService(UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb"));
|
||||||
|
addSupportedService(UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"));
|
||||||
|
addSupportedService(UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb"));
|
||||||
|
IntentFilter commandFilter = new IntentFilter(QHYBRID_COMMAND_CONTROL);
|
||||||
|
commandFilter.addAction(QHYBRID_COMMAND_UNCONTROL);
|
||||||
|
commandFilter.addAction(QHYBRID_COMMAND_SET);
|
||||||
|
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);
|
||||||
|
BroadcastReceiver 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");
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case QHYBRID_COMMAND_CONTROL: {
|
||||||
|
log("sending control request");
|
||||||
|
watchAdapter.requestHandsControl();
|
||||||
|
if (config != null) {
|
||||||
|
watchAdapter.setHands(config.getHour(), config.getMin());
|
||||||
|
} else {
|
||||||
|
watchAdapter.setHands((short) 0, (short) 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QHYBRID_COMMAND_UNCONTROL: {
|
||||||
|
watchAdapter.releaseHandsControl();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QHYBRID_COMMAND_SET: {
|
||||||
|
watchAdapter.setHands(config.getHour(), config.getMin());
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QHYBRID_COMMAND_VIBRATE: {
|
||||||
|
watchAdapter.vibrate(config.getVibration());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QHYBRID_COMMAND_NOTIFICATION: {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
LocalBroadcastManager.getInstance(getContext()).registerReceiver(commandReceiver, commandFilter);
|
||||||
|
|
||||||
|
try {
|
||||||
|
helper = new PackageConfigHelper(GBApplication.getContext());
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error getting database", GB.ERROR, e);
|
||||||
|
GB.toast("error getting database", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
try {
|
||||||
|
throw e;
|
||||||
|
} catch (GBException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IntentFilter globalFilter = new IntentFilter();
|
||||||
|
globalFilter.addAction(QHYBRID_ACTION_SET_ACTIVITY_HAND);
|
||||||
|
BroadcastReceiver globalCommandReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
//noinspection SwitchStatementWithTooFewBranches
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GBApplication.getContext().registerReceiver(globalCommandReceiver, globalFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
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
|
||||||
|
.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")))
|
||||||
|
;
|
||||||
|
|
||||||
|
loadTimeOffset();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFetchRecordedData(int dataTypes) {
|
||||||
|
if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
|
||||||
|
this.watchAdapter.onFetchActivityData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
|
log("notif from " + notificationSpec.sourceAppId + " " + notificationSpec.sender + " " + notificationSpec.phoneNumber);
|
||||||
|
//new Exception().printStackTrace();
|
||||||
|
String packageName = notificationSpec.sourceName;
|
||||||
|
|
||||||
|
NotificationConfiguration config = null;
|
||||||
|
try {
|
||||||
|
config = helper.getNotificationConfiguration(packageName);
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error getting notification configuration", GB.ERROR, 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 void onDeleteNotification(int id) {
|
||||||
|
super.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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double calculateNotificationProgress() {
|
||||||
|
HashMap<NotificationConfiguration, Boolean> configs = new HashMap<>(0);
|
||||||
|
try {
|
||||||
|
for (NotificationConfiguration config : helper.getNotificationConfigurations()) {
|
||||||
|
configs.put(config, false);
|
||||||
|
}
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error getting notification configuration", GB.ERROR, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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) {
|
||||||
|
try {
|
||||||
|
if (watchAdapter.supportsExtendedVibration()) {
|
||||||
|
GB.toast("Device does not support brr brr", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
notifiyException(e);
|
||||||
|
GB.toast("Please contact dakhnod@gmail.com\n", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start && searchDevice) return;
|
||||||
|
|
||||||
|
searchDevice = start;
|
||||||
|
|
||||||
|
if (start) {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int i = 0;
|
||||||
|
while (searchDevice) {
|
||||||
|
QHybridSupport.this.watchAdapter.vibrateFindMyDevicePattern();
|
||||||
|
try {
|
||||||
|
Thread.sleep(2500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTestNewFunction() {
|
||||||
|
watchAdapter.onTestNewFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backupFile(DownloadFileRequest request) {
|
||||||
|
try {
|
||||||
|
File f = new File("/sdcard/qFiles/");
|
||||||
|
if (!f.exists()) f.mkdir();
|
||||||
|
|
||||||
|
File file = new File("/sdcard/qFiles/" + request.timeStamp);
|
||||||
|
if (file.exists()) {
|
||||||
|
throw new Exception("file " + file.getPath() + " exists");
|
||||||
|
}
|
||||||
|
logger.debug("Writing file " + file.getPath());
|
||||||
|
file.createNewFile();
|
||||||
|
FileOutputStream fos = new FileOutputStream(file);
|
||||||
|
fos.write(request.file);
|
||||||
|
fos.close();
|
||||||
|
logger.debug("file written.");
|
||||||
|
|
||||||
|
FileOutputStream fos2 = new FileOutputStream("/sdcard/qFiles/steps", true);
|
||||||
|
fos2.write(("file " + request.timeStamp + " cut\n\n").getBytes());
|
||||||
|
fos2.close();
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifiyException(Exception e){
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
|
e.printStackTrace(pw);
|
||||||
|
String sStackTrace = sw.toString();
|
||||||
|
|
||||||
|
Notification.Builder notificationBuilder = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
notificationBuilder = new Notification.Builder(getContext(), GB.NOTIFICATION_CHANNEL_ID);
|
||||||
|
} else {
|
||||||
|
notificationBuilder = new Notification.Builder(getContext());
|
||||||
|
}
|
||||||
|
notificationBuilder
|
||||||
|
.setContentTitle("Q Error")
|
||||||
|
.setSmallIcon(R.drawable.ic_notification_qhybrid)
|
||||||
|
.setContentText(sStackTrace)
|
||||||
|
.setStyle(new Notification.BigTextStyle())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
|
||||||
|
"mailto","dakhnod@gmail.com", null));
|
||||||
|
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Exception Report");
|
||||||
|
emailIntent.putExtra(Intent.EXTRA_TEXT, "Here's a crash from your stupid app: \n\n" + sStackTrace);
|
||||||
|
|
||||||
|
PendingIntent intent = PendingIntent.getActivity(getContext(), 0, emailIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
|
||||||
|
notificationBuilder.addAction(new Notification.Action(0, "report", intent));
|
||||||
|
}else{
|
||||||
|
notificationBuilder.addAction(0, "report", intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify((int) System.currentTimeMillis(), notificationBuilder.build());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
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) {
|
||||||
|
notifiyException(e);
|
||||||
|
GB.toast("Please contact dakhnod@gmail.com\n", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
|
||||||
|
public abstract class WatchAdapter {
|
||||||
|
private QHybridSupport deviceSupport;
|
||||||
|
|
||||||
|
public WatchAdapter(QHybridSupport deviceSupport){
|
||||||
|
this.deviceSupport = deviceSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QHybridSupport getDeviceSupport(){
|
||||||
|
return this.deviceSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext(){
|
||||||
|
return getDeviceSupport().getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void initialize();
|
||||||
|
|
||||||
|
public abstract void playPairingAnimation();
|
||||||
|
public abstract void playNotification(NotificationConfiguration config);
|
||||||
|
public abstract void setTime();
|
||||||
|
public abstract void overwriteButtons(String buttonConfigJson);
|
||||||
|
public abstract void setActivityHand(double progress);
|
||||||
|
public abstract void setHands(short hour, short minute);
|
||||||
|
public abstract void vibrate(PlayNotificationRequest.VibrationType vibration);
|
||||||
|
public abstract void vibrateFindMyDevicePattern();
|
||||||
|
public abstract void requestHandsControl();
|
||||||
|
public abstract void releaseHandsControl();
|
||||||
|
public abstract void setStepGoal(int stepGoal);
|
||||||
|
public abstract void setVibrationStrength(short strength);
|
||||||
|
public abstract void syncNotificationSettings();
|
||||||
|
public abstract void onTestNewFunction();
|
||||||
|
public abstract void setTimezoneOffsetMinutes(short offset);
|
||||||
|
|
||||||
|
public abstract boolean supportsFindDevice();
|
||||||
|
public abstract boolean supportsExtendedVibration();
|
||||||
|
public abstract boolean supportsActivityHand();
|
||||||
|
|
||||||
|
public String getModelName() {
|
||||||
|
String modelNumber = getDeviceSupport().getDevice().getModel();
|
||||||
|
switch (modelNumber) {
|
||||||
|
case "HW.0.0":
|
||||||
|
return "Q Commuter";
|
||||||
|
case "HL.0.0":
|
||||||
|
return "Q Activist";
|
||||||
|
case "DN.1.0":
|
||||||
|
return "Hybrid HR Collider";
|
||||||
|
}
|
||||||
|
return "unknwon Q";
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onFetchActivityData();
|
||||||
|
|
||||||
|
public abstract void onSetAlarms(ArrayList<? extends Alarm> alarms);
|
||||||
|
|
||||||
|
public abstract boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status){};
|
||||||
|
|
||||||
|
|
||||||
|
public String arrayToString(byte[] bytes) {
|
||||||
|
if (bytes.length == 0) return "";
|
||||||
|
StringBuilder s = new StringBuilder();
|
||||||
|
final String chars = "0123456789ABCDEF";
|
||||||
|
for (byte b : bytes) {
|
||||||
|
s.append(chars.charAt((b >> 4) & 0xF)).append(chars.charAt(b & 0xF)).append(" ");
|
||||||
|
}
|
||||||
|
return s.substring(0, s.length() - 1) + "\n";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter;
|
||||||
|
|
||||||
|
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.adapter.misfit.MisfitWatchAdapter;
|
||||||
|
|
||||||
|
public final class WatchAdapterFactory {
|
||||||
|
public final WatchAdapter createWatchAdapter(String firmwareVersion, QHybridSupport deviceSupport){
|
||||||
|
char major = firmwareVersion.charAt(6);
|
||||||
|
switch (major){
|
||||||
|
case '1': return new MisfitWatchAdapter(deviceSupport);
|
||||||
|
case '2': return new FossilWatchAdapter(deviceSupport);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException("Firmware " + firmwareVersion + " not supported");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,595 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigFileBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigPayload;
|
||||||
|
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.RequestMtuRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.AlarmsSetRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.NotificationFilterPutRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.AnimationRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ReleaseHandsControlRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.RequestHandControlRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_STEP_GOAL;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_TIMEZONE_OFFSET;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_VIBRATION_STRENGTH;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_MULTI_BUTTON_PRESS;
|
||||||
|
|
||||||
|
public class FossilWatchAdapter extends WatchAdapter {
|
||||||
|
private ArrayList<Request> requestQueue = new ArrayList<>();
|
||||||
|
|
||||||
|
private FossilRequest fossilRequest;
|
||||||
|
|
||||||
|
private int MTU = 23;
|
||||||
|
|
||||||
|
private final String ITEM_MTU = "MTU";
|
||||||
|
static public final String ITEM_BUTTONS = "BUTTONS";
|
||||||
|
|
||||||
|
private final String CONFIG_ITEM_STEP_GOAL = "step_goal";
|
||||||
|
private final String CONFIG_ITEM_VIBRATION_STRENGTH = "vibration_strength";
|
||||||
|
private final String CONFIG_ITEM_TIMEZONE_OFFSET = "timezone_offset";
|
||||||
|
public final String CONFIG_ITEM_BUTTONS = "buttons";
|
||||||
|
|
||||||
|
private int lastButtonIndex = -1;
|
||||||
|
|
||||||
|
Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
public FossilWatchAdapter(QHybridSupport deviceSupport) {
|
||||||
|
super(deviceSupport);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
playPairingAnimation();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
queueWrite(new RequestMtuRequest(512), false);
|
||||||
|
}
|
||||||
|
// queueWrite(new FileCloseRequest((short) 0xFFFF));
|
||||||
|
// queueWrite(new ConfigurationGetRequest(this), false);
|
||||||
|
|
||||||
|
syncConfiguration();
|
||||||
|
|
||||||
|
syncNotificationSettings();
|
||||||
|
|
||||||
|
syncButtonSettings();
|
||||||
|
|
||||||
|
/* queueWrite(new ButtonConfigurationGetRequest(this) {
|
||||||
|
@Override
|
||||||
|
public void onConfigurationsGet(ConfigPayload[] configs) {
|
||||||
|
super.onConfigurationsGet(configs);
|
||||||
|
|
||||||
|
JSONArray buttons = new JSONArray();
|
||||||
|
for (ConfigPayload payload : configs) buttons.put(String.valueOf(payload));
|
||||||
|
String json = buttons.toString();
|
||||||
|
getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(ITEM_BUTTONS, json));
|
||||||
|
}
|
||||||
|
}); */
|
||||||
|
|
||||||
|
queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncButtonSettings(){
|
||||||
|
String buttonConfig = getDeviceSpecificPreferences().getString(CONFIG_ITEM_BUTTONS, null);
|
||||||
|
getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(ITEM_BUTTONS, buttonConfig));
|
||||||
|
overwriteButtons(buttonConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SharedPreferences getDeviceSpecificPreferences(){
|
||||||
|
return GBApplication.getDeviceSpecificSharedPrefs(
|
||||||
|
getDeviceSupport().getDevice().getAddress()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncConfiguration(){
|
||||||
|
SharedPreferences preferences = getDeviceSpecificPreferences();
|
||||||
|
|
||||||
|
int stepGoal = preferences.getInt(CONFIG_ITEM_STEP_GOAL, 1000000);
|
||||||
|
byte vibrationStrength = (byte) preferences.getInt(CONFIG_ITEM_VIBRATION_STRENGTH, 100);
|
||||||
|
int timezoneOffset = preferences.getInt(CONFIG_ITEM_TIMEZONE_OFFSET, 0);
|
||||||
|
|
||||||
|
GBDevice device = getDeviceSupport().getDevice();
|
||||||
|
|
||||||
|
device.addDeviceInfo(new GenericItem(ITEM_STEP_GOAL, String.valueOf(stepGoal)));
|
||||||
|
device.addDeviceInfo(new GenericItem(ITEM_VIBRATION_STRENGTH, String.valueOf(vibrationStrength)));
|
||||||
|
device.addDeviceInfo(new GenericItem(ITEM_TIMEZONE_OFFSET, String.valueOf(timezoneOffset)));
|
||||||
|
|
||||||
|
queueWrite(new ConfigurationPutRequest(new ConfigurationPutRequest.ConfigItem[]{
|
||||||
|
new ConfigurationPutRequest.DailyStepGoalConfigItem(stepGoal),
|
||||||
|
new ConfigurationPutRequest.VibrationStrengthConfigItem(vibrationStrength),
|
||||||
|
new ConfigurationPutRequest.TimezoneOffsetConfigItem((short) timezoneOffset)
|
||||||
|
}, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMTU() {
|
||||||
|
if (this.MTU < 0) throw new RuntimeException("MTU not configured");
|
||||||
|
|
||||||
|
return this.MTU;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playPairingAnimation() {
|
||||||
|
queueWrite(new AnimationRequest(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playNotification(NotificationConfiguration config) {
|
||||||
|
if (config.getPackageName() == null) {
|
||||||
|
log("package name in notification not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
queueWrite(new PlayNotificationRequest(config.getPackageName(), this), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime() {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
TimeZone zone = new GregorianCalendar().getTimeZone();
|
||||||
|
|
||||||
|
queueWrite(
|
||||||
|
new 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overwriteButtons(String jsonConfigString) {
|
||||||
|
try {
|
||||||
|
if(jsonConfigString == null) return;
|
||||||
|
getDeviceSpecificPreferences()
|
||||||
|
.edit()
|
||||||
|
.putString(CONFIG_ITEM_BUTTONS, jsonConfigString)
|
||||||
|
.apply();
|
||||||
|
JSONArray buttonConfigJson = new JSONArray(jsonConfigString);
|
||||||
|
// JSONArray buttonConfigJson = new JSONArray(getDeviceSupport().getDevice().getDeviceInfo(ITEM_BUTTONS).getDetails());
|
||||||
|
|
||||||
|
ConfigPayload[] payloads = new ConfigPayload[buttonConfigJson.length()];
|
||||||
|
|
||||||
|
for (int i = 0; i < buttonConfigJson.length(); i++) {
|
||||||
|
try {
|
||||||
|
payloads[i] = ConfigPayload.valueOf(buttonConfigJson.getString(i));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
payloads[i] = ConfigPayload.FORWARD_TO_PHONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFileBuilder builder = new ConfigFileBuilder(payloads);
|
||||||
|
|
||||||
|
FilePutRequest fileUploadRequets = new FilePutRequest((short) 0x0600, builder.build(true), this) {
|
||||||
|
@Override
|
||||||
|
public void onFilePut(boolean success) {
|
||||||
|
if (success)
|
||||||
|
GB.toast("successfully overwritten button settings", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
else GB.toast("error overwriting button settings", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
queueWrite(fileUploadRequets);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActivityHand(double progress) {
|
||||||
|
queueWrite(new ConfigurationPutRequest(
|
||||||
|
new ConfigurationPutRequest.CurrentStepCountConfigItem(Math.min(999999, (int) (1000000 * progress))),
|
||||||
|
this
|
||||||
|
), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHands(short hour, short minute) {
|
||||||
|
queueWrite(new MoveHandsRequest(false, minute, hour, (short) -1), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void vibrate(nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest.VibrationType vibration) {
|
||||||
|
// queueWrite(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest(vibration, -1, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void vibrateFindMyDevicePattern() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestHandsControl() {
|
||||||
|
queueWrite(new RequestHandControlRequest(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseHandsControl() {
|
||||||
|
queueWrite(new ReleaseHandsControlRequest(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStepGoal(int stepGoal) {
|
||||||
|
getDeviceSpecificPreferences()
|
||||||
|
.edit()
|
||||||
|
.putInt(CONFIG_ITEM_STEP_GOAL, stepGoal)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
queueWrite(new ConfigurationPutRequest(new ConfigurationPutRequest.DailyStepGoalConfigItem(stepGoal), this) {
|
||||||
|
@Override
|
||||||
|
public void onFilePut(boolean success) {
|
||||||
|
if (success)
|
||||||
|
GB.toast("successfully updated step goal", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
else GB.toast("error updating step goal", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVibrationStrength(short strength) {
|
||||||
|
getDeviceSpecificPreferences()
|
||||||
|
.edit()
|
||||||
|
.putInt(CONFIG_ITEM_VIBRATION_STRENGTH, (byte) strength)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
ConfigurationPutRequest.ConfigItem vibrationItem = new ConfigurationPutRequest.VibrationStrengthConfigItem((byte) strength);
|
||||||
|
|
||||||
|
|
||||||
|
queueWrite(
|
||||||
|
new ConfigurationPutRequest(new ConfigurationPutRequest.ConfigItem[]{vibrationItem}, this) {
|
||||||
|
@Override
|
||||||
|
public void onFilePut(boolean success) {
|
||||||
|
if (success)
|
||||||
|
GB.toast("successfully updated vibration strength", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
else
|
||||||
|
GB.toast("error updating vibration strength", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
}, false
|
||||||
|
);
|
||||||
|
// queueWrite(new FileVerifyRequest((short) 0x0800));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void syncNotificationSettings() {
|
||||||
|
log("syncing notification settings...");
|
||||||
|
try {
|
||||||
|
PackageConfigHelper helper = new PackageConfigHelper(getContext());
|
||||||
|
final ArrayList<NotificationConfiguration> configurations = helper.getNotificationConfigurations();
|
||||||
|
if (configurations.size() == 1) configurations.add(configurations.get(0));
|
||||||
|
queueWrite(new NotificationFilterPutRequest(configurations, FossilWatchAdapter.this) {
|
||||||
|
@Override
|
||||||
|
public void onFilePut(boolean success) {
|
||||||
|
super.onFilePut(success);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
GB.toast("error writing notification settings", Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
|
||||||
|
getDeviceSupport().getDevice().setState(GBDevice.State.NOT_CONNECTED);
|
||||||
|
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeviceSupport().getDevice().setState(GBDevice.State.INITIALIZED);
|
||||||
|
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
} catch (GBException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTestNewFunction() {
|
||||||
|
queueWrite(new FilePutRequest(
|
||||||
|
(short) 0x0600,
|
||||||
|
new byte[]{
|
||||||
|
(byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x01, (byte) 0x24, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x30, (byte) 0x52, (byte) 0xFF, (byte) 0x26, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x09, (byte) 0x04, (byte) 0x01, (byte) 0x03, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x09, (byte) 0x04, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x24, (byte) 0x00, (byte) 0x00, (byte) 0x24, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x50, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x1F, (byte) 0xBE, (byte) 0xB4, (byte) 0x1B
|
||||||
|
},
|
||||||
|
this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimezoneOffsetMinutes(short offset) {
|
||||||
|
getDeviceSpecificPreferences()
|
||||||
|
.edit()
|
||||||
|
.putInt(CONFIG_ITEM_TIMEZONE_OFFSET, offset)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
queueWrite(new ConfigurationPutRequest(new ConfigurationPutRequest.TimezoneOffsetConfigItem(offset), this){
|
||||||
|
@Override
|
||||||
|
public void onFilePut(boolean success) {
|
||||||
|
super.onFilePut(success);
|
||||||
|
|
||||||
|
if(success) GB.toast("successfully updated timezone", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
else GB.toast("error updating timezone", Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFindDevice() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsExtendedVibration() {
|
||||||
|
String modelNumber = getDeviceSupport().getDevice().getModel();
|
||||||
|
switch (modelNumber) {
|
||||||
|
case "HW.0.0":
|
||||||
|
return true;
|
||||||
|
case "HL.0.0":
|
||||||
|
return false;
|
||||||
|
case "DN.1.0":
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("model " + modelNumber + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityHand() {
|
||||||
|
String modelNumber = getDeviceSupport().getDevice().getModel();
|
||||||
|
switch (modelNumber) {
|
||||||
|
case "HW.0.0":
|
||||||
|
return true;
|
||||||
|
case "HL.0.0":
|
||||||
|
return false;
|
||||||
|
case "DN.1.0":
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFetchActivityData() {
|
||||||
|
|
||||||
|
// queueWrite(new ConfigurationPutRequest(new ConfigurationPutRequest.ConfigItem[0], this));
|
||||||
|
setVibrationStrength((byte) 50);
|
||||||
|
// queueWrite(new FileCloseRequest((short) 0x0800));
|
||||||
|
// queueWrite(new ConfigurationGetRequest(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||||
|
// throw new RuntimeException("noope");
|
||||||
|
ArrayList<nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm> activeAlarms = new ArrayList<>();
|
||||||
|
for (Alarm alarm : alarms){
|
||||||
|
if(!alarm.getEnabled()) continue;
|
||||||
|
if(alarm.getRepetition() == 0){
|
||||||
|
activeAlarms.add(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm(
|
||||||
|
(byte) alarm.getMinute(),
|
||||||
|
(byte) alarm.getHour(),
|
||||||
|
false
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int repitition = alarm.getRepetition();
|
||||||
|
repitition = (repitition << 1) | ((repitition >> 6) & 1);
|
||||||
|
activeAlarms.add(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm(
|
||||||
|
(byte) alarm.getMinute(),
|
||||||
|
(byte) alarm.getHour(),
|
||||||
|
(byte) repitition
|
||||||
|
));
|
||||||
|
}
|
||||||
|
queueWrite(new AlarmsSetRequest(activeAlarms.toArray(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm[0]), this){
|
||||||
|
@Override
|
||||||
|
public void onFilePut(boolean success) {
|
||||||
|
super.onFilePut(success);
|
||||||
|
if(success) GB.toast("successfully set alarms", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
else GB.toast("error setting alarms", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||||
|
switch (characteristic.getUuid().toString()) {
|
||||||
|
case "3dda0006-957f-7d4a-34a6-74696673696d": {
|
||||||
|
handleBackgroundCharacteristic(characteristic);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "3dda0002-957f-7d4a-34a6-74696673696d":
|
||||||
|
case "3dda0004-957f-7d4a-34a6-74696673696d":
|
||||||
|
case "3dda0003-957f-7d4a-34a6-74696673696d": {
|
||||||
|
if (fossilRequest != null) {
|
||||||
|
boolean requestFinished;
|
||||||
|
try {
|
||||||
|
if (characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")) {
|
||||||
|
byte requestType = (byte) (characteristic.getValue()[0] & 0x0F);
|
||||||
|
|
||||||
|
if (requestType != 0x0A && requestType != fossilRequest.getType()) {
|
||||||
|
// throw new RuntimeException("Answer type " + requestType + " does not match current request " + fossilRequest.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fossilRequest.handleResponse(characteristic);
|
||||||
|
requestFinished = fossilRequest.isFinished();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
getDeviceSupport().notifiyException(e);
|
||||||
|
GB.toast(fossilRequest.getName() + " failed", Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
requestFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestFinished) {
|
||||||
|
log(fossilRequest.getName() + " finished");
|
||||||
|
fossilRequest = null;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queueNextRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
switch (value[1]) {
|
||||||
|
case 2: {
|
||||||
|
byte syncId = value[2];
|
||||||
|
getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(QHybridSupport.ITEM_LAST_HEARTBEAT, DateFormat.getTimeInstance().format(new Date())));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
if (value.length != 12) {
|
||||||
|
throw new RuntimeException("wrong button message");
|
||||||
|
}
|
||||||
|
int index = value[2] & 0xFF;
|
||||||
|
int button = value[9] >> 4 & 0xFF;
|
||||||
|
|
||||||
|
if (index != this.lastButtonIndex) {
|
||||||
|
lastButtonIndex = index;
|
||||||
|
log("Button press on button " + button);
|
||||||
|
|
||||||
|
Intent i = new Intent(QHYBRID_EVENT_BUTTON_PRESS);
|
||||||
|
i.putExtra("BUTTON", button);
|
||||||
|
getContext().sendBroadcast(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5: {
|
||||||
|
if (value.length != 4) {
|
||||||
|
throw new RuntimeException("wrong button message");
|
||||||
|
}
|
||||||
|
int action = value[3];
|
||||||
|
|
||||||
|
String actionString = "SINGLE";
|
||||||
|
if(action == 3) actionString = "DOUBLE";
|
||||||
|
else if(action == 4) actionString = "LONG";
|
||||||
|
|
||||||
|
// lastButtonIndex = index;
|
||||||
|
log(actionString + " button press");
|
||||||
|
|
||||||
|
Intent i = new Intent(QHYBRID_EVENT_MULTI_BUTTON_PRESS);
|
||||||
|
i.putExtra("ACTION", actionString);
|
||||||
|
getContext().sendBroadcast(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
super.onMtuChanged(gatt, mtu, status);
|
||||||
|
|
||||||
|
log("MTU changed: " + mtu);
|
||||||
|
|
||||||
|
this.MTU = mtu;
|
||||||
|
|
||||||
|
getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(ITEM_MTU, String.valueOf(mtu)));
|
||||||
|
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
|
|
||||||
|
((RequestMtuRequest) fossilRequest).setFinished(true);
|
||||||
|
queueNextRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queueWrite(RequestMtuRequest request, boolean priorise) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
new TransactionBuilder("requestMtu")
|
||||||
|
.requestMtu(512)
|
||||||
|
.queue(getDeviceSupport().getQueue());
|
||||||
|
|
||||||
|
this.fossilRequest = request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String message) {
|
||||||
|
logger.debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queueWrite(SetDeviceStateRequest request, boolean priorise) {
|
||||||
|
if (fossilRequest != null && !fossilRequest.isFinished()) {
|
||||||
|
log("queing request: " + request.getName());
|
||||||
|
if (priorise) {
|
||||||
|
requestQueue.add(0, request);
|
||||||
|
} else {
|
||||||
|
requestQueue.add(request);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("setting device state: " + request.getDeviceState());
|
||||||
|
getDeviceSupport().getDevice().setState(request.getDeviceState());
|
||||||
|
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
|
queueNextRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queueWrite(FossilRequest request, boolean priorise) {
|
||||||
|
if (fossilRequest != null && !fossilRequest.isFinished()) {
|
||||||
|
log("queing request: " + request.getName());
|
||||||
|
if (priorise) {
|
||||||
|
requestQueue.add(0, request);
|
||||||
|
} else {
|
||||||
|
requestQueue.add(request);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("executing request: " + request.getName());
|
||||||
|
this.fossilRequest = request;
|
||||||
|
new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queueWrite(Request request, boolean priorise) {
|
||||||
|
new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
|
||||||
|
|
||||||
|
queueNextRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void queueWrite(Request request) {
|
||||||
|
if (request instanceof SetDeviceStateRequest)
|
||||||
|
queueWrite((SetDeviceStateRequest) request, false);
|
||||||
|
else if (request instanceof RequestMtuRequest)
|
||||||
|
queueWrite((RequestMtuRequest) request, false);
|
||||||
|
else if (request instanceof FossilRequest) queueWrite((FossilRequest) request, false);
|
||||||
|
else queueWrite(request, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queueNextRequest() {
|
||||||
|
try {
|
||||||
|
Request request = requestQueue.remove(0);
|
||||||
|
queueWrite(request);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
log("requestsQueue empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,483 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ActivityPointGetRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.AnimationRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.BatteryLevelRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.DownloadFileRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.EraseFileRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.FileRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetCountdownSettingsRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetCurrentStepCountRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetStepGoalRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetVibrationStrengthRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GoalTrackingGetRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ListFilesRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.OTAEnterRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.OTAEraseRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ReleaseHandsControlRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.RequestHandControlRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetCurrentStepCountRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetStepGoalRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetTimeRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetVibrationStrengthRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.UploadFileRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.VibrateRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_ACTIVITY_POINT;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_STEP_COUNT;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_STEP_GOAL;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_VIBRATION_STRENGTH;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_FILE_UPLOADED;
|
||||||
|
|
||||||
|
public class MisfitWatchAdapter extends WatchAdapter {
|
||||||
|
private int lastButtonIndex = -1;
|
||||||
|
private final SparseArray<Request> responseFilters = new SparseArray<>();
|
||||||
|
|
||||||
|
private UploadFileRequest uploadFileRequest;
|
||||||
|
private Request fileRequest = null;
|
||||||
|
|
||||||
|
private Queue<Request> requestQueue = new ArrayDeque<>();
|
||||||
|
|
||||||
|
Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
public MisfitWatchAdapter(QHybridSupport deviceSupport) {
|
||||||
|
super(deviceSupport);
|
||||||
|
|
||||||
|
fillResponseList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
requestQueue.add(new GetStepGoalRequest());
|
||||||
|
requestQueue.add(new GetVibrationStrengthRequest());
|
||||||
|
requestQueue.add(new ActivityPointGetRequest());
|
||||||
|
requestQueue.add(prepareSetTimeRequest());
|
||||||
|
requestQueue.add(new AnimationRequest());
|
||||||
|
requestQueue.add(new SetCurrentStepCountRequest((int) (999999 * getDeviceSupport().calculateNotificationProgress())));
|
||||||
|
|
||||||
|
queueWrite(new GetCurrentStepCountRequest());
|
||||||
|
|
||||||
|
getDeviceSupport().getDevice().setState(GBDevice.State.INITIALIZED);
|
||||||
|
getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private SetTimeRequest prepareSetTimeRequest() {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
TimeZone zone = new GregorianCalendar().getTimeZone();
|
||||||
|
return new SetTimeRequest(
|
||||||
|
(int) (millis / 1000 + getDeviceSupport().getTimeOffset() * 60),
|
||||||
|
(short) (millis % 1000),
|
||||||
|
(short) ((zone.getRawOffset() + zone.getDSTSavings()) / 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playPairingAnimation() {
|
||||||
|
queueWrite(new AnimationRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playNotification(NotificationConfiguration config) {
|
||||||
|
queueWrite(new PlayNotificationRequest(
|
||||||
|
config.getVibration(),
|
||||||
|
config.getHour(),
|
||||||
|
config.getMin(),
|
||||||
|
config.getSubEye()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime() {
|
||||||
|
queueWrite(prepareSetTimeRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||||
|
GBDevice gbDevice = getDeviceSupport().getDevice();
|
||||||
|
switch (characteristic.getUuid().toString()) {
|
||||||
|
case "3dda0004-957f-7d4a-34a6-74696673696d":
|
||||||
|
case "3dda0003-957f-7d4a-34a6-74696673696d": {
|
||||||
|
return handleFileDownloadCharacteristic(characteristic);
|
||||||
|
}
|
||||||
|
case "3dda0007-957f-7d4a-34a6-74696673696d": {
|
||||||
|
return handleFileUploadCharacteristic(characteristic);
|
||||||
|
}
|
||||||
|
case "3dda0002-957f-7d4a-34a6-74696673696d": {
|
||||||
|
return handleBasicCharacteristic(characteristic);
|
||||||
|
}
|
||||||
|
case "3dda0006-957f-7d4a-34a6-74696673696d": {
|
||||||
|
return handleButtonCharacteristic(characteristic);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
getDeviceSupport().handleGBDeviceEvent(batteryInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
log("unknown shit on " + characteristic.getUuid().toString() + ": " + arrayToString(characteristic.getValue()));
|
||||||
|
try {
|
||||||
|
File charLog = new File("/sdcard/qFiles/charLog.txt");
|
||||||
|
if (!charLog.exists()) {
|
||||||
|
charLog.createNewFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream fos = new FileOutputStream(charLog, true);
|
||||||
|
fos.write((new Date().toString() + ": " + characteristic.getUuid().toString() + ": " + arrayToString(characteristic.getValue())).getBytes());
|
||||||
|
} catch (IOException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getDeviceSupport().onCharacteristicChanged(gatt, characteristic);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillResponseList() {
|
||||||
|
Class<? extends Request>[] classes = new Class[]{
|
||||||
|
BatteryLevelRequest.class,
|
||||||
|
GetStepGoalRequest.class,
|
||||||
|
GetVibrationStrengthRequest.class,
|
||||||
|
GetCurrentStepCountRequest.class,
|
||||||
|
OTAEnterRequest.class,
|
||||||
|
GoalTrackingGetRequest.class,
|
||||||
|
ActivityPointGetRequest.class,
|
||||||
|
GetCountdownSettingsRequest.class
|
||||||
|
};
|
||||||
|
for (Class<? extends Request> c : classes) {
|
||||||
|
try {
|
||||||
|
c.getSuperclass().getDeclaredMethod("handleResponse", BluetoothGattCharacteristic.class);
|
||||||
|
Request object = c.newInstance();
|
||||||
|
byte[] sequence = object.getStartSequence();
|
||||||
|
if (sequence.length > 1) {
|
||||||
|
responseFilters.put((int) object.getStartSequence()[1], object);
|
||||||
|
log("response filter " + object.getStartSequence()[1] + ": " + c.getSimpleName());
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException e) {
|
||||||
|
log("skipping class " + c.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleBasicCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] values = characteristic.getValue();
|
||||||
|
Request request = resolveAnswer(characteristic);
|
||||||
|
GBDevice gbDevice = getDeviceSupport().getDevice();
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
StringBuilder valueString = new StringBuilder(String.valueOf(values[0]));
|
||||||
|
for (int i = 1; i < characteristic.getValue().length; i++) {
|
||||||
|
valueString.append(", ").append(values[i]);
|
||||||
|
}
|
||||||
|
log("unable to resolve " + characteristic.getUuid().toString() + ": " + valueString);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log("response: " + request.getClass().getSimpleName());
|
||||||
|
request.handleResponse(characteristic);
|
||||||
|
|
||||||
|
if (request instanceof GetStepGoalRequest) {
|
||||||
|
gbDevice.addDeviceInfo(new GenericItem(ITEM_STEP_GOAL, String.valueOf(((GetStepGoalRequest) request).stepGoal)));
|
||||||
|
} else if (request instanceof GetVibrationStrengthRequest) {
|
||||||
|
int strength = ((GetVibrationStrengthRequest) request).strength;
|
||||||
|
gbDevice.addDeviceInfo(new GenericItem(ITEM_VIBRATION_STRENGTH, String.valueOf(strength)));
|
||||||
|
} else if (request instanceof GetCurrentStepCountRequest) {
|
||||||
|
int steps = ((GetCurrentStepCountRequest) request).steps;
|
||||||
|
logger.debug("get current steps: " + steps);
|
||||||
|
try {
|
||||||
|
File f = new File("/sdcard/qFiles/");
|
||||||
|
if (!f.exists()) f.mkdir();
|
||||||
|
|
||||||
|
File file = new File("/sdcard/qFiles/steps");
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.createNewFile();
|
||||||
|
}
|
||||||
|
logger.debug("Writing file " + file.getPath());
|
||||||
|
FileOutputStream fos = new FileOutputStream(file, true);
|
||||||
|
fos.write((System.currentTimeMillis() + ": " + steps + "\n").getBytes());
|
||||||
|
fos.close();
|
||||||
|
logger.debug("file written.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
}
|
||||||
|
gbDevice.addDeviceInfo(new GenericItem(ITEM_STEP_COUNT, String.valueOf(((GetCurrentStepCountRequest) request).steps)));
|
||||||
|
} else if (request instanceof OTAEnterRequest) {
|
||||||
|
if (((OTAEnterRequest) request).success) {
|
||||||
|
fileRequest = new OTAEraseRequest(1024 << 16);
|
||||||
|
queueWrite(fileRequest);
|
||||||
|
}
|
||||||
|
} else if (request instanceof ActivityPointGetRequest) {
|
||||||
|
gbDevice.addDeviceInfo(new GenericItem(ITEM_ACTIVITY_POINT, String.valueOf(((ActivityPointGetRequest) request).activityPoint)));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
queueWrite(requestQueue.remove());
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
}
|
||||||
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(DeviceManager.ACTION_DEVICES_CHANGED));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Request resolveAnswer(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] values = characteristic.getValue();
|
||||||
|
if (values[0] != 3) return null;
|
||||||
|
return responseFilters.get(values[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleFileDownloadCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||||
|
Request request;
|
||||||
|
request = fileRequest;
|
||||||
|
request.handleResponse(characteristic);
|
||||||
|
if (request instanceof ListFilesRequest) {
|
||||||
|
if (((ListFilesRequest) request).completed) {
|
||||||
|
logger.debug("File count: " + ((ListFilesRequest) request).fileCount + " size: " + ((ListFilesRequest) request).size);
|
||||||
|
if (((ListFilesRequest) request).fileCount == 0) return true;
|
||||||
|
// queueWrite(new DownloadFileRequest((short) (256 + ((ListFilesRequest) request).fileCount)));
|
||||||
|
}
|
||||||
|
} else if (request instanceof DownloadFileRequest) {
|
||||||
|
if (((FileRequest) request).completed) {
|
||||||
|
logger.debug("file " + ((DownloadFileRequest) request).fileHandle + " completed: " + ((DownloadFileRequest) request).size);
|
||||||
|
// backupFile((DownloadFileRequest) request);
|
||||||
|
}
|
||||||
|
} else if (request instanceof EraseFileRequest) {
|
||||||
|
if (((EraseFileRequest) request).fileHandle > 257) {
|
||||||
|
queueWrite(new DownloadFileRequest((short) (((EraseFileRequest) request).fileHandle - 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean handleFileUploadCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||||
|
if (uploadFileRequest == null) {
|
||||||
|
logger.debug("no uploadFileRequest to handle response");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadFileRequest.handleResponse(characteristic);
|
||||||
|
|
||||||
|
switch (uploadFileRequest.state) {
|
||||||
|
case ERROR:
|
||||||
|
Intent fileIntent = new Intent(QHYBRID_EVENT_FILE_UPLOADED);
|
||||||
|
fileIntent.putExtra("EXTRA_ERROR", true);
|
||||||
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(fileIntent);
|
||||||
|
uploadFileRequest = null;
|
||||||
|
break;
|
||||||
|
case UPLOAD:
|
||||||
|
for (byte[] packet : this.uploadFileRequest.packets) {
|
||||||
|
new TransactionBuilder("File upload").write(characteristic, packet).queue(getDeviceSupport().getQueue());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UPLOADED:
|
||||||
|
fileIntent = new Intent(QHYBRID_EVENT_FILE_UPLOADED);
|
||||||
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(fileIntent);
|
||||||
|
uploadFileRequest = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleButtonCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
if (value.length != 11) {
|
||||||
|
logger.debug("wrong button message");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int index = value[6] & 0xFF;
|
||||||
|
int button = value[8] >> 4 & 0xFF;
|
||||||
|
|
||||||
|
if (index != this.lastButtonIndex) {
|
||||||
|
lastButtonIndex = index;
|
||||||
|
logger.debug("Button press on button " + button);
|
||||||
|
|
||||||
|
Intent i = new Intent(QHYBRID_EVENT_BUTTON_PRESS);
|
||||||
|
i.putExtra("BUTTON", button);
|
||||||
|
|
||||||
|
//ByteBuffer buffer = ByteBuffer.allocate(16);
|
||||||
|
//buffer.put(new byte[]{0x01, 0x00, 0x08});
|
||||||
|
//buffer.put(value, 2, 8);
|
||||||
|
//buffer.put(new byte[]{(byte)0xFF, 0x05, 0x00, 0x01, 0x00});
|
||||||
|
|
||||||
|
//FilePutRequest request = new FilePutRequest((short)0, buffer.array());
|
||||||
|
//for(byte[] packet : request.packets){
|
||||||
|
// new TransactionBuilder("File upload").write(getCharacteristic(UUID.fromString("3dda0007-957f-7d4a-34a6-74696673696d")), packet).queue(getQueue());
|
||||||
|
//}
|
||||||
|
|
||||||
|
getContext().sendBroadcast(i);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String message){
|
||||||
|
logger.debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivityHand(double progress) {
|
||||||
|
queueWrite(new SetCurrentStepCountRequest(Math.min((int) (1000000 * progress), 999999)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setHands(short hour, short minute) {
|
||||||
|
queueWrite(new MoveHandsRequest(false, minute, hour, (short) -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void vibrate(PlayNotificationRequest.VibrationType vibration) {
|
||||||
|
queueWrite(new PlayNotificationRequest(vibration, -1, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void vibrateFindMyDevicePattern() {
|
||||||
|
queueWrite(new VibrateRequest(false, (short) 4, (short) 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestHandsControl() {
|
||||||
|
queueWrite(new RequestHandControlRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseHandsControl() {
|
||||||
|
queueWrite(new ReleaseHandsControlRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStepGoal(int stepGoal) {
|
||||||
|
queueWrite(new SetStepGoalRequest(stepGoal));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVibrationStrength(short strength) {
|
||||||
|
queueWrite(new SetVibrationStrengthRequest(strength));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void syncNotificationSettings() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTestNewFunction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimezoneOffsetMinutes(short offset) {
|
||||||
|
GB.toast("old firmware does't support timezones", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFindDevice() {
|
||||||
|
return supportsExtendedVibration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsExtendedVibration() {
|
||||||
|
String modelNumber = getDeviceSupport().getDevice().getModel();
|
||||||
|
switch (modelNumber) {
|
||||||
|
case "HW.0.0":
|
||||||
|
return true;
|
||||||
|
case "HL.0.0":
|
||||||
|
return false;
|
||||||
|
case "DN.1.0":
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityHand() {
|
||||||
|
String modelNumber = getDeviceSupport().getDevice().getModel();
|
||||||
|
switch (modelNumber) {
|
||||||
|
case "HW.0.0":
|
||||||
|
return true;
|
||||||
|
case "HL.0.0":
|
||||||
|
return false;
|
||||||
|
case "DN.1.0":
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFetchActivityData() {
|
||||||
|
requestQueue.add(new BatteryLevelRequest());
|
||||||
|
requestQueue.add(new GetCurrentStepCountRequest());
|
||||||
|
// requestQueue.add(new ListFilesRequest());
|
||||||
|
queueWrite(new ActivityPointGetRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||||
|
GB.toast("alarms not supported with this firmware", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overwriteButtons(String jsonConfigString) {
|
||||||
|
uploadFileRequest = new UploadFileRequest((short) 0x0800, new byte[]{
|
||||||
|
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x10, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00, (byte) 0x00, (byte) 0x20, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x30, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x2E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
|
||||||
|
(byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x0F, (byte) 0x00, (byte) 0x8B, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01,
|
||||||
|
(byte) 0x08, (byte) 0x01, (byte) 0x14, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xBF, (byte) 0xD5, (byte) 0x54, (byte) 0xD1,
|
||||||
|
(byte) 0x00
|
||||||
|
});
|
||||||
|
queueWrite(uploadFileRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queueWrite(Request request) {
|
||||||
|
new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
|
||||||
|
// if (request instanceof FileRequest) this.fileRequest = request;
|
||||||
|
|
||||||
|
if (!request.expectsResponse()) {
|
||||||
|
try {
|
||||||
|
queueWrite(requestQueue.remove());
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class ConfigFileBuilder {
|
||||||
|
private ConfigPayload[] configs;
|
||||||
|
|
||||||
|
public ConfigFileBuilder(ConfigPayload[] configs) {
|
||||||
|
this.configs = configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] build(boolean appendChecksum) {
|
||||||
|
int payloadSize = 0;
|
||||||
|
for (ConfigPayload payload : this.configs) {
|
||||||
|
payloadSize += payload.getData().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
int headerSize = 0;
|
||||||
|
for (ConfigPayload payload : this.configs) {
|
||||||
|
headerSize += payload.getHeader().length + 3; // button + version + null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(
|
||||||
|
3 // version bytes
|
||||||
|
+ 1 // header count byte
|
||||||
|
+ headerSize
|
||||||
|
+ 1 // payload count byte
|
||||||
|
+ payloadSize
|
||||||
|
+ 1 // customization count byte
|
||||||
|
+ (appendChecksum ? 4 : 0) // checksum
|
||||||
|
);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
buffer.put(new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x00}); // version
|
||||||
|
buffer.put((byte) this.configs.length);
|
||||||
|
int buttonIndex = 0x00;
|
||||||
|
for (ConfigPayload payload : configs) {
|
||||||
|
buffer.put((byte) (buttonIndex += 0x10));
|
||||||
|
buffer.put((byte) 0x01);
|
||||||
|
buffer.put(payload.getHeader());
|
||||||
|
buffer.put((byte) 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<ConfigPayload> distinctPayloads = new ArrayList<>(3);
|
||||||
|
|
||||||
|
// distinctPayloads.add(configs[0].getData());
|
||||||
|
|
||||||
|
compareLoop:
|
||||||
|
for (int payloadIndex = 0; payloadIndex < configs.length; payloadIndex++) {
|
||||||
|
for (int compareTo = 0; compareTo < distinctPayloads.size(); compareTo++) {
|
||||||
|
if (configs[payloadIndex].equals(distinctPayloads.get(compareTo))) {
|
||||||
|
continue compareLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
distinctPayloads.add(configs[payloadIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.put((byte) distinctPayloads.size());
|
||||||
|
for (ConfigPayload payload : distinctPayloads) {
|
||||||
|
buffer.put(payload.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.put((byte) 0x00);
|
||||||
|
|
||||||
|
ByteBuffer buffer2 = ByteBuffer.allocate(buffer.position() + (appendChecksum ? 4 : 0));
|
||||||
|
buffer2.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer2.put(buffer.array(), 0, buffer.position());
|
||||||
|
|
||||||
|
if (!appendChecksum) return buffer2.array();
|
||||||
|
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(buffer.array(), 0, buffer.position());
|
||||||
|
|
||||||
|
buffer2.putInt((int) crc.getValue());
|
||||||
|
|
||||||
|
return buffer2.array();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public enum ConfigPayload {
|
||||||
|
FORWARD_TO_PHONE(
|
||||||
|
"forward to phone",
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00},
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x2E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x0F, (byte) 0x00, (byte) 0x8B, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x08, (byte) 0x01, (byte) 0x14, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xBF, (byte) 0xD5, (byte) 0x54, (byte) 0xD1,}
|
||||||
|
),
|
||||||
|
FORWARD_TO_PHONE_MULTI(
|
||||||
|
"forward to phone (multifunction)",
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x00},
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x44, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xCD, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB6, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB5, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x7B, (byte) 0x56, (byte) 0x4E, (byte) 0x97}
|
||||||
|
),
|
||||||
|
STOPWATCH(
|
||||||
|
"stopwatch",
|
||||||
|
new byte[]{(byte) 0x02, (byte) 0x01, (byte) 0x20, (byte) 0x01},
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x20, (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x07, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x08, (byte) 0x00, (byte) 0x92, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x0F, (byte) 0xC0, (byte) 0x5F, (byte) 0x2A}
|
||||||
|
),
|
||||||
|
DATE(
|
||||||
|
"show date",
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x01, (byte) 0x14, (byte) 0x00},
|
||||||
|
new byte[]{(byte) 0x01 , (byte) 0x00 , (byte) 0x01 , (byte) 0x01 , (byte) 0x14 , (byte) 0x2D , (byte) 0x00 , (byte) 0x00 , (byte) 0x00 , (byte) 0x01 , (byte) 0x00 , (byte) 0x06 , (byte) 0x00 , (byte) 0x02 , (byte) 0x00 , (byte) 0x00 , (byte) 0x07 , (byte) 0x00 , (byte) 0x01 , (byte) 0x01 , (byte) 0x16 , (byte) 0x00 , (byte) 0x89 , (byte) 0x05 , (byte) 0x01 , (byte) 0x07 , (byte) 0xB0 , (byte) 0x00 , (byte) 0x00 , (byte) 0xB0 , (byte) 0x00 , (byte) 0x00 , (byte) 0xB0 , (byte) 0x00 , (byte) 0x00 , (byte) 0x08 , (byte) 0x01 , (byte) 0x50 , (byte) 0x00 , (byte) 0x01 , (byte) 0x00 , (byte) 0xD0 , (byte) 0x89 , (byte) 0xDE , (byte) 0x6E}
|
||||||
|
),
|
||||||
|
LAST_NOTIFICATION(
|
||||||
|
"show last notification",
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x01, (byte) 0x18, (byte) 0x00},
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x18, (byte) 0x2F, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x16, (byte) 0x00, (byte) 0x89, (byte) 0x05, (byte) 0x01, (byte) 0x07, (byte) 0xB0, (byte) 0x02, (byte) 0x00, (byte) 0xB0, (byte) 0x02, (byte) 0x00, (byte) 0xB0, (byte) 0x02, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x50, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x6B, (byte) 0x9D, (byte) 0x55, (byte) 0x3A}
|
||||||
|
),
|
||||||
|
SECOND_TIMEZONE(
|
||||||
|
"show second timezone",
|
||||||
|
new byte[]{0x01, (byte) 0x01, (byte) 0x16, (byte) 0x00},
|
||||||
|
new byte[]{0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x16, (byte) 0x2F, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0x02, (byte) 0x02, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x16, (byte) 0x00, (byte) 0x89, (byte) 0x05, (byte) 0x01, (byte) 0x07, (byte) 0xB0, (byte) 0x01, (byte) 0x00, (byte) 0xB0, (byte) 0x01, (byte) 0x00, (byte) 0xB0, (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x50, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x3D, (byte) 0x07, (byte) 0x28, (byte) 0x01}
|
||||||
|
)
|
||||||
|
/* PLAY_PAUSE(
|
||||||
|
"play/pause music",
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x00},
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x44, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xCD, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB6, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB5, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x7B, (byte) 0x56, (byte) 0x4E, (byte) 0x97}
|
||||||
|
),
|
||||||
|
VOLUME_UP(
|
||||||
|
"music volume up",
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x04, (byte) 0x12, (byte) 0x00},
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x04, (byte) 0x12, (byte) 0x5E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x48, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xE9, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0B, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xE9, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x04, (byte) 0x0A, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xC6, (byte) 0xB2, (byte) 0xCB, (byte) 0xAC}
|
||||||
|
),
|
||||||
|
VOLUME_DOWN(
|
||||||
|
"music volume down",
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x05, (byte) 0x12, (byte) 0x00},
|
||||||
|
new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x12, (byte) 0x5E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x48, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xEA, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0B, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xEA, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x04, (byte) 0x0A, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xFA, (byte) 0x18, (byte) 0x49, (byte) 0x03}
|
||||||
|
) */
|
||||||
|
;
|
||||||
|
private byte[] header, data;
|
||||||
|
|
||||||
|
static public ConfigPayload fromId(short id) throws RuntimeException{
|
||||||
|
for(ConfigPayload payload : ConfigPayload.values()){
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(payload.header);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
if(id == buffer.getShort(1)) return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("app " + id + " not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(ConfigPayload p1, ConfigPayload p2){
|
||||||
|
return Arrays.equals(p1.getData(), p2.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
ConfigPayload(String description, byte[] header, byte[] data) {
|
||||||
|
this.description = description;
|
||||||
|
this.header = header;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public abstract class Request {
|
||||||
|
protected byte[] data;
|
||||||
|
private Logger logger = (Logger) LoggerFactory.getLogger(getName());
|
||||||
|
//protected ByteBuffer buffer;
|
||||||
|
|
||||||
|
public Request(){
|
||||||
|
this.data = getStartSequence();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ByteBuffer createBuffer(){
|
||||||
|
return createBuffer(getPayloadLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer createBuffer(int length){
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(length);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.put(getStartSequence());
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRequestData(){
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getRequestUUID(){
|
||||||
|
return UUID.fromString("3dda0002-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPayloadLength(){ return getStartSequence().length; }
|
||||||
|
|
||||||
|
public abstract byte[] getStartSequence();
|
||||||
|
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic){};
|
||||||
|
|
||||||
|
public String getName(){
|
||||||
|
Class thisClass = getClass();
|
||||||
|
while(thisClass.isAnonymousClass()) thisClass = thisClass.getSuperclass();
|
||||||
|
return thisClass.getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void log(String message){
|
||||||
|
logger.debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBasicRequest(){
|
||||||
|
return this.getRequestUUID().toString().equals("3dda0002-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean expectsResponse(){
|
||||||
|
return this.data[0] == 1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public abstract class FossilRequest extends Request {
|
||||||
|
public abstract boolean isFinished();
|
||||||
|
|
||||||
|
public byte getType(){
|
||||||
|
return getStartSequence()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
public class RequestMtuRequest extends FossilRequest {
|
||||||
|
private int mtu;
|
||||||
|
private boolean finished = false;
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public RequestMtuRequest(int mtu) {
|
||||||
|
this.mtu = mtu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMtu() {
|
||||||
|
return mtu;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinished(boolean finished) {
|
||||||
|
this.finished = finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
|
||||||
|
public class SetDeviceStateRequest extends FossilRequest {
|
||||||
|
private GBDevice.State deviceState;
|
||||||
|
|
||||||
|
public SetDeviceStateRequest(GBDevice.State deviceState) {
|
||||||
|
this.deviceState = deviceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GBDevice.State getDeviceState() {
|
||||||
|
return deviceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class Alarm {
|
||||||
|
public final int WEEKDAY_SUNDAY = 0;
|
||||||
|
public final int WEEKDAY_MONDAY = 1;
|
||||||
|
public final int WEEKDAY_TUESDAY = 2;
|
||||||
|
public final int WEEKDAY_THURSDAY = 3;
|
||||||
|
public final int WEEKDAY_WEDNESDAY = 4;
|
||||||
|
public final int WEEKDAY_FRIDAY = 5;
|
||||||
|
public final int WEEKDAY_SATURDAY = 6;
|
||||||
|
private byte days = 0;
|
||||||
|
private byte minute, hour;
|
||||||
|
private boolean repeat;
|
||||||
|
|
||||||
|
public Alarm(byte minute, byte hour){
|
||||||
|
this.minute = minute;
|
||||||
|
this.hour = hour;
|
||||||
|
this.repeat = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Alarm(byte minute, byte hour, boolean repeat){
|
||||||
|
this.minute = minute;
|
||||||
|
this.hour = hour;
|
||||||
|
this.repeat = repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Alarm(byte minute, byte hour, byte days){
|
||||||
|
this.minute = minute;
|
||||||
|
this.hour = hour;
|
||||||
|
this.repeat = true;
|
||||||
|
this.days = days;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDayEnabled(int day, boolean enabled){
|
||||||
|
if(enabled) this.days |= 1 << day;
|
||||||
|
else this.days &= ~(1 << day);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData(){
|
||||||
|
byte first = (byte) 0xFF;
|
||||||
|
if(repeat){
|
||||||
|
first = (byte) (0x80 | this.days);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte second = (byte) this.minute;
|
||||||
|
|
||||||
|
if(repeat) second |= 0x80;
|
||||||
|
|
||||||
|
byte third = this.hour;
|
||||||
|
|
||||||
|
return new byte[]{first, second, third};
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Alarm fromBytes(byte[] bytes){
|
||||||
|
if(bytes.length != 3) throw new RuntimeException("alarm bytes length must be 3");
|
||||||
|
|
||||||
|
byte days = bytes[0];
|
||||||
|
|
||||||
|
byte minutes = (byte)(bytes[1] & 0b01111111);
|
||||||
|
boolean repeat = (bytes[1] & 0x80) == 0x80;
|
||||||
|
|
||||||
|
if(repeat) {
|
||||||
|
return new Alarm(minutes, bytes[2], days);
|
||||||
|
}
|
||||||
|
return new Alarm(minutes, bytes[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String description = this.hour + ":" + this.minute + " ";
|
||||||
|
if(repeat){
|
||||||
|
String[] dayNames = new String[]{"sunday", "monday", "tuesday", "thursday", "wednesday", "friday", "saturday"};
|
||||||
|
for(int i = WEEKDAY_SUNDAY; i <= WEEKDAY_SATURDAY; i++){
|
||||||
|
if((days & 1 << i) != 0){
|
||||||
|
description += dayNames[i] + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
description += "not repeating";
|
||||||
|
}
|
||||||
|
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupAndGetRequest;
|
||||||
|
|
||||||
|
public class AlarmsGetRequest extends FileLookupAndGetRequest {
|
||||||
|
public AlarmsGetRequest(FossilWatchAdapter adapter) {
|
||||||
|
super((byte) 0x0A, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleFileData(byte[] fileData) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(fileData);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
short handle = buffer.getShort(0);
|
||||||
|
if(handle != (short) 0x0A00) throw new RuntimeException("wrong alarm handle");
|
||||||
|
|
||||||
|
int length = buffer.getInt(8) / 3;
|
||||||
|
Alarm[] alarms = new Alarm[length];
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++){
|
||||||
|
buffer.position(12 + i * 3);
|
||||||
|
byte[] alarmBytes = new byte[]{
|
||||||
|
buffer.get(),
|
||||||
|
buffer.get(),
|
||||||
|
buffer.get()
|
||||||
|
};
|
||||||
|
alarms[i] = Alarm.fromBytes(alarmBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleAlarms(alarms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleAlarms(Alarm[] alarms){
|
||||||
|
Alarm[] alarms2 = new Alarm[alarms.length];
|
||||||
|
|
||||||
|
for(int i = 0; i < alarms.length; i++){
|
||||||
|
alarms2[i] = Alarm.fromBytes(alarms[i].getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
|
||||||
|
|
||||||
|
public class AlarmsSetRequest extends FilePutRequest {
|
||||||
|
public AlarmsSetRequest(Alarm[] alarms, FossilWatchAdapter adapter) {
|
||||||
|
super((short) 0x0A00, createFileFromAlarms(alarms), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] createFileFromAlarms(Alarm[] alarms){
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(alarms.length * 3);
|
||||||
|
for(Alarm alarm : alarms) buffer.put(alarm.getData());
|
||||||
|
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.button;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
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.requests.fossil.file.FileGetRequest;
|
||||||
|
|
||||||
|
public class ButtonConfigurationGetRequest extends FileGetRequest {
|
||||||
|
public ButtonConfigurationGetRequest(FossilWatchAdapter adapter) {
|
||||||
|
super((short) 0x0600, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleFileData(byte[] fileData) {
|
||||||
|
log("fileData");
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(fileData);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
short fileHandle = buffer.getShort(0);
|
||||||
|
// TODO check file handle
|
||||||
|
// if(fileData != )
|
||||||
|
|
||||||
|
byte count = buffer.get(15);
|
||||||
|
|
||||||
|
ConfigPayload[] configs = new ConfigPayload[count];
|
||||||
|
|
||||||
|
buffer.position(16);
|
||||||
|
for(int i = 0; i < count; i++){
|
||||||
|
int buttonIndex = buffer.get() >> 4;
|
||||||
|
int entryCount = buffer.get();
|
||||||
|
buffer.get();
|
||||||
|
short appId = buffer.getShort();
|
||||||
|
|
||||||
|
buffer.position(buffer.position() + entryCount * 5 - 3);
|
||||||
|
|
||||||
|
try {
|
||||||
|
configs[buttonIndex - 1] = ConfigPayload.fromId(appId);
|
||||||
|
}catch (RuntimeException e){
|
||||||
|
configs[buttonIndex - 1] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onConfigurationsGet(configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onConfigurationsGet(ConfigPayload[] configs){}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||||
|
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.requests.fossil.file.FileGetRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupAndGetRequest;
|
||||||
|
|
||||||
|
public class ConfigurationGetRequest extends FileGetRequest {
|
||||||
|
public ConfigurationGetRequest(FossilWatchAdapter adapter) {
|
||||||
|
super((byte) 8, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleFileData(byte[] fileData) {
|
||||||
|
byte[] data = new byte[fileData.length - 12 - 4];
|
||||||
|
|
||||||
|
System.arraycopy(fileData, 12, data, 0, data.length);
|
||||||
|
log("config file: " + getAdapter().arrayToString(fileData));
|
||||||
|
log("config file: " + getAdapter().arrayToString(data));
|
||||||
|
|
||||||
|
GBDevice device = getAdapter().getDeviceSupport().getDevice();
|
||||||
|
|
||||||
|
ConfigurationPutRequest.ConfigItem[] items = ConfigurationPutRequest.parsePayload(data);
|
||||||
|
for(ConfigurationPutRequest.ConfigItem item : items){
|
||||||
|
if(item instanceof ConfigurationPutRequest.VibrationStrengthConfigItem){
|
||||||
|
device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_VIBRATION_STRENGTH, String.valueOf(((ConfigurationPutRequest.VibrationStrengthConfigItem) item).getValue())));
|
||||||
|
}else if(item instanceof ConfigurationPutRequest.DailyStepGoalConfigItem){
|
||||||
|
device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_GOAL, String.valueOf(((ConfigurationPutRequest.DailyStepGoalConfigItem) item).getValue())));
|
||||||
|
}else if(item instanceof ConfigurationPutRequest.CurrentStepCountConfigItem){
|
||||||
|
device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_COUNT, String.valueOf(((ConfigurationPutRequest.CurrentStepCountConfigItem) item).getValue())));
|
||||||
|
}else if(item instanceof ConfigurationPutRequest.TimezoneOffsetConfigItem) {
|
||||||
|
device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_TIMEZONE_OFFSET, String.valueOf(((ConfigurationPutRequest.TimezoneOffsetConfigItem) item).getValue())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device.sendDeviceUpdateIntent(getAdapter().getContext());
|
||||||
|
|
||||||
|
handleConfigurationLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleConfigurationLoaded(){}
|
||||||
|
}
|
@ -0,0 +1,263 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
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.requests.fossil.file.FileCloseAndPutRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class ConfigurationPutRequest extends FilePutRequest {
|
||||||
|
private static HashMap<Short, Class<? extends ConfigItem>> itemsById = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
itemsById.put((short)3, DailyStepGoalConfigItem.class);
|
||||||
|
itemsById.put((short)10, VibrationStrengthConfigItem.class);
|
||||||
|
itemsById.put((short)2, CurrentStepCountConfigItem.class);
|
||||||
|
itemsById.put((short)3, DailyStepGoalConfigItem.class);
|
||||||
|
itemsById.put((short)12, TimeConfigItem.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConfigItem[] parsePayload(byte[] data) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
ArrayList<ConfigItem> configItems = new ArrayList<>();
|
||||||
|
|
||||||
|
while(buffer.hasRemaining()){
|
||||||
|
short id = buffer.getShort();
|
||||||
|
byte length = buffer.get();
|
||||||
|
byte[] payload = new byte[length];
|
||||||
|
|
||||||
|
for(int i = 0; i < length; i++){
|
||||||
|
payload[i] = buffer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends ConfigItem> configClass = itemsById.get(id);
|
||||||
|
if(configClass == null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ConfigItem item = null;
|
||||||
|
try {
|
||||||
|
item = configClass.newInstance();
|
||||||
|
} catch (IllegalAccessException | InstantiationException e) {
|
||||||
|
GB.log("error", GB.ERROR, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.parseData(payload);
|
||||||
|
|
||||||
|
configItems.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return configItems.toArray(new ConfigItem[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationPutRequest(ConfigItem item, FossilWatchAdapter adapter) {
|
||||||
|
super((short) 0x0800, createFileContent(new ConfigItem[]{item}), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationPutRequest(ConfigItem[] items, FossilWatchAdapter 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class ConfigItem {
|
||||||
|
public abstract int getItemSize();
|
||||||
|
|
||||||
|
public abstract short getId();
|
||||||
|
|
||||||
|
public abstract byte[] getContent();
|
||||||
|
|
||||||
|
public abstract void parseData(byte[] data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public class GenericConfigItem<T> extends ConfigItem {
|
||||||
|
private T value;
|
||||||
|
private short configId;
|
||||||
|
|
||||||
|
public GenericConfigItem(short configId, T value) {
|
||||||
|
this.value = value;
|
||||||
|
this.configId = configId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue(){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemSize() {
|
||||||
|
switch (value.getClass().getName()) {
|
||||||
|
case "java.lang.Byte":
|
||||||
|
return 1;
|
||||||
|
case "java.lang.Short":
|
||||||
|
return 2;
|
||||||
|
case "java.lang.Integer":
|
||||||
|
return 4;
|
||||||
|
case "java.lang.Long":
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("config type " + value.getClass().getName() + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getId() {
|
||||||
|
return this.configId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getContent() {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(getItemSize());
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
switch (value.getClass().getName()) {
|
||||||
|
case "java.lang.Byte": {
|
||||||
|
buffer.put((Byte) this.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "java.lang.Integer": {
|
||||||
|
buffer.putInt((Integer) this.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "java.lang.Long": {
|
||||||
|
buffer.putLong((Long) this.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "java.lang.Short": {
|
||||||
|
buffer.putShort((Short) this.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseData(byte[] data) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
switch (data.length){
|
||||||
|
case 1:{
|
||||||
|
this.value = (T) (Byte) buffer.get();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2:{
|
||||||
|
this.value = (T) (Short) buffer.getShort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4:{
|
||||||
|
this.value = (T) (Integer) buffer.getInt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 8:{
|
||||||
|
this.value = (T) (Long) buffer.getLong();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public class DailyStepGoalConfigItem extends GenericConfigItem<Integer> {
|
||||||
|
public DailyStepGoalConfigItem(){
|
||||||
|
this(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DailyStepGoalConfigItem(int value) {
|
||||||
|
super((short) 3, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public class TimezoneOffsetConfigItem extends GenericConfigItem<Short> {
|
||||||
|
public TimezoneOffsetConfigItem(Short value) {
|
||||||
|
super((short) 17, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public class VibrationStrengthConfigItem extends GenericConfigItem<Byte> {
|
||||||
|
public VibrationStrengthConfigItem(){
|
||||||
|
this((byte) -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VibrationStrengthConfigItem(Byte value) {
|
||||||
|
super((short) 10, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public class CurrentStepCountConfigItem extends GenericConfigItem<Integer> {
|
||||||
|
public CurrentStepCountConfigItem(){
|
||||||
|
this(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurrentStepCountConfigItem(Integer value) {
|
||||||
|
super((short) 2, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public class TimeConfigItem extends ConfigItem {
|
||||||
|
private int epochSeconds;
|
||||||
|
private short millis, offsetMinutes;
|
||||||
|
|
||||||
|
public TimeConfigItem(){
|
||||||
|
this(-1, (short) -1, (short) -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeConfigItem(int epochSeconds, short millis, short offsetMinutes) {
|
||||||
|
this.epochSeconds = epochSeconds;
|
||||||
|
this.millis = millis;
|
||||||
|
this.offsetMinutes = offsetMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemSize() {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getId() {
|
||||||
|
return (short) 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getContent() {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(getItemSize());
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.putInt(this.epochSeconds);
|
||||||
|
buffer.putShort(millis);
|
||||||
|
buffer.putShort(offsetMinutes);
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseData(byte[] data) {
|
||||||
|
if(data.length != 8) throw new RuntimeException("wrong data");
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
this.epochSeconds = buffer.getInt();
|
||||||
|
this.millis = buffer.getShort();
|
||||||
|
this.offsetMinutes = buffer.getShort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.connection;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||||
|
|
||||||
|
public class SetConnectionParametersRequest extends FossilRequest {
|
||||||
|
private boolean finished = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0002-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
this.finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{0x02, 0x09, 0x0C, 0x00, 0x0C, 0x00, 0x2D, 0x00, 0x58, 0x02};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBasicRequest() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
|
||||||
|
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.NotificationFilterPutRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class FileCloseAndPutRequest extends FileCloseRequest {
|
||||||
|
FossilWatchAdapter adapter;
|
||||||
|
byte[] data;
|
||||||
|
|
||||||
|
public FileCloseAndPutRequest(short fileHandle, byte[] data, FossilWatchAdapter adapter) {
|
||||||
|
super(fileHandle);
|
||||||
|
this.adapter = adapter;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepare() {
|
||||||
|
super.onPrepare();
|
||||||
|
adapter.queueWrite(new FilePutRequest(getHandle(), this.data, adapter) {
|
||||||
|
@Override
|
||||||
|
public void onFilePut(boolean success) {
|
||||||
|
super.onFilePut(success);
|
||||||
|
FileCloseAndPutRequest.this.onFilePut(success);
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFilePut(boolean success){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||||
|
|
||||||
|
public class FileCloseRequest extends FossilRequest {
|
||||||
|
private boolean isFinished = false;
|
||||||
|
private short handle;
|
||||||
|
|
||||||
|
public FileCloseRequest(short fileHandle) {
|
||||||
|
this.handle = fileHandle;
|
||||||
|
ByteBuffer buffer = this.createBuffer();
|
||||||
|
buffer.putShort(fileHandle);
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
|
||||||
|
if(!characteristic.getUuid().toString().equals(this.getRequestUUID().toString())){
|
||||||
|
throw new RuntimeException("wrong response UUID");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
byte type = (byte)(value[0] & 0x0F);
|
||||||
|
|
||||||
|
if(type != 9) throw new RuntimeException("wrong response type");
|
||||||
|
|
||||||
|
if(value.length != 4) throw new RuntimeException("wrong response length");
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
if(this.handle != buffer.getShort(1)) throw new RuntimeException("wrong response handle");
|
||||||
|
|
||||||
|
byte status = buffer.get(3);
|
||||||
|
|
||||||
|
if(status != 0) throw new RuntimeException("wrong response status");
|
||||||
|
|
||||||
|
this.isFinished = true;
|
||||||
|
|
||||||
|
this.onPrepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPrepare(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{9};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished(){
|
||||||
|
return this.isFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||||
|
|
||||||
|
public class FileDeleteRequest extends FossilRequest {
|
||||||
|
private boolean finished = false;
|
||||||
|
private short handle;
|
||||||
|
|
||||||
|
public FileDeleteRequest(short handle) {
|
||||||
|
this.handle = handle;
|
||||||
|
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
|
||||||
|
buffer.putShort(handle);
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
if(!characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d"))
|
||||||
|
throw new RuntimeException("wrong response UUID");
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
if(value.length != 4) throw new RuntimeException("wrong response length");
|
||||||
|
|
||||||
|
if(value[0] != (byte) 0x8B) throw new RuntimeException("wrong response start");
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
if(buffer.getShort(1) != this.handle) throw new RuntimeException("wrong response handle");
|
||||||
|
|
||||||
|
if(buffer.get(3) != 0) throw new RuntimeException("wrong response status: " + buffer.get(3));
|
||||||
|
|
||||||
|
this.finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{(byte) 0x0B};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
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.requests.Request;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||||
|
|
||||||
|
public abstract class FileGetRequest extends FossilRequest {
|
||||||
|
private short handle;
|
||||||
|
private FossilWatchAdapter adapter;
|
||||||
|
|
||||||
|
private ByteBuffer fileBuffer;
|
||||||
|
|
||||||
|
private byte[] fileData;
|
||||||
|
|
||||||
|
private boolean finished = false;
|
||||||
|
|
||||||
|
public FileGetRequest(short handle, FossilWatchAdapter adapter) {
|
||||||
|
this.handle = handle;
|
||||||
|
this.adapter = adapter;
|
||||||
|
|
||||||
|
this.data =
|
||||||
|
createBuffer()
|
||||||
|
.putShort(handle)
|
||||||
|
.putInt(0)
|
||||||
|
.putInt(0xFFFFFFFF)
|
||||||
|
.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FossilWatchAdapter getAdapter() {
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished(){
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
byte first = value[0];
|
||||||
|
if(characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")){
|
||||||
|
if((first & 0x0F) == 1){
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
short handle = buffer.getShort(1);
|
||||||
|
int size = buffer.getInt(4);
|
||||||
|
|
||||||
|
byte status = buffer.get(3);
|
||||||
|
|
||||||
|
if(status != 0){
|
||||||
|
throw new RuntimeException("FileGet error: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.handle != handle){
|
||||||
|
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||||
|
}
|
||||||
|
log("file size: " + size);
|
||||||
|
fileBuffer = ByteBuffer.allocate(size);
|
||||||
|
}else if((first & 0x0F) == 8){
|
||||||
|
this.finished = true;
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
short handle = buffer.getShort(1);
|
||||||
|
if(this.handle != handle){
|
||||||
|
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(this.fileData);
|
||||||
|
|
||||||
|
int crcExpected = buffer.getInt(8);
|
||||||
|
|
||||||
|
if((int) crc.getValue() != crcExpected){
|
||||||
|
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleFileData(this.fileData);
|
||||||
|
}
|
||||||
|
}else if(characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")){
|
||||||
|
fileBuffer.put(value, 1, value.length - 1);
|
||||||
|
if((first & 0x80) == 0x80){
|
||||||
|
this.fileData = fileBuffer.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public void handleFileData(byte[] fileData);
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
|
||||||
|
public abstract class FileLookupAndGetRequest extends FileLookupRequest {
|
||||||
|
public FileLookupAndGetRequest(byte fileType, FossilWatchAdapter adapter) {
|
||||||
|
super(fileType, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleFileLookup(short fileHandle){
|
||||||
|
getAdapter().queueWrite(new FileGetRequest(getHandle(), getAdapter()) {
|
||||||
|
@Override
|
||||||
|
public void handleFileData(byte[] fileData) {
|
||||||
|
FileLookupAndGetRequest.this.handleFileData(fileData);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public void handleFileData(byte[] fileData);
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
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.requests.Request;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||||
|
|
||||||
|
public class FileLookupRequest extends FossilRequest {
|
||||||
|
private short handle = -1;
|
||||||
|
private byte fileType;
|
||||||
|
|
||||||
|
private FossilWatchAdapter adapter;
|
||||||
|
|
||||||
|
private ByteBuffer fileBuffer;
|
||||||
|
|
||||||
|
private byte[] fileData;
|
||||||
|
|
||||||
|
protected boolean finished = false;
|
||||||
|
|
||||||
|
public FileLookupRequest(byte fileType, FossilWatchAdapter adapter) {
|
||||||
|
this.fileType = fileType;
|
||||||
|
this.adapter = adapter;
|
||||||
|
|
||||||
|
this.data =
|
||||||
|
createBuffer()
|
||||||
|
.put(fileType)
|
||||||
|
.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected FossilWatchAdapter getAdapter() {
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getHandle() {
|
||||||
|
if(!finished){
|
||||||
|
throw new UnsupportedOperationException("File lookup not finished");
|
||||||
|
}
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished(){
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
byte first = value[0];
|
||||||
|
if(characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")){
|
||||||
|
if((first & 0x0F) == 2){
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
short handle = buffer.getShort(1);
|
||||||
|
int size = buffer.getInt(4);
|
||||||
|
|
||||||
|
byte status = buffer.get(3);
|
||||||
|
|
||||||
|
if(status != 0){
|
||||||
|
throw new RuntimeException("file lookup error: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.handle != handle){
|
||||||
|
// throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||||
|
}
|
||||||
|
log("file size: " + size);
|
||||||
|
fileBuffer = ByteBuffer.allocate(size);
|
||||||
|
}else if((first & 0x0F) == 8){
|
||||||
|
this.finished = true;
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(this.fileData);
|
||||||
|
|
||||||
|
int crcExpected = buffer.getInt(8);
|
||||||
|
|
||||||
|
if((int) crc.getValue() != crcExpected){
|
||||||
|
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer dataBuffer = ByteBuffer.wrap(fileData);
|
||||||
|
dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
this.handle = dataBuffer.getShort(0);
|
||||||
|
|
||||||
|
this.handleFileLookup(this.handle);
|
||||||
|
}
|
||||||
|
}else if(characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")){
|
||||||
|
fileBuffer.put(value, 1, value.length - 1);
|
||||||
|
if((first & 0x80) == 0x80){
|
||||||
|
this.fileData = fileBuffer.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleFileLookup(short fileHandle){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, (byte) 0xFF};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,231 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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 nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.CRC32C;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class FilePutRequest extends FossilRequest {
|
||||||
|
public enum UploadState {INITIALIZED, UPLOADING, CLOSING, UPLOADED}
|
||||||
|
|
||||||
|
public UploadState state;
|
||||||
|
|
||||||
|
public ArrayList<byte[]> packets = new ArrayList<>();
|
||||||
|
|
||||||
|
private short handle;
|
||||||
|
|
||||||
|
private FossilWatchAdapter adapter;
|
||||||
|
|
||||||
|
byte[] file;
|
||||||
|
|
||||||
|
int fullCRC;
|
||||||
|
|
||||||
|
public FilePutRequest(short handle, byte[] file, FossilWatchAdapter 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);
|
||||||
|
|
||||||
|
for (byte[] packet : packets) {
|
||||||
|
transactionBuilder.write(uploadCharacteristic, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
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: " + 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: " + 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);
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||||
|
|
||||||
|
public class FileVerifyRequest extends FossilRequest {
|
||||||
|
private boolean isFinished = false;
|
||||||
|
private short handle;
|
||||||
|
|
||||||
|
public FileVerifyRequest(short fileHandle) {
|
||||||
|
this.handle = fileHandle;
|
||||||
|
ByteBuffer buffer = this.createBuffer();
|
||||||
|
buffer.putShort(fileHandle);
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
|
||||||
|
if(!characteristic.getUuid().toString().equals(this.getRequestUUID().toString())){
|
||||||
|
throw new RuntimeException("wrong response UUID");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
byte type = (byte)(value[0] & 0x0F);
|
||||||
|
|
||||||
|
if(type == 0x0A) return;
|
||||||
|
|
||||||
|
if(type != 4) throw new RuntimeException("wrong response type");
|
||||||
|
|
||||||
|
if(value.length != 4) throw new RuntimeException("wrong response length");
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
if(this.handle != buffer.getShort(1)) throw new RuntimeException("wrong response handle");
|
||||||
|
|
||||||
|
byte status = buffer.get(3);
|
||||||
|
|
||||||
|
if(status != 0) throw new RuntimeException("wrong response status");
|
||||||
|
|
||||||
|
this.isFinished = true;
|
||||||
|
|
||||||
|
this.onPrepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPrepare(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{4};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished(){
|
||||||
|
return this.isFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,160 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.microapp;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public interface MicroAppCommand {
|
||||||
|
byte[] getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StartCriticalCommand implements MicroAppCommand{
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return new byte[]{(byte) 0x03, (byte) 0x00};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CloseCommand implements MicroAppCommand{
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return new byte[]{(byte) 0x01, (byte) 0x00};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DelayCommand implements MicroAppCommand{
|
||||||
|
private double delayInSeconds;
|
||||||
|
|
||||||
|
public DelayCommand(double delayInSeconds) {
|
||||||
|
this.delayInSeconds = delayInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return ByteBuffer.wrap(new byte[]{0x08, 0x01, 0x00, 0x00})
|
||||||
|
.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
.putShort(2, (short)(delayInSeconds * 10f))
|
||||||
|
.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VibrationType{
|
||||||
|
NORMAL((byte) 0x04);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
VibrationType(byte value){
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue(){ return this.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class VibrateCommand implements MicroAppCommand{
|
||||||
|
private VibrationType vibrationType;
|
||||||
|
|
||||||
|
public VibrateCommand(VibrationType vibrationType) {
|
||||||
|
this.vibrationType = vibrationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return ByteBuffer.wrap(new byte[]{(byte) 0x93, 0x00, 0x00})
|
||||||
|
.put(2, vibrationType.getValue())
|
||||||
|
.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MovingDirection{
|
||||||
|
CLOCKWISE((byte) 0x00),
|
||||||
|
COUNTER_CLOCKWISE((byte) 0x01),
|
||||||
|
SHORTEST((byte) 0x02),
|
||||||
|
;
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
MovingDirection(byte value){ this.value = value; }
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MovingSpeed{
|
||||||
|
MAX((byte) 0x00),
|
||||||
|
HALF((byte) 0x01),
|
||||||
|
QUARTER((byte) 0x02),
|
||||||
|
EIGHTH((byte) 0x03),
|
||||||
|
SIXTEENTH((byte) 0x04),
|
||||||
|
;
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
MovingSpeed(byte value){ this.value = value; }
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StreamCommand implements MicroAppCommand{
|
||||||
|
private byte type;
|
||||||
|
|
||||||
|
public StreamCommand(byte type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return new byte[]{(byte) 0x8B, (byte) 0x00, this.type};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RepeatStartCommand implements MicroAppCommand{
|
||||||
|
private byte count;
|
||||||
|
|
||||||
|
public RepeatStartCommand(byte count) {
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return new byte[]{(byte) 0x86, (byte) 0x00, this.count};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RepeatStopCommand implements MicroAppCommand{
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return new byte[]{(byte) 0x07, (byte) 0x00};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimationCommand implements MicroAppCommand{
|
||||||
|
private short hour, minute;
|
||||||
|
private MovingDirection direction;
|
||||||
|
private MovingSpeed speed;
|
||||||
|
private byte absoluteMovementFlag;
|
||||||
|
|
||||||
|
public AnimationCommand(short hour, short minute) {
|
||||||
|
this.hour = hour;
|
||||||
|
this.minute = minute;
|
||||||
|
this.speed = MovingSpeed.MAX;
|
||||||
|
this.direction = MovingDirection.SHORTEST;
|
||||||
|
this.absoluteMovementFlag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData() {
|
||||||
|
return ByteBuffer.allocate(10)
|
||||||
|
.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
.put((byte) 0x09)
|
||||||
|
.put((byte) 0x04)
|
||||||
|
.put((byte) 0x01)
|
||||||
|
.put((byte) 0x03)
|
||||||
|
.put((byte) ((direction.getValue() << 6) | (byte)(absoluteMovementFlag << 5) | this.speed.getValue()))
|
||||||
|
.putShort(this.hour)
|
||||||
|
.put((byte) ((direction.getValue() << 6) | (byte)(absoluteMovementFlag << 5) | this.speed.getValue()))
|
||||||
|
.putShort(this.minute)
|
||||||
|
.array();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.microapp;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
|
||||||
|
|
||||||
|
public class PlayCrazyShitRequest extends FilePutRequest {
|
||||||
|
public PlayCrazyShitRequest(byte[] appData, FossilWatchAdapter adapter) {
|
||||||
|
super((short) 0x0600, createPayload(appData), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createPayload(byte[] appData) {
|
||||||
|
List<MicroAppCommand> commands = new ArrayList<>();
|
||||||
|
|
||||||
|
commands.add(new StartCriticalCommand());
|
||||||
|
// commands.add(new RepeatStartCommand((byte) 10));
|
||||||
|
commands.add(new VibrateCommand(VibrationType.NORMAL));
|
||||||
|
commands.add(new DelayCommand(1));
|
||||||
|
// commands.add(new RepeatStopCommand());
|
||||||
|
// commands.add(new StreamCommand((byte) 0b11111111));
|
||||||
|
// commands.add(new AnimationCommand((short) 300, (short) 60));
|
||||||
|
// commands.add(new DelayCommand(2));
|
||||||
|
commands.add(new CloseCommand());
|
||||||
|
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
for (MicroAppCommand command : commands) length += command.getData().length;
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(
|
||||||
|
3 /* magic bytes */
|
||||||
|
+ 8 /* button header copy */
|
||||||
|
+ 1 /* 0xFF */
|
||||||
|
+ 2 /* payload length */
|
||||||
|
+ length
|
||||||
|
+ 4 /* crc */
|
||||||
|
);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.put((byte) 0x01);
|
||||||
|
buffer.put((byte) 0x00);
|
||||||
|
buffer.put((byte) 0x08);
|
||||||
|
buffer.put(appData, 3, 8);
|
||||||
|
buffer.put((byte) 0xFF);
|
||||||
|
buffer.putShort((short)(length + 3));
|
||||||
|
|
||||||
|
for(MicroAppCommand command : commands) buffer.put(command.getData());
|
||||||
|
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(buffer.array(), 0, buffer.position());
|
||||||
|
|
||||||
|
buffer.putInt((int) crc.getValue());
|
||||||
|
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.CRC32C;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
|
||||||
|
|
||||||
|
public class NotificationFilterGetRequest extends FileGetRequest {
|
||||||
|
public NotificationFilterGetRequest(FossilWatchAdapter adapter) {
|
||||||
|
super((short) 0x0C00, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleFileData(byte[] fileData) {
|
||||||
|
log("handleFileData");
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(fileData);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
byte[] data = new byte[fileData.length - 12 - 4];
|
||||||
|
|
||||||
|
System.arraycopy(fileData, 12, data, 0, data.length);
|
||||||
|
|
||||||
|
CRC32C crc32c = new CRC32C();
|
||||||
|
crc32c.update(data);
|
||||||
|
|
||||||
|
if((int) crc32c.getValue() != buffer.getInt(fileData.length - 4)){
|
||||||
|
throw new RuntimeException("CRC invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileCloseAndPutRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
|
||||||
|
|
||||||
|
public class NotificationFilterPutRequest extends FilePutRequest {
|
||||||
|
public NotificationFilterPutRequest(NotificationConfiguration[] configs, FossilWatchAdapter adapter) {
|
||||||
|
super((short) 0x0C00, createFile(configs), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public NotificationFilterPutRequest(ArrayList<NotificationConfiguration> configs, FossilWatchAdapter adapter) {
|
||||||
|
super((short) 0x0C00, createFile(configs.toArray(new NotificationConfiguration[0])), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createFile(NotificationConfiguration[] configs){
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(configs.length * 27);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for(NotificationConfiguration config : configs){
|
||||||
|
buffer.putShort((short) 25); //packet length
|
||||||
|
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(config.getPackageName().getBytes());
|
||||||
|
|
||||||
|
buffer.put(PacketID.PACKAGE_NAME_CRC.id);
|
||||||
|
buffer.put((byte) 4);
|
||||||
|
buffer.putInt((int) crc.getValue());
|
||||||
|
|
||||||
|
buffer.put(PacketID.GROUP_ID.id);
|
||||||
|
buffer.put((byte) 1);
|
||||||
|
buffer.put((byte) 2);
|
||||||
|
|
||||||
|
buffer.put(PacketID.PRIORITY.id);
|
||||||
|
buffer.put((byte) 1);
|
||||||
|
buffer.put((byte) 0xFF);
|
||||||
|
|
||||||
|
buffer.put(PacketID.MOVEMENT.id);
|
||||||
|
buffer.put((byte) 8);
|
||||||
|
buffer.putShort(config.getHour())
|
||||||
|
.putShort(config.getMin())
|
||||||
|
.putShort(config.getSubEye())
|
||||||
|
.putShort((short) 5000);
|
||||||
|
|
||||||
|
buffer.put(PacketID.VIBRATION.id);
|
||||||
|
buffer.put((byte) 1);
|
||||||
|
buffer.put(config.getVibration().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PacketID{
|
||||||
|
PACKAGE_NAME((byte) 1),
|
||||||
|
SENDER_NAME((byte) 2),
|
||||||
|
PACKAGE_NAME_CRC((byte) 4),
|
||||||
|
GROUP_ID((byte) 128),
|
||||||
|
APP_DISPLAY_NAME((byte) 129),
|
||||||
|
PRIORITY((byte) 0xC1),
|
||||||
|
MOVEMENT((byte) 0xC2),
|
||||||
|
VIBRATION((byte) 0xC3);
|
||||||
|
|
||||||
|
byte id;
|
||||||
|
|
||||||
|
PacketID(byte id){
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VibrationType{
|
||||||
|
SINGLE_SHORT((byte) 5),
|
||||||
|
DOUBLE_SHORT((byte) 6),
|
||||||
|
TRIPLE_SHORT((byte) 7),
|
||||||
|
SINGLE_LONG((byte) 8),
|
||||||
|
SILENT((byte) 9);
|
||||||
|
|
||||||
|
byte id;
|
||||||
|
VibrationType(byte id){
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
|
||||||
|
|
||||||
|
public class PlayNotificationRequest extends FilePutRequest {
|
||||||
|
|
||||||
|
public PlayNotificationRequest(String packageName, FossilWatchAdapter adapter) {
|
||||||
|
// super((short) 0x0900, createFile("org.telegram.messenger", "org.telegram.messenger", "org.telegram.messenger"), adapter);
|
||||||
|
super((short) 0x0900, createFile(packageName), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createFile(String packageName){
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(packageName.getBytes());
|
||||||
|
return createFile(packageName, packageName, packageName, (int)crc.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createFile(String title, String sender, String message, int packageCrc) {
|
||||||
|
// return new byte[]{(byte) 0x57, (byte) 0x00, (byte) 0x0A, (byte) 0x03, (byte) 0x02, (byte) 0x04, (byte) 0x04, (byte) 0x17, (byte) 0x17, (byte) 0x17, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x49, (byte) 0x7B, (byte) 0x3B, (byte) 0x62, (byte) 0x6F, (byte) 0x72, (byte) 0x67, (byte) 0x2E, (byte) 0x74, (byte) 0x65, (byte) 0x6C, (byte) 0x65, (byte) 0x67, (byte) 0x72, (byte) 0x61, (byte) 0x6D, (byte) 0x2E, (byte) 0x6D, (byte) 0x65, (byte) 0x73, (byte) 0x73, (byte) 0x65, (byte) 0x6E, (byte) 0x67, (byte) 0x65, (byte) 0x72, (byte) 0x00, (byte) 0x6F, (byte) 0x72, (byte) 0x67, (byte) 0x2E, (byte) 0x74, (byte) 0x65, (byte) 0x6C, (byte) 0x65, (byte) 0x67, (byte) 0x72, (byte) 0x61, (byte) 0x6D, (byte) 0x2E, (byte) 0x6D, (byte) 0x65, (byte) 0x73, (byte) 0x73, (byte) 0x65, (byte) 0x6E, (byte) 0x67, (byte) 0x65, (byte) 0x72, (byte) 0x00, (byte) 0x6F, (byte) 0x72, (byte) 0x67, (byte) 0x2E, (byte) 0x74, (byte) 0x65, (byte) 0x6C, (byte) 0x65, (byte) 0x67, (byte) 0x72, (byte) 0x61, (byte) 0x6D, (byte) 0x2E, (byte) 0x6D, (byte) 0x65, (byte) 0x73, (byte) 0x73, (byte) 0x65, (byte) 0x6E, (byte) 0x67, (byte) 0x65, (byte) 0x72, (byte) 0x00};
|
||||||
|
// gwb.k(var6, "ByteBuffer.allocate(10)");
|
||||||
|
byte lengthBufferLength = (byte) 10;
|
||||||
|
byte typeId = 3;
|
||||||
|
byte flags = getFlags();
|
||||||
|
byte uidLength = (byte) 4;
|
||||||
|
byte appBundleCRCLength = (byte) 4;
|
||||||
|
String nullTerminatedTitle = terminateNull(title);
|
||||||
|
|
||||||
|
Charset charsetUTF8 = Charset.forName("UTF-8");
|
||||||
|
byte[] titleBytes = nullTerminatedTitle.getBytes(charsetUTF8);
|
||||||
|
// gwb.k(var13, "(this as java.lang.String).getBytes(charset)");
|
||||||
|
String nullTerminatedSender = terminateNull(sender);
|
||||||
|
byte[] senderBytes = nullTerminatedSender.getBytes(charsetUTF8);
|
||||||
|
// gwb.k(var15, "(this as java.lang.String).getBytes(charset)");
|
||||||
|
String nullTerminatedMessage = terminateNull(message);
|
||||||
|
byte[] messageBytes = nullTerminatedMessage.getBytes(charsetUTF8);
|
||||||
|
// gwb.k(var17, "(this as java.lang.String).getBytes(charset)");
|
||||||
|
|
||||||
|
short mainBufferLength = (short) (lengthBufferLength + uidLength + appBundleCRCLength + titleBytes.length + senderBytes.length + messageBytes.length);
|
||||||
|
|
||||||
|
ByteBuffer lengthBuffer = ByteBuffer.allocate(lengthBufferLength);
|
||||||
|
lengthBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
lengthBuffer.putShort(mainBufferLength);
|
||||||
|
lengthBuffer.put(lengthBufferLength);
|
||||||
|
lengthBuffer.put(typeId);
|
||||||
|
lengthBuffer.put(flags);
|
||||||
|
lengthBuffer.put(uidLength);
|
||||||
|
lengthBuffer.put(appBundleCRCLength);
|
||||||
|
lengthBuffer.put((byte) titleBytes.length);
|
||||||
|
lengthBuffer.put((byte) senderBytes.length);
|
||||||
|
lengthBuffer.put((byte) messageBytes.length);
|
||||||
|
|
||||||
|
ByteBuffer mainBuffer = ByteBuffer.allocate(mainBufferLength);
|
||||||
|
// gwb.k(var11, "ByteBuffer.allocate(totalLen.toInt())");
|
||||||
|
mainBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
mainBuffer.put(lengthBuffer.array());
|
||||||
|
|
||||||
|
lengthBuffer = ByteBuffer.allocate(mainBufferLength - lengthBufferLength);
|
||||||
|
// gwb.k(var6, "ByteBuffer.allocate(totalLen - headerLen)");
|
||||||
|
lengthBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
lengthBuffer.putInt(0);
|
||||||
|
lengthBuffer.putInt(packageCrc);
|
||||||
|
lengthBuffer.put(titleBytes);
|
||||||
|
lengthBuffer.put(senderBytes);
|
||||||
|
lengthBuffer.put(messageBytes);
|
||||||
|
mainBuffer.put(lengthBuffer.array());
|
||||||
|
return mainBuffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte getFlags(){
|
||||||
|
return (byte) 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String terminateNull(String input){
|
||||||
|
if(input.length() == 0){
|
||||||
|
return new String(new byte[]{(byte) 0});
|
||||||
|
}
|
||||||
|
char lastChar = input.charAt(input.length() - 1);
|
||||||
|
if(lastChar == 0) return input;
|
||||||
|
|
||||||
|
byte[] newArray = new byte[input.length() + 1];
|
||||||
|
System.arraycopy(input.getBytes(), 0, newArray, 0, input.length());
|
||||||
|
|
||||||
|
newArray[newArray.length - 1] = 0;
|
||||||
|
|
||||||
|
return new String(newArray);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class ActivityPointGetRequest extends Request {
|
||||||
|
public int activityPoint;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1, 6};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
if (value.length != 6) return;
|
||||||
|
|
||||||
|
ByteBuffer wrap = ByteBuffer.wrap(value);
|
||||||
|
wrap.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
activityPoint = wrap.getInt(2) >> 8;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class AnimationRequest extends Request {
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{(byte)2, (byte) -15, (byte)5};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class BatteryLevelRequest extends Request {
|
||||||
|
public short level = -1;
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1, 8};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
if (value.length >= 3) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
level = buffer.get(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class DownloadFileRequest extends FileRequest {
|
||||||
|
ByteBuffer buffer = null;
|
||||||
|
public byte[] file = null;
|
||||||
|
public int fileHandle;
|
||||||
|
public int size;
|
||||||
|
public long timeStamp;
|
||||||
|
|
||||||
|
public DownloadFileRequest(short handle){
|
||||||
|
init(handle, 0, 65535);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadFileRequest(short handle, int offset, int length) {
|
||||||
|
init(handle, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(short handle, int offset, int length) {
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putShort(handle);
|
||||||
|
buffer.putInt(offset);
|
||||||
|
buffer.putInt(length);
|
||||||
|
this.data = buffer.array();
|
||||||
|
this.fileHandle = handle;
|
||||||
|
this.timeStamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
byte[] data = characteristic.getValue();
|
||||||
|
if(characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")){
|
||||||
|
if(buffer == null){
|
||||||
|
buffer = ByteBuffer.allocate(4096);
|
||||||
|
ByteBuffer buffer1 = ByteBuffer.wrap(data);
|
||||||
|
buffer1.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
this.status = buffer1.get(3);
|
||||||
|
short realHandle = buffer1.getShort(1);
|
||||||
|
if(status != 0){
|
||||||
|
log("wrong status: " + status);
|
||||||
|
}else if(realHandle != fileHandle){
|
||||||
|
log("wrong handle: " + realHandle);
|
||||||
|
completed = true;
|
||||||
|
}else{
|
||||||
|
log("handle: " + realHandle);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
}else if(characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")){
|
||||||
|
buffer.put(data, 1, data.length - 1);
|
||||||
|
if((data[0] & -128) != 0){
|
||||||
|
ByteBuffer buffer1 = ByteBuffer.allocate(buffer.position());
|
||||||
|
buffer1.put(buffer.array(), 0, buffer.position());
|
||||||
|
buffer1.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
file = buffer1.array();
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(file, 0, file.length - 4);
|
||||||
|
this.size = file.length;
|
||||||
|
log("file content: " + bytesToString(file));
|
||||||
|
if(crc.getValue() != cutBits(buffer1.getInt(size - 4))){
|
||||||
|
log("checksum invalid expected: " + buffer1.getInt(size - 4) + " actual: " + crc.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long cutBits(int value) {
|
||||||
|
return value & 0b11111111111111111111111111111111L;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String bytesToString(byte[] bytes){
|
||||||
|
String s = "";
|
||||||
|
String chars = "0123456789ABCDEF";
|
||||||
|
for(byte b : bytes){
|
||||||
|
s += chars.charAt((b >> 4) & 0xF);
|
||||||
|
s += chars.charAt((b >> 0) & 0xF);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class EraseFileRequest extends FileRequest{
|
||||||
|
public short fileHandle, deletedHandle;
|
||||||
|
|
||||||
|
public EraseFileRequest(short handle) {
|
||||||
|
fileHandle = handle;
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putShort(handle);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
if(!characteristic.getUuid().toString().equals(getRequestUUID().toString())){
|
||||||
|
log("wrong descriptor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(characteristic.getValue());
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
deletedHandle = buffer.getShort(1);
|
||||||
|
status = buffer.get(3);
|
||||||
|
|
||||||
|
log("file " + deletedHandle + " erased: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{3};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class EventStreamRequest extends Request {
|
||||||
|
public EventStreamRequest(short handle) {
|
||||||
|
super();
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putShort(1, handle);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0006-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class FileRequest extends Request {
|
||||||
|
public boolean completed = false;
|
||||||
|
public int status;
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class GetCountdownSettingsRequest extends Request {
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1, 19, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
if (value.length != 14) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
long startTime = j(buffer.getInt(3));
|
||||||
|
long endTime = j(buffer.getInt(7));
|
||||||
|
byte progress = buffer.get(13);
|
||||||
|
short offset = buffer.getShort(11);
|
||||||
|
|
||||||
|
log("progress: " + progress);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static long j(final int n) {
|
||||||
|
if (n < 0) {
|
||||||
|
return 4294967296L + n;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class GetCurrentStepCountRequest extends Request {
|
||||||
|
public int steps = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1, 17};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
if (value.length >= 6) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
steps = buffer.getInt(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class GetStepGoalRequest extends Request {
|
||||||
|
public int stepGoal = -1;
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.handleResponse(characteristic);
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
if (value.length < 6) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
stepGoal = buffer.getInt(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1, 16};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class GetTripleTapEnabledRequest extends Request {
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1, 7, 3};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class GetVibrationStrengthRequest extends Request {
|
||||||
|
public int strength = -1;
|
||||||
|
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
if (value.length < 4) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
strength = (int) buffer.get(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{1, 15, 8};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class GoalTrackingGetRequest extends Request {
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
if(value.length != 5) return;
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
short id = buffer.get(3);
|
||||||
|
|
||||||
|
boolean state = buffer.get(4) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{01, 20, 01};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class GoalTrackingSetRequest extends Request {
|
||||||
|
public GoalTrackingSetRequest(int id, boolean state) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{02, 20, 01};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class ListFilesRequest extends FileRequest{
|
||||||
|
public int fileCount = -1;
|
||||||
|
public int size = 0;
|
||||||
|
private ByteBuffer buffer = null;
|
||||||
|
private int length = 0;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
String uuid = characteristic.getUuid().toString();
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
if(uuid.equals("3dda0004-957f-7d4a-34a6-74696673696d")){
|
||||||
|
buffer.put(value, 1, value.length - 1);
|
||||||
|
length += value.length - 1;
|
||||||
|
if((value[0] & -128) != 0){
|
||||||
|
ByteBuffer buffer2 = ByteBuffer.wrap(buffer.array(), 0, length);
|
||||||
|
buffer2.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
fileCount = buffer2.get(0);
|
||||||
|
size = buffer2.getInt(1);
|
||||||
|
}
|
||||||
|
}else if(uuid.equals("3dda0003-957f-7d4a-34a6-74696673696d")){
|
||||||
|
if(buffer == null){
|
||||||
|
buffer = ByteBuffer.allocate(128);
|
||||||
|
}else{
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{(byte)5};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class MoveHandsRequest extends Request {
|
||||||
|
public MoveHandsRequest(boolean moveRelative, short degreesMin, short degreesHour, short degreesSub){
|
||||||
|
init(moveRelative, degreesMin, degreesHour, degreesSub);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(boolean moveRelative, short degreesMin, short degreesHour, short degreesSub) {
|
||||||
|
int count = 0;
|
||||||
|
if(degreesHour != -1) count++;
|
||||||
|
if(degreesMin != -1) count++;
|
||||||
|
if(degreesSub != -1) count++;
|
||||||
|
|
||||||
|
ByteBuffer buffer = createBuffer(count * 5 + 5);
|
||||||
|
buffer.put(moveRelative ? 1 : (byte)2);
|
||||||
|
buffer.put((byte)count);
|
||||||
|
|
||||||
|
if(degreesHour > -1){
|
||||||
|
buffer.put((byte)1);
|
||||||
|
buffer.putShort(degreesHour);
|
||||||
|
buffer.put((byte)3);
|
||||||
|
buffer.put((byte)1);
|
||||||
|
}
|
||||||
|
if(degreesMin > -1){
|
||||||
|
buffer.put((byte)2);
|
||||||
|
buffer.putShort(degreesMin);
|
||||||
|
buffer.put((byte)3);
|
||||||
|
buffer.put((byte)1);
|
||||||
|
}
|
||||||
|
if(degreesSub > -1){
|
||||||
|
buffer.put((byte)3);
|
||||||
|
buffer.putShort(degreesSub);
|
||||||
|
buffer.put((byte)3);
|
||||||
|
buffer.put((byte)1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 21, 3};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class OTAEnterRequest extends Request {
|
||||||
|
public boolean success = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, -15, 8};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] result = characteristic.getValue();
|
||||||
|
success = result[2] == 9;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class OTAEraseRequest extends Request {
|
||||||
|
|
||||||
|
public OTAEraseRequest(int pageOffset) {
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putShort((short) 23131);
|
||||||
|
buffer.putInt(pageOffset);
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{18};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public UUID getRequestUUID(){
|
||||||
|
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] bytes = characteristic.getValue();
|
||||||
|
final ByteBuffer wrap = ByteBuffer.wrap(bytes);
|
||||||
|
wrap.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
short fileHandle = wrap.getShort(1);
|
||||||
|
byte status = wrap.get(3);
|
||||||
|
int sizeWritten = wrap.getInt(4);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class OTAResetRequest extends Request {
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, -15, 10};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class PlayNotificationRequest extends Request {
|
||||||
|
|
||||||
|
public enum VibrationType{
|
||||||
|
SINGLE_SHORT(3),
|
||||||
|
DOUBLE_SHORT(2),
|
||||||
|
TRIPLE_SHORT(1),
|
||||||
|
SINGLE_NORMAL(5),
|
||||||
|
DOUBLE_NORMAL(6),
|
||||||
|
TRIPLE_NORMAL(7),
|
||||||
|
SINGLE_LONG(8),
|
||||||
|
NO_VIBE(9);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
VibrationType(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VibrationType fromValue(byte value){
|
||||||
|
for(VibrationType type : values()){
|
||||||
|
if(type.getValue() == value) return type;
|
||||||
|
}
|
||||||
|
throw new InvalidParameterException("vibration Type not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayNotificationRequest(VibrationType vibrationType, int degreesHour, int degreesMins, int degreesActivityHand){
|
||||||
|
int length = 0;
|
||||||
|
if(degreesHour > -1) length++;
|
||||||
|
if(degreesMins > -1) length++;
|
||||||
|
if(degreesActivityHand > -1) length++;
|
||||||
|
ByteBuffer buffer = createBuffer(length * 2 + 10);
|
||||||
|
buffer.put(vibrationType.getValue());
|
||||||
|
buffer.put((byte)5);
|
||||||
|
buffer.put((byte)(length * 2 + 2));
|
||||||
|
buffer.putShort((short)0);
|
||||||
|
if(degreesHour > -1){
|
||||||
|
buffer.putShort((short) ((degreesHour % 360) | (1 << 12)));
|
||||||
|
}
|
||||||
|
if(degreesMins > -1){
|
||||||
|
buffer.putShort((short)((degreesMins % 360) | (2 << 12)));
|
||||||
|
}
|
||||||
|
if(degreesActivityHand > -1) {
|
||||||
|
buffer.putShort((short)((degreesActivityHand % 360) | (3 << 12)));
|
||||||
|
}
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public PlayNotificationRequest(VibrationType vibrationType, int degreesHour, int degreesMins){
|
||||||
|
this(vibrationType, degreesHour, degreesMins, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 7, 15, 10, 1};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class PutSettingsFileRequest extends Request {
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class ReleaseHandsControlRequest extends Request {
|
||||||
|
public ReleaseHandsControlRequest(){
|
||||||
|
super();
|
||||||
|
init((short)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(short delayBeforeRelease) {
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putShort(3, delayBeforeRelease);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 21, 2};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class RequestHandControlRequest extends Request {
|
||||||
|
public RequestHandControlRequest(byte priority, boolean moveCompleteNotify, boolean controlLostNOtify){
|
||||||
|
super();
|
||||||
|
init(priority, moveCompleteNotify, controlLostNOtify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestHandControlRequest(){
|
||||||
|
super();
|
||||||
|
init((byte)1, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(byte priority, boolean moveCompleteNotify, boolean controlLostNOtify) {
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.put(priority);
|
||||||
|
buffer.put(moveCompleteNotify ? (byte)1 : 0);
|
||||||
|
buffer.put(controlLostNOtify ? (byte)1 : 0);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 21, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SetCountdownSettings extends Request {
|
||||||
|
public SetCountdownSettings(int startTime, int endTime, short offset) {
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
|
||||||
|
buffer.putInt(startTime);
|
||||||
|
buffer.putInt(endTime);
|
||||||
|
buffer.putShort(offset);
|
||||||
|
// buff
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 19, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 13;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SetCurrentStepCountRequest extends Request {
|
||||||
|
public SetCurrentStepCountRequest(int steps){
|
||||||
|
super();
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putInt(steps);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 17};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SetCurrentTimeServiceRequest extends Request {
|
||||||
|
public SetCurrentTimeServiceRequest(int timeStampSecs, short millis, short offsetInMins){
|
||||||
|
super();
|
||||||
|
init(timeStampSecs, millis, offsetInMins);
|
||||||
|
}
|
||||||
|
private void init(int timeStampSecs, short millis, short offsetInMins){
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putInt(timeStampSecs);
|
||||||
|
buffer.putShort(millis);
|
||||||
|
buffer.putShort(offsetInMins);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 18, 2};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SetStepGoalRequest extends Request {
|
||||||
|
public SetStepGoalRequest(int goal){
|
||||||
|
super();
|
||||||
|
init(goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int goal) {
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putInt(goal);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 16};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SetTimeRequest extends Request {
|
||||||
|
public SetTimeRequest(int epochSeconds, short millis, short minutesOffset) {
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.putInt(epochSeconds);
|
||||||
|
buffer.putShort(millis);
|
||||||
|
buffer.putShort(minutesOffset);
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 4};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SetTripleTapEnabledRequest extends Request {
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 7, 3, 1};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SetVibrationStrengthRequest extends Request {
|
||||||
|
public SetVibrationStrengthRequest(short strength){
|
||||||
|
super();
|
||||||
|
init(strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int strength){
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
buffer.put((byte)strength);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 15, 8};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class SettingsFilePutRequest extends Request {
|
||||||
|
public int fileLength;
|
||||||
|
public byte[] file;
|
||||||
|
|
||||||
|
public SettingsFilePutRequest(byte[] file){
|
||||||
|
this.fileLength = file.length;
|
||||||
|
this.file = file;
|
||||||
|
ByteBuffer buffer = this.createBuffer();
|
||||||
|
buffer.putShort(1, (short)0x0800);
|
||||||
|
buffer.putInt(3, 0);
|
||||||
|
buffer.putInt(7, fileLength - 10);
|
||||||
|
buffer.putInt(11, fileLength - 10);
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{17};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0007-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class UploadFileRequest extends Request {
|
||||||
|
public enum UploadState{INITIALIZED, UPLOAD, UPLOADED, ERROR}
|
||||||
|
|
||||||
|
public UploadState state;
|
||||||
|
|
||||||
|
public ArrayList<byte[]> packets = new ArrayList<>();
|
||||||
|
|
||||||
|
public UploadFileRequest(short handle, byte[] file) {
|
||||||
|
int fileLength = file.length + 4;
|
||||||
|
ByteBuffer buffer = this.createBuffer();
|
||||||
|
buffer.putShort(1, handle);
|
||||||
|
buffer.putInt(3, 0);
|
||||||
|
buffer.putInt(7, fileLength);
|
||||||
|
buffer.putInt(11, fileLength);
|
||||||
|
|
||||||
|
this.data = buffer.array();
|
||||||
|
|
||||||
|
prepareFilePackets(file);
|
||||||
|
|
||||||
|
state = UploadState.INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
if (value.length == 4) {
|
||||||
|
if (value[1] != 0) {
|
||||||
|
state = UploadState.ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state = UploadState.UPLOAD;
|
||||||
|
}else if(value.length == 9){
|
||||||
|
if(value[1] != 0){
|
||||||
|
state = UploadState.ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state = UploadState.UPLOADED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareFilePackets(byte[] file) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(file.length + 4);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
buffer.put(file);
|
||||||
|
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(file);
|
||||||
|
buffer.putInt((int) crc.getValue());
|
||||||
|
|
||||||
|
byte[] fileFull = buffer.array();
|
||||||
|
for (int i = 0, sequence = 0; i < fileFull.length + 4; i += 18, sequence++) {
|
||||||
|
byte[] packet;
|
||||||
|
if (i + 18 >= fileFull.length) {
|
||||||
|
packet = new byte[fileFull.length - i + 2];
|
||||||
|
System.arraycopy(fileFull, i, packet, 2, fileFull.length - i);
|
||||||
|
} else {
|
||||||
|
packet = new byte[20];
|
||||||
|
System.arraycopy(fileFull, i, packet, 2, 18);
|
||||||
|
}
|
||||||
|
packet[0] = 0x12;
|
||||||
|
packet[1] = (byte) sequence;
|
||||||
|
packets.add(packet);
|
||||||
|
}
|
||||||
|
packets.get(0)[1] |= 0x40;
|
||||||
|
if (packets.size() > 1) {
|
||||||
|
packets.get(packets.size() - 1)[1] |= 0x80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{17};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getRequestUUID() {
|
||||||
|
return UUID.fromString("3dda0007-957f-7d4a-34a6-74696673696d");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||||
|
|
||||||
|
public class VibrateRequest extends Request {
|
||||||
|
|
||||||
|
public VibrateRequest(boolean longVibration, short repeats, short millisBetween){
|
||||||
|
ByteBuffer buffer = createBuffer();
|
||||||
|
|
||||||
|
buffer.put(longVibration ? (byte)1 : 0);
|
||||||
|
buffer.put((byte) repeats);
|
||||||
|
buffer.putShort(millisBetween);
|
||||||
|
this.data = buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPayloadLength() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getStartSequence() {
|
||||||
|
return new byte[]{2, 15, 5};
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd02.MijiaLywsd02Co
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miscale2.MiScale2DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miscale2.MiScale2DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
|
||||||
@ -226,6 +227,7 @@ public class DeviceHelper {
|
|||||||
result.add(new EXRIZUK8Coordinator());
|
result.add(new EXRIZUK8Coordinator());
|
||||||
result.add(new TeclastH30Coordinator());
|
result.add(new TeclastH30Coordinator());
|
||||||
result.add(new XWatchCoordinator());
|
result.add(new XWatchCoordinator());
|
||||||
|
result.add(new QHybridCoordinator());
|
||||||
result.add(new ZeTimeCoordinator());
|
result.add(new ZeTimeCoordinator());
|
||||||
result.add(new ID115Coordinator());
|
result.add(new ID115Coordinator());
|
||||||
result.add(new Watch9DeviceCoordinator());
|
result.add(new Watch9DeviceCoordinator());
|
||||||
|
@ -53,4 +53,13 @@ public class LimitedQueue {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized public Object lookupByValue(Object value){
|
||||||
|
for (Pair entry : list) {
|
||||||
|
if (value.equals(entry.second)) {
|
||||||
|
return entry.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_notification_qhybrid.png
Normal file
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 8.0 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_notification_qhybrid.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 22 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_notification_qhybrid.png
Normal file
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 44 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_notification_qhybrid.png
Normal file
After Width: | Height: | Size: 43 KiB |