mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 01:55:50 +01:00
Fossil Q hybrid and Skagen support added
Opening this branch for the second time inorder to purge certain files form the repo
This commit is contained in:
parent
487c4b7498
commit
09f6cada87
@ -4,6 +4,8 @@ The wiki on github.com is a read-only mirror, as is the git repo itself. Issues
|
||||
Gadgetbridge
|
||||
============
|
||||
|
||||
This implementation for the Fossil Q hybrid is not officially supported by Fossil nor does Fossil provide any warranty concerning the functionality of this code.
|
||||
|
||||
Gadgetbridge is an Android (4.4+) application which will allow you to use your
|
||||
Pebble, Mi Band, Amazfit Bip and HPlus device (and more) without the vendor's closed source application
|
||||
and without the need to create an account and transmit any of your data to the
|
||||
@ -48,6 +50,8 @@ vendor's servers.
|
||||
* XWatch (Affordable Chinese Casio-like smartwatches)
|
||||
* Vibratissimo (experimental)
|
||||
* ZeTime (WIP) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime)
|
||||
* Fossil Q Hybrid
|
||||
* Skagen Connected
|
||||
|
||||
|
||||
## Features
|
||||
|
@ -66,7 +66,6 @@ dependencies {
|
||||
testImplementation "org.robolectric:robolectric:4.2.1"
|
||||
testImplementation "com.google.code.gson:gson:2.8.5"
|
||||
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "androidx.appcompat:appcompat:1.0.2"
|
||||
implementation "androidx.preference:preference:1.1.0-alpha05"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
@ -85,12 +84,21 @@ 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'
|
||||
|
||||
implementation 'com.android.support:support-v4:28.1.0'
|
||||
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
}
|
||||
|
||||
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" />
|
||||
@ -471,7 +471,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>
|
||||
|
@ -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.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
@ -105,6 +106,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) {
|
||||
@ -285,16 +295,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);
|
||||
@ -424,8 +434,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,483 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
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.IBinder;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
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.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 nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||
|
||||
public class ConfigActivity extends AbstractGBActivity implements ServiceConnection, QHybridSupport.OnVibrationStrengthListener {
|
||||
PackageAdapter adapter;
|
||||
ArrayList<PackageConfig> list;
|
||||
PackageConfigHelper helper;
|
||||
|
||||
final int REQUEST_CODE_ADD_APP = 0;
|
||||
|
||||
private boolean hasControl = false;
|
||||
|
||||
QHybridSupport support;
|
||||
|
||||
SharedPreferences prefs;
|
||||
|
||||
TextView timeOffsetView;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_qhybrid_settings);
|
||||
|
||||
Log.d("Config", "device: " + (getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE) == null));
|
||||
|
||||
findViewById(R.id.buttonOverwriteButtons).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
setSettingsEnables(false);
|
||||
ConfigActivity.this.support.overwriteButtons(new QHybridSupport.OnButtonOverwriteListener() {
|
||||
@Override
|
||||
public void OnButtonOverwrite(final boolean success) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSettingsEnables(true);
|
||||
if(!success){
|
||||
Toast.makeText(ConfigActivity.this, "Error overwriting buttons", Toast.LENGTH_SHORT).show();
|
||||
}else{
|
||||
Toast.makeText(ConfigActivity.this, "successfully overwritten buttons.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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("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));
|
||||
Toast.makeText(ConfigActivity.this, "change might take some seconds...", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("cancel", null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
updateTimeOffset();
|
||||
|
||||
bindService(new Intent(getApplicationContext(), DeviceCommunicationService.class), this, 0);
|
||||
|
||||
setTitle(R.string.preferences_qhybrid_settings);
|
||||
|
||||
ListView appList = findViewById(R.id.qhybrid_appList);
|
||||
|
||||
helper = new PackageConfigHelper(getApplicationContext());
|
||||
list = helper.getSettings();
|
||||
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(AdapterView<?> adapterView, View view, final int i, long l) {
|
||||
PopupMenu menu = new PopupMenu(ConfigActivity.this, view);
|
||||
menu.getMenu().add("edit");
|
||||
menu.getMenu().add("delete");
|
||||
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
switch (menuItem.getTitle().toString()) {
|
||||
case "edit": {
|
||||
TimePicker picker = new TimePicker(ConfigActivity.this, list.get(i));
|
||||
picker.finishListener = new TimePicker.OnFinishListener() {
|
||||
@Override
|
||||
public void onFinish(boolean success, PackageConfig config) {
|
||||
setControl(false, null);
|
||||
if (success) {
|
||||
helper.saveConfig(config);
|
||||
refreshList();
|
||||
}
|
||||
}
|
||||
};
|
||||
picker.handsListener = new TimePicker.OnHandsSetListener() {
|
||||
@Override
|
||||
public void onHandsSet(PackageConfig config) {
|
||||
setHands(config);
|
||||
}
|
||||
};
|
||||
picker.vibrationListener = new TimePicker.OnVibrationSetListener() {
|
||||
@Override
|
||||
public void onVibrationSet(PackageConfig config) {
|
||||
vibrate(config);
|
||||
}
|
||||
};
|
||||
setControl(true, picker.getSettings());
|
||||
break;
|
||||
}
|
||||
case "delete": {
|
||||
helper.deleteConfig(list.get(i));
|
||||
refreshList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
menu.show();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
appList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
if(ConfigActivity.this.support == null){
|
||||
Toast.makeText(ConfigActivity.this, "connect device to test notification", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
ConfigActivity.this.support.playNotification(list.get(i));
|
||||
}
|
||||
});
|
||||
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;
|
||||
support.setVibrationStrength((int) Math.pow(2, progress) * 25);
|
||||
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 setSettingsEnables(boolean enables) {
|
||||
findViewById(R.id.settingsLayout).setAlpha(enables ? 1f : 0.2f);
|
||||
findViewById(R.id.vibrationSettingProgressBar).setVisibility(enables ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void updateSettings() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSettingsEnables(false);
|
||||
}
|
||||
});
|
||||
this.support.getGoal(new QHybridSupport.OnGoalListener() {
|
||||
@Override
|
||||
public void onGoal(final long goal) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
EditText et = findViewById(R.id.stepGoalEt);
|
||||
et.setOnEditorActionListener(null);
|
||||
final String text = String.valueOf(goal);
|
||||
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.equals(text)) {
|
||||
support.setGoal(Integer.parseInt(t));
|
||||
updateSettings();
|
||||
}
|
||||
((InputMethodManager) getApplicationContext().getSystemService(Activity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.support.getVibrationStrength(this);
|
||||
}
|
||||
|
||||
private void setControl(boolean control, PackageConfig 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(PackageConfig config) {
|
||||
sendControl(config, QHybridSupport.QHYBRID_COMMAND_SET);
|
||||
}
|
||||
|
||||
private void vibrate(PackageConfig config) {
|
||||
sendControl(config, QHybridSupport.QHYBRID_COMMAND_VIBRATE);
|
||||
}
|
||||
|
||||
private void sendControl(PackageConfig config, String request) {
|
||||
Intent intent = new Intent(request);
|
||||
intent.putExtra("CONFIG", config);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void refreshList() {
|
||||
list.clear();
|
||||
list.addAll(helper.getSettings());
|
||||
list.add(null);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
unbindService(this);
|
||||
helper.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
refreshList();
|
||||
registerReceiver(buttonReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
unregisterReceiver(buttonReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
Log.d("Config", "service connected");
|
||||
DeviceCommunicationService.CommunicationServiceBinder binder = (DeviceCommunicationService.CommunicationServiceBinder) iBinder;
|
||||
if (binder == null) {
|
||||
Log.d("Config", "Service not running");
|
||||
setSettingsError("Service not running");
|
||||
return;
|
||||
}
|
||||
DeviceSupport support = ((DeviceCommunicationService.CommunicationServiceBinder) iBinder).getDeviceSupport();
|
||||
if (!(support instanceof QHybridSupport)) {
|
||||
Log.d("Config", "Watch not connected");
|
||||
setSettingsError("Watch not connected");
|
||||
return;
|
||||
}
|
||||
this.support = (QHybridSupport) support;
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVibrationStrength(int strength) {
|
||||
final int strengthProgress = (int) (Math.log(strength / 25) / Math.log(2));
|
||||
Log.d("Config", "got strength: " + strength);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSettingsEnables(true);
|
||||
SeekBar seekBar = findViewById(R.id.vibrationStrengthBar);
|
||||
seekBar.setProgress(strengthProgress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSettingsError(final String error) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSettingsEnables(false);
|
||||
findViewById(R.id.vibrationSettingProgressBar).setVisibility(View.GONE);
|
||||
((TextView) findViewById(R.id.settingsErrorText)).setVisibility(View.VISIBLE);
|
||||
((TextView) findViewById(R.id.settingsErrorText)).setText(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class PackageAdapter extends ArrayAdapter<PackageConfig> {
|
||||
PackageManager manager;
|
||||
|
||||
PackageAdapter(@NonNull Context context, int resource, @NonNull List<PackageConfig> 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);
|
||||
PackageConfig 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) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
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 buttonReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Toast.makeText(ConfigActivity.this, "Button " + intent.getIntExtra("BUTTON", -1) + " pressed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
|
||||
class AddPackageConfig extends PackageConfig{
|
||||
AddPackageConfig() {
|
||||
super(null, null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.PlayNotificationRequest;
|
||||
|
||||
public class PackageConfig implements Serializable {
|
||||
private short min, hour;
|
||||
private String packageName, appName;
|
||||
private int vibration;
|
||||
private boolean respectSilentMode;
|
||||
private long id = -1;
|
||||
|
||||
PackageConfig(short min, short hour, String packageName, String appName, boolean respectSilentMode, int vibration) {
|
||||
this.min = min;
|
||||
this.hour = hour;
|
||||
this.packageName = packageName;
|
||||
this.appName = appName;
|
||||
this.respectSilentMode = respectSilentMode;
|
||||
this.vibration = vibration;
|
||||
}
|
||||
|
||||
PackageConfig(short min, short hour, String packageName, String appName, boolean respectSilentMode, int vibration, long id) {
|
||||
this.min = min;
|
||||
this.hour = hour;
|
||||
this.packageName = packageName;
|
||||
this.appName = appName;
|
||||
this.respectSilentMode = respectSilentMode;
|
||||
this.vibration = vibration;
|
||||
this.id = id;
|
||||
}
|
||||
PackageConfig(String packageName, String appName) {
|
||||
this.min = 0;
|
||||
this.hour = 0;
|
||||
this.packageName = packageName;
|
||||
this.appName = appName;
|
||||
this.respectSilentMode = false;
|
||||
this.vibration = PlayNotificationRequest.VibrationType.SINGLE_NORMAL.getValue();
|
||||
this.id = -1;
|
||||
}
|
||||
|
||||
public int getVibration() {
|
||||
return vibration;
|
||||
}
|
||||
|
||||
public void setVibration(int 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 String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
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.database.DBOpenHelper;
|
||||
|
||||
public class PackageConfigHelper extends DBOpenHelper {
|
||||
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";
|
||||
|
||||
SQLiteDatabase database;
|
||||
|
||||
|
||||
public PackageConfigHelper(Context context) {
|
||||
super(context, DB_NAME, null);
|
||||
this.database = getWritableDatabase();
|
||||
initDB();
|
||||
}
|
||||
|
||||
public void saveConfig(PackageConfig settings){
|
||||
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());
|
||||
values.put(DB_RESPECT_SILENT, settings.getRespectSilentMode());
|
||||
|
||||
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())});
|
||||
}
|
||||
//LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent());
|
||||
}
|
||||
|
||||
public ArrayList<PackageConfig> getSettings(){
|
||||
Cursor cursor = database.query(DB_TABLE, new String[]{"*"}, null, null, null, null, null);
|
||||
int size = cursor.getCount();
|
||||
ArrayList<PackageConfig> 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 PackageConfig(
|
||||
(short)cursor.getInt(minPos),
|
||||
(short)cursor.getInt(hourPos),
|
||||
cursor.getString(packageNamePos),
|
||||
cursor.getString(appNamePos),
|
||||
cursor.getInt(silentPos) == 1,
|
||||
cursor.getInt(vibrationPos),
|
||||
cursor.getInt(idPos)
|
||||
));
|
||||
Log.d("Settings", "setting #" + cursor.getPosition() + ": " + cursor.getInt(silentPos));
|
||||
}while (cursor.moveToNext());
|
||||
}
|
||||
cursor.close();
|
||||
return list;
|
||||
}
|
||||
|
||||
public PackageConfig getSetting(String appName){
|
||||
if(appName == null) return null;
|
||||
Cursor c = database.query(DB_TABLE, new String[]{"*"}, DB_APPNAME + "=?", new String[]{appName}, null, null, null);
|
||||
if(c.getCount() == 0){
|
||||
c.close();
|
||||
return null;
|
||||
}
|
||||
c.moveToFirst();
|
||||
PackageConfig settings = new PackageConfig(
|
||||
(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,
|
||||
c.getInt(c.getColumnIndex(DB_VIBRATION)),
|
||||
c.getInt(c.getColumnIndex(DB_ID))
|
||||
);
|
||||
c.close();
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void initDB(){
|
||||
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);");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(){
|
||||
super.close();
|
||||
database.close();
|
||||
}
|
||||
|
||||
public void deleteConfig(PackageConfig packageSettings) {
|
||||
Log.d("DB", "deleting id " + packageSettings.getId());
|
||||
if(packageSettings.getId() == -1) return;
|
||||
this.database.delete(DB_TABLE, DB_ID + "=?", new String[]{String.valueOf(packageSettings.getId())});
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
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 java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||
|
||||
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);
|
||||
|
||||
helper = new PackageConfigHelper(getApplicationContext());
|
||||
|
||||
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() {
|
||||
Collections.sort(packages, new Comparator<PackageInfo>() {
|
||||
@Override
|
||||
public int compare(PackageInfo packageInfo, PackageInfo t1) {
|
||||
return manager.getApplicationLabel(packageInfo.applicationInfo)
|
||||
.toString()
|
||||
.compareToIgnoreCase(
|
||||
manager.getApplicationLabel(t1.applicationInfo)
|
||||
.toString()
|
||||
);
|
||||
}
|
||||
});
|
||||
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();
|
||||
helper.close();
|
||||
}
|
||||
|
||||
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(PackageConfig config){
|
||||
sendControl(config, QHybridSupport.QHYBRID_COMMAND_SET);
|
||||
}
|
||||
|
||||
private void vibrate(PackageConfig config){
|
||||
sendControl(config, QHybridSupport.QHYBRID_COMMAND_VIBRATE);
|
||||
}
|
||||
|
||||
private void sendControl(PackageConfig 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, PackageConfig config) {
|
||||
setControl(false);
|
||||
if(success){
|
||||
helper.saveConfig(config);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
picker.handsListener = new TimePicker.OnHandsSetListener() {
|
||||
@Override
|
||||
public void onHandsSet(PackageConfig config) {
|
||||
setHands(config);
|
||||
}
|
||||
};
|
||||
|
||||
picker.vibrationListener = new TimePicker.OnVibrationSetListener() {
|
||||
@Override
|
||||
public void onVibrationSet(PackageConfig 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,149 @@
|
||||
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 nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
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;
|
||||
|
||||
public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
for(ParcelUuid uuid : candidate.getServiceUuids()){
|
||||
if(uuid.getUuid().toString().equals("00001812-0000-1000-8000-00805f9b34fb")){
|
||||
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("00001812-0000-1000-8000-00805f9b34fb")).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.FOSSILQHYBRID;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@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() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return 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 false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return 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,93 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.twofortyfouram.locale.sdk.client.ui.activity.AbstractPluginActivity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.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.getValue() + ")");
|
||||
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,27 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
PackageConfig config = new PackageConfig((short)minDegrees, (short)hourDegrees, null, null, false, Integer.parseInt(vibration));
|
||||
|
||||
Intent send = new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION);
|
||||
send.putExtra("CONFIG", config);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(send);
|
||||
}
|
||||
}
|
@ -0,0 +1,305 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentValues;
|
||||
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.PlayNotificationRequest;
|
||||
|
||||
public class TimePicker extends AlertDialog.Builder {
|
||||
ImageView pickerView;
|
||||
Canvas pickerCanvas;
|
||||
Bitmap pickerBitmap;
|
||||
|
||||
PackageConfig 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 PackageConfig(info.packageName, context.getApplicationContext().getPackageManager().getApplicationLabel(info.applicationInfo).toString());
|
||||
initGraphics(context);
|
||||
}
|
||||
|
||||
protected TimePicker(Context context, PackageConfig 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());
|
||||
group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(RadioGroup radioGroup, int i) {
|
||||
settings.setVibration(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();
|
||||
if(this.settings.getHour() == -1 && this.settings.getMin() == -1) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setClickable(false);
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setAlpha(0.4f);
|
||||
}
|
||||
|
||||
pickerView.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
handleTouch(dialog, motionEvent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PackageConfig 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) {
|
||||
linePaint.setAlpha(100);
|
||||
pickerCanvas.drawLine(width / 2, height / 2, width / 2, height / 2f - radius3, linePaint);
|
||||
|
||||
paint.setAlpha(255);
|
||||
paint.setColor(Color.WHITE);
|
||||
pickerCanvas.drawCircle(width / 2f, height / 2f - radius3, radius, paint);
|
||||
paint.setAlpha(100);
|
||||
paint.setColor(Color.BLUE);
|
||||
pickerCanvas.drawCircle(width / 2f, height / 2f - radius3, radius, paint);
|
||||
} else {
|
||||
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(100);
|
||||
if (settings.getMin() != -1) {
|
||||
linePaint.setAlpha(100);
|
||||
pickerCanvas.drawLine(width / 2, height / 2, width / 2, height / 2f - radius2, linePaint);
|
||||
}
|
||||
paint.setAlpha(255);
|
||||
paint.setColor(Color.WHITE);
|
||||
pickerCanvas.drawCircle(width / 2f, height / 2f - radius2, radius, paint);
|
||||
paint.setAlpha(100);
|
||||
paint.setColor(Color.BLUE);
|
||||
pickerCanvas.drawCircle(width / 2f, height / 2f - radius2, radius, paint);
|
||||
} else {
|
||||
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, PackageConfig config);
|
||||
}
|
||||
|
||||
interface OnHandsSetListener{
|
||||
public void onHandsSet(PackageConfig config);
|
||||
}
|
||||
|
||||
interface OnVibrationSetListener{
|
||||
public void onVibrationSet(PackageConfig config);
|
||||
}
|
||||
}
|
@ -48,6 +48,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_default, R.drawable.ic_device_default_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),
|
||||
|
@ -32,6 +32,7 @@ import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.widget.Toast;
|
||||
@ -813,9 +814,16 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
}
|
||||
|
||||
public class CommunicationServiceBinder extends Binder{
|
||||
public DeviceSupport getDeviceSupport(){
|
||||
if(mDeviceSupport == null) return null;
|
||||
return ((ServiceDeviceSupport)DeviceCommunicationService.this.mDeviceSupport).getDelegate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
return new CommunicationServiceBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport
|
||||
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;
|
||||
@ -168,7 +169,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:
|
||||
|
@ -63,6 +63,10 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public DeviceSupport getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
|
||||
delegate.setContext(gbDevice, btAdapter, context);
|
||||
|
@ -0,0 +1,179 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
|
||||
public class QHybridBaseSupport extends AbstractBTLEDeviceSupport {
|
||||
public 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,599 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
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.wifi.aware.Characteristics;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.AnimationRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.BatteryLevelRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.DownloadFileRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.EraseFileRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.FileRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.GetCurrentStepCountRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.GetStepGoalRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.GetVibrationStrengthRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.ListFilesRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.MoveHandsRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.OTAEnterRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.OTAEraseRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.PlayNotificationRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.ReleaseHandsControlRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.RequestHandControlRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.SetCurrentTimeServiceRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.SetStepGoalRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.SetVibrationStrengthRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.SettingsFilePutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.UploadFileRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.VibrateRequest;
|
||||
|
||||
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_NOTIFICATION = "qhybrid_command_notification";
|
||||
|
||||
public static final String QHYBRID_EVENT_BUTTON_PRESS = "nodomain.freeyourgadget.gadgetbridge.Q_BUTTON_PRESSED";
|
||||
|
||||
private static final String ITEM_STEP_GOAL = "STEP_GOAL";
|
||||
private static final String ITEM_VIBRATION_STRENGTH = "VIBRATION_STRENGTH";
|
||||
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(QHybridSupport.class);
|
||||
|
||||
private PackageConfigHelper helper;
|
||||
|
||||
private volatile boolean searchDevice = false;
|
||||
|
||||
private int lastButtonIndex = -1;
|
||||
|
||||
private final SparseArray<Request> responseFilters = new SparseArray<>();
|
||||
|
||||
private OnVibrationStrengthListener vibrationStrengthListener;
|
||||
private OnGoalListener goalListener;
|
||||
private OnButtonOverwriteListener buttonOverwriteListener;
|
||||
|
||||
private Request fileRequest = null;
|
||||
|
||||
private boolean dumpInited = false;
|
||||
|
||||
private long timeOffset;
|
||||
|
||||
private UploadFileRequest uploadFileRequest;
|
||||
|
||||
private PendingIntent dumpIntent;
|
||||
private PendingIntent stepIntent;
|
||||
|
||||
public QHybridSupport() {
|
||||
super(logger);
|
||||
addSupportedService(UUID.fromString("3dda0001-957f-7d4a-34a6-74696673696d"));
|
||||
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_NOTIFICATION);
|
||||
LocalBroadcastManager.getInstance(getContext()).registerReceiver(commandReceiver, commandFilter);
|
||||
fillResponseList();
|
||||
}
|
||||
|
||||
private void fillResponseList() {
|
||||
|
||||
Class<? extends Request>[] classes = new Class[]{
|
||||
BatteryLevelRequest.class,
|
||||
GetStepGoalRequest.class,
|
||||
GetVibrationStrengthRequest.class,
|
||||
GetCurrentStepCountRequest.class,
|
||||
OTAEnterRequest.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.d("Service", "response filter " + object.getStartSequence()[1] + ": " + c.getSimpleName());
|
||||
}
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException e) {
|
||||
Log.d("Service", "skipping class " + c.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getTimeOffset() {
|
||||
timeOffset = getContext().getSharedPreferences(getContext().getPackageName(), Context.MODE_PRIVATE).getInt("QHYBRID_TIME_OFFSET", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver);
|
||||
getContext().unregisterReceiver(dumpReceiver);
|
||||
((AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE)).cancel(dumpIntent);
|
||||
getContext().unregisterReceiver(stepReceiver);
|
||||
((AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE)).cancel(stepIntent);
|
||||
dumpInited = false;
|
||||
}
|
||||
|
||||
public void getGoal(OnGoalListener listener) {
|
||||
this.goalListener = listener;
|
||||
queueWrite(new GetStepGoalRequest());
|
||||
}
|
||||
|
||||
public void setGoal(int goal) {
|
||||
queueWrite(new SetStepGoalRequest(goal));
|
||||
}
|
||||
|
||||
public void getVibrationStrength(OnVibrationStrengthListener listener) {
|
||||
this.vibrationStrengthListener = listener;
|
||||
queueWrite(new GetVibrationStrengthRequest());
|
||||
}
|
||||
|
||||
public void setVibrationStrength(int strength) {
|
||||
queueWrite(new SetVibrationStrengthRequest((short) strength));
|
||||
}
|
||||
|
||||
private void queueWrite(Request request) {
|
||||
new TransactionBuilder(request.getClass().getSimpleName()).write(getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getQueue());
|
||||
if (request instanceof FileRequest) this.fileRequest = request;
|
||||
}
|
||||
|
||||
private final BroadcastReceiver stepReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
queueWrite(new GetCurrentStepCountRequest());
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver dumpReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d("Dump", "dumping...");
|
||||
downloadActivityFiles();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
|
||||
for (int i = 2; i <= 7; i++)
|
||||
builder.notify(getCharacteristic(UUID.fromString("3dda000" + i + "-957f-7d4a-34a6-74696673696d")), true);
|
||||
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
|
||||
helper = new PackageConfigHelper(getContext());
|
||||
|
||||
if (!dumpInited) {
|
||||
getContext().registerReceiver(dumpReceiver, new IntentFilter("dumpReceiver2"));
|
||||
getContext().registerReceiver(stepReceiver, new IntentFilter("stepDumpReceiver"));
|
||||
dumpIntent = PendingIntent.getBroadcast(getContext(), 0, new Intent("dumpReceiver2"), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
stepIntent = PendingIntent.getBroadcast(getContext(), 0, new Intent("stepDumpReceiver"), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
((AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE)).setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, AlarmManager.INTERVAL_HOUR, dumpIntent);
|
||||
((AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE)).setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, AlarmManager.INTERVAL_HOUR / 60, stepIntent);
|
||||
dumpInited = true;
|
||||
}
|
||||
getTimeOffset();
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(BluetoothGatt gatt) {
|
||||
super.onServicesDiscovered(gatt);
|
||||
|
||||
playAnimation();
|
||||
queueWrite(new BatteryLevelRequest());
|
||||
|
||||
logger.debug("onServicesDiscovered");
|
||||
}
|
||||
|
||||
private void playAnimation() {
|
||||
queueWrite(new AnimationRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
Log.d("Service", "notif from " + notificationSpec.sourceAppId + " " + notificationSpec.sender + " " + notificationSpec.phoneNumber);
|
||||
//new Exception().printStackTrace();
|
||||
String packageName = notificationSpec.sourceName;
|
||||
|
||||
PackageConfig config = helper.getSetting(packageName);
|
||||
if (config == null) return;
|
||||
|
||||
Log.d("Service", "handling notification");
|
||||
|
||||
int mode = ((AudioManager) getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE)).getRingerMode();
|
||||
if (mode == AudioManager.RINGER_MODE_SILENT && config.getRespectSilentMode()) return;
|
||||
|
||||
playNotification(config);
|
||||
}
|
||||
|
||||
public void playNotification(PackageConfig config){
|
||||
queueWrite(new PlayNotificationRequest(config.getVibration(), config.getHour(), config.getMin()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
long millis = System.currentTimeMillis();
|
||||
TimeZone zone = new GregorianCalendar().getTimeZone();
|
||||
SetCurrentTimeServiceRequest request = new SetCurrentTimeServiceRequest(
|
||||
(int) (millis / 1000 + timeOffset * 60),
|
||||
(short) (millis % 1000),
|
||||
(short) ((zone.getRawOffset() + zone.getDSTSavings()) / 60000));
|
||||
queueWrite(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
logger.debug("onFindDevice");
|
||||
if (start && searchDevice) return;
|
||||
|
||||
searchDevice = start;
|
||||
|
||||
if (start) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
VibrateRequest request = new VibrateRequest(false, (short) 4, (short) 1);
|
||||
BluetoothGattCharacteristic chara = getCharacteristic(request.getRequestUUID());
|
||||
int i = 0;
|
||||
while (searchDevice) {
|
||||
new TransactionBuilder("findDevice#" + i++).write(chara, request.getRequestData()).queue(getQueue());
|
||||
try {
|
||||
Thread.sleep(2500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
//downloadActivityFiles();
|
||||
//queueWrite(new GetCurrentStepCountRequest());
|
||||
// queueWrite(new EventStreamRequest((short)4));
|
||||
// queueWrite(new OTAEraseRequest(0));
|
||||
// queueWrite(new OTAResetRequest());
|
||||
new UploadFileRequest((short)00, new byte[]{0x01, 0x00, 0x08, 0x01, 0x01, 0x0C, 0x00, (byte)0xBD, 0x01, 0x30, 0x71, (byte)0xFF, 0x05, 0x00, 0x01, 0x00});
|
||||
}
|
||||
|
||||
public void overwriteButtons(OnButtonOverwriteListener listener){
|
||||
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
|
||||
});
|
||||
this.buttonOverwriteListener = listener;
|
||||
queueWrite(uploadFileRequest);
|
||||
}
|
||||
|
||||
private void downloadActivityFiles() {
|
||||
queueWrite(new ListFilesRequest());
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
queueWrite(new EraseFileRequest((short) request.fileHandle));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (request.fileHandle > 257) {
|
||||
queueWrite(new DownloadFileRequest((short) (request.fileHandle - 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
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);
|
||||
}
|
||||
default:{
|
||||
Log.d("Service", "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) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return super.onCharacteristicChanged(gatt, characteristic);
|
||||
}
|
||||
|
||||
private boolean handleFileUploadCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||
uploadFileRequest.handleResponse(characteristic);
|
||||
|
||||
switch (uploadFileRequest.state){
|
||||
case ERROR:
|
||||
buttonOverwriteListener.OnButtonOverwrite(false);
|
||||
break;
|
||||
case UPLOAD:
|
||||
for(byte[] packet : this.uploadFileRequest.packets){
|
||||
new TransactionBuilder("File upload").write(characteristic, packet).queue(getQueue());
|
||||
}
|
||||
break;
|
||||
case UPLOADED:
|
||||
buttonOverwriteListener.OnButtonOverwrite(true);
|
||||
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});
|
||||
|
||||
UploadFileRequest request = new UploadFileRequest((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 boolean handleBasicCharacteristic(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] values = characteristic.getValue();
|
||||
Request request;
|
||||
request = resolveAnswer(characteristic);
|
||||
|
||||
StringBuilder valueString = new StringBuilder(String.valueOf(values[0]));
|
||||
for (int i = 1; i < characteristic.getValue().length; i++) {
|
||||
valueString.append(", ").append(values[i]);
|
||||
}
|
||||
if (request == null) {
|
||||
Log.d("Service", "unable to resolve " + characteristic.getUuid().toString() + ": " + valueString);
|
||||
return true;
|
||||
}
|
||||
Log.d("Service", "response: " + request.getClass().getSimpleName());
|
||||
request.handleResponse(characteristic);
|
||||
|
||||
if (request instanceof BatteryLevelRequest) {
|
||||
gbDevice.setBatteryLevel(((BatteryLevelRequest) request).level);
|
||||
} else if (request instanceof GetStepGoalRequest) {
|
||||
if (this.goalListener != null) {
|
||||
this.goalListener.onGoal(((GetStepGoalRequest) request).stepGoal);
|
||||
this.goalListener = null;
|
||||
}
|
||||
gbDevice.addDeviceInfo(new GenericItem(ITEM_STEP_GOAL, String.valueOf(((GetStepGoalRequest) request).stepGoal)));
|
||||
} else if (request instanceof GetVibrationStrengthRequest) {
|
||||
if (this.vibrationStrengthListener != null) {
|
||||
logger.debug("got vibration: " + ((GetVibrationStrengthRequest) request).strength);
|
||||
this.vibrationStrengthListener.onVibrationStrength(((GetVibrationStrengthRequest) request).strength);
|
||||
this.vibrationStrengthListener = null;
|
||||
}
|
||||
gbDevice.addDeviceInfo(new GenericItem(ITEM_VIBRATION_STRENGTH, String.valueOf(((GetVibrationStrengthRequest) request).strength)));
|
||||
} else if (fileRequest instanceof ListFilesRequest) {
|
||||
ListFilesRequest r = (ListFilesRequest) fileRequest;
|
||||
//if(r.fileCount != -1){
|
||||
if (r.completed) {
|
||||
Log.d("Service", "FileCount: " + r.fileCount);
|
||||
this.fileRequest = null;
|
||||
}
|
||||
//}
|
||||
} 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) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (request instanceof OTAEnterRequest) {
|
||||
if (((OTAEnterRequest) request).success) {
|
||||
fileRequest = new OTAEraseRequest(1024 << 16);
|
||||
queueWrite(fileRequest);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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 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";
|
||||
}
|
||||
|
||||
private Request resolveAnswer(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] values = characteristic.getValue();
|
||||
if (values[0] != 3) return null;
|
||||
return responseFilters.get(values[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
return super.onCharacteristicWrite(gatt, characteristic, status);
|
||||
}
|
||||
|
||||
private void setHands(short hour, short minute) {
|
||||
queueWrite(new MoveHandsRequest(false, minute, hour, (short) -1));
|
||||
}
|
||||
|
||||
private void vibrate(int vibration) {
|
||||
queueWrite(new PlayNotificationRequest(vibration, -1, -1));
|
||||
}
|
||||
|
||||
private final BroadcastReceiver commandReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
PackageConfig config = extras == null ? null : (PackageConfig) intent.getExtras().get("CONFIG");
|
||||
switch (intent.getAction()) {
|
||||
case QHYBRID_COMMAND_CONTROL: {
|
||||
Log.d("Service", "sending control request");
|
||||
queueWrite(new RequestHandControlRequest());
|
||||
if (config != null) {
|
||||
setHands(config.getHour(), config.getMin());
|
||||
} else {
|
||||
setHands((short) 0, (short) 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QHYBRID_COMMAND_UNCONTROL: {
|
||||
queueWrite(new ReleaseHandsControlRequest());
|
||||
break;
|
||||
}
|
||||
case QHYBRID_COMMAND_SET: {
|
||||
setHands(config.getHour(), config.getMin());
|
||||
|
||||
break;
|
||||
}
|
||||
case QHYBRID_COMMAND_VIBRATE: {
|
||||
vibrate(config.getVibration());
|
||||
break;
|
||||
}
|
||||
case QHYBRID_COMMAND_NOTIFICATION: {
|
||||
queueWrite(new PlayNotificationRequest(config.getVibration(), config.getHour(), config.getMin()));
|
||||
break;
|
||||
}
|
||||
case QHYBRID_COMMAND_UPDATE: {
|
||||
getTimeOffset();
|
||||
onSetTime();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public interface OnVibrationStrengthListener {
|
||||
void onVibrationStrength(int strength);
|
||||
}
|
||||
|
||||
public interface OnGoalListener {
|
||||
void onGoal(long goal);
|
||||
}
|
||||
|
||||
public interface OnButtonOverwriteListener{
|
||||
void OnButtonOverwrite(boolean success);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
public class AnimationRequest extends Request{
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{(byte)2, (byte) -15, (byte)5};
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,99 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.UUID;
|
||||
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,44 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.UUID;
|
||||
|
||||
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.d(getName(), "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,29 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
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,17 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
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,28 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,27 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
public class GetTripleTapEnabledRequest extends Request {
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{1, 7, 3};
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,45 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.UUID;
|
||||
|
||||
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,47 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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,18 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
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,43 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.UUID;
|
||||
|
||||
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,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
public class OTAResetRequest extends Request {
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{2, -15, 10};
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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);
|
||||
|
||||
public byte value;
|
||||
|
||||
VibrationType(int value) {
|
||||
this.value = (byte)value;
|
||||
}
|
||||
|
||||
public byte getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public PlayNotificationRequest(int vibrationType, int degreesHour, int degreesMins){
|
||||
int length = 0;
|
||||
if(degreesHour > -1) length++;
|
||||
if(degreesMins > -1) length++;
|
||||
ByteBuffer buffer = createBuffer(length * 2 + 10);
|
||||
buffer.put((byte)vibrationType);
|
||||
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)));
|
||||
}
|
||||
this.data = buffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{2, 7, 15, 10, 1};
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
public class PutSettingsFileRequest extends Request {
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,51 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class Request {
|
||||
protected byte[] data;
|
||||
//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(){
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
protected void log(String message){
|
||||
Log.d(getName(), message);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class RequestHandControlRequest extends Request {
|
||||
public RequestHandControlRequest(byte priority, boolean moveCompleteNotify, boolean controlLostNOtify){
|
||||
super();
|
||||
init(priority, moveCompleteNotify, controlLostNOtify);
|
||||
}
|
||||
|
||||
public RequestHandControlRequest(){
|
||||
super();
|
||||
init((byte)1, false, false);
|
||||
}
|
||||
|
||||
private void init(byte priority, boolean moveCompleteNotify, boolean controlLostNOtify) {
|
||||
ByteBuffer buffer = createBuffer();
|
||||
buffer.put(priority);
|
||||
buffer.put(moveCompleteNotify ? (byte)1 : 0);
|
||||
buffer.put(controlLostNOtify ? (byte)1 : 0);
|
||||
this.data = buffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{2, 21, 1};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength() {
|
||||
return 6;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,27 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
public class SetTripleTapEnabledRequest extends Request{
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{2, 7, 3, 1};
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
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,36 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
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,95 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
ByteBuffer buffer = this.createBuffer();
|
||||
buffer.putShort(1, handle);
|
||||
buffer.putInt(3, 0);
|
||||
buffer.putInt(7, fileLength - 10);
|
||||
buffer.putInt(11, fileLength - 10);
|
||||
|
||||
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;
|
||||
}
|
||||
state = UploadState.UPLOAD;
|
||||
}else if(value.length == 9){
|
||||
if(value[1] != 0){
|
||||
state = UploadState.ERROR;
|
||||
}
|
||||
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,26 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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};
|
||||
}
|
||||
}
|
@ -62,6 +62,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||
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;
|
||||
@ -219,6 +220,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());
|
||||
|
28
app/src/main/res/layout/activity_qhybrid_app_choser.xml
Normal file
28
app/src/main/res/layout/activity_qhybrid_app_choser.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".devices.qhybrid.QHybridAppChoserActivity">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:paddingTop="40dp"
|
||||
android:id="@+id/qhybrid_packageChooserLoading"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/qhybrid_appChooserList"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
96
app/src/main/res/layout/activity_qhybrid_settings.xml
Normal file
96
app/src/main/res/layout/activity_qhybrid_settings.xml
Normal file
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/qhybridMain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/settingsLayout"
|
||||
android:focusableInTouchMode="true" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="vibration strength:" />
|
||||
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/vibrationStrengthBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:max="2"
|
||||
android:min="0" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Goal in steps" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/stepGoalEt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberDecimal" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:text="time shift" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qhybridTimeOffset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonOverwriteButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="overwrite buttons" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/vibrationSettingProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/settingsLayout"
|
||||
android:layout_alignTop="@id/settingsLayout"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settingsErrorText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@id/settingsLayout"
|
||||
android:layout_alignBottom="@id/settingsLayout"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:visibility="gone"
|
||||
android:gravity="center" />
|
||||
|
||||
|
||||
|
||||
<ListView
|
||||
android:id="@+id/qhybrid_appList"
|
||||
android:layout_marginTop="50dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_below="@id/settingsLayout"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
|
||||
</RelativeLayout>
|
41
app/src/main/res/layout/activity_tasker_plugin.xml
Normal file
41
app/src/main/res/layout/activity_tasker_plugin.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".devices.qhybrid.TaskerPluginActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="hour degrees:" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/qhybrid_hour_degrees"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="minute degrees:" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/qhybrid_minute_degrees"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/qhybrid_tasker_vibration"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
22
app/src/main/res/layout/qhybrid_app_view.xml
Normal file
22
app/src/main/res/layout/qhybrid_app_view.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_alignParentStart="true"
|
||||
android:id="@+id/qhybrid_appChooserItemIcon"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:textSize="20dp"
|
||||
android:id="@+id/qhybrid_appChooserItemText"/>
|
||||
|
||||
</RelativeLayout>
|
29
app/src/main/res/layout/qhybrid_package_settings_item.xml
Normal file
29
app/src/main/res/layout/qhybrid_package_settings_item.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="5dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_alignParentStart="true"
|
||||
android:id="@+id/packageIcon"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/packageName"
|
||||
android:textSize="20dp"
|
||||
android:layout_centerInParent="true"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:scaleType="fitXY"
|
||||
android:id="@+id/packageClock"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
</RelativeLayout>
|
@ -358,8 +358,8 @@
|
||||
<string name="pref_summary_pebble_health_store_raw">Speichert Daten \"wie sie sind\" und erhöht die Datenbanknutzung, um eine spätere Auswertung zu ermöglichen.</string>
|
||||
<string name="action_db_management">Datenbankverwaltung</string>
|
||||
<string name="title_activity_db_management">Datenbankverwaltung</string>
|
||||
<string name="activity_db_management_import_export_explanation">Die Datenbankoperationen verwenden den folgenden Pfad auf deinem Gerät.
|
||||
\nDieser Pfad ist für andere Android-Apps und deinem Computer zugänglich.
|
||||
<string name="activity_db_management_import_export_explanation">Die Datenbankoperationen verwenden den folgenden Pfad auf deinem Gerät.
|
||||
\nDieser Pfad ist für andere Android-Apps und deinem Computer zugänglich.
|
||||
\nDu findest die exportierte Datenbank hier (bzw. lege die zu importierende dort ab):</string>
|
||||
<string name="activity_db_management_merge_old_title">Legacy-Datenbank löschen</string>
|
||||
<string name="dbmanagementactivvity_cannot_access_export_path">Kann nicht auf den Exportpfad zugreifen. Bitte die Entwickler kontaktieren.</string>
|
||||
@ -546,6 +546,7 @@
|
||||
<string name="whitelist_all_for_notifications">Whitelist für alle Benachrichtigungen</string>
|
||||
<string name="pref_auto_fetch_summary">Abruf erfolgt beim Entsperren des Bildschirms. Funktioniert nur, wenn ein Sperrmechanismus eingestellt ist!</string>
|
||||
<string name="watch9_pairing_tap_hint">Wenn deine Uhr vibriert, schüttel das Gerät oder drücke die Taste.</string>
|
||||
<string name="preferences_qhybrid_settings">Q Hybrid Einstellungen</string>
|
||||
<string name="watch9_calibration_button">Kalibrieren</string>
|
||||
<string name="title_activity_watch9_pairing">Watch 9 koppeln</string>
|
||||
<string name="watch9_time_minutes">Minuten:</string>
|
||||
|
@ -633,6 +633,7 @@
|
||||
<string name="devicetype_no1_f1">No.1 F1</string>
|
||||
<string name="devicetype_teclast_h30">Teclast H30</string>
|
||||
<string name="devicetype_xwatch">XWatch</string>
|
||||
<string name="devicetype_qhybrid">Fossil Q Hybrid</string>
|
||||
<string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
|
||||
<string name="devicetype_id115">ID115</string>
|
||||
<string name="devicetype_watch9">Watch 9</string>
|
||||
@ -655,6 +656,7 @@
|
||||
<string name="menuitem_compass">Compass</string>
|
||||
<string name="menuitem_settings">Settings</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
<string name="preferences_qhybrid_settings">Q Hybrid Settings</string>
|
||||
<string name="menuitem_music">Music</string>
|
||||
<string name="menuitem_more">More</string>
|
||||
<string name="watch9_time_minutes">Minutes:</string>
|
||||
@ -664,6 +666,7 @@
|
||||
<string name="watch9_calibration_button">Calibrate</string>
|
||||
<string name="title_activity_watch9_pairing">Watch 9 pairing</string>
|
||||
<string name="title_activity_watch9_calibration">Watch 9 calibration</string>
|
||||
<string name="tasker_notification">Play Q Hybrid notification</string>
|
||||
<string name="pref_title_contextual_arabic">Contextual Arabic</string>
|
||||
<string name="pref_summary_contextual_arabic">Enable this to support contextual Arabic</string>
|
||||
<string name="preferences_rtl_settings">Right To Left Support</string>
|
||||
|
@ -267,6 +267,12 @@
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/preferences_category_device_specific_settings">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_device_pebble"
|
||||
android:key="pref_key_qhybrid"
|
||||
android:title="@string/preferences_qhybrid_settings" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_device_miband"
|
||||
android:key="pref_key_miband"
|
||||
|
Loading…
Reference in New Issue
Block a user