mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-02-18 05:17:08 +01:00
Merge branch 'master' of github.com:gfwilliams/Gadgetbridge
This commit is contained in:
commit
fb70da856a
@ -1,5 +1,5 @@
|
||||
**IF YOU WANT TO EDIT THE WIKI**, do so on [codeberg.org](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki)
|
||||
The wiki on github.com is a read-only mirror, as is the git repo itself. Issues and PRs will move to codeberg summer 2019, if you want your issue/PR comments migrated properly, please create a codeberg acount before we will migrate.
|
||||
The wiki on github.com is a read-only mirror, as is the git repo itself. Issues and PRs will move to codeberg on 2019-12-10 if everything works out as planned, if you want your issue/PR comments migrated properly, please create a codeberg acount before we will migrate.
|
||||
|
||||
Gadgetbridge
|
||||
============
|
||||
@ -37,6 +37,7 @@ vendor's servers.
|
||||
* Amazfit GTS (NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT ONCE) [Wiki] (https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTS)
|
||||
* BFH-16
|
||||
* Casio GB-6900B
|
||||
* Fossil Q Hybrid
|
||||
* HPlus Devices (e.g. ZeBand) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/HPlus)
|
||||
* ID115
|
||||
* Lenovo Watch 9
|
||||
@ -79,6 +80,7 @@ Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/ma
|
||||
* Jean-François Greffier (Mi Scale 2)
|
||||
* Johannes Schmitt (BFH-16)
|
||||
* Lukas Schwichtenberg (Makibes HR3)
|
||||
* Daniel Dakhno (Fossil Q Hybrid)
|
||||
|
||||
## Contribute
|
||||
|
||||
|
@ -70,7 +70,7 @@ dependencies {
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.preference:preference:1.1.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
implementation "com.google.android.material:material:1.0.0"
|
||||
@ -85,12 +85,18 @@ dependencies {
|
||||
implementation "net.e175.klaus:solarpositioning:0.0.9"
|
||||
// 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.
|
||||
|
||||
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.apache.commons:commons-lang3:3.7"
|
||||
implementation "org.cyanogenmod:platform.sdk:6.0"
|
||||
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
||||
// implementation project(":DaoCore")
|
||||
implementation 'com.github.wax911:android-emojify:0.1.7'
|
||||
|
||||
}
|
||||
|
||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||
|
@ -15,7 +15,7 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<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.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
||||
@ -499,7 +499,32 @@
|
||||
<data android:scheme="gadgetbridge" />
|
||||
</intent-filter>
|
||||
</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>
|
||||
|
||||
</manifest>
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, boun, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, JohnnySun, jonnsoft, Lem Dulfo, Taavi Eomäe, Uwe Hermann
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, boun, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti, JohnnySun, jonnsoft, Lem Dulfo, Taavi Eomäe,
|
||||
Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -80,7 +81,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
private ScanCallback newLeScanCallback = null;
|
||||
|
||||
// Disabled for testing, it seems worse for a few people
|
||||
private final boolean disableNewBLEScanning = true;
|
||||
private boolean disableNewBLEScanning = false;
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
@ -267,6 +268,11 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
disableNewBLEScanning = GBApplication.getPrefs().getBoolean("disable_new_ble_scanning", false);
|
||||
if (disableNewBLEScanning) {
|
||||
LOG.info("new BLE scanning disabled via settings, using old method");
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_discovery);
|
||||
startButton = findViewById(R.id.discovery_start);
|
||||
startButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Felix Konstantin Maurer, José Rebelo, Martin, Normano64,
|
||||
Pavel Elagin, Sebastian Kranz, vanous
|
||||
Daniel Dakhno, Daniele Gobbetti, Felix Konstantin Maurer, José Rebelo,
|
||||
Martin, Normano64, Pavel Elagin, Sebastian Kranz, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
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.activities.charts.ChartsPreferencesActivity;
|
||||
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.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
@ -296,16 +306,16 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
|
||||
pref = findPreference("weather_city");
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
// reset city id and force a new lookup
|
||||
GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid", null).apply();
|
||||
preference.setSummary(newVal.toString());
|
||||
Intent intent = new Intent("GB_UPDATE_WEATHER");
|
||||
intent.setPackage(BuildConfig.APPLICATION_ID);
|
||||
sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
// reset city id and force a new lookup
|
||||
GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid", null).apply();
|
||||
preference.setSummary(newVal.toString());
|
||||
Intent intent = new Intent("GB_UPDATE_WEATHER");
|
||||
intent.setPackage(BuildConfig.APPLICATION_ID);
|
||||
sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference(GBPrefs.AUTO_EXPORT_LOCATION);
|
||||
@ -435,8 +445,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||
}
|
||||
}
|
||||
catch (Exception fdfsdfds) {
|
||||
} catch (Exception fdfsdfds) {
|
||||
LOG.warn("fuck");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,657 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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,131 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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,158 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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,206 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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,185 @@
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti, José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,109 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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,51 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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,294 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.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);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/* Copyright (C) 2015-2019 abettenburg, Andreas Shimokawa, AndrewBedscastle,
|
||||
Carsten Pfeiffer, Daniele Gobbetti, Frank Slezak, Hasan Ammar, José Rebelo,
|
||||
Julien Pivotto, Kevin Richter, Matthieu Baerts, Normano64, Steffen Liebergeld,
|
||||
Taavi Eomäe, veecue, Zhong Jianxin
|
||||
Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Frank Slezak, Hasan Ammar,
|
||||
José Rebelo, Julien Pivotto, Kevin Richter, Matthieu Baerts, Normano64,
|
||||
Steffen Liebergeld, Taavi Eomäe, veecue, Zhong Jianxin
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -32,6 +32,7 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.MediaMetadata;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
@ -43,6 +44,7 @@ import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.RemoteInput;
|
||||
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> notificationOldRepeatPrevention = new HashMap<>();
|
||||
|
||||
public static ArrayList<String> notificationStack = new ArrayList<>();
|
||||
|
||||
private long activeCallPostTime;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@ -223,6 +227,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
notificationStack.clear();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -241,6 +246,9 @@ public class NotificationListener extends NotificationListenerService {
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
notificationStack.remove(sbn.getPackageName());
|
||||
notificationStack.add(sbn.getPackageName());
|
||||
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
if ("call".equals(sbn.getNotification().category) && prefs.getBoolean("notification_support_voip_calls", false)) {
|
||||
handleCallNotification(sbn);
|
||||
@ -384,7 +392,6 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}else {
|
||||
LOG.info("This app might show old/duplicate notifications. notification.when is 0 for " + source);
|
||||
}
|
||||
|
||||
GBApplication.deviceService().onNotification(notificationSpec);
|
||||
}
|
||||
|
||||
@ -626,29 +633,30 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||
LOG.info("Notification removed: " + sbn.getPackageName());
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
LOG.info("Notification removed: " + sbn.getPackageName() + ", category: " + sbn.getNotification().category);
|
||||
if (Notification.CATEGORY_CALL.equals(sbn.getNotification().category) && activeCallPostTime == sbn.getPostTime()) {
|
||||
activeCallPostTime = 0;
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = CallSpec.CALL_END;
|
||||
GBApplication.deviceService().onSetCallState(callSpec);
|
||||
}
|
||||
LOG.info("Notification removed: " + sbn.getPackageName() + ": " + sbn.getNotification().category);
|
||||
|
||||
notificationStack.remove(sbn.getPackageName());
|
||||
|
||||
if(Notification.CATEGORY_CALL.equals(sbn.getNotification().category) && activeCallPostTime == sbn.getPostTime()) {
|
||||
activeCallPostTime = 0;
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = CallSpec.CALL_END;
|
||||
GBApplication.deviceService().onSetCallState(callSpec);
|
||||
}
|
||||
// FIXME: DISABLED for now
|
||||
/*
|
||||
|
||||
if (shouldIgnore(sbn))
|
||||
return;
|
||||
|
||||
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");
|
||||
GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime());
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Alberto, Andreas Böhler, Andreas Shimokawa,
|
||||
Carsten Pfeiffer, criogenic, dakhnod, Daniele Gobbetti, Frank Slezak,
|
||||
Carsten Pfeiffer, criogenic, Daniel Dakhno, Daniele Gobbetti, Frank Slezak,
|
||||
ivanovlev, José Rebelo, Julien Pivotto, Kasha, Roi Greenberg, Sebastian
|
||||
Kranz, Steffen Liebergeld
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, dakhnod,
|
||||
Daniele Gobbetti, Frank Slezak, ivanovlev, JohnnySun, José Rebelo, Julien
|
||||
Pivotto, Kasha, Sebastian Kranz, Steffen Liebergeld
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti, Frank Slezak, ivanovlev, JohnnySun, José Rebelo,
|
||||
Julien Pivotto, Kasha, Sebastian Kranz, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Cre3per, Daniele Gobbetti, Jean-François Greffier, João Paulo
|
||||
Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß, maxirnilian, protomors,
|
||||
Quallenauge, Sami Alaoui, Sebastian Kranz, Sophanimus, tiparega, Vadim Kaushan
|
||||
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Jean-François Greffier,
|
||||
João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß, maxirnilian,
|
||||
protomors, Quallenauge, Sami Alaoui, Sebastian Kranz, Sophanimus, tiparega,
|
||||
Vadim Kaushan
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -51,6 +52,7 @@ public enum DeviceType {
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
|
@ -1,8 +1,8 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Avamander,
|
||||
Carsten Pfeiffer, dakhnod, Daniele Gobbetti, Daniel Hauck, Dikay900, Frank
|
||||
Slezak, ivanovlev, João Paulo Barraca, José Rebelo, Julien Pivotto, Kasha,
|
||||
keeshii, Martin, Matthieu Baerts, Nephiel, Sebastian Kranz, Sergey Trofimov,
|
||||
Steffen Liebergeld, Taavi Eomäe, Uwe Hermann
|
||||
Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Daniel Hauck, Dikay900,
|
||||
Frank Slezak, ivanovlev, João Paulo Barraca, José Rebelo, Julien Pivotto,
|
||||
Kasha, keeshii, Martin, Matthieu Baerts, Nephiel, Sebastian Kranz, Sergey
|
||||
Trofimov, Steffen Liebergeld, Taavi Eomäe, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Cre3per, criogenic, Daniele Gobbetti, Jean-François Greffier,
|
||||
João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß, maxirnilian,
|
||||
protomors, Quallenauge, Sami Alaoui, Sergey Trofimov, Sophanimus, tiparega,
|
||||
Vadim Kaushan
|
||||
Pfeiffer, Cre3per, criogenic, Daniel Dakhno, Daniele Gobbetti, Jean-François
|
||||
Greffier, João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß,
|
||||
maxirnilian, protomors, Quallenauge, Sami Alaoui, Sergey Trofimov, Sophanimus,
|
||||
tiparega, Vadim Kaushan
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -53,6 +53,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd02.MijiaL
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
||||
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.vibratissimo.VibratissimoSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
|
||||
@ -184,7 +185,10 @@ public class DeviceSupportFactory {
|
||||
case XWATCH:
|
||||
deviceSupport = new ServiceDeviceSupport(new XWatchSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case ZETIME:
|
||||
case FOSSILQHYBRID:
|
||||
deviceSupport = new ServiceDeviceSupport(new QHybridSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case ZETIME:
|
||||
deviceSupport = new ServiceDeviceSupport(new ZeTimeDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case ID115:
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Julien Pivotto, Kasha, Sebastian Kranz, Steffen
|
||||
Liebergeld
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti, José Rebelo, Julien Pivotto, Kasha, Sebastian
|
||||
Kranz, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti, JohnnySun, José Rebelo
|
||||
Pfeiffer, Daniel Dakhno, Daniele Gobbetti, JohnnySun, José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -351,6 +351,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetFmFrequency(float frequency) {
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Uwe Hermann
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -209,4 +210,9 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
mSupport.onReadRemoteRssi(gatt, rssi, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
mSupport.onMtuChanged(gatt, mtu, status);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2019 Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Carsten Pfeiffer, Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -60,4 +60,8 @@ public abstract class AbstractGattCallback implements GattCallback {
|
||||
@Override
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Cre3per, Daniele Gobbetti, Sergey Trofimov, Uwe Hermann
|
||||
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Sergey Trofimov,
|
||||
Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -532,6 +533,19 @@ public final class BtLEQueue {
|
||||
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
|
||||
public void onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
|
@ -104,6 +104,8 @@ public interface GattCallback {
|
||||
*/
|
||||
void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status);
|
||||
|
||||
void onMtuChanged(BluetoothGatt gatt, int mtu, int status);
|
||||
|
||||
// /**
|
||||
// * @see BluetoothGattCallback#onMtuChanged(BluetoothGatt, int, int)
|
||||
// * @param gatt
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -18,13 +18,17 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.os.Build;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyAction;
|
||||
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.WriteAction;
|
||||
|
||||
@ -56,6 +60,13 @@ public class TransactionBuilder {
|
||||
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) {
|
||||
if (characteristic == null) {
|
||||
LOG.warn("Unable to notify characteristic: null");
|
||||
|
@ -0,0 +1,47 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.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,196 @@
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, José Rebelo, Julien Pivotto, Sebastian Kranz, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
|
||||
|
||||
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,577 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
|
||||
|
||||
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,97 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.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,33 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.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,611 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.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,499 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.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,98 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.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,105 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.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,81 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
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,34 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
|
||||
|
||||
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,49 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
|
||||
|
||||
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,41 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
|
||||
|
||||
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,103 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,62 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,35 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,66 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,60 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,279 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,53 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,53 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,95 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,75 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,132 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,37 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,139 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,243 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
|
||||
|
||||
public class FilePutRequest extends FossilRequest {
|
||||
public enum UploadState {INITIALIZED, UPLOADING, CLOSING, UPLOADED}
|
||||
|
||||
public UploadState state;
|
||||
|
||||
private ArrayList<byte[]> packets = new ArrayList<>();
|
||||
|
||||
private short handle;
|
||||
|
||||
private FossilWatchAdapter adapter;
|
||||
|
||||
private byte[] file;
|
||||
|
||||
private 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,0,data.length);
|
||||
buffer.putInt((int) crc.getValue());
|
||||
|
||||
byte[] data = buffer.array();
|
||||
|
||||
CRC32 fullCRC = new CRC32();
|
||||
|
||||
fullCRC.update(data);
|
||||
this.fullCRC = (int) fullCRC.getValue();
|
||||
|
||||
int packetCount = (int) Math.ceil(data.length / (float) maxPacketSize);
|
||||
|
||||
for (int i = 0; i < packetCount; i++) {
|
||||
int currentPacketLength = Math.min(maxPacketSize, data.length - i * maxPacketSize);
|
||||
byte[] packet = new byte[currentPacketLength + 1];
|
||||
packet[0] = (byte) i;
|
||||
System.arraycopy(data, i * maxPacketSize, packet, 1, currentPacketLength);
|
||||
|
||||
packets.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void onFilePut(boolean success) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{0x03};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength() {
|
||||
return 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getRequestUUID() {
|
||||
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,176 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,75 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,48 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
|
||||
|
||||
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.util.CRC32C;
|
||||
|
||||
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,0,data.length);
|
||||
|
||||
if((int) crc32c.getValue() != buffer.getInt(fileData.length - 4)){
|
||||
throw new RuntimeException("CRC invalid");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,109 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.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,46 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,26 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,45 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,114 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,59 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,47 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,35 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,55 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,46 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,45 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,26 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,45 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,44 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,35 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,58 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,65 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,36 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,61 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,26 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,86 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,26 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,45 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,52 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,44 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,40 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,45 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,44 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,42 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,26 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,45 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,54 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,113 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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,44 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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};
|
||||
}
|
||||
}
|
@ -0,0 +1,633 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Some portions of this file Copyright (c) 2004-2006 Intel Corportation
|
||||
* and licensed under the BSD license.
|
||||
*/
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
/**
|
||||
* A pure-java implementation of the CRC32 checksum that uses
|
||||
* the CRC32-C polynomial, the same polynomial used by iSCSI
|
||||
* and implemented on many Intel chipsets supporting SSE4.2.
|
||||
*/
|
||||
|
||||
public class CRC32C implements Checksum {
|
||||
|
||||
/** the current CRC value, bit-flipped */
|
||||
private int crc;
|
||||
|
||||
/** Create a new PureJavaCrc32 object. */
|
||||
public CRC32C() {
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getValue() {
|
||||
long ret = crc;
|
||||
return (~ret) & 0xffffffffL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
crc = 0xffffffff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] b, int off, int len) {
|
||||
int localCrc = crc;
|
||||
|
||||
while(len > 7) {
|
||||
final int c0 =(b[off+0] ^ localCrc) & 0xff;
|
||||
final int c1 =(b[off+1] ^ (localCrc >>>= 8)) & 0xff;
|
||||
final int c2 =(b[off+2] ^ (localCrc >>>= 8)) & 0xff;
|
||||
final int c3 =(b[off+3] ^ (localCrc >>>= 8)) & 0xff;
|
||||
localCrc = (T[T8_7_start + c0] ^ T[T8_6_start + c1])
|
||||
^ (T[T8_5_start + c2] ^ T[T8_4_start + c3]);
|
||||
|
||||
final int c4 = b[off+4] & 0xff;
|
||||
final int c5 = b[off+5] & 0xff;
|
||||
final int c6 = b[off+6] & 0xff;
|
||||
final int c7 = b[off+7] & 0xff;
|
||||
|
||||
localCrc ^= (T[T8_3_start + c4] ^ T[T8_2_start + c5])
|
||||
^ (T[T8_1_start + c6] ^ T[T8_0_start + c7]);
|
||||
|
||||
off += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
/* loop unroll - duff's device style */
|
||||
switch(len) {
|
||||
case 7: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
|
||||
case 6: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
|
||||
case 5: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
|
||||
case 4: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
|
||||
case 3: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
|
||||
case 2: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
|
||||
case 1: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
|
||||
default:
|
||||
/* nothing */
|
||||
}
|
||||
|
||||
// Publish crc out to object
|
||||
crc = localCrc;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public void update(int b) {
|
||||
crc = (crc >>> 8) ^ T[T8_0_start + ((crc ^ b) & 0xff)];
|
||||
}
|
||||
|
||||
// CRC polynomial tables generated by:
|
||||
// java -cp build/test/classes/:build/classes/ \
|
||||
// org.apache.hadoop.util.TestPureJavaCrc32\$Table 82F63B78
|
||||
|
||||
private static final int T8_0_start = 0*256;
|
||||
private static final int T8_1_start = 1*256;
|
||||
private static final int T8_2_start = 2*256;
|
||||
private static final int T8_3_start = 3*256;
|
||||
private static final int T8_4_start = 4*256;
|
||||
private static final int T8_5_start = 5*256;
|
||||
private static final int T8_6_start = 6*256;
|
||||
private static final int T8_7_start = 7*256;
|
||||
|
||||
private static final int[] T = new int[] {
|
||||
/* T8_0 */
|
||||
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4,
|
||||
0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
|
||||
0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
|
||||
0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
|
||||
0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B,
|
||||
0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
|
||||
0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54,
|
||||
0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
|
||||
0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
|
||||
0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
|
||||
0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5,
|
||||
0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
|
||||
0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45,
|
||||
0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
|
||||
0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
|
||||
0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
|
||||
0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48,
|
||||
0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
|
||||
0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687,
|
||||
0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
|
||||
0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
|
||||
0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
|
||||
0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8,
|
||||
0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
|
||||
0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096,
|
||||
0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
|
||||
0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
|
||||
0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
|
||||
0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9,
|
||||
0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
|
||||
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36,
|
||||
0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
|
||||
0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
|
||||
0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
|
||||
0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043,
|
||||
0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
|
||||
0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3,
|
||||
0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
|
||||
0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
|
||||
0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
|
||||
0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652,
|
||||
0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
|
||||
0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D,
|
||||
0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
|
||||
0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
|
||||
0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
|
||||
0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2,
|
||||
0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
|
||||
0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530,
|
||||
0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
|
||||
0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
|
||||
0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
|
||||
0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F,
|
||||
0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
|
||||
0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90,
|
||||
0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
|
||||
0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
|
||||
0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
|
||||
0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321,
|
||||
0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
|
||||
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81,
|
||||
0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
|
||||
0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
|
||||
0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
|
||||
/* T8_1 */
|
||||
0x00000000, 0x13A29877, 0x274530EE, 0x34E7A899,
|
||||
0x4E8A61DC, 0x5D28F9AB, 0x69CF5132, 0x7A6DC945,
|
||||
0x9D14C3B8, 0x8EB65BCF, 0xBA51F356, 0xA9F36B21,
|
||||
0xD39EA264, 0xC03C3A13, 0xF4DB928A, 0xE7790AFD,
|
||||
0x3FC5F181, 0x2C6769F6, 0x1880C16F, 0x0B225918,
|
||||
0x714F905D, 0x62ED082A, 0x560AA0B3, 0x45A838C4,
|
||||
0xA2D13239, 0xB173AA4E, 0x859402D7, 0x96369AA0,
|
||||
0xEC5B53E5, 0xFFF9CB92, 0xCB1E630B, 0xD8BCFB7C,
|
||||
0x7F8BE302, 0x6C297B75, 0x58CED3EC, 0x4B6C4B9B,
|
||||
0x310182DE, 0x22A31AA9, 0x1644B230, 0x05E62A47,
|
||||
0xE29F20BA, 0xF13DB8CD, 0xC5DA1054, 0xD6788823,
|
||||
0xAC154166, 0xBFB7D911, 0x8B507188, 0x98F2E9FF,
|
||||
0x404E1283, 0x53EC8AF4, 0x670B226D, 0x74A9BA1A,
|
||||
0x0EC4735F, 0x1D66EB28, 0x298143B1, 0x3A23DBC6,
|
||||
0xDD5AD13B, 0xCEF8494C, 0xFA1FE1D5, 0xE9BD79A2,
|
||||
0x93D0B0E7, 0x80722890, 0xB4958009, 0xA737187E,
|
||||
0xFF17C604, 0xECB55E73, 0xD852F6EA, 0xCBF06E9D,
|
||||
0xB19DA7D8, 0xA23F3FAF, 0x96D89736, 0x857A0F41,
|
||||
0x620305BC, 0x71A19DCB, 0x45463552, 0x56E4AD25,
|
||||
0x2C896460, 0x3F2BFC17, 0x0BCC548E, 0x186ECCF9,
|
||||
0xC0D23785, 0xD370AFF2, 0xE797076B, 0xF4359F1C,
|
||||
0x8E585659, 0x9DFACE2E, 0xA91D66B7, 0xBABFFEC0,
|
||||
0x5DC6F43D, 0x4E646C4A, 0x7A83C4D3, 0x69215CA4,
|
||||
0x134C95E1, 0x00EE0D96, 0x3409A50F, 0x27AB3D78,
|
||||
0x809C2506, 0x933EBD71, 0xA7D915E8, 0xB47B8D9F,
|
||||
0xCE1644DA, 0xDDB4DCAD, 0xE9537434, 0xFAF1EC43,
|
||||
0x1D88E6BE, 0x0E2A7EC9, 0x3ACDD650, 0x296F4E27,
|
||||
0x53028762, 0x40A01F15, 0x7447B78C, 0x67E52FFB,
|
||||
0xBF59D487, 0xACFB4CF0, 0x981CE469, 0x8BBE7C1E,
|
||||
0xF1D3B55B, 0xE2712D2C, 0xD69685B5, 0xC5341DC2,
|
||||
0x224D173F, 0x31EF8F48, 0x050827D1, 0x16AABFA6,
|
||||
0x6CC776E3, 0x7F65EE94, 0x4B82460D, 0x5820DE7A,
|
||||
0xFBC3FAF9, 0xE861628E, 0xDC86CA17, 0xCF245260,
|
||||
0xB5499B25, 0xA6EB0352, 0x920CABCB, 0x81AE33BC,
|
||||
0x66D73941, 0x7575A136, 0x419209AF, 0x523091D8,
|
||||
0x285D589D, 0x3BFFC0EA, 0x0F186873, 0x1CBAF004,
|
||||
0xC4060B78, 0xD7A4930F, 0xE3433B96, 0xF0E1A3E1,
|
||||
0x8A8C6AA4, 0x992EF2D3, 0xADC95A4A, 0xBE6BC23D,
|
||||
0x5912C8C0, 0x4AB050B7, 0x7E57F82E, 0x6DF56059,
|
||||
0x1798A91C, 0x043A316B, 0x30DD99F2, 0x237F0185,
|
||||
0x844819FB, 0x97EA818C, 0xA30D2915, 0xB0AFB162,
|
||||
0xCAC27827, 0xD960E050, 0xED8748C9, 0xFE25D0BE,
|
||||
0x195CDA43, 0x0AFE4234, 0x3E19EAAD, 0x2DBB72DA,
|
||||
0x57D6BB9F, 0x447423E8, 0x70938B71, 0x63311306,
|
||||
0xBB8DE87A, 0xA82F700D, 0x9CC8D894, 0x8F6A40E3,
|
||||
0xF50789A6, 0xE6A511D1, 0xD242B948, 0xC1E0213F,
|
||||
0x26992BC2, 0x353BB3B5, 0x01DC1B2C, 0x127E835B,
|
||||
0x68134A1E, 0x7BB1D269, 0x4F567AF0, 0x5CF4E287,
|
||||
0x04D43CFD, 0x1776A48A, 0x23910C13, 0x30339464,
|
||||
0x4A5E5D21, 0x59FCC556, 0x6D1B6DCF, 0x7EB9F5B8,
|
||||
0x99C0FF45, 0x8A626732, 0xBE85CFAB, 0xAD2757DC,
|
||||
0xD74A9E99, 0xC4E806EE, 0xF00FAE77, 0xE3AD3600,
|
||||
0x3B11CD7C, 0x28B3550B, 0x1C54FD92, 0x0FF665E5,
|
||||
0x759BACA0, 0x663934D7, 0x52DE9C4E, 0x417C0439,
|
||||
0xA6050EC4, 0xB5A796B3, 0x81403E2A, 0x92E2A65D,
|
||||
0xE88F6F18, 0xFB2DF76F, 0xCFCA5FF6, 0xDC68C781,
|
||||
0x7B5FDFFF, 0x68FD4788, 0x5C1AEF11, 0x4FB87766,
|
||||
0x35D5BE23, 0x26772654, 0x12908ECD, 0x013216BA,
|
||||
0xE64B1C47, 0xF5E98430, 0xC10E2CA9, 0xD2ACB4DE,
|
||||
0xA8C17D9B, 0xBB63E5EC, 0x8F844D75, 0x9C26D502,
|
||||
0x449A2E7E, 0x5738B609, 0x63DF1E90, 0x707D86E7,
|
||||
0x0A104FA2, 0x19B2D7D5, 0x2D557F4C, 0x3EF7E73B,
|
||||
0xD98EEDC6, 0xCA2C75B1, 0xFECBDD28, 0xED69455F,
|
||||
0x97048C1A, 0x84A6146D, 0xB041BCF4, 0xA3E32483,
|
||||
/* T8_2 */
|
||||
0x00000000, 0xA541927E, 0x4F6F520D, 0xEA2EC073,
|
||||
0x9EDEA41A, 0x3B9F3664, 0xD1B1F617, 0x74F06469,
|
||||
0x38513EC5, 0x9D10ACBB, 0x773E6CC8, 0xD27FFEB6,
|
||||
0xA68F9ADF, 0x03CE08A1, 0xE9E0C8D2, 0x4CA15AAC,
|
||||
0x70A27D8A, 0xD5E3EFF4, 0x3FCD2F87, 0x9A8CBDF9,
|
||||
0xEE7CD990, 0x4B3D4BEE, 0xA1138B9D, 0x045219E3,
|
||||
0x48F3434F, 0xEDB2D131, 0x079C1142, 0xA2DD833C,
|
||||
0xD62DE755, 0x736C752B, 0x9942B558, 0x3C032726,
|
||||
0xE144FB14, 0x4405696A, 0xAE2BA919, 0x0B6A3B67,
|
||||
0x7F9A5F0E, 0xDADBCD70, 0x30F50D03, 0x95B49F7D,
|
||||
0xD915C5D1, 0x7C5457AF, 0x967A97DC, 0x333B05A2,
|
||||
0x47CB61CB, 0xE28AF3B5, 0x08A433C6, 0xADE5A1B8,
|
||||
0x91E6869E, 0x34A714E0, 0xDE89D493, 0x7BC846ED,
|
||||
0x0F382284, 0xAA79B0FA, 0x40577089, 0xE516E2F7,
|
||||
0xA9B7B85B, 0x0CF62A25, 0xE6D8EA56, 0x43997828,
|
||||
0x37691C41, 0x92288E3F, 0x78064E4C, 0xDD47DC32,
|
||||
0xC76580D9, 0x622412A7, 0x880AD2D4, 0x2D4B40AA,
|
||||
0x59BB24C3, 0xFCFAB6BD, 0x16D476CE, 0xB395E4B0,
|
||||
0xFF34BE1C, 0x5A752C62, 0xB05BEC11, 0x151A7E6F,
|
||||
0x61EA1A06, 0xC4AB8878, 0x2E85480B, 0x8BC4DA75,
|
||||
0xB7C7FD53, 0x12866F2D, 0xF8A8AF5E, 0x5DE93D20,
|
||||
0x29195949, 0x8C58CB37, 0x66760B44, 0xC337993A,
|
||||
0x8F96C396, 0x2AD751E8, 0xC0F9919B, 0x65B803E5,
|
||||
0x1148678C, 0xB409F5F2, 0x5E273581, 0xFB66A7FF,
|
||||
0x26217BCD, 0x8360E9B3, 0x694E29C0, 0xCC0FBBBE,
|
||||
0xB8FFDFD7, 0x1DBE4DA9, 0xF7908DDA, 0x52D11FA4,
|
||||
0x1E704508, 0xBB31D776, 0x511F1705, 0xF45E857B,
|
||||
0x80AEE112, 0x25EF736C, 0xCFC1B31F, 0x6A802161,
|
||||
0x56830647, 0xF3C29439, 0x19EC544A, 0xBCADC634,
|
||||
0xC85DA25D, 0x6D1C3023, 0x8732F050, 0x2273622E,
|
||||
0x6ED23882, 0xCB93AAFC, 0x21BD6A8F, 0x84FCF8F1,
|
||||
0xF00C9C98, 0x554D0EE6, 0xBF63CE95, 0x1A225CEB,
|
||||
0x8B277743, 0x2E66E53D, 0xC448254E, 0x6109B730,
|
||||
0x15F9D359, 0xB0B84127, 0x5A968154, 0xFFD7132A,
|
||||
0xB3764986, 0x1637DBF8, 0xFC191B8B, 0x595889F5,
|
||||
0x2DA8ED9C, 0x88E97FE2, 0x62C7BF91, 0xC7862DEF,
|
||||
0xFB850AC9, 0x5EC498B7, 0xB4EA58C4, 0x11ABCABA,
|
||||
0x655BAED3, 0xC01A3CAD, 0x2A34FCDE, 0x8F756EA0,
|
||||
0xC3D4340C, 0x6695A672, 0x8CBB6601, 0x29FAF47F,
|
||||
0x5D0A9016, 0xF84B0268, 0x1265C21B, 0xB7245065,
|
||||
0x6A638C57, 0xCF221E29, 0x250CDE5A, 0x804D4C24,
|
||||
0xF4BD284D, 0x51FCBA33, 0xBBD27A40, 0x1E93E83E,
|
||||
0x5232B292, 0xF77320EC, 0x1D5DE09F, 0xB81C72E1,
|
||||
0xCCEC1688, 0x69AD84F6, 0x83834485, 0x26C2D6FB,
|
||||
0x1AC1F1DD, 0xBF8063A3, 0x55AEA3D0, 0xF0EF31AE,
|
||||
0x841F55C7, 0x215EC7B9, 0xCB7007CA, 0x6E3195B4,
|
||||
0x2290CF18, 0x87D15D66, 0x6DFF9D15, 0xC8BE0F6B,
|
||||
0xBC4E6B02, 0x190FF97C, 0xF321390F, 0x5660AB71,
|
||||
0x4C42F79A, 0xE90365E4, 0x032DA597, 0xA66C37E9,
|
||||
0xD29C5380, 0x77DDC1FE, 0x9DF3018D, 0x38B293F3,
|
||||
0x7413C95F, 0xD1525B21, 0x3B7C9B52, 0x9E3D092C,
|
||||
0xEACD6D45, 0x4F8CFF3B, 0xA5A23F48, 0x00E3AD36,
|
||||
0x3CE08A10, 0x99A1186E, 0x738FD81D, 0xD6CE4A63,
|
||||
0xA23E2E0A, 0x077FBC74, 0xED517C07, 0x4810EE79,
|
||||
0x04B1B4D5, 0xA1F026AB, 0x4BDEE6D8, 0xEE9F74A6,
|
||||
0x9A6F10CF, 0x3F2E82B1, 0xD50042C2, 0x7041D0BC,
|
||||
0xAD060C8E, 0x08479EF0, 0xE2695E83, 0x4728CCFD,
|
||||
0x33D8A894, 0x96993AEA, 0x7CB7FA99, 0xD9F668E7,
|
||||
0x9557324B, 0x3016A035, 0xDA386046, 0x7F79F238,
|
||||
0x0B899651, 0xAEC8042F, 0x44E6C45C, 0xE1A75622,
|
||||
0xDDA47104, 0x78E5E37A, 0x92CB2309, 0x378AB177,
|
||||
0x437AD51E, 0xE63B4760, 0x0C158713, 0xA954156D,
|
||||
0xE5F54FC1, 0x40B4DDBF, 0xAA9A1DCC, 0x0FDB8FB2,
|
||||
0x7B2BEBDB, 0xDE6A79A5, 0x3444B9D6, 0x91052BA8,
|
||||
/* T8_3 */
|
||||
0x00000000, 0xDD45AAB8, 0xBF672381, 0x62228939,
|
||||
0x7B2231F3, 0xA6679B4B, 0xC4451272, 0x1900B8CA,
|
||||
0xF64463E6, 0x2B01C95E, 0x49234067, 0x9466EADF,
|
||||
0x8D665215, 0x5023F8AD, 0x32017194, 0xEF44DB2C,
|
||||
0xE964B13D, 0x34211B85, 0x560392BC, 0x8B463804,
|
||||
0x924680CE, 0x4F032A76, 0x2D21A34F, 0xF06409F7,
|
||||
0x1F20D2DB, 0xC2657863, 0xA047F15A, 0x7D025BE2,
|
||||
0x6402E328, 0xB9474990, 0xDB65C0A9, 0x06206A11,
|
||||
0xD725148B, 0x0A60BE33, 0x6842370A, 0xB5079DB2,
|
||||
0xAC072578, 0x71428FC0, 0x136006F9, 0xCE25AC41,
|
||||
0x2161776D, 0xFC24DDD5, 0x9E0654EC, 0x4343FE54,
|
||||
0x5A43469E, 0x8706EC26, 0xE524651F, 0x3861CFA7,
|
||||
0x3E41A5B6, 0xE3040F0E, 0x81268637, 0x5C632C8F,
|
||||
0x45639445, 0x98263EFD, 0xFA04B7C4, 0x27411D7C,
|
||||
0xC805C650, 0x15406CE8, 0x7762E5D1, 0xAA274F69,
|
||||
0xB327F7A3, 0x6E625D1B, 0x0C40D422, 0xD1057E9A,
|
||||
0xABA65FE7, 0x76E3F55F, 0x14C17C66, 0xC984D6DE,
|
||||
0xD0846E14, 0x0DC1C4AC, 0x6FE34D95, 0xB2A6E72D,
|
||||
0x5DE23C01, 0x80A796B9, 0xE2851F80, 0x3FC0B538,
|
||||
0x26C00DF2, 0xFB85A74A, 0x99A72E73, 0x44E284CB,
|
||||
0x42C2EEDA, 0x9F874462, 0xFDA5CD5B, 0x20E067E3,
|
||||
0x39E0DF29, 0xE4A57591, 0x8687FCA8, 0x5BC25610,
|
||||
0xB4868D3C, 0x69C32784, 0x0BE1AEBD, 0xD6A40405,
|
||||
0xCFA4BCCF, 0x12E11677, 0x70C39F4E, 0xAD8635F6,
|
||||
0x7C834B6C, 0xA1C6E1D4, 0xC3E468ED, 0x1EA1C255,
|
||||
0x07A17A9F, 0xDAE4D027, 0xB8C6591E, 0x6583F3A6,
|
||||
0x8AC7288A, 0x57828232, 0x35A00B0B, 0xE8E5A1B3,
|
||||
0xF1E51979, 0x2CA0B3C1, 0x4E823AF8, 0x93C79040,
|
||||
0x95E7FA51, 0x48A250E9, 0x2A80D9D0, 0xF7C57368,
|
||||
0xEEC5CBA2, 0x3380611A, 0x51A2E823, 0x8CE7429B,
|
||||
0x63A399B7, 0xBEE6330F, 0xDCC4BA36, 0x0181108E,
|
||||
0x1881A844, 0xC5C402FC, 0xA7E68BC5, 0x7AA3217D,
|
||||
0x52A0C93F, 0x8FE56387, 0xEDC7EABE, 0x30824006,
|
||||
0x2982F8CC, 0xF4C75274, 0x96E5DB4D, 0x4BA071F5,
|
||||
0xA4E4AAD9, 0x79A10061, 0x1B838958, 0xC6C623E0,
|
||||
0xDFC69B2A, 0x02833192, 0x60A1B8AB, 0xBDE41213,
|
||||
0xBBC47802, 0x6681D2BA, 0x04A35B83, 0xD9E6F13B,
|
||||
0xC0E649F1, 0x1DA3E349, 0x7F816A70, 0xA2C4C0C8,
|
||||
0x4D801BE4, 0x90C5B15C, 0xF2E73865, 0x2FA292DD,
|
||||
0x36A22A17, 0xEBE780AF, 0x89C50996, 0x5480A32E,
|
||||
0x8585DDB4, 0x58C0770C, 0x3AE2FE35, 0xE7A7548D,
|
||||
0xFEA7EC47, 0x23E246FF, 0x41C0CFC6, 0x9C85657E,
|
||||
0x73C1BE52, 0xAE8414EA, 0xCCA69DD3, 0x11E3376B,
|
||||
0x08E38FA1, 0xD5A62519, 0xB784AC20, 0x6AC10698,
|
||||
0x6CE16C89, 0xB1A4C631, 0xD3864F08, 0x0EC3E5B0,
|
||||
0x17C35D7A, 0xCA86F7C2, 0xA8A47EFB, 0x75E1D443,
|
||||
0x9AA50F6F, 0x47E0A5D7, 0x25C22CEE, 0xF8878656,
|
||||
0xE1873E9C, 0x3CC29424, 0x5EE01D1D, 0x83A5B7A5,
|
||||
0xF90696D8, 0x24433C60, 0x4661B559, 0x9B241FE1,
|
||||
0x8224A72B, 0x5F610D93, 0x3D4384AA, 0xE0062E12,
|
||||
0x0F42F53E, 0xD2075F86, 0xB025D6BF, 0x6D607C07,
|
||||
0x7460C4CD, 0xA9256E75, 0xCB07E74C, 0x16424DF4,
|
||||
0x106227E5, 0xCD278D5D, 0xAF050464, 0x7240AEDC,
|
||||
0x6B401616, 0xB605BCAE, 0xD4273597, 0x09629F2F,
|
||||
0xE6264403, 0x3B63EEBB, 0x59416782, 0x8404CD3A,
|
||||
0x9D0475F0, 0x4041DF48, 0x22635671, 0xFF26FCC9,
|
||||
0x2E238253, 0xF36628EB, 0x9144A1D2, 0x4C010B6A,
|
||||
0x5501B3A0, 0x88441918, 0xEA669021, 0x37233A99,
|
||||
0xD867E1B5, 0x05224B0D, 0x6700C234, 0xBA45688C,
|
||||
0xA345D046, 0x7E007AFE, 0x1C22F3C7, 0xC167597F,
|
||||
0xC747336E, 0x1A0299D6, 0x782010EF, 0xA565BA57,
|
||||
0xBC65029D, 0x6120A825, 0x0302211C, 0xDE478BA4,
|
||||
0x31035088, 0xEC46FA30, 0x8E647309, 0x5321D9B1,
|
||||
0x4A21617B, 0x9764CBC3, 0xF54642FA, 0x2803E842,
|
||||
/* T8_4 */
|
||||
0x00000000, 0x38116FAC, 0x7022DF58, 0x4833B0F4,
|
||||
0xE045BEB0, 0xD854D11C, 0x906761E8, 0xA8760E44,
|
||||
0xC5670B91, 0xFD76643D, 0xB545D4C9, 0x8D54BB65,
|
||||
0x2522B521, 0x1D33DA8D, 0x55006A79, 0x6D1105D5,
|
||||
0x8F2261D3, 0xB7330E7F, 0xFF00BE8B, 0xC711D127,
|
||||
0x6F67DF63, 0x5776B0CF, 0x1F45003B, 0x27546F97,
|
||||
0x4A456A42, 0x725405EE, 0x3A67B51A, 0x0276DAB6,
|
||||
0xAA00D4F2, 0x9211BB5E, 0xDA220BAA, 0xE2336406,
|
||||
0x1BA8B557, 0x23B9DAFB, 0x6B8A6A0F, 0x539B05A3,
|
||||
0xFBED0BE7, 0xC3FC644B, 0x8BCFD4BF, 0xB3DEBB13,
|
||||
0xDECFBEC6, 0xE6DED16A, 0xAEED619E, 0x96FC0E32,
|
||||
0x3E8A0076, 0x069B6FDA, 0x4EA8DF2E, 0x76B9B082,
|
||||
0x948AD484, 0xAC9BBB28, 0xE4A80BDC, 0xDCB96470,
|
||||
0x74CF6A34, 0x4CDE0598, 0x04EDB56C, 0x3CFCDAC0,
|
||||
0x51EDDF15, 0x69FCB0B9, 0x21CF004D, 0x19DE6FE1,
|
||||
0xB1A861A5, 0x89B90E09, 0xC18ABEFD, 0xF99BD151,
|
||||
0x37516AAE, 0x0F400502, 0x4773B5F6, 0x7F62DA5A,
|
||||
0xD714D41E, 0xEF05BBB2, 0xA7360B46, 0x9F2764EA,
|
||||
0xF236613F, 0xCA270E93, 0x8214BE67, 0xBA05D1CB,
|
||||
0x1273DF8F, 0x2A62B023, 0x625100D7, 0x5A406F7B,
|
||||
0xB8730B7D, 0x806264D1, 0xC851D425, 0xF040BB89,
|
||||
0x5836B5CD, 0x6027DA61, 0x28146A95, 0x10050539,
|
||||
0x7D1400EC, 0x45056F40, 0x0D36DFB4, 0x3527B018,
|
||||
0x9D51BE5C, 0xA540D1F0, 0xED736104, 0xD5620EA8,
|
||||
0x2CF9DFF9, 0x14E8B055, 0x5CDB00A1, 0x64CA6F0D,
|
||||
0xCCBC6149, 0xF4AD0EE5, 0xBC9EBE11, 0x848FD1BD,
|
||||
0xE99ED468, 0xD18FBBC4, 0x99BC0B30, 0xA1AD649C,
|
||||
0x09DB6AD8, 0x31CA0574, 0x79F9B580, 0x41E8DA2C,
|
||||
0xA3DBBE2A, 0x9BCAD186, 0xD3F96172, 0xEBE80EDE,
|
||||
0x439E009A, 0x7B8F6F36, 0x33BCDFC2, 0x0BADB06E,
|
||||
0x66BCB5BB, 0x5EADDA17, 0x169E6AE3, 0x2E8F054F,
|
||||
0x86F90B0B, 0xBEE864A7, 0xF6DBD453, 0xCECABBFF,
|
||||
0x6EA2D55C, 0x56B3BAF0, 0x1E800A04, 0x269165A8,
|
||||
0x8EE76BEC, 0xB6F60440, 0xFEC5B4B4, 0xC6D4DB18,
|
||||
0xABC5DECD, 0x93D4B161, 0xDBE70195, 0xE3F66E39,
|
||||
0x4B80607D, 0x73910FD1, 0x3BA2BF25, 0x03B3D089,
|
||||
0xE180B48F, 0xD991DB23, 0x91A26BD7, 0xA9B3047B,
|
||||
0x01C50A3F, 0x39D46593, 0x71E7D567, 0x49F6BACB,
|
||||
0x24E7BF1E, 0x1CF6D0B2, 0x54C56046, 0x6CD40FEA,
|
||||
0xC4A201AE, 0xFCB36E02, 0xB480DEF6, 0x8C91B15A,
|
||||
0x750A600B, 0x4D1B0FA7, 0x0528BF53, 0x3D39D0FF,
|
||||
0x954FDEBB, 0xAD5EB117, 0xE56D01E3, 0xDD7C6E4F,
|
||||
0xB06D6B9A, 0x887C0436, 0xC04FB4C2, 0xF85EDB6E,
|
||||
0x5028D52A, 0x6839BA86, 0x200A0A72, 0x181B65DE,
|
||||
0xFA2801D8, 0xC2396E74, 0x8A0ADE80, 0xB21BB12C,
|
||||
0x1A6DBF68, 0x227CD0C4, 0x6A4F6030, 0x525E0F9C,
|
||||
0x3F4F0A49, 0x075E65E5, 0x4F6DD511, 0x777CBABD,
|
||||
0xDF0AB4F9, 0xE71BDB55, 0xAF286BA1, 0x9739040D,
|
||||
0x59F3BFF2, 0x61E2D05E, 0x29D160AA, 0x11C00F06,
|
||||
0xB9B60142, 0x81A76EEE, 0xC994DE1A, 0xF185B1B6,
|
||||
0x9C94B463, 0xA485DBCF, 0xECB66B3B, 0xD4A70497,
|
||||
0x7CD10AD3, 0x44C0657F, 0x0CF3D58B, 0x34E2BA27,
|
||||
0xD6D1DE21, 0xEEC0B18D, 0xA6F30179, 0x9EE26ED5,
|
||||
0x36946091, 0x0E850F3D, 0x46B6BFC9, 0x7EA7D065,
|
||||
0x13B6D5B0, 0x2BA7BA1C, 0x63940AE8, 0x5B856544,
|
||||
0xF3F36B00, 0xCBE204AC, 0x83D1B458, 0xBBC0DBF4,
|
||||
0x425B0AA5, 0x7A4A6509, 0x3279D5FD, 0x0A68BA51,
|
||||
0xA21EB415, 0x9A0FDBB9, 0xD23C6B4D, 0xEA2D04E1,
|
||||
0x873C0134, 0xBF2D6E98, 0xF71EDE6C, 0xCF0FB1C0,
|
||||
0x6779BF84, 0x5F68D028, 0x175B60DC, 0x2F4A0F70,
|
||||
0xCD796B76, 0xF56804DA, 0xBD5BB42E, 0x854ADB82,
|
||||
0x2D3CD5C6, 0x152DBA6A, 0x5D1E0A9E, 0x650F6532,
|
||||
0x081E60E7, 0x300F0F4B, 0x783CBFBF, 0x402DD013,
|
||||
0xE85BDE57, 0xD04AB1FB, 0x9879010F, 0xA0686EA3,
|
||||
/* T8_5 */
|
||||
0x00000000, 0xEF306B19, 0xDB8CA0C3, 0x34BCCBDA,
|
||||
0xB2F53777, 0x5DC55C6E, 0x697997B4, 0x8649FCAD,
|
||||
0x6006181F, 0x8F367306, 0xBB8AB8DC, 0x54BAD3C5,
|
||||
0xD2F32F68, 0x3DC34471, 0x097F8FAB, 0xE64FE4B2,
|
||||
0xC00C303E, 0x2F3C5B27, 0x1B8090FD, 0xF4B0FBE4,
|
||||
0x72F90749, 0x9DC96C50, 0xA975A78A, 0x4645CC93,
|
||||
0xA00A2821, 0x4F3A4338, 0x7B8688E2, 0x94B6E3FB,
|
||||
0x12FF1F56, 0xFDCF744F, 0xC973BF95, 0x2643D48C,
|
||||
0x85F4168D, 0x6AC47D94, 0x5E78B64E, 0xB148DD57,
|
||||
0x370121FA, 0xD8314AE3, 0xEC8D8139, 0x03BDEA20,
|
||||
0xE5F20E92, 0x0AC2658B, 0x3E7EAE51, 0xD14EC548,
|
||||
0x570739E5, 0xB83752FC, 0x8C8B9926, 0x63BBF23F,
|
||||
0x45F826B3, 0xAAC84DAA, 0x9E748670, 0x7144ED69,
|
||||
0xF70D11C4, 0x183D7ADD, 0x2C81B107, 0xC3B1DA1E,
|
||||
0x25FE3EAC, 0xCACE55B5, 0xFE729E6F, 0x1142F576,
|
||||
0x970B09DB, 0x783B62C2, 0x4C87A918, 0xA3B7C201,
|
||||
0x0E045BEB, 0xE13430F2, 0xD588FB28, 0x3AB89031,
|
||||
0xBCF16C9C, 0x53C10785, 0x677DCC5F, 0x884DA746,
|
||||
0x6E0243F4, 0x813228ED, 0xB58EE337, 0x5ABE882E,
|
||||
0xDCF77483, 0x33C71F9A, 0x077BD440, 0xE84BBF59,
|
||||
0xCE086BD5, 0x213800CC, 0x1584CB16, 0xFAB4A00F,
|
||||
0x7CFD5CA2, 0x93CD37BB, 0xA771FC61, 0x48419778,
|
||||
0xAE0E73CA, 0x413E18D3, 0x7582D309, 0x9AB2B810,
|
||||
0x1CFB44BD, 0xF3CB2FA4, 0xC777E47E, 0x28478F67,
|
||||
0x8BF04D66, 0x64C0267F, 0x507CEDA5, 0xBF4C86BC,
|
||||
0x39057A11, 0xD6351108, 0xE289DAD2, 0x0DB9B1CB,
|
||||
0xEBF65579, 0x04C63E60, 0x307AF5BA, 0xDF4A9EA3,
|
||||
0x5903620E, 0xB6330917, 0x828FC2CD, 0x6DBFA9D4,
|
||||
0x4BFC7D58, 0xA4CC1641, 0x9070DD9B, 0x7F40B682,
|
||||
0xF9094A2F, 0x16392136, 0x2285EAEC, 0xCDB581F5,
|
||||
0x2BFA6547, 0xC4CA0E5E, 0xF076C584, 0x1F46AE9D,
|
||||
0x990F5230, 0x763F3929, 0x4283F2F3, 0xADB399EA,
|
||||
0x1C08B7D6, 0xF338DCCF, 0xC7841715, 0x28B47C0C,
|
||||
0xAEFD80A1, 0x41CDEBB8, 0x75712062, 0x9A414B7B,
|
||||
0x7C0EAFC9, 0x933EC4D0, 0xA7820F0A, 0x48B26413,
|
||||
0xCEFB98BE, 0x21CBF3A7, 0x1577387D, 0xFA475364,
|
||||
0xDC0487E8, 0x3334ECF1, 0x0788272B, 0xE8B84C32,
|
||||
0x6EF1B09F, 0x81C1DB86, 0xB57D105C, 0x5A4D7B45,
|
||||
0xBC029FF7, 0x5332F4EE, 0x678E3F34, 0x88BE542D,
|
||||
0x0EF7A880, 0xE1C7C399, 0xD57B0843, 0x3A4B635A,
|
||||
0x99FCA15B, 0x76CCCA42, 0x42700198, 0xAD406A81,
|
||||
0x2B09962C, 0xC439FD35, 0xF08536EF, 0x1FB55DF6,
|
||||
0xF9FAB944, 0x16CAD25D, 0x22761987, 0xCD46729E,
|
||||
0x4B0F8E33, 0xA43FE52A, 0x90832EF0, 0x7FB345E9,
|
||||
0x59F09165, 0xB6C0FA7C, 0x827C31A6, 0x6D4C5ABF,
|
||||
0xEB05A612, 0x0435CD0B, 0x308906D1, 0xDFB96DC8,
|
||||
0x39F6897A, 0xD6C6E263, 0xE27A29B9, 0x0D4A42A0,
|
||||
0x8B03BE0D, 0x6433D514, 0x508F1ECE, 0xBFBF75D7,
|
||||
0x120CEC3D, 0xFD3C8724, 0xC9804CFE, 0x26B027E7,
|
||||
0xA0F9DB4A, 0x4FC9B053, 0x7B757B89, 0x94451090,
|
||||
0x720AF422, 0x9D3A9F3B, 0xA98654E1, 0x46B63FF8,
|
||||
0xC0FFC355, 0x2FCFA84C, 0x1B736396, 0xF443088F,
|
||||
0xD200DC03, 0x3D30B71A, 0x098C7CC0, 0xE6BC17D9,
|
||||
0x60F5EB74, 0x8FC5806D, 0xBB794BB7, 0x544920AE,
|
||||
0xB206C41C, 0x5D36AF05, 0x698A64DF, 0x86BA0FC6,
|
||||
0x00F3F36B, 0xEFC39872, 0xDB7F53A8, 0x344F38B1,
|
||||
0x97F8FAB0, 0x78C891A9, 0x4C745A73, 0xA344316A,
|
||||
0x250DCDC7, 0xCA3DA6DE, 0xFE816D04, 0x11B1061D,
|
||||
0xF7FEE2AF, 0x18CE89B6, 0x2C72426C, 0xC3422975,
|
||||
0x450BD5D8, 0xAA3BBEC1, 0x9E87751B, 0x71B71E02,
|
||||
0x57F4CA8E, 0xB8C4A197, 0x8C786A4D, 0x63480154,
|
||||
0xE501FDF9, 0x0A3196E0, 0x3E8D5D3A, 0xD1BD3623,
|
||||
0x37F2D291, 0xD8C2B988, 0xEC7E7252, 0x034E194B,
|
||||
0x8507E5E6, 0x6A378EFF, 0x5E8B4525, 0xB1BB2E3C,
|
||||
/* T8_6 */
|
||||
0x00000000, 0x68032CC8, 0xD0065990, 0xB8057558,
|
||||
0xA5E0C5D1, 0xCDE3E919, 0x75E69C41, 0x1DE5B089,
|
||||
0x4E2DFD53, 0x262ED19B, 0x9E2BA4C3, 0xF628880B,
|
||||
0xEBCD3882, 0x83CE144A, 0x3BCB6112, 0x53C84DDA,
|
||||
0x9C5BFAA6, 0xF458D66E, 0x4C5DA336, 0x245E8FFE,
|
||||
0x39BB3F77, 0x51B813BF, 0xE9BD66E7, 0x81BE4A2F,
|
||||
0xD27607F5, 0xBA752B3D, 0x02705E65, 0x6A7372AD,
|
||||
0x7796C224, 0x1F95EEEC, 0xA7909BB4, 0xCF93B77C,
|
||||
0x3D5B83BD, 0x5558AF75, 0xED5DDA2D, 0x855EF6E5,
|
||||
0x98BB466C, 0xF0B86AA4, 0x48BD1FFC, 0x20BE3334,
|
||||
0x73767EEE, 0x1B755226, 0xA370277E, 0xCB730BB6,
|
||||
0xD696BB3F, 0xBE9597F7, 0x0690E2AF, 0x6E93CE67,
|
||||
0xA100791B, 0xC90355D3, 0x7106208B, 0x19050C43,
|
||||
0x04E0BCCA, 0x6CE39002, 0xD4E6E55A, 0xBCE5C992,
|
||||
0xEF2D8448, 0x872EA880, 0x3F2BDDD8, 0x5728F110,
|
||||
0x4ACD4199, 0x22CE6D51, 0x9ACB1809, 0xF2C834C1,
|
||||
0x7AB7077A, 0x12B42BB2, 0xAAB15EEA, 0xC2B27222,
|
||||
0xDF57C2AB, 0xB754EE63, 0x0F519B3B, 0x6752B7F3,
|
||||
0x349AFA29, 0x5C99D6E1, 0xE49CA3B9, 0x8C9F8F71,
|
||||
0x917A3FF8, 0xF9791330, 0x417C6668, 0x297F4AA0,
|
||||
0xE6ECFDDC, 0x8EEFD114, 0x36EAA44C, 0x5EE98884,
|
||||
0x430C380D, 0x2B0F14C5, 0x930A619D, 0xFB094D55,
|
||||
0xA8C1008F, 0xC0C22C47, 0x78C7591F, 0x10C475D7,
|
||||
0x0D21C55E, 0x6522E996, 0xDD279CCE, 0xB524B006,
|
||||
0x47EC84C7, 0x2FEFA80F, 0x97EADD57, 0xFFE9F19F,
|
||||
0xE20C4116, 0x8A0F6DDE, 0x320A1886, 0x5A09344E,
|
||||
0x09C17994, 0x61C2555C, 0xD9C72004, 0xB1C40CCC,
|
||||
0xAC21BC45, 0xC422908D, 0x7C27E5D5, 0x1424C91D,
|
||||
0xDBB77E61, 0xB3B452A9, 0x0BB127F1, 0x63B20B39,
|
||||
0x7E57BBB0, 0x16549778, 0xAE51E220, 0xC652CEE8,
|
||||
0x959A8332, 0xFD99AFFA, 0x459CDAA2, 0x2D9FF66A,
|
||||
0x307A46E3, 0x58796A2B, 0xE07C1F73, 0x887F33BB,
|
||||
0xF56E0EF4, 0x9D6D223C, 0x25685764, 0x4D6B7BAC,
|
||||
0x508ECB25, 0x388DE7ED, 0x808892B5, 0xE88BBE7D,
|
||||
0xBB43F3A7, 0xD340DF6F, 0x6B45AA37, 0x034686FF,
|
||||
0x1EA33676, 0x76A01ABE, 0xCEA56FE6, 0xA6A6432E,
|
||||
0x6935F452, 0x0136D89A, 0xB933ADC2, 0xD130810A,
|
||||
0xCCD53183, 0xA4D61D4B, 0x1CD36813, 0x74D044DB,
|
||||
0x27180901, 0x4F1B25C9, 0xF71E5091, 0x9F1D7C59,
|
||||
0x82F8CCD0, 0xEAFBE018, 0x52FE9540, 0x3AFDB988,
|
||||
0xC8358D49, 0xA036A181, 0x1833D4D9, 0x7030F811,
|
||||
0x6DD54898, 0x05D66450, 0xBDD31108, 0xD5D03DC0,
|
||||
0x8618701A, 0xEE1B5CD2, 0x561E298A, 0x3E1D0542,
|
||||
0x23F8B5CB, 0x4BFB9903, 0xF3FEEC5B, 0x9BFDC093,
|
||||
0x546E77EF, 0x3C6D5B27, 0x84682E7F, 0xEC6B02B7,
|
||||
0xF18EB23E, 0x998D9EF6, 0x2188EBAE, 0x498BC766,
|
||||
0x1A438ABC, 0x7240A674, 0xCA45D32C, 0xA246FFE4,
|
||||
0xBFA34F6D, 0xD7A063A5, 0x6FA516FD, 0x07A63A35,
|
||||
0x8FD9098E, 0xE7DA2546, 0x5FDF501E, 0x37DC7CD6,
|
||||
0x2A39CC5F, 0x423AE097, 0xFA3F95CF, 0x923CB907,
|
||||
0xC1F4F4DD, 0xA9F7D815, 0x11F2AD4D, 0x79F18185,
|
||||
0x6414310C, 0x0C171DC4, 0xB412689C, 0xDC114454,
|
||||
0x1382F328, 0x7B81DFE0, 0xC384AAB8, 0xAB878670,
|
||||
0xB66236F9, 0xDE611A31, 0x66646F69, 0x0E6743A1,
|
||||
0x5DAF0E7B, 0x35AC22B3, 0x8DA957EB, 0xE5AA7B23,
|
||||
0xF84FCBAA, 0x904CE762, 0x2849923A, 0x404ABEF2,
|
||||
0xB2828A33, 0xDA81A6FB, 0x6284D3A3, 0x0A87FF6B,
|
||||
0x17624FE2, 0x7F61632A, 0xC7641672, 0xAF673ABA,
|
||||
0xFCAF7760, 0x94AC5BA8, 0x2CA92EF0, 0x44AA0238,
|
||||
0x594FB2B1, 0x314C9E79, 0x8949EB21, 0xE14AC7E9,
|
||||
0x2ED97095, 0x46DA5C5D, 0xFEDF2905, 0x96DC05CD,
|
||||
0x8B39B544, 0xE33A998C, 0x5B3FECD4, 0x333CC01C,
|
||||
0x60F48DC6, 0x08F7A10E, 0xB0F2D456, 0xD8F1F89E,
|
||||
0xC5144817, 0xAD1764DF, 0x15121187, 0x7D113D4F,
|
||||
/* T8_7 */
|
||||
0x00000000, 0x493C7D27, 0x9278FA4E, 0xDB448769,
|
||||
0x211D826D, 0x6821FF4A, 0xB3657823, 0xFA590504,
|
||||
0x423B04DA, 0x0B0779FD, 0xD043FE94, 0x997F83B3,
|
||||
0x632686B7, 0x2A1AFB90, 0xF15E7CF9, 0xB86201DE,
|
||||
0x847609B4, 0xCD4A7493, 0x160EF3FA, 0x5F328EDD,
|
||||
0xA56B8BD9, 0xEC57F6FE, 0x37137197, 0x7E2F0CB0,
|
||||
0xC64D0D6E, 0x8F717049, 0x5435F720, 0x1D098A07,
|
||||
0xE7508F03, 0xAE6CF224, 0x7528754D, 0x3C14086A,
|
||||
0x0D006599, 0x443C18BE, 0x9F789FD7, 0xD644E2F0,
|
||||
0x2C1DE7F4, 0x65219AD3, 0xBE651DBA, 0xF759609D,
|
||||
0x4F3B6143, 0x06071C64, 0xDD439B0D, 0x947FE62A,
|
||||
0x6E26E32E, 0x271A9E09, 0xFC5E1960, 0xB5626447,
|
||||
0x89766C2D, 0xC04A110A, 0x1B0E9663, 0x5232EB44,
|
||||
0xA86BEE40, 0xE1579367, 0x3A13140E, 0x732F6929,
|
||||
0xCB4D68F7, 0x827115D0, 0x593592B9, 0x1009EF9E,
|
||||
0xEA50EA9A, 0xA36C97BD, 0x782810D4, 0x31146DF3,
|
||||
0x1A00CB32, 0x533CB615, 0x8878317C, 0xC1444C5B,
|
||||
0x3B1D495F, 0x72213478, 0xA965B311, 0xE059CE36,
|
||||
0x583BCFE8, 0x1107B2CF, 0xCA4335A6, 0x837F4881,
|
||||
0x79264D85, 0x301A30A2, 0xEB5EB7CB, 0xA262CAEC,
|
||||
0x9E76C286, 0xD74ABFA1, 0x0C0E38C8, 0x453245EF,
|
||||
0xBF6B40EB, 0xF6573DCC, 0x2D13BAA5, 0x642FC782,
|
||||
0xDC4DC65C, 0x9571BB7B, 0x4E353C12, 0x07094135,
|
||||
0xFD504431, 0xB46C3916, 0x6F28BE7F, 0x2614C358,
|
||||
0x1700AEAB, 0x5E3CD38C, 0x857854E5, 0xCC4429C2,
|
||||
0x361D2CC6, 0x7F2151E1, 0xA465D688, 0xED59ABAF,
|
||||
0x553BAA71, 0x1C07D756, 0xC743503F, 0x8E7F2D18,
|
||||
0x7426281C, 0x3D1A553B, 0xE65ED252, 0xAF62AF75,
|
||||
0x9376A71F, 0xDA4ADA38, 0x010E5D51, 0x48322076,
|
||||
0xB26B2572, 0xFB575855, 0x2013DF3C, 0x692FA21B,
|
||||
0xD14DA3C5, 0x9871DEE2, 0x4335598B, 0x0A0924AC,
|
||||
0xF05021A8, 0xB96C5C8F, 0x6228DBE6, 0x2B14A6C1,
|
||||
0x34019664, 0x7D3DEB43, 0xA6796C2A, 0xEF45110D,
|
||||
0x151C1409, 0x5C20692E, 0x8764EE47, 0xCE589360,
|
||||
0x763A92BE, 0x3F06EF99, 0xE44268F0, 0xAD7E15D7,
|
||||
0x572710D3, 0x1E1B6DF4, 0xC55FEA9D, 0x8C6397BA,
|
||||
0xB0779FD0, 0xF94BE2F7, 0x220F659E, 0x6B3318B9,
|
||||
0x916A1DBD, 0xD856609A, 0x0312E7F3, 0x4A2E9AD4,
|
||||
0xF24C9B0A, 0xBB70E62D, 0x60346144, 0x29081C63,
|
||||
0xD3511967, 0x9A6D6440, 0x4129E329, 0x08159E0E,
|
||||
0x3901F3FD, 0x703D8EDA, 0xAB7909B3, 0xE2457494,
|
||||
0x181C7190, 0x51200CB7, 0x8A648BDE, 0xC358F6F9,
|
||||
0x7B3AF727, 0x32068A00, 0xE9420D69, 0xA07E704E,
|
||||
0x5A27754A, 0x131B086D, 0xC85F8F04, 0x8163F223,
|
||||
0xBD77FA49, 0xF44B876E, 0x2F0F0007, 0x66337D20,
|
||||
0x9C6A7824, 0xD5560503, 0x0E12826A, 0x472EFF4D,
|
||||
0xFF4CFE93, 0xB67083B4, 0x6D3404DD, 0x240879FA,
|
||||
0xDE517CFE, 0x976D01D9, 0x4C2986B0, 0x0515FB97,
|
||||
0x2E015D56, 0x673D2071, 0xBC79A718, 0xF545DA3F,
|
||||
0x0F1CDF3B, 0x4620A21C, 0x9D642575, 0xD4585852,
|
||||
0x6C3A598C, 0x250624AB, 0xFE42A3C2, 0xB77EDEE5,
|
||||
0x4D27DBE1, 0x041BA6C6, 0xDF5F21AF, 0x96635C88,
|
||||
0xAA7754E2, 0xE34B29C5, 0x380FAEAC, 0x7133D38B,
|
||||
0x8B6AD68F, 0xC256ABA8, 0x19122CC1, 0x502E51E6,
|
||||
0xE84C5038, 0xA1702D1F, 0x7A34AA76, 0x3308D751,
|
||||
0xC951D255, 0x806DAF72, 0x5B29281B, 0x1215553C,
|
||||
0x230138CF, 0x6A3D45E8, 0xB179C281, 0xF845BFA6,
|
||||
0x021CBAA2, 0x4B20C785, 0x906440EC, 0xD9583DCB,
|
||||
0x613A3C15, 0x28064132, 0xF342C65B, 0xBA7EBB7C,
|
||||
0x4027BE78, 0x091BC35F, 0xD25F4436, 0x9B633911,
|
||||
0xA777317B, 0xEE4B4C5C, 0x350FCB35, 0x7C33B612,
|
||||
0x866AB316, 0xCF56CE31, 0x14124958, 0x5D2E347F,
|
||||
0xE54C35A1, 0xAC704886, 0x7734CFEF, 0x3E08B2C8,
|
||||
0xC451B7CC, 0x8D6DCAEB, 0x56294D82, 0x1F1530A5
|
||||
};
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Cre3per, Daniele Gobbetti, Jean-François Greffier, João Paulo
|
||||
Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß, maxirnilian, protomors,
|
||||
Quallenauge, Sami Alaoui, Sophanimus, tiparega, Vadim Kaushan
|
||||
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Jean-François Greffier,
|
||||
João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß, maxirnilian,
|
||||
protomors, Quallenauge, Sami Alaoui, Sophanimus, tiparega, Vadim Kaushan
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -68,6 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd02.MijiaLywsd02Co
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miscale2.MiScale2DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
|
||||
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.Roidmi3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
|
||||
@ -227,6 +228,7 @@ public class DeviceHelper {
|
||||
result.add(new EXRIZUK8Coordinator());
|
||||
result.add(new TeclastH30Coordinator());
|
||||
result.add(new XWatchCoordinator());
|
||||
result.add(new QHybridCoordinator());
|
||||
result.add(new ZeTimeCoordinator());
|
||||
result.add(new ID115Coordinator());
|
||||
result.add(new Watch9DeviceCoordinator());
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Daniele Gobbetti, Julien Pivotto
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Daniel Dakhno, Daniele Gobbetti,
|
||||
Julien Pivotto
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -53,4 +54,13 @@ public class LimitedQueue {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
synchronized public Object lookupByValue(Object value){
|
||||
for (Pair entry : list) {
|
||||
if (value.equals(entry.second)) {
|
||||
return entry.first;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_notification_qhybrid.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_notification_qhybrid.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_notification_qhybrid.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_notification_qhybrid.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user