mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-04 09:17:29 +01:00
Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge
This commit is contained in:
commit
0f6ef140e2
@ -1,3 +1,6 @@
|
||||
os: linux
|
||||
dist: trusty
|
||||
|
||||
language: android
|
||||
|
||||
jdk:
|
||||
|
41
CHANGELOG.md
41
CHANGELOG.md
@ -1,5 +1,46 @@
|
||||
### Changelog
|
||||
|
||||
#### Version 0.35.2
|
||||
* Mi Band 1/2: Crash when updating firmware while phone is set to Spanish
|
||||
* Mi Band 4: Enable music info support (displays now on the band)
|
||||
* Mi Band 4: Support setting date format (for built-in watchfaces)
|
||||
* Amazfit Cor 2: Try to fix empty menu on device
|
||||
|
||||
#### Version 0.35.1
|
||||
* Mi Band 4: Support flashing watchfaces, res and firmware (.ft untested)
|
||||
|
||||
#### Version 0.35.0
|
||||
* Mi Band 4: Initial support (WARNING: INITIAL SETUP NEEDS MI FIT WITH ACCOUNT AND ROOT, NOT A RECOMMENDED DEVICE FOR GADGETBRIDGE)
|
||||
|
||||
#### Version 0.34.1
|
||||
* Mi Band 1: Fix crash when entering per-device settings
|
||||
* Mi Band 3: Allow setting date format in per-device settings
|
||||
* ZeTime: Fix timestmaps
|
||||
* Fix a crash when flashing an non-whitelisted firmware while using Gadgetbridge in Spanish
|
||||
|
||||
#### Version 0.34.0
|
||||
* Mi Band 1/2/3/Bip/Cor: Migrate many settings to per-device settings (new settings icon in device card in main activity)
|
||||
* Mi Band 3: Fix setting menu items with 2.4 firmware and add support for the new timer menu
|
||||
* Amazfit Bip/Cor, Casio: Add support for muting incoming calls
|
||||
* ZeTime: Remove endless recursion in ZeTime settings
|
||||
* Recognize FairEmail notifications as generic email notifications
|
||||
|
||||
#### Version 0.33.1
|
||||
* Mi Band 3: Recognize "Xiaomi Band 3"
|
||||
* Amazfit Bip: Add German, Italian, French and Turkish to language settings
|
||||
|
||||
#### Version 0.33.0
|
||||
* BFH-16: Initial support
|
||||
* Mi Band 2/3/Bip/Cor: Generate random per-device security keys when pairing, allow manual override to still support multiple android devices connecting to the same device
|
||||
* Mi Band 3: Add Indonesian, Thai, Arabic, Vietnamese, Portuguese, Dutch, Turkish and Ukrainian to language settings
|
||||
* Mi Band 3: Support flashing latest Japanese-Korean font
|
||||
* Amazfit Cor 2: Initial experimental support (untested)
|
||||
* Pebble: Add pebblekit extension for reopening last app
|
||||
* Casio: Bugfixes and improvements
|
||||
* Lookup contacts also in work profile
|
||||
* Fix searching in application name when blacklisting
|
||||
* Remove misleading title from database management activity when no legacy database is available
|
||||
|
||||
#### Version 0.32.4
|
||||
* Make voip call support optional (disabled by default)
|
||||
* Amazfit Bip: GPX export corrections
|
||||
|
11
README.md
11
README.md
@ -1,3 +1,6 @@
|
||||
**IF YOU WANT TO EDIT THE WIKI**, do so on [codeberg.org](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki)
|
||||
The wiki on github.com is a read-only mirror, as is the git repo itself. Issues and PRs will move to codeberg summer 2019, if you want your issue/PR comments migrated properly, please create a codeberg acount before we will migrate.
|
||||
|
||||
Gadgetbridge
|
||||
============
|
||||
|
||||
@ -17,6 +20,7 @@ vendor's servers.
|
||||
[![Build](https://travis-ci.org/Freeyourgadget/Gadgetbridge.svg?branch=master)](https://travis-ci.org/Freeyourgadget/Gadgetbridge)
|
||||
[![Code Quality: Java](https://img.shields.io/lgtm/grade/java/g/Freeyourgadget/Gadgetbridge.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Freeyourgadget/Gadgetbridge/context:java)
|
||||
[![Total Alerts](https://img.shields.io/lgtm/alerts/g/Freeyourgadget/Gadgetbridge.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Freeyourgadget/Gadgetbridge/alerts)
|
||||
[![Translate](https://hosted.weblate.org/widgets/freeyourgadget/-/gadgetbridge/svg-badge.svg)](https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge)
|
||||
|
||||
## Download
|
||||
|
||||
@ -27,6 +31,8 @@ vendor's servers.
|
||||
## Supported Devices
|
||||
* Amazfit Bip [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
|
||||
* Amazfit Cor [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor)
|
||||
* Amazfit Cor 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor-2)
|
||||
* BFH-16
|
||||
* Casio GB-6900B (WIP)
|
||||
* HPlus Devices (e.g. ZeBand) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/HPlus)
|
||||
* ID115 (WIP)
|
||||
@ -36,6 +42,8 @@ vendor's servers.
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
|
||||
* Mi Band 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2)
|
||||
* Mi Band 3 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-3)
|
||||
* Mi Band 4 (WIP, NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT AND ROOT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-4)
|
||||
* Mi Scale 2 (currently only displays a toast after stepping on the scale)
|
||||
* NO.1 F1 (WIP)
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Pebble 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
@ -100,6 +108,8 @@ Please [this wiki article](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki
|
||||
* Vadim Kaushan (ID115)
|
||||
* "maxirnilian" (Lenovo Watch 9)
|
||||
* Andreas Böhler (Casio GB-6900B)
|
||||
* Jean-François Greffier (Mi Scale 2)
|
||||
* Johannes Schmitt (BFH-16)
|
||||
|
||||
## Contribute
|
||||
|
||||
@ -115,6 +125,7 @@ Feel free to open an issue on our issue tracker, but please:
|
||||
- do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting
|
||||
- use the search functionality to ensure that your question wasn't already answered. Don't forget to check the **closed** issues as well!
|
||||
- remember that this is a community project, people are contributing in their free time because they like doing so: don't take the fun away! Be kind and constructive.
|
||||
- Do not ask for help regarding your own projects, unless they are Gadgetbridge related
|
||||
|
||||
## Having problems?
|
||||
|
||||
|
@ -25,8 +25,8 @@ android {
|
||||
targetSdkVersion 27
|
||||
|
||||
// Note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.32.4"
|
||||
versionCode 147
|
||||
versionName "0.35.2"
|
||||
versionCode 154
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
@ -63,10 +63,12 @@ dependencies {
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
testImplementation "junit:junit:4.12"
|
||||
testImplementation "org.mockito:mockito-core:1.10.19"
|
||||
testImplementation "org.robolectric:robolectric:3.6.1"
|
||||
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.appcompat:appcompat:1.1.0-rc01"
|
||||
implementation "androidx.preference:preference:1.1.0-rc01"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.0.0"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
@ -84,7 +86,7 @@ dependencies {
|
||||
// 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 "org.greenrobot:greendao:2.2.1"
|
||||
implementation "org.apache.commons:commons-lang3:3.5"
|
||||
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")
|
||||
@ -152,10 +154,10 @@ task findbugs(type: FindBugs) {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
xml {
|
||||
destination file("$project.buildDir/reports/findbugs/findbugs-output.xml")
|
||||
destination file ("$project.buildDir/reports/findbugs/findbugs-output.xml")
|
||||
}
|
||||
html {
|
||||
destination file("$project.buildDir/reports/findbugs/findbugs-output.html")
|
||||
destination file ("$project.buildDir/reports/findbugs/findbugs-output.html")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -406,7 +406,10 @@
|
||||
<activity
|
||||
android:name=".activities.ConfigureAlarms"
|
||||
android:label="@string/title_activity_set_alarm"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.devicesettings.DeviceSettingsActivity"
|
||||
android:label="@string/title_activity_device_specific_settings" />
|
||||
<activity
|
||||
android:name=".activities.AlarmDetails"
|
||||
android:label="@string/title_activity_alarm_details"
|
||||
|
@ -42,6 +42,8 @@ import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
@ -52,17 +54,19 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothStateChangeReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.NotificationCollectorMonitorService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
@ -71,6 +75,13 @@ import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITBIP;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR2;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND2;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND3;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.fromKey;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
|
||||
|
||||
/**
|
||||
@ -88,7 +99,7 @@ public class GBApplication extends Application {
|
||||
private static SharedPreferences sharedPrefs;
|
||||
private static final String PREFS_VERSION = "shared_preferences_version";
|
||||
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
|
||||
private static final int CURRENT_PREFS_VERSION = 2;
|
||||
private static final int CURRENT_PREFS_VERSION = 4;
|
||||
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
|
||||
private static Prefs prefs;
|
||||
private static GBPrefs gbPrefs;
|
||||
@ -183,9 +194,9 @@ public class GBApplication extends Application {
|
||||
if (isRunningMarshmallowOrLater()) {
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
//the following will ensure the notification manager is kept alive
|
||||
if(isRunningOreoOrLater()) {
|
||||
if (isRunningOreoOrLater()) {
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
|
||||
if(channel == null) {
|
||||
if (channel == null) {
|
||||
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
|
||||
getString(R.string.notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
@ -228,7 +239,7 @@ public class GBApplication extends Application {
|
||||
logging.setupLogging(enabled);
|
||||
}
|
||||
|
||||
public static String getLogPath(){
|
||||
public static String getLogPath() {
|
||||
return logging.getLogPath();
|
||||
}
|
||||
|
||||
@ -316,11 +327,12 @@ public class GBApplication extends Application {
|
||||
public static boolean isRunningMarshmallowOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||
}
|
||||
|
||||
public static boolean isRunningNougatOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||
}
|
||||
|
||||
public static boolean isRunningOreoOrLater(){
|
||||
public static boolean isRunningOreoOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
@ -481,14 +493,14 @@ public class GBApplication extends Application {
|
||||
saveAppsPebbleBlackList();
|
||||
}
|
||||
|
||||
public static String packageNameToPebbleMsgSender(String packageName) {
|
||||
if ("eu.siacs.conversations".equals(packageName)){
|
||||
return("Conversations");
|
||||
} else if ("net.osmand.plus".equals(packageName)) {
|
||||
return("OsmAnd");
|
||||
public static String packageNameToPebbleMsgSender(String packageName) {
|
||||
if ("eu.siacs.conversations".equals(packageName)) {
|
||||
return ("Conversations");
|
||||
} else if ("net.osmand.plus".equals(packageName)) {
|
||||
return ("OsmAnd");
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
private static HashSet<String> calendars_blacklist = null;
|
||||
|
||||
@ -581,47 +593,184 @@ public static String packageNameToPebbleMsgSender(String packageName) {
|
||||
|
||||
private void migratePrefs(int oldVersion) {
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
switch (oldVersion) {
|
||||
case 0:
|
||||
String legacyGender = sharedPrefs.getString("mi_user_gender", null);
|
||||
String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null);
|
||||
String legacyWeight = sharedPrefs.getString("mi_user_weight_kg", null);
|
||||
String legacyYOB = sharedPrefs.getString("mi_user_year_of_birth", null);
|
||||
if (legacyGender != null) {
|
||||
int gender = "male".equals(legacyGender) ? 1 : "female".equals(legacyGender) ? 0 : 2;
|
||||
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(gender));
|
||||
editor.remove("mi_user_gender");
|
||||
}
|
||||
if (legacyHeight != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_HEIGHT_CM, legacyHeight);
|
||||
editor.remove("mi_user_height_cm");
|
||||
}
|
||||
if (legacyWeight != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeight);
|
||||
editor.remove("mi_user_weight_kg");
|
||||
}
|
||||
if (legacyYOB != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_YEAR_OF_BIRTH, legacyYOB);
|
||||
editor.remove("mi_user_year_of_birth");
|
||||
}
|
||||
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
|
||||
break;
|
||||
case 1:
|
||||
//migrate the integer version of gender introduced in version 1 to a string value, needed for the way Android accesses the shared preferences
|
||||
int legacyGender_1 = 2;
|
||||
try {
|
||||
legacyGender_1 = sharedPrefs.getInt(ActivityUser.PREF_USER_GENDER, 2);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Could not access legacy activity gender", e);
|
||||
}
|
||||
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(legacyGender_1));
|
||||
//also silently migrate the version to a string value
|
||||
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
|
||||
break;
|
||||
if (oldVersion == 0) {
|
||||
String legacyGender = sharedPrefs.getString("mi_user_gender", null);
|
||||
String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null);
|
||||
String legacyWeight = sharedPrefs.getString("mi_user_weight_kg", null);
|
||||
String legacyYOB = sharedPrefs.getString("mi_user_year_of_birth", null);
|
||||
if (legacyGender != null) {
|
||||
int gender = "male".equals(legacyGender) ? 1 : "female".equals(legacyGender) ? 0 : 2;
|
||||
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(gender));
|
||||
editor.remove("mi_user_gender");
|
||||
}
|
||||
if (legacyHeight != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_HEIGHT_CM, legacyHeight);
|
||||
editor.remove("mi_user_height_cm");
|
||||
}
|
||||
if (legacyWeight != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeight);
|
||||
editor.remove("mi_user_weight_kg");
|
||||
}
|
||||
if (legacyYOB != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_YEAR_OF_BIRTH, legacyYOB);
|
||||
editor.remove("mi_user_year_of_birth");
|
||||
}
|
||||
}
|
||||
if (oldVersion < 2) {
|
||||
//migrate the integer version of gender introduced in version 1 to a string value, needed for the way Android accesses the shared preferences
|
||||
int legacyGender_1 = 2;
|
||||
try {
|
||||
legacyGender_1 = sharedPrefs.getInt(ActivityUser.PREF_USER_GENDER, 2);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Could not access legacy activity gender", e);
|
||||
}
|
||||
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(legacyGender_1));
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
try (DBHandler db = acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences.Editor deviceSharedPrefsEdit = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier()).edit();
|
||||
if (sharedPrefs != null) {
|
||||
String preferenceKey = dbDevice.getIdentifier() + "_lastSportsActivityTimeMillis";
|
||||
long lastSportsActivityTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
|
||||
if (lastSportsActivityTimeMillis != 0) {
|
||||
deviceSharedPrefsEdit.putLong("lastSportsActivityTimeMillis", lastSportsActivityTimeMillis);
|
||||
editor.remove(preferenceKey);
|
||||
}
|
||||
preferenceKey = dbDevice.getIdentifier() + "_lastSyncTimeMillis";
|
||||
long lastSyncTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
|
||||
if (lastSyncTimeMillis != 0) {
|
||||
deviceSharedPrefsEdit.putLong("lastSyncTimeMillis", lastSyncTimeMillis);
|
||||
editor.remove(preferenceKey);
|
||||
}
|
||||
|
||||
String newLanguage = null;
|
||||
Set<String> displayItems = null;
|
||||
|
||||
DeviceType deviceType = fromKey(dbDevice.getType());
|
||||
|
||||
if (deviceType == AMAZFITBIP || deviceType == AMAZFITCOR || deviceType == AMAZFITCOR2) {
|
||||
int oldLanguage = prefs.getInt("amazfitbip_language", -1);
|
||||
newLanguage = "auto";
|
||||
String[] oldLanguageLookup = {"zh_CN", "zh_TW", "en_US", "es_ES", "ru_RU", "de_DE", "it_IT", "fr_FR", "tr_TR"};
|
||||
if (oldLanguage >= 0 && oldLanguage < oldLanguageLookup.length) {
|
||||
newLanguage = oldLanguageLookup[oldLanguage];
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceType == AMAZFITBIP || deviceType == AMAZFITCOR) {
|
||||
deviceSharedPrefsEdit.putString("disconnect_notification", prefs.getString("disconnect_notification", "off"));
|
||||
deviceSharedPrefsEdit.putString("disconnect_notification_start", prefs.getString("disconnect_notification_start", "8:00"));
|
||||
deviceSharedPrefsEdit.putString("disconnect_notification_end", prefs.getString("disconnect_notification_end", "22:00"));
|
||||
}
|
||||
if (deviceType == MIBAND2 || deviceType == MIBAND3) {
|
||||
deviceSharedPrefsEdit.putString("do_not_disturb", prefs.getString("mi2_do_not_disturb", "off"));
|
||||
deviceSharedPrefsEdit.putString("do_not_disturb_start", prefs.getString("mi2_do_not_disturb_start", "1:00"));
|
||||
deviceSharedPrefsEdit.putString("do_not_disturb_end", prefs.getString("mi2_do_not_disturb_end", "6:00"));
|
||||
}
|
||||
if (dbDevice.getManufacturer().equals("Huami")) {
|
||||
deviceSharedPrefsEdit.putString("activate_display_on_lift_wrist", prefs.getString("activate_display_on_lift_wrist", "off"));
|
||||
deviceSharedPrefsEdit.putString("display_on_lift_start", prefs.getString("display_on_lift_start", "0:00"));
|
||||
deviceSharedPrefsEdit.putString("display_on_lift_end", prefs.getString("display_on_lift_end", "0:00"));
|
||||
}
|
||||
switch (deviceType) {
|
||||
case MIBAND:
|
||||
deviceSharedPrefsEdit.putBoolean("low_latency_fw_update", prefs.getBoolean("mi_low_latency_fw_update", true));
|
||||
deviceSharedPrefsEdit.putInt("device_time_offset_hours", prefs.getInt("mi_device_time_offset_hours", 0));
|
||||
break;
|
||||
case AMAZFITCOR:
|
||||
displayItems = prefs.getStringSet("cor_display_items", null);
|
||||
break;
|
||||
case AMAZFITBIP:
|
||||
displayItems = prefs.getStringSet("bip_display_items", null);
|
||||
break;
|
||||
case MIBAND2:
|
||||
displayItems = prefs.getStringSet("mi2_display_items", null);
|
||||
deviceSharedPrefsEdit.putBoolean("mi2_enable_text_notifications", prefs.getBoolean("mi2_enable_text_notifications", true));
|
||||
deviceSharedPrefsEdit.putString("mi2_dateformat", prefs.getString("mi2_dateformat", "dateformat_time"));
|
||||
deviceSharedPrefsEdit.putBoolean("rotate_wrist_to_cycle_info", prefs.getBoolean("mi2_rotate_wrist_to_switch_info", false));
|
||||
break;
|
||||
case MIBAND3:
|
||||
newLanguage = prefs.getString("miband3_language", "auto");
|
||||
displayItems = prefs.getStringSet("miband3_display_items", null);
|
||||
deviceSharedPrefsEdit.putBoolean("swipe_unlock", prefs.getBoolean("mi3_band_screen_unlock", false));
|
||||
deviceSharedPrefsEdit.putString("night_mode", prefs.getString("mi3_night_mode", "off"));
|
||||
deviceSharedPrefsEdit.putString("night_mode_start", prefs.getString("mi3_night_mode_start", "16:00"));
|
||||
deviceSharedPrefsEdit.putString("night_mode_end", prefs.getString("mi3_night_mode_end", "7:00"));
|
||||
|
||||
}
|
||||
if (displayItems != null) {
|
||||
deviceSharedPrefsEdit.putStringSet("display_items", displayItems);
|
||||
}
|
||||
if (newLanguage != null) {
|
||||
deviceSharedPrefsEdit.putString("language", newLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
deviceSharedPrefsEdit.apply();
|
||||
}
|
||||
editor.remove("amazfitbip_language");
|
||||
editor.remove("bip_display_items");
|
||||
editor.remove("cor_display_items");
|
||||
editor.remove("disconnect_notification");
|
||||
editor.remove("disconnect_notification_start");
|
||||
editor.remove("disconnect_notification_end");
|
||||
editor.remove("activate_display_on_lift_wrist");
|
||||
editor.remove("display_on_lift_start");
|
||||
editor.remove("display_on_lift_end");
|
||||
|
||||
editor.remove("mi_low_latency_fw_update");
|
||||
editor.remove("mi_device_time_offset_hours");
|
||||
editor.remove("mi2_do_not_disturb");
|
||||
editor.remove("mi2_do_not_disturb_start");
|
||||
editor.remove("mi2_do_not_disturb_end");
|
||||
editor.remove("mi2_dateformat");
|
||||
editor.remove("mi2_display_items");
|
||||
editor.remove("mi2_rotate_wrist_to_switch_info");
|
||||
editor.remove("mi2_enable_text_notifications");
|
||||
editor.remove("mi3_band_screen_unlock");
|
||||
editor.remove("mi3_night_mode");
|
||||
editor.remove("mi3_night_mode_start");
|
||||
editor.remove("mi3_night_mode_end");
|
||||
editor.remove("miband3_language");
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "error acquiring DB lock");
|
||||
}
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
try (DBHandler db = acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
|
||||
DeviceType deviceType = fromKey(dbDevice.getType());
|
||||
|
||||
if (deviceType == MIBAND) {
|
||||
int deviceTimeOffsetHours = deviceSharedPrefs.getInt("device_time_offset_hours",0);
|
||||
deviceSharedPrefsEdit.putString("device_time_offset_hours", Integer.toString(deviceTimeOffsetHours) );
|
||||
}
|
||||
|
||||
deviceSharedPrefsEdit.apply();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "error acquiring DB lock");
|
||||
}
|
||||
}
|
||||
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static SharedPreferences getDeviceSpecificSharedPrefs(String deviceIdentifier) {
|
||||
if (deviceIdentifier == null || deviceIdentifier.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return context.getSharedPreferences("devicesettings_" + deviceIdentifier, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void setLanguage(String lang) {
|
||||
if (lang.equals("default")) {
|
||||
language = Resources.getSystem().getConfiguration().locale;
|
||||
|
@ -36,6 +36,10 @@ import android.widget.DatePicker;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.io.File;
|
||||
@ -45,9 +49,6 @@ import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
|
||||
@ -240,10 +241,10 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
date.set(year, monthOfYear, dayOfMonth);
|
||||
|
||||
long timestamp = date.getTimeInMillis() - 1000;
|
||||
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
|
||||
editor.remove(mGBDevice.getAddress() + "_" + "lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
|
||||
editor.putLong(mGBDevice.getAddress() + "_" + "lastSportsActivityTimeMillis", timestamp);
|
||||
editor.commit();
|
||||
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()).edit();
|
||||
editor.remove("lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
|
||||
editor.putLong("lastSportsActivityTimeMillis", timestamp);
|
||||
editor.apply();
|
||||
}
|
||||
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
|
||||
}
|
||||
|
@ -29,17 +29,20 @@ import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences;
|
||||
@ -48,30 +51,23 @@ import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences;
|
||||
public class DbManagementActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DbManagementActivity.class);
|
||||
private static SharedPreferences sharedPrefs;
|
||||
private ImportExportSharedPreferences shared_file = new ImportExportSharedPreferences();
|
||||
|
||||
private Button exportDBButton;
|
||||
private Button importDBButton;
|
||||
private Button deleteOldActivityDBButton;
|
||||
private Button deleteDBButton;
|
||||
private TextView dbPath;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_db_management);
|
||||
|
||||
dbPath = (TextView) findViewById(R.id.activity_db_management_path);
|
||||
TextView dbPath = findViewById(R.id.activity_db_management_path);
|
||||
dbPath.setText(getExternalPath());
|
||||
|
||||
exportDBButton = (Button) findViewById(R.id.exportDBButton);
|
||||
Button exportDBButton = findViewById(R.id.exportDBButton);
|
||||
exportDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
exportDB();
|
||||
}
|
||||
});
|
||||
importDBButton = (Button) findViewById(R.id.importDBButton);
|
||||
Button importDBButton = findViewById(R.id.importDBButton);
|
||||
importDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -81,7 +77,10 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
|
||||
int oldDBVisibility = hasOldActivityDatabase() ? View.VISIBLE : View.GONE;
|
||||
|
||||
deleteOldActivityDBButton = (Button) findViewById(R.id.deleteOldActivityDB);
|
||||
TextView deleteOldActivityTitle = findViewById(R.id.mergeOldActivityDataTitle);
|
||||
deleteOldActivityTitle.setVisibility(oldDBVisibility);
|
||||
|
||||
Button deleteOldActivityDBButton = findViewById(R.id.deleteOldActivityDB);
|
||||
deleteOldActivityDBButton.setVisibility(oldDBVisibility);
|
||||
deleteOldActivityDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -90,7 +89,7 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
deleteDBButton = (Button) findViewById(R.id.emptyDBButton);
|
||||
Button deleteDBButton = findViewById(R.id.emptyDBButton);
|
||||
deleteDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -115,27 +114,57 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
}
|
||||
|
||||
private void exportShared() {
|
||||
// BEGIN EXAMPLE
|
||||
File myPath = null;
|
||||
try {
|
||||
myPath = FileUtils.getExternalFilesDir();
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference");
|
||||
shared_file.exportToFile(sharedPrefs,myFile,null);
|
||||
ImportExportSharedPreferences.exportToFile(sharedPrefs, myFile, null);
|
||||
} catch (IOException ex) {
|
||||
GB.toast(this, getString(R.string.dbmanagementactivity_error_exporting_shared, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
try (DBHandler lockHandler = GBApplication.acquireDB()) {
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (sharedPrefs != null) {
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference_" + dbDevice.getIdentifier());
|
||||
try {
|
||||
ImportExportSharedPreferences.exportToFile(deviceSharedPrefs, myFile, null);
|
||||
} catch (Exception ignore) {
|
||||
// some devices no not have device specific preferences
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error exporting device specific preferences", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void importShared() {
|
||||
// BEGIN EXAMPLE
|
||||
File myPath = null;
|
||||
try {
|
||||
myPath = FileUtils.getExternalFilesDir();
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference");
|
||||
shared_file.importFromFile(sharedPrefs,myFile );
|
||||
ImportExportSharedPreferences.importFromFile(sharedPrefs, myFile);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
try (DBHandler lockHandler = GBApplication.acquireDB()) {
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (sharedPrefs != null) {
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference_" + dbDevice.getIdentifier());
|
||||
try {
|
||||
ImportExportSharedPreferences.importFromFile(deviceSharedPrefs, myFile);
|
||||
} catch (Exception ignore) {
|
||||
// some devices no not have device specific preferences
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error importing device specific preferences", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportDB() {
|
||||
@ -159,7 +188,6 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
importShared();
|
||||
DBHelper helper = new DBHelper(DbManagementActivity.this);
|
||||
File dir = FileUtils.getExternalFilesDir();
|
||||
SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
|
||||
@ -170,6 +198,7 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
importShared();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@ -227,10 +256,9 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@ -49,6 +49,9 @@ import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -56,9 +59,9 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -69,12 +72,15 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener {
|
||||
public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);
|
||||
private static final long SCAN_DURATION = 60000; // 60s
|
||||
|
||||
private ScanCallback newLeScanCallback = null;
|
||||
|
||||
// Disabled for testing, it seems worse for a few people
|
||||
private final boolean disableNewBLEScanning = true;
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
|
||||
@ -93,7 +99,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
// continue with LE scan, if available
|
||||
if (isScanning == Scanning.SCANNING_BT) {
|
||||
checkAndRequestLocationPermission();
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) {
|
||||
startDiscovery(Scanning.SCANNING_NEW_BTLE);
|
||||
} else {
|
||||
startDiscovery(Scanning.SCANNING_BTLE);
|
||||
@ -279,6 +285,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
cadidateListAdapter = new DeviceCandidateAdapter(this, deviceCandidates);
|
||||
deviceCandidatesView.setAdapter(cadidateListAdapter);
|
||||
deviceCandidatesView.setOnItemClickListener(this);
|
||||
deviceCandidatesView.setOnItemLongClickListener(this);
|
||||
|
||||
IntentFilter bluetoothIntents = new IntentFilter();
|
||||
bluetoothIntents.addAction(BluetoothDevice.ACTION_FOUND);
|
||||
@ -294,7 +301,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelableArrayList("deviceCandidates", deviceCandidates);
|
||||
}
|
||||
@ -577,6 +584,27 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
GBDeviceCandidate deviceCandidate = deviceCandidates.get(position);
|
||||
if (deviceCandidate == null) {
|
||||
LOG.error("Device candidate clicked, but item not found");
|
||||
return true;
|
||||
}
|
||||
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||
if (coordinator.getSupportedDeviceSpecificSettings(device) == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(this, DeviceSettingsActivity.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
startActivity(startIntent);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
GBDeviceCandidate deviceCandidate = deviceCandidates.get(position);
|
||||
@ -628,7 +656,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
super.onPause();
|
||||
stopBTDiscovery();
|
||||
stopBTLEDiscovery();
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) {
|
||||
stopNewBTLEDiscovery();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2018-2019 abettenburg, AndrewBedscastle, Daniele Gobbetti
|
||||
/* Copyright (C) 2018-2019 abettenburg, AndrewBedscastle, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Felix Konstantin Maurer, José Rebelo, Martin, Normano64,
|
||||
Pavel Elagin
|
||||
Pavel Elagin, Sebastian Kranz
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -38,6 +38,9 @@ import android.preference.PreferenceManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -46,14 +49,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
@ -63,23 +63,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DISPLAY_ITEMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_BAND_SCREEN_UNLOCK;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_SLEEP_DURATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL;
|
||||
@ -374,199 +357,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
autoFetchInterval);
|
||||
pref.setSummary(summary);
|
||||
|
||||
|
||||
|
||||
final Preference displayPages = findPreference("bip_display_items");
|
||||
displayPages.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference setDateFormat = findPreference(PREF_MI2_DATEFORMAT);
|
||||
setDateFormat.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DATEFORMAT);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference miBand2DisplayItems = findPreference(PREF_MI2_DISPLAY_ITEMS);
|
||||
miBand2DisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference miBand3ScreenUnlock = findPreference(PREF_MI3_BAND_SCREEN_UNLOCK);
|
||||
miBand3ScreenUnlock.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_BAND_SCREEN_UNLOCK);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference miBand3DisplayItems = findPreference("miband3_display_items");
|
||||
miBand3DisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
String nightModeState = prefs.getString(MiBandConst.PREF_MI3_NIGHT_MODE, PREF_MI3_NIGHT_MODE_OFF);
|
||||
boolean nightModeScheduled = nightModeState.equals(PREF_MI3_NIGHT_MODE_SCHEDULED);
|
||||
|
||||
final Preference nightModeStart = findPreference(PREF_MI3_NIGHT_MODE_START);
|
||||
nightModeStart.setEnabled(nightModeScheduled);
|
||||
nightModeStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference nightModeEnd = findPreference(PREF_MI3_NIGHT_MODE_END);
|
||||
nightModeEnd.setEnabled(nightModeScheduled);
|
||||
nightModeEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference nightMode = findPreference(PREF_MI3_NIGHT_MODE);
|
||||
nightMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_MI3_NIGHT_MODE_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
nightModeStart.setEnabled(scheduled);
|
||||
nightModeEnd.setEnabled(scheduled);
|
||||
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference corDisplayItems = findPreference("cor_display_items");
|
||||
corDisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
String disconnectNotificationState = prefs.getString(PREF_DISCONNECT_NOTIFICATION, PREF_MI2_DO_NOT_DISTURB_OFF);
|
||||
boolean disconnectNotificationScheduled = disconnectNotificationState.equals(PREF_MI2_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference disconnectNotificationStart = findPreference(PREF_DISCONNECT_NOTIFICATION_START);
|
||||
disconnectNotificationStart.setEnabled(disconnectNotificationScheduled);
|
||||
disconnectNotificationStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISCONNECT_NOTIFICATION_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference disconnectNotificationEnd = findPreference(PREF_DISCONNECT_NOTIFICATION_END);
|
||||
disconnectNotificationStart.setEnabled(disconnectNotificationScheduled);
|
||||
disconnectNotificationStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISCONNECT_NOTIFICATION_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference disconnectNotification = findPreference(PREF_DISCONNECT_NOTIFICATION);
|
||||
disconnectNotification.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_MI2_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
disconnectNotificationStart.setEnabled(scheduled);
|
||||
disconnectNotificationEnd.setEnabled(scheduled);
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISCONNECT_NOTIFICATION);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Get all receivers of Media Buttons
|
||||
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
|
||||
@ -695,7 +485,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
PREF_USER_WEIGHT_KG,
|
||||
PREF_USER_SLEEP_DURATION,
|
||||
PREF_USER_STEPS_GOAL,
|
||||
PREF_MI2_ENABLE_TEXT_NOTIFICATIONS,
|
||||
"weather_city",
|
||||
};
|
||||
}
|
||||
|
@ -429,7 +429,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
||||
startActivity(startIntent);
|
||||
return true;
|
||||
case R.id.appmanager_app_openinstore:
|
||||
String url = "https://pebble-appstore.romanport.com/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/0/?query=" + selectedApp.getName();
|
||||
String url = "https://apps.rebble.io/en_US/search/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/1/?native=true&?query=" + Uri.encode(selectedApp.getName());
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(url));
|
||||
startActivity(intent);
|
||||
|
@ -48,6 +48,7 @@ import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -58,7 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
|
||||
public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
|
||||
protected final int TOTAL_DAYS = 7;
|
||||
protected final int TOTAL_DAYS = getRangeDays();
|
||||
|
||||
private Locale mLocale;
|
||||
private int mTargetValue = 0;
|
||||
@ -102,6 +103,17 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
// mBalanceView.setText(getBalanceMessage(balance));
|
||||
}
|
||||
|
||||
private String getWeeksChartsLabel(Calendar day){
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
//month, show day date
|
||||
return String.valueOf(day.get(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
else{
|
||||
//week, show short day name
|
||||
return day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale);
|
||||
}
|
||||
}
|
||||
|
||||
private WeekChartsData<BarData> refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) {
|
||||
day = (Calendar) day.clone(); // do not modify the caller's argument
|
||||
day.add(Calendar.DATE, -TOTAL_DAYS);
|
||||
@ -114,7 +126,7 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
|
||||
balance += calculateBalance(amounts);
|
||||
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
|
||||
labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale));
|
||||
labels.add(getWeeksChartsLabel(day));
|
||||
day.add(Calendar.DATE, 1);
|
||||
}
|
||||
|
||||
@ -130,7 +142,28 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
barChart.getAxisLeft().removeAllLimitLines();
|
||||
barChart.getAxisLeft().addLimitLine(target);
|
||||
|
||||
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
|
||||
float average = 0;
|
||||
if (TOTAL_DAYS > 0) {
|
||||
average = Math.abs(balance / TOTAL_DAYS);
|
||||
}
|
||||
LimitLine average_line = new LimitLine(average);
|
||||
average_line.setLabel(getString(R.string.average, getAverage(average)));
|
||||
|
||||
if (average > (mTargetValue)) {
|
||||
average_line.setLineColor(Color.GREEN);
|
||||
average_line.setTextColor(Color.GREEN);
|
||||
}
|
||||
else {
|
||||
average_line.setLineColor(Color.RED);
|
||||
average_line.setTextColor(Color.RED);
|
||||
}
|
||||
if (average > 0) {
|
||||
if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
|
||||
barChart.getAxisLeft().addLimitLine(average_line);
|
||||
}
|
||||
}
|
||||
|
||||
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
|
||||
}
|
||||
|
||||
private DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) {
|
||||
@ -315,6 +348,16 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
return amounts;
|
||||
}
|
||||
|
||||
private int getRangeDays(){
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return 30;}
|
||||
else{
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
abstract String getAverage(float value);
|
||||
|
||||
abstract int getGoal();
|
||||
|
||||
abstract int getOffsetHours();
|
||||
|
@ -338,6 +338,24 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return 5;
|
||||
}
|
||||
|
||||
private String getSleepTitle() {
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weeksleepchart_sleep_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStepsTitle() {
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weekstepschart_steps_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
@ -346,9 +364,9 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
case 1:
|
||||
return getString(R.string.sleepchart_your_sleep);
|
||||
case 2:
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
return getSleepTitle();
|
||||
case 3:
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
return getStepsTitle();
|
||||
case 4:
|
||||
return getString(R.string.stats_title);
|
||||
case 5:
|
||||
|
@ -40,7 +40,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weeksleepchart_sleep_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -167,4 +172,10 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
private String getHM(long value) {
|
||||
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getAverage(float value) {
|
||||
return getHM((long)value);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,7 +30,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weekstepschart_steps_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -113,4 +118,9 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
} else
|
||||
return getString(R.string.no_data);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getAverage(float value) {
|
||||
return String.format("%.0f", value);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
/* Copyright (C) 2018-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
|
||||
public class DeviceSettingsActivity extends AbstractGBActivity implements
|
||||
PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceSettingsActivity.class);
|
||||
|
||||
GBDevice device;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_device_settings);
|
||||
if (savedInstanceState == null) {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(DeviceSpecificSettingsFragment.FRAGMENT_TAG);
|
||||
if (fragment == null) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
fragment = DeviceSpecificSettingsFragment.newInstance(device.getAddress(), coordinator.getSupportedDeviceSpecificSettings(device));
|
||||
}
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings_container, fragment, DeviceSpecificSettingsFragment.FRAGMENT_TAG)
|
||||
.commit();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preferenceScreen) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
|
||||
PreferenceFragmentCompat fragment = DeviceSpecificSettingsFragment.newInstance(device.getAddress(), coordinator.getSupportedDeviceSpecificSettings(device));
|
||||
Bundle args = fragment.getArguments();
|
||||
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
|
||||
fragment.setArguments(args);
|
||||
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings_container, fragment, preferenceScreen.getKey())
|
||||
.addToBackStack(preferenceScreen.getKey())
|
||||
.commit();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,418 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ITEMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_LANGUAGE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_SWIPE_UNLOCK;
|
||||
|
||||
public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceSpecificSettingsFragment.class);
|
||||
|
||||
static final String FRAGMENT_TAG = "DEVICE_SPECIFIC_SETTINGS_FRAGMENT";
|
||||
|
||||
private void setSettingsFileSuffix(String settingsFileSuffix, @NonNull int[] supportedSettings) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("settingsFileSuffix", settingsFileSuffix);
|
||||
args.putIntArray("supportedSettings", supportedSettings);
|
||||
setArguments(args);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments == null) {
|
||||
return;
|
||||
}
|
||||
String settingsFileSuffix = arguments.getString("settingsFileSuffix", null);
|
||||
int[] supportedSettings = arguments.getIntArray("supportedSettings");
|
||||
if (settingsFileSuffix == null || supportedSettings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getPreferenceManager().setSharedPreferencesName("devicesettings_" + settingsFileSuffix);
|
||||
|
||||
if (rootKey == null) {
|
||||
// we are the main preference screen
|
||||
boolean first = true;
|
||||
for (int setting : supportedSettings) {
|
||||
if (first) {
|
||||
setPreferencesFromResource(setting, null);
|
||||
first = false;
|
||||
} else {
|
||||
addPreferencesFromResource(setting);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Now, this is ugly: search all the xml files for the rootKey
|
||||
for (int setting : supportedSettings) {
|
||||
try {
|
||||
setPreferencesFromResource(setting, rootKey);
|
||||
} catch (Exception ignore) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
setChangeListener();
|
||||
}
|
||||
|
||||
/*
|
||||
* delayed execution so that the preferences are applied first
|
||||
*/
|
||||
private void invokeLater(Runnable runnable) {
|
||||
getListView().post(runnable);
|
||||
}
|
||||
|
||||
private void setChangeListener() {
|
||||
Prefs prefs = new Prefs(getPreferenceManager().getSharedPreferences());
|
||||
String disconnectNotificationState = prefs.getString(PREF_DISCONNECT_NOTIFICATION, PREF_DO_NOT_DISTURB_OFF);
|
||||
boolean disconnectNotificationScheduled = disconnectNotificationState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference disconnectNotificationStart = findPreference(PREF_DISCONNECT_NOTIFICATION_START);
|
||||
if (disconnectNotificationStart != null) {
|
||||
disconnectNotificationStart.setEnabled(disconnectNotificationScheduled);
|
||||
disconnectNotificationStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISCONNECT_NOTIFICATION_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference disconnectNotificationEnd = findPreference(PREF_DISCONNECT_NOTIFICATION_END);
|
||||
if (disconnectNotificationEnd != null) {
|
||||
disconnectNotificationEnd.setEnabled(disconnectNotificationScheduled);
|
||||
disconnectNotificationEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISCONNECT_NOTIFICATION_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference disconnectNotification = findPreference(PREF_DISCONNECT_NOTIFICATION);
|
||||
if (disconnectNotification != null) {
|
||||
disconnectNotification.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
Objects.requireNonNull(disconnectNotificationStart).setEnabled(scheduled);
|
||||
Objects.requireNonNull(disconnectNotificationEnd).setEnabled(scheduled);
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISCONNECT_NOTIFICATION);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
String nightModeState = prefs.getString(MiBandConst.PREF_NIGHT_MODE, PREF_NIGHT_MODE_OFF);
|
||||
boolean nightModeScheduled = nightModeState.equals(PREF_NIGHT_MODE_SCHEDULED);
|
||||
|
||||
final Preference nightModeStart = findPreference(PREF_NIGHT_MODE_START);
|
||||
if (nightModeStart != null) {
|
||||
nightModeStart.setEnabled(nightModeScheduled);
|
||||
nightModeStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_NIGHT_MODE_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference nightModeEnd = findPreference(PREF_NIGHT_MODE_END);
|
||||
if (nightModeEnd != null) {
|
||||
nightModeEnd.setEnabled(nightModeScheduled);
|
||||
nightModeEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_NIGHT_MODE_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference nightMode = findPreference(PREF_NIGHT_MODE);
|
||||
if (nightMode != null) {
|
||||
|
||||
nightMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_NIGHT_MODE_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
Objects.requireNonNull(nightModeStart).setEnabled(scheduled);
|
||||
Objects.requireNonNull(nightModeEnd).setEnabled(scheduled);
|
||||
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_NIGHT_MODE);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
String doNotDisturbState = prefs.getString(MiBandConst.PREF_DO_NOT_DISTURB, PREF_DO_NOT_DISTURB_OFF);
|
||||
boolean doNotDisturbScheduled = doNotDisturbState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference doNotDisturbStart = findPreference(PREF_DO_NOT_DISTURB_START);
|
||||
if (doNotDisturbStart != null) {
|
||||
doNotDisturbStart.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DO_NOT_DISTURB_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference doNotDisturbEnd = findPreference(PREF_DO_NOT_DISTURB_END);
|
||||
if (doNotDisturbEnd != null) {
|
||||
doNotDisturbEnd.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DO_NOT_DISTURB_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference doNotDisturb = findPreference(PREF_DO_NOT_DISTURB);
|
||||
if (doNotDisturb != null) {
|
||||
doNotDisturb.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
Objects.requireNonNull(doNotDisturbStart).setEnabled(scheduled);
|
||||
Objects.requireNonNull(doNotDisturbEnd).setEnabled(scheduled);
|
||||
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DO_NOT_DISTURB);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addPreferenceHandlerFor(PREF_SWIPE_UNLOCK);
|
||||
addPreferenceHandlerFor(PREF_MI2_DATEFORMAT);
|
||||
addPreferenceHandlerFor(PREF_DATEFORMAT);
|
||||
addPreferenceHandlerFor(PREF_DISPLAY_ITEMS);
|
||||
addPreferenceHandlerFor(PREF_LANGUAGE);
|
||||
|
||||
String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF);
|
||||
boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference rotateWristCycleInfo = findPreference(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
if (rotateWristCycleInfo != null) {
|
||||
rotateWristCycleInfo.setEnabled(!PREF_DO_NOT_DISTURB_OFF.equals(displayOnLiftState));
|
||||
rotateWristCycleInfo.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference displayOnLiftStart = findPreference(PREF_DISPLAY_ON_LIFT_START);
|
||||
if (displayOnLiftStart != null) {
|
||||
displayOnLiftStart.setEnabled(displayOnLiftScheduled);
|
||||
displayOnLiftStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISPLAY_ON_LIFT_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference displayOnLiftEnd = findPreference(PREF_DISPLAY_ON_LIFT_END);
|
||||
if (displayOnLiftEnd != null) {
|
||||
displayOnLiftEnd.setEnabled(displayOnLiftScheduled);
|
||||
displayOnLiftEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISPLAY_ON_LIFT_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference displayOnLift = findPreference(PREF_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
if (displayOnLift != null) {
|
||||
displayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
Objects.requireNonNull(displayOnLiftStart).setEnabled(scheduled);
|
||||
Objects.requireNonNull(displayOnLiftEnd).setEnabled(scheduled);
|
||||
if (rotateWristCycleInfo != null) {
|
||||
rotateWristCycleInfo.setEnabled(!PREF_DO_NOT_DISTURB_OFF.equals(newVal.toString()));
|
||||
}
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
EditTextPreference pref = findPreference(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
|
||||
if (pref != null) {
|
||||
pref.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
|
||||
@Override
|
||||
public void onBindEditText(@NonNull EditText editText) {
|
||||
editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static DeviceSpecificSettingsFragment newInstance(String settingsFileSuffix, @NonNull int[] supportedSettings) {
|
||||
DeviceSpecificSettingsFragment fragment = new DeviceSpecificSettingsFragment();
|
||||
fragment.setSettingsFileSuffix(settingsFileSuffix, supportedSettings);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
DialogFragment dialogFragment;
|
||||
if (preference instanceof XTimePreference) {
|
||||
dialogFragment = new XTimePreferenceFragment();
|
||||
Bundle bundle = new Bundle(1);
|
||||
bundle.putString("key", preference.getKey());
|
||||
dialogFragment.setArguments(bundle);
|
||||
dialogFragment.setTargetFragment(this, 0);
|
||||
if (getFragmentManager() != null) {
|
||||
dialogFragment.show(getFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG");
|
||||
}
|
||||
} else {
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
}
|
||||
|
||||
private void addPreferenceHandlerFor(final String preferenceKey) {
|
||||
Preference pref = findPreference(preferenceKey);
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(preferenceKey);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -223,7 +223,7 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
|
||||
|
||||
for (ApplicationInfo ai : originalList) {
|
||||
CharSequence name = mPm.getApplicationLabel(ai);
|
||||
if (name.toString().contains(filterPattern) ||
|
||||
if (name.toString().toLowerCase().contains(filterPattern) ||
|
||||
(ai.packageName.contains(filterPattern))) {
|
||||
filteredList.add(ai);
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9CalibrationActivity;
|
||||
@ -150,6 +151,21 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
holder.batteryIcon.setImageLevel(200);
|
||||
}
|
||||
|
||||
//device specific settings
|
||||
holder.deviceSpecificSettingsView.setVisibility(coordinator.getSupportedDeviceSpecificSettings(device) != null ? View.VISIBLE : View.GONE);
|
||||
holder.deviceSpecificSettingsView.setOnClickListener(new View.OnClickListener()
|
||||
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(context, DeviceSettingsActivity.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
context.startActivity(startIntent);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//fetch activity data
|
||||
holder.fetchActivityDataBox.setVisibility((device.isInitialized() && coordinator.supportsActivityDataFetching()) ? View.VISIBLE : View.GONE);
|
||||
holder.fetchActivityData.setOnClickListener(new View.OnClickListener()
|
||||
@ -472,6 +488,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
LinearLayout batteryStatusBox;
|
||||
TextView batteryStatusLabel;
|
||||
ImageView batteryIcon;
|
||||
ImageView deviceSpecificSettingsView;
|
||||
LinearLayout fetchActivityDataBox;
|
||||
ImageView fetchActivityData;
|
||||
ProgressBar busyIndicator;
|
||||
@ -504,6 +521,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
batteryStatusBox = view.findViewById(R.id.device_battery_status_box);
|
||||
batteryStatusLabel = view.findViewById(R.id.battery_status);
|
||||
batteryIcon = view.findViewById(R.id.device_battery_status);
|
||||
deviceSpecificSettingsView = view.findViewById(R.id.device_specific_settings);
|
||||
fetchActivityDataBox = view.findViewById(R.id.device_action_fetch_activity_box);
|
||||
fetchActivityData = view.findViewById(R.id.device_action_fetch_activity);
|
||||
busyIndicator = view.findViewById(R.id.device_busy_indicator);
|
||||
|
@ -28,5 +28,6 @@ public class GBDeviceEventCallControl extends GBDeviceEvent {
|
||||
OUTGOING,
|
||||
REJECT,
|
||||
START,
|
||||
IGNORE,
|
||||
}
|
||||
}
|
||||
|
@ -152,4 +152,10 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsUnicodeEmojis() { return false; }
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import java.util.Collection;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
@ -278,4 +279,9 @@ public interface DeviceCoordinator {
|
||||
* Indicates whether the device supports unicode emojis.
|
||||
*/
|
||||
boolean supportsUnicodeEmojis();
|
||||
|
||||
/**
|
||||
* Indicates which device specific settings the device supports (not per device type or family, but unique per device).
|
||||
*/
|
||||
int[] getSupportedDeviceSpecificSettings(GBDevice device);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Julien Pivotto, Kasha, Steffen Liebergeld, Uwe Hermann
|
||||
Gobbetti, José Rebelo, Julien Pivotto, Kasha, Sebastian Kranz, Steffen
|
||||
Liebergeld, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2018-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
/* Copyright (C) 2016-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti, José Rebelo
|
||||
based on code from BlueWatcher, https://github.com/masterjc/bluewatcher
|
||||
|
||||
|
@ -47,6 +47,8 @@ public class HuamiConst {
|
||||
public static final String MI_BAND2_NAME = "MI Band 2";
|
||||
public static final String MI_BAND2_NAME_HRX = "Mi Band HRX";
|
||||
public static final String MI_BAND3_NAME = "Mi Band 3";
|
||||
public static final String MI_BAND3_NAME_2 = "Xiaomi Band 3";
|
||||
public static final String MI_BAND4_NAME = "Mi Smart Band 4";
|
||||
|
||||
public static final String PREF_ACTIVATE_DISPLAY_ON_LIFT = "activate_display_on_lift_wrist";
|
||||
public static final String PREF_DISPLAY_ON_LIFT_START = "display_on_lift_start";
|
||||
@ -56,6 +58,11 @@ public class HuamiConst {
|
||||
public static final String PREF_DISCONNECT_NOTIFICATION_START = "disconnect_notification_start";
|
||||
public static final String PREF_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end";
|
||||
|
||||
public static final String PREF_DISPLAY_ITEMS = "display_items";
|
||||
public static final String PREF_LANGUAGE = "language";
|
||||
public static final String PREF_DATEFORMAT = "dateformat";
|
||||
|
||||
|
||||
public static int toActivityKind(int rawType) {
|
||||
switch (rawType) {
|
||||
case TYPE_DEEP_SLEEP:
|
||||
|
@ -21,9 +21,12 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -34,7 +37,6 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
@ -119,22 +121,27 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{R.xml.devicesettings_pairingkey};
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends AbstractActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new MiBand2SampleProvider(device, session);
|
||||
}
|
||||
|
||||
public static DateTimeDisplay getDateDisplay(Context context) throws IllegalArgumentException {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
public static DateTimeDisplay getDateDisplay(Context context, String deviceAddress) throws IllegalArgumentException {
|
||||
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
String dateFormatTime = context.getString(R.string.p_dateformat_time);
|
||||
if (dateFormatTime.equals(prefs.getString(MiBandConst.PREF_MI2_DATEFORMAT, dateFormatTime))) {
|
||||
if (dateFormatTime.equals(sharedPrefs.getString(MiBandConst.PREF_MI2_DATEFORMAT, dateFormatTime))) {
|
||||
return DateTimeDisplay.TIME;
|
||||
}
|
||||
return DateTimeDisplay.DATE_TIME;
|
||||
}
|
||||
|
||||
public static ActivateDisplayOnLift getActivateDisplayOnLiftWrist(Context context) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
public static ActivateDisplayOnLift getActivateDisplayOnLiftWrist(Context context, String deviceAddress) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
|
||||
String liftOff = context.getString(R.string.p_off);
|
||||
String liftOn = context.getString(R.string.p_on);
|
||||
@ -151,16 +158,16 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
||||
return ActivateDisplayOnLift.OFF;
|
||||
}
|
||||
|
||||
public static Date getDisplayOnLiftStart() {
|
||||
return getTimePreference(HuamiConst.PREF_DISPLAY_ON_LIFT_START, "00:00");
|
||||
public static Date getDisplayOnLiftStart(String deviceAddress) {
|
||||
return getTimePreference(HuamiConst.PREF_DISPLAY_ON_LIFT_START, "00:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Date getDisplayOnLiftEnd() {
|
||||
return getTimePreference(HuamiConst.PREF_DISPLAY_ON_LIFT_END, "00:00");
|
||||
public static Date getDisplayOnLiftEnd(String deviceAddress) {
|
||||
return getTimePreference(HuamiConst.PREF_DISPLAY_ON_LIFT_END, "00:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static DisconnectNotificationSetting getDisconnectNotificationSetting(Context context) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
public static DisconnectNotificationSetting getDisconnectNotificationSetting(Context context, String deviceAddress) {
|
||||
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
|
||||
|
||||
String liftOff = context.getString(R.string.p_off);
|
||||
String liftOn = context.getString(R.string.p_on);
|
||||
@ -177,17 +184,17 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
||||
return DisconnectNotificationSetting.OFF;
|
||||
}
|
||||
|
||||
public static Date getDisconnectNotificationStart() {
|
||||
return getTimePreference(HuamiConst.PREF_DISCONNECT_NOTIFICATION_START, "00:00");
|
||||
public static Date getDisconnectNotificationStart(String deviceAddress) {
|
||||
return getTimePreference(HuamiConst.PREF_DISCONNECT_NOTIFICATION_START, "00:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Date getDisconnectNotificationEnd() {
|
||||
return getTimePreference(HuamiConst.PREF_DISCONNECT_NOTIFICATION_END, "00:00");
|
||||
public static Date getDisconnectNotificationEnd(String deviceAddress) {
|
||||
return getTimePreference(HuamiConst.PREF_DISCONNECT_NOTIFICATION_END, "00:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Set<String> getDisplayItems() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getStringSet(MiBandConst.PREF_MI2_DISPLAY_ITEMS, null);
|
||||
public static Set<String> getDisplayItems(String deviceAddress) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
return prefs.getStringSet(HuamiConst.PREF_DISPLAY_ITEMS, null);
|
||||
}
|
||||
|
||||
public static boolean getGoalNotification() {
|
||||
@ -195,8 +202,8 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_GOAL_NOTIFICATION, false);
|
||||
}
|
||||
|
||||
public static boolean getRotateWristToSwitchInfo() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
public static boolean getRotateWristToSwitchInfo(String deviceAddress) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO, false);
|
||||
}
|
||||
|
||||
@ -231,28 +238,43 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_END, "14:00");
|
||||
}
|
||||
|
||||
public static Date getDoNotDisturbStart() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_DO_NOT_DISTURB_START, "01:00");
|
||||
public static Date getDoNotDisturbStart(String deviceAddress) {
|
||||
return getTimePreference(MiBandConst.PREF_DO_NOT_DISTURB_START, "01:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Date getDoNotDisturbEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_DO_NOT_DISTURB_END, "06:00");
|
||||
public static Date getDoNotDisturbEnd(String deviceAddress) {
|
||||
return getTimePreference(MiBandConst.PREF_DO_NOT_DISTURB_END, "06:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Date getTimePreference(String key, String defaultValue) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
public static boolean getBandScreenUnlock(String deviceAddress) {
|
||||
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
|
||||
return prefs.getBoolean(MiBandConst.PREF_SWIPE_UNLOCK, false);
|
||||
}
|
||||
|
||||
protected static Date getTimePreference(String key, String defaultValue, String deviceAddress) {
|
||||
Prefs prefs;
|
||||
|
||||
if (deviceAddress == null) {
|
||||
prefs = GBApplication.getPrefs();
|
||||
} else {
|
||||
prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
|
||||
}
|
||||
String time = prefs.getString(key, defaultValue);
|
||||
|
||||
DateFormat df = new SimpleDateFormat("HH:mm");
|
||||
try {
|
||||
return df.parse(time);
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new Date();
|
||||
}
|
||||
|
||||
protected static Date getTimePreference(String key, String defaultValue) {
|
||||
return getTimePreference(key, defaultValue, null);
|
||||
}
|
||||
|
||||
public static MiBandConst.DistanceUnit getDistanceUnit() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
String unit = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
||||
@ -263,18 +285,14 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
public static DoNotDisturb getDoNotDisturb(Context context) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
public static DoNotDisturb getDoNotDisturb(String deviceAddress) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
|
||||
String dndOff = context.getString(R.string.p_off);
|
||||
String dndAutomatic = context.getString(R.string.p_automatic);
|
||||
String dndScheduled = context.getString(R.string.p_scheduled);
|
||||
String pref = prefs.getString(MiBandConst.PREF_DO_NOT_DISTURB, MiBandConst.PREF_DO_NOT_DISTURB_OFF);
|
||||
|
||||
String pref = prefs.getString(MiBandConst.PREF_MI2_DO_NOT_DISTURB, dndOff);
|
||||
|
||||
if (dndAutomatic.equals(pref)) {
|
||||
if (MiBandConst.PREF_DO_NOT_DISTURB_AUTOMATIC.equals(pref)) {
|
||||
return DoNotDisturb.AUTOMATIC;
|
||||
} else if (dndScheduled.equals(pref)) {
|
||||
} else if (MiBandConst.PREF_DO_NOT_DISTURB_SCHEDULED.equals(pref)) {
|
||||
return DoNotDisturb.SCHEDULED;
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,8 @@ public class HuamiService {
|
||||
// service uuid fee1
|
||||
public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
|
||||
public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700");
|
||||
public static final UUID UUID_CHARACTERISTIC_AUDIO = UUID.fromString("00000012-0000-3512-2118-0009af100700");
|
||||
public static final UUID UUID_CHARACTERISTIC_AUDIODATA = UUID.fromString("00000013-0000-3512-2118-0009af100700");
|
||||
|
||||
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER = UUID.fromString("00000020-0000-3512-2118-0009af100700");
|
||||
|
||||
@ -136,6 +138,7 @@ public class HuamiService {
|
||||
public static final byte[] DATEFORMAT_TIME = new byte[] {ENDPOINT_DISPLAY, 0x0a, 0x0, 0x0 };
|
||||
public static final byte[] DATEFORMAT_TIME_12_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x0 };
|
||||
public static final byte[] DATEFORMAT_TIME_24_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x1 };
|
||||
public static final byte[] DATEFORMAT_DATE_MM_DD_YYYY = new byte[]{ENDPOINT_DISPLAY, 30, 0x00, 'M', 'M', '/', 'd', 'd', '/', 'y', 'y', 'y', 'y'};
|
||||
public static final byte[] COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||
|
@ -21,10 +21,12 @@ import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -74,4 +76,14 @@ public class AmazfitBipCoordinator extends HuamiCoordinator {
|
||||
public boolean supportsWeather() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitbip,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_disconnectnotification,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,12 @@ import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -77,4 +79,13 @@ public class AmazfitCorCoordinator extends HuamiCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsUnicodeEmojis() { return true; }
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitcor,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_disconnectnotification,
|
||||
R.xml.devicesettings_pairingkey};
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Daniele Gobbetti, João
|
||||
Paulo Barraca, Matthieu Baerts
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class AmazfitCor2Coordinator extends HuamiCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitCor2Coordinator.class);
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.AMAZFITCOR2;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && (name.equalsIgnoreCase("Amazfit Band 2") || name.equalsIgnoreCase("Amazfit Cor 2"))) {
|
||||
return DeviceType.AMAZFITCOR2;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("unable to check device support", ex);
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
AmazfitCor2FWInstallHandler handler = new AmazfitCor2FWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsUnicodeEmojis() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitcor,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_disconnectnotification,
|
||||
R.xml.devicesettings_pairingkey};
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2.AmazfitCor2FirmwareInfo;
|
||||
|
||||
public class AmazfitCor2FWHelper extends HuamiFWHelper {
|
||||
|
||||
public AmazfitCor2FWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new AmazfitCor2FirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a an Amazfit Cor 2 firmware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
class AmazfitCor2FWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
AmazfitCor2FWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFwUpgradeNotice() {
|
||||
return mContext.getString(R.string.fw_upgrade_notice_amazfitcor2, helper.getHumanFirmwareVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitCor2FWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.AMAZFITCOR2;
|
||||
}
|
||||
}
|
@ -21,10 +21,12 @@ import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
@ -77,4 +79,15 @@ public class MiBand2Coordinator extends HuamiCoordinator {
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_miband2,
|
||||
R.xml.devicesettings_donotdisturb_withauto,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_rotatewrist_cycleinfo,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -21,13 +21,15 @@ import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
@ -51,7 +53,7 @@ public class MiBand3Coordinator extends HuamiCoordinator {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase(HuamiConst.MI_BAND3_NAME)) {
|
||||
if (name != null && (name.equalsIgnoreCase(HuamiConst.MI_BAND3_NAME) || name.equalsIgnoreCase(HuamiConst.MI_BAND3_NAME_2))) {
|
||||
return DeviceType.MIBAND3;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@ -82,22 +84,31 @@ public class MiBand3Coordinator extends HuamiCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean getBandScreenUnlock() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI3_BAND_SCREEN_UNLOCK, false);
|
||||
|
||||
public static String getNightMode(String deviceAddress) {
|
||||
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
|
||||
|
||||
return prefs.getString(MiBandConst.PREF_NIGHT_MODE, MiBandConst.PREF_NIGHT_MODE_OFF);
|
||||
}
|
||||
|
||||
public static String getNightMode() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
return prefs.getString(MiBandConst.PREF_MI3_NIGHT_MODE, MiBandConst.PREF_MI3_NIGHT_MODE_OFF);
|
||||
public static Date getNightModeStart(String deviceAddress) {
|
||||
return getTimePreference(MiBandConst.PREF_NIGHT_MODE_START, "16:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Date getNightModeStart() {
|
||||
return getTimePreference( MiBandConst.PREF_MI3_NIGHT_MODE_START, "16:00");
|
||||
public static Date getNightModeEnd(String deviceAddress) {
|
||||
return getTimePreference(MiBandConst.PREF_NIGHT_MODE_END, "07:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Date getNightModeEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI3_NIGHT_MODE_END, "07:00");
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_miband3,
|
||||
R.xml.devicesettings_dateformat,
|
||||
R.xml.devicesettings_nightmode,
|
||||
R.xml.devicesettings_donotdisturb_withauto,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_swipeunlock,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.EN
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
|
||||
|
||||
public class MiBand3Service {
|
||||
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_ENABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_NIGHT_MODE_OFF = new byte[]{0x1a, 0x00};
|
||||
|
@ -0,0 +1,99 @@
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class MiBand4Coordinator extends HuamiCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBand4Coordinator.class);
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.MIBAND4;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase(HuamiConst.MI_BAND4_NAME)) {
|
||||
return DeviceType.MIBAND4;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("unable to check device support", ex);
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
MiBand4FWInstallHandler handler = new MiBand4FWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracks() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_miband3,
|
||||
R.xml.devicesettings_dateformat,
|
||||
R.xml.devicesettings_nightmode,
|
||||
R.xml.devicesettings_donotdisturb_withauto,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_swipeunlock,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
|
||||
|
||||
public class MiBand4FWHelper extends HuamiFWHelper {
|
||||
|
||||
public MiBand4FWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new MiBand4FirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a Mi Band 4 firmware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
class MiBand4FWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
MiBand4FWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFwUpgradeNotice() {
|
||||
return mContext.getString(R.string.fw_upgrade_notice_miband4, helper.getHumanFirmwareVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new MiBand4FWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.MIBAND4;
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/* Copyright (C) 2019 Sophanimus
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
|
||||
|
||||
public final class BFH16Constants {
|
||||
|
||||
//Known Services
|
||||
public static final UUID BFH16_GENERIC_ACCESS_SERVICE = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID BFH16_GENERIC_ARTTRIBUTE_SERVICE = UUID.fromString("00001801-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
public static final UUID BFH16_IDENTIFICATION_SERVICE1 = UUID.fromString("0000fef5-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID BFH16_IDENTIFICATION_SERVICE2 = UUID.fromString("0000fee7-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
public static final UUID BFH16_SERVICE1 = UUID.fromString("000056ff-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID BFH16_SERVICE1_WRITE = UUID.fromString("000033f3-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID BFH16_SERVICE1_NOTIFY = UUID.fromString("000033f4-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
public static final UUID BFH16_SERVICE2 = UUID.fromString("0000fee7-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID BFH16_SERVICE2_WRITE = UUID.fromString("0000fec7-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID BFH16_SERVICE2_INDICATE= UUID.fromString("0000fec8-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID BFH16_SERVICE2_READ = UUID.fromString("0000fec9-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
//Verified command bytes
|
||||
public static final byte CMD_SET_ALARM_1 = (byte)0x09;
|
||||
public static final byte CMD_SET_ALARM_2 = (byte)0x22;
|
||||
public static final byte CMD_SET_ALARM_3 = (byte)0x23;
|
||||
|
||||
public static final byte CMD_SET_DATE_AND_TIME = 0x08;
|
||||
|
||||
|
||||
public static final byte CMD_MEASURE_HEART = (byte)0x0D; //param1: 0, param2: 0 -> STOP | 1 -> START
|
||||
public static final byte CMD_VIBRATE = (byte)0x07; //param1: 0, param2: 1
|
||||
|
||||
|
||||
public static final byte CMD_SWITCH_PHOTO_MODE = (byte)0x25; //param1: 0, param2: 0 -> OFF | 1 -> ON
|
||||
public static final byte CMD_SWITCH_12HOUR_MODE = (byte)0x3E; //byte1: 1 -> 12HourMode | 0 -> 24HourMode
|
||||
public static final byte CMD_SWITCH_METRIC_IMPERIAL = (byte)0x3A; //param1: 0, param2: 0 -> METRIC | 1 -> IMPERIAL //Also requests walked steps
|
||||
|
||||
|
||||
//Verified receive bytes
|
||||
public static final byte RECEIVE_DEVICE_INFO = (byte)0xF6;
|
||||
public static final byte RECEIVE_BATTERY_LEVEL = (byte)0xF7;
|
||||
public static final byte RECEIVE_STEPS_DATA = (byte)0xF9;
|
||||
public static final byte RECEIVE_HEART_DATA = (byte)0xE8;
|
||||
public static final byte RECEIVE_PHOTO_TRIGGER = (byte)0xF3;
|
||||
|
||||
//Verified icon bytes
|
||||
public static final byte ICON_CALL = (byte)0x00;
|
||||
public static final byte ICON_SMS = (byte)0x01;
|
||||
public static final byte ICON_WECHAT = (byte)0x02;
|
||||
public static final byte ICON_QQ = (byte)0x03;
|
||||
public static final byte ICON_FACEBOOK = (byte)0x04;
|
||||
public static final byte ICON_SKYPE = (byte)0x05;
|
||||
public static final byte ICON_TWITTER = (byte)0x06;
|
||||
public static final byte ICON_WHATSAPP = (byte)0x07;
|
||||
public static final byte ICON_LINE = (byte)0x08;
|
||||
public static final byte ICON_TALK = (byte)0x09;
|
||||
public static final byte ICON_RUNNER = (byte)0x0A;
|
||||
|
||||
|
||||
|
||||
//Most probably correct command bytes
|
||||
public static final byte CMD_SET_STEPLENGTH = (byte)0x3F; //param1: 0, param2: STEPLENGTH
|
||||
|
||||
|
||||
//Probably correct command bytes
|
||||
public static final byte CMD_SET_INACTIVITY_WARNING_TIME = (byte)0x24; //param1: 0, param2: time
|
||||
|
||||
public static final byte CMD_SET_HEART_TARGET = (byte)0x01; //param1: 0, param2: HEART TARGET
|
||||
public static final byte CMD_SET_STEP_TARGET = (byte)0x03; //param1: 0, param2: STEP TARGET
|
||||
|
||||
public static final byte CMD_FIND_DEVICE = (byte)0x36; //param1: 0, param2: 1
|
||||
public static final byte CMD_SET_DISCONNECT_REMIND = (byte)0x37; //param1: 0, param2: 0 -> ??? | 1 -> ???
|
||||
public static final byte CMD_SET_AUTODETECT_HEART = (byte)0x38; //param1: 0, param2: 0 -> ??? | 1 -> ???
|
||||
|
||||
public static final byte CMD_READ_HISTORY_SLEEP_COUNT = (byte)0x32; //param1: 0, param2: 0
|
||||
|
||||
public static final byte CMD_SET_NOON_TIME = (byte)0x26; //param1: start time, param2: end time
|
||||
public static final byte CMD_SET_SLEEP_TIME = (byte)0x27; //param1: start time, param2: end time
|
||||
|
||||
|
||||
//Could be correct command bytes
|
||||
//Send PhoneName 0x17 and 0x18
|
||||
//Send PhoneNumber 0x19 and 0x20
|
||||
//Weather 0x3B
|
||||
//Power Management 0x39
|
||||
//User Id 0x35
|
||||
//
|
||||
|
||||
|
||||
|
||||
//______________________________________________________________________________________________
|
||||
//It may be that BFH16 uses the same communication protocol as JYOU
|
||||
//copied the following JYOU vars:
|
||||
|
||||
public static final byte CMD_SET_HEARTRATE_AUTO = 0x38;
|
||||
public static final byte CMD_SET_HEARTRATE_WARNING_VALUE = 0x01;
|
||||
public static final byte CMD_SET_TARGET_STEPS = 0x03;
|
||||
//public static final byte CMD_GET_STEP_COUNT = 0x1D;
|
||||
public static final byte CMD_GET_SLEEP_TIME = 0x32;
|
||||
public static final byte CMD_SET_DND_SETTINGS = 0x39;
|
||||
|
||||
public static final byte CMD_ACTION_HEARTRATE_SWITCH = 0x0D;
|
||||
public static final byte CMD_ACTION_SHOW_NOTIFICATION = 0x2C;
|
||||
public static final byte CMD_ACTION_REBOOT_DEVICE = 0x0E;
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
/* Copyright (C) 2019 Sophanimus
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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 BFH16DeviceCoordinator extends AbstractDeviceCoordinator
|
||||
{
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(BFH16DeviceCoordinator.class);
|
||||
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.BFH16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Denver";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends ScanFilter> createBLEScanFilters() {
|
||||
|
||||
ParcelUuid bfhService1 = new ParcelUuid(BFH16Constants.BFH16_IDENTIFICATION_SERVICE1);
|
||||
ParcelUuid bfhService2 = new ParcelUuid(BFH16Constants.BFH16_IDENTIFICATION_SERVICE2);
|
||||
|
||||
ScanFilter filter = new ScanFilter.Builder()
|
||||
.setServiceUuid(bfhService1)
|
||||
.setServiceUuid(bfhService2)
|
||||
.build();
|
||||
|
||||
return Collections.singletonList(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null) {
|
||||
if (name.startsWith("BFH-16")) {
|
||||
return DeviceType.BFH16;
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceType.UNKNOWN;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle(GBDevice deviceCandidate){
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity(){
|
||||
return null;
|
||||
}
|
||||
|
||||
//Additional required functions ______________________________________
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//Supported ________________________________________________________
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//NOT Supported ________________________________________________________
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsLedColor()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRgbLedColor()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -51,7 +51,7 @@ public abstract class AbstractMiBandFWHelper {
|
||||
}
|
||||
|
||||
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
|
||||
this.fw = FileUtils.readAll(in, 1024 * 1536); // 1.5 MB
|
||||
this.fw = FileUtils.readAll(in, 1024 * 2048); // 2.0 MB
|
||||
determineFirmwareInfo(fw);
|
||||
} catch (IOException ex) {
|
||||
throw ex; // pass through
|
||||
|
@ -39,24 +39,23 @@ public final class MiBandConst {
|
||||
public static final String PREF_MIBAND_BUTTON_ACTION_DELAY = "mi_button_press_count_match_delay";
|
||||
public static final String PREF_MIBAND_BUTTON_PRESS_BROADCAST = "mi_button_press_broadcast";
|
||||
public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
|
||||
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours";
|
||||
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "device_time_offset_hours";
|
||||
public static final String PREF_MI2_DATEFORMAT = "mi2_dateformat";
|
||||
public static final String PREF_MI2_GOAL_NOTIFICATION = "mi2_goal_notification";
|
||||
public static final String PREF_MI2_DISPLAY_ITEMS = "mi2_display_items";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_CLOCK = "clock";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_STEPS = "steps";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_DISTANCE = "distance";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_CALORIES = "calories";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_HEART_RATE = "heart_rate";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_BATTERY = "battery";
|
||||
public static final String PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO = "mi2_rotate_wrist_to_switch_info";
|
||||
public static final String PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO = "rotate_wrist_to_cycle_info";
|
||||
public static final String PREF_MI2_ENABLE_TEXT_NOTIFICATIONS = "mi2_enable_text_notifications";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB = "mi2_do_not_disturb";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_OFF = "off";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_AUTOMATIC = "automatic";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_SCHEDULED = "scheduled";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_START = "mi2_do_not_disturb_start";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_END = "mi2_do_not_disturb_end";
|
||||
public static final String PREF_DO_NOT_DISTURB = "do_not_disturb";
|
||||
public static final String PREF_DO_NOT_DISTURB_OFF = "off";
|
||||
public static final String PREF_DO_NOT_DISTURB_AUTOMATIC = "automatic";
|
||||
public static final String PREF_DO_NOT_DISTURB_SCHEDULED = "scheduled";
|
||||
public static final String PREF_DO_NOT_DISTURB_START = "do_not_disturb_start";
|
||||
public static final String PREF_DO_NOT_DISTURB_END = "do_not_disturb_end";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS = "mi2_inactivity_warnings";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD = "mi2_inactivity_warnings_threshold";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_START = "mi2_inactivity_warnings_start";
|
||||
@ -66,13 +65,13 @@ public final class MiBandConst {
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND_END = "mi2_inactivity_warnings_dnd_end";
|
||||
public static final String PREF_MIBAND_SETUP_BT_PAIRING = "mi_setup_bt_pairing";
|
||||
|
||||
public static final String PREF_MI3_BAND_SCREEN_UNLOCK = "mi3_band_screen_unlock";
|
||||
public static final String PREF_MI3_NIGHT_MODE = "mi3_night_mode";
|
||||
public static final String PREF_MI3_NIGHT_MODE_START = "mi3_night_mode_start";
|
||||
public static final String PREF_MI3_NIGHT_MODE_END = "mi3_night_mode_end";
|
||||
public static final String PREF_MI3_NIGHT_MODE_OFF = "off";
|
||||
public static final String PREF_MI3_NIGHT_MODE_SUNSET = "sunset";
|
||||
public static final String PREF_MI3_NIGHT_MODE_SCHEDULED = "scheduled";
|
||||
public static final String PREF_SWIPE_UNLOCK = "swipe_unlock";
|
||||
public static final String PREF_NIGHT_MODE = "night_mode";
|
||||
public static final String PREF_NIGHT_MODE_START = "night_mode_start";
|
||||
public static final String PREF_NIGHT_MODE_END = "night_mode_end";
|
||||
public static final String PREF_NIGHT_MODE_OFF = "off";
|
||||
public static final String PREF_NIGHT_MODE_SUNSET = "sunset";
|
||||
public static final String PREF_NIGHT_MODE_SCHEDULED = "scheduled";
|
||||
|
||||
public static final String ORIGIN_INCOMING_CALL = "incoming_call";
|
||||
public static final String ORIGIN_ALARM_CLOCK = "alarm_clock";
|
||||
|
@ -22,6 +22,7 @@ import android.app.Activity;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelUuid;
|
||||
@ -36,6 +37,7 @@ import androidx.annotation.NonNull;
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
@ -235,8 +237,8 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return location;
|
||||
}
|
||||
|
||||
public static int getDeviceTimeOffsetHours() throws IllegalArgumentException {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
public static int getDeviceTimeOffsetHours(String deviceAddress) throws IllegalArgumentException {
|
||||
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
|
||||
return prefs.getInt(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, 0);
|
||||
}
|
||||
|
||||
@ -256,6 +258,14 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return isMi1S(hwVersion) || isMiPro(hwVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_lowlatency_fwupdate,
|
||||
R.xml.devicesettings_fake_timeoffset
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isMi1S(String hardwareVersion) {
|
||||
return MiBandConst.MI_1S.equals(hardwareVersion);
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ public class MiBandDateConverter {
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static GregorianCalendar rawBytesToCalendar(byte[] value) {
|
||||
public static GregorianCalendar rawBytesToCalendar(byte[] value, String deviceAddress) {
|
||||
if (value.length == 6) {
|
||||
return rawBytesToCalendar(value, 0);
|
||||
return rawBytesToCalendar(value, 0, deviceAddress);
|
||||
}
|
||||
return createCalendar();
|
||||
}
|
||||
@ -47,7 +47,7 @@ public class MiBandDateConverter {
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static GregorianCalendar rawBytesToCalendar(byte[] value, int offset) {
|
||||
public static GregorianCalendar rawBytesToCalendar(byte[] value, int offset, String deviceAddress) {
|
||||
if (value.length - offset >= 6) {
|
||||
GregorianCalendar timestamp = new GregorianCalendar(
|
||||
value[offset] + 2000,
|
||||
@ -57,7 +57,7 @@ public class MiBandDateConverter {
|
||||
value[offset + 4],
|
||||
value[offset + 5]);
|
||||
|
||||
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
|
||||
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours(deviceAddress);
|
||||
if(offsetInHours != 0)
|
||||
timestamp.add(Calendar.HOUR_OF_DAY,-offsetInHours);
|
||||
|
||||
@ -73,7 +73,7 @@ public class MiBandDateConverter {
|
||||
* @param timestamp
|
||||
* @return
|
||||
*/
|
||||
public static byte[] calendarToRawBytes(Calendar timestamp) {
|
||||
public static byte[] calendarToRawBytes(Calendar timestamp, String deviceAddress) {
|
||||
|
||||
// The mi-band device currently records sleep
|
||||
// only if it happens after 10pm and before 7am.
|
||||
@ -82,7 +82,7 @@ public class MiBandDateConverter {
|
||||
// If you usually sleep, say, from 6am to 2pm, set the
|
||||
// shift to -8, so at 6am the device thinks it's still 10pm
|
||||
// of the day before.
|
||||
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
|
||||
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours(deviceAddress);
|
||||
if(offsetInHours != 0)
|
||||
timestamp.add(Calendar.HOUR_OF_DAY,offsetInHours);
|
||||
|
||||
|
@ -23,16 +23,19 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
@ -116,7 +119,7 @@ public class MiBandPairingActivity extends AbstractGBActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_mi_band_pairing);
|
||||
|
||||
message = (TextView) findViewById(R.id.miband_pair_message);
|
||||
message = findViewById(R.id.miband_pair_message);
|
||||
Intent intent = getIntent();
|
||||
deviceCandidate = intent.getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE);
|
||||
if (deviceCandidate == null && savedInstanceState != null) {
|
||||
@ -129,6 +132,21 @@ public class MiBandPairingActivity extends AbstractGBActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||
|
||||
if (coordinator.getSupportedDeviceSpecificSettings(device) != null) { // FIXME: this will no longer be sane in the future
|
||||
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
|
||||
String authKey = sharedPrefs.getString("authkey", null);
|
||||
if (authKey == null || authKey.isEmpty()) {
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
|
||||
String randomAuthkey = RandomStringUtils.random(16, true, true);
|
||||
editor.putString("authkey", randomAuthkey);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
if (!MiBandCoordinator.hasValidUserInfo()) {
|
||||
Intent userSettingsIntent = new Intent(this, MiBandPreferencesActivity.class);
|
||||
startActivityForResult(userSettingsIntent, REQ_CODE_USER_SETTINGS, null);
|
||||
|
@ -41,11 +41,8 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_ALARM_CLOCK;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_GOAL_NOTIFICATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND;
|
||||
@ -56,7 +53,6 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PR
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS;
|
||||
@ -106,37 +102,6 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final Preference activateDisplayOnLift = findPreference(PREF_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
activateDisplayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_MI2_DO_NOT_DISTURB_OFF);
|
||||
boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_MI2_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference rotateWristCycleInfo = findPreference(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
rotateWristCycleInfo.setEnabled(!PREF_MI2_DO_NOT_DISTURB_OFF.equals(displayOnLiftState));
|
||||
rotateWristCycleInfo.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarnings = findPreference(PREF_MI2_INACTIVITY_WARNINGS);
|
||||
inactivityWarnings.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@ -236,108 +201,6 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
String doNotDisturbState = prefs.getString(MiBandConst.PREF_MI2_DO_NOT_DISTURB, PREF_MI2_DO_NOT_DISTURB_OFF);
|
||||
boolean doNotDisturbScheduled = doNotDisturbState.equals(PREF_MI2_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference doNotDisturbStart = findPreference(PREF_MI2_DO_NOT_DISTURB_START);
|
||||
doNotDisturbStart.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference doNotDisturbEnd = findPreference(PREF_MI2_DO_NOT_DISTURB_END);
|
||||
doNotDisturbEnd.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference doNotDisturb = findPreference(PREF_MI2_DO_NOT_DISTURB);
|
||||
doNotDisturb.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_MI2_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
doNotDisturbStart.setEnabled(scheduled);
|
||||
doNotDisturbEnd.setEnabled(scheduled);
|
||||
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference displayOnLiftStart = findPreference(PREF_DISPLAY_ON_LIFT_START);
|
||||
displayOnLiftStart.setEnabled(displayOnLiftScheduled);
|
||||
displayOnLiftStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISPLAY_ON_LIFT_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference displayOnLiftEnd = findPreference(PREF_DISPLAY_ON_LIFT_END);
|
||||
displayOnLiftEnd.setEnabled(displayOnLiftScheduled);
|
||||
displayOnLiftEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_DISPLAY_ON_LIFT_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference displayOnLift = findPreference(PREF_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
displayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_MI2_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
displayOnLiftStart.setEnabled(scheduled);
|
||||
displayOnLiftEnd.setEnabled(scheduled);
|
||||
rotateWristCycleInfo.setEnabled(!PREF_MI2_DO_NOT_DISTURB_OFF.equals(newVal.toString()));
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference fitnessGoal = findPreference(ActivityUser.PREF_USER_STEPS_GOAL);
|
||||
fitnessGoal.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@ -408,7 +271,6 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
prefKeys.add(PREF_MIBAND_ADDRESS);
|
||||
prefKeys.add(ActivityUser.PREF_USER_STEPS_GOAL);
|
||||
prefKeys.add(PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR);
|
||||
prefKeys.add(PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
|
||||
prefKeys.add(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
|
||||
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_ALARM_CLOCK));
|
||||
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL));
|
||||
|
@ -0,0 +1,140 @@
|
||||
/* Copyright (C) 2019 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd02;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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 MijiaLywsd02Coordinator extends AbstractDeviceCoordinator {
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null && name.equals("LYWSD02")) {
|
||||
return DeviceType.MIJIA_LYWSD02;
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.MIJIA_LYWSD02;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle(GBDevice deviceCandidate) {
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
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 boolean supportsScreenshots() {
|
||||
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 "Xiaomi";
|
||||
}
|
||||
|
||||
@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 false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||
// nothing to delete, yet
|
||||
}
|
||||
}
|
@ -1,3 +1,20 @@
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Jean-François Greffier, Vadim Kaushan
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.miscale2;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
|
@ -22,7 +22,7 @@ import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
@ -129,7 +130,7 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||
// nothing to delete, yet
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,27 @@
|
||||
/* Copyright (C) 2018-2019 Sebastian Kranz
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.widget.Toast;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class ZeTimePreferenceActivity extends AbstractSettingsActivity {
|
||||
@Override
|
||||
@ -18,14 +29,9 @@ public class ZeTimePreferenceActivity extends AbstractSettingsActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.zetime_preferences);
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
GBApplication.deviceService().onReadConfiguration("do_it");
|
||||
|
||||
//addTryListeners();
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
final Preference heartrateMeasurementInterval = findPreference(ZeTimeConstants.PREF_ZETIME_HEARTRATE_INTERVAL);
|
||||
heartrateMeasurementInterval.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2017-2019 AndrewH, Carsten Pfeiffer, Daniele Gobbetti,
|
||||
Dikay900
|
||||
Dikay900, Nick Spacek
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -23,6 +23,7 @@ import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -34,12 +35,23 @@ public class PhoneCallReceiver extends BroadcastReceiver {
|
||||
|
||||
private static int mLastState = TelephonyManager.CALL_STATE_IDLE;
|
||||
private static String mSavedNumber;
|
||||
private boolean mRestoreMutedCall = false;
|
||||
private int mLastRingerMode;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
|
||||
if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
|
||||
mSavedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
|
||||
} else if(intent.getAction().equals("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL")) {
|
||||
// Handle the mute request only if the phone is currently ringing
|
||||
if(mLastState != TelephonyManager.CALL_STATE_RINGING)
|
||||
return;
|
||||
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
mLastRingerMode = audioManager.getRingerMode();
|
||||
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
|
||||
mRestoreMutedCall = true;
|
||||
} else {
|
||||
if (intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)) {
|
||||
String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
|
||||
@ -75,6 +87,11 @@ public class PhoneCallReceiver extends BroadcastReceiver {
|
||||
} else {
|
||||
callCommand = CallSpec.CALL_END;
|
||||
}
|
||||
if(mRestoreMutedCall) {
|
||||
mRestoreMutedCall = false;
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.setRingerMode(mLastRingerMode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (callCommand != CallSpec.CALL_UNDEFINED) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
criogenic, dakhnod, Daniele Gobbetti, Frank Slezak, ivanovlev, José Rebelo,
|
||||
Julien Pivotto, Kasha, Roi Greenberg, Steffen Liebergeld
|
||||
Julien Pivotto, Kasha, Roi Greenberg, Sebastian Kranz, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.ContactsContract;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -406,7 +407,12 @@ public class GBDeviceService implements DeviceService {
|
||||
* @return contact DisplayName, if found it
|
||||
*/
|
||||
private String getContactDisplayNameByNumber(String number) {
|
||||
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
|
||||
Uri uri;
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(number));
|
||||
} else {
|
||||
uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
|
||||
}
|
||||
String name = number;
|
||||
|
||||
if (number == null || number.equals("")) {
|
||||
|
@ -116,9 +116,9 @@ public class ActivityKind {
|
||||
case TYPE_NOT_MEASURED:
|
||||
return R.drawable.ic_activity_not_measured;
|
||||
case TYPE_LIGHT_SLEEP:
|
||||
return R.drawable.ic_activity_light_sleep;
|
||||
return R.drawable.ic_activity_sleep;
|
||||
case TYPE_DEEP_SLEEP:
|
||||
return R.drawable.ic_activity_deep_sleep;
|
||||
return R.drawable.ic_activity_sleep;
|
||||
case TYPE_RUNNING:
|
||||
return R.drawable.ic_activity_running;
|
||||
case TYPE_WALKING:
|
||||
@ -128,7 +128,9 @@ public class ActivityKind {
|
||||
case TYPE_TREADMILL:
|
||||
return R.drawable.ic_activity_walking;
|
||||
case TYPE_EXERCISE: // fall through
|
||||
return R.drawable.ic_activity_exercise;
|
||||
case TYPE_SWIMMING: // fall through
|
||||
return R.drawable.ic_activity_swimming;
|
||||
case TYPE_NOT_WORN: // fall through
|
||||
case TYPE_ACTIVITY: // fall through
|
||||
case TYPE_UNKNOWN: // fall through
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
Daniele Gobbetti, Sebastian Kranz
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -40,6 +40,7 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
||||
put("com.android.email", NotificationType.GENERIC_EMAIL);
|
||||
put("ch.protonmail.android", NotificationType.GENERIC_EMAIL);
|
||||
put("security.pEp", NotificationType.GENERIC_EMAIL);
|
||||
put("eu.faircode.email", NotificationType.GENERIC_EMAIL);
|
||||
|
||||
// Generic SMS
|
||||
put("com.moez.QKSMS", NotificationType.GENERIC_SMS);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, dakhnod,
|
||||
Daniele Gobbetti, Frank Slezak, ivanovlev, JohnnySun, José Rebelo, Julien
|
||||
Pivotto, Kasha, Steffen Liebergeld
|
||||
Pivotto, Kasha, Sebastian Kranz, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti, João Paulo Barraca, José Rebelo, Kranz,
|
||||
ladbsoft, maxirnilian, protomors, Quallenauge, Sami Alaoui, tiparega,
|
||||
Vadim Kaushan
|
||||
Pfeiffer, Daniele Gobbetti, Jean-François Greffier, João Paulo Barraca,
|
||||
José Rebelo, Kranz, ladbsoft, maxirnilian, protomors, Quallenauge, Sami
|
||||
Alaoui, Sebastian Kranz, Sophanimus, tiparega, Vadim Kaushan
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -37,6 +37,8 @@ public enum DeviceType {
|
||||
AMAZFITBIP(12, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_bip),
|
||||
AMAZFITCOR(13, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_amazfit_cor),
|
||||
MIBAND3(14, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband3),
|
||||
AMAZFITCOR2(15, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_amazfit_cor2),
|
||||
MIBAND4(16, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband4),
|
||||
VIBRATISSIMO(20, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo),
|
||||
LIVEVIEW(30, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_liveview),
|
||||
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_hplus),
|
||||
@ -55,6 +57,8 @@ public enum DeviceType {
|
||||
ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3),
|
||||
CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900),
|
||||
MISCALE2(131, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_miscale2),
|
||||
BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16),
|
||||
MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02),
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||
|
||||
private final int key;
|
||||
|
@ -198,6 +198,13 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
private void handleGBDeviceEvent(GBDeviceEventCallControl callEvent) {
|
||||
Context context = getContext();
|
||||
LOG.info("Got event for CALL_CONTROL");
|
||||
if(callEvent.event == GBDeviceEventCallControl.Event.IGNORE) {
|
||||
LOG.info("Sending intent for mute");
|
||||
Intent broadcastIntent = new Intent("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL");
|
||||
broadcastIntent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(broadcastIntent);
|
||||
return;
|
||||
}
|
||||
Intent callIntent = new Intent(GBCallControlReceiver.ACTION_CALLCONTROL);
|
||||
callIntent.putExtra("event", callEvent.event.ordinal());
|
||||
callIntent.setPackage(context.getPackageName());
|
||||
|
@ -1,7 +1,8 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Avamander, Carsten Pfeiffer,
|
||||
dakhnod, Daniele Gobbetti, Daniel Hauck, Dikay900, Frank Slezak, ivanovlev,
|
||||
João Paulo Barraca, José Rebelo, Julien Pivotto, Kasha, Martin, Matthieu
|
||||
Baerts, Sergey Trofimov, Steffen Liebergeld, Taavi Eomäe, Uwe Hermann
|
||||
Baerts, Sebastian Kranz, Sergey Trofimov, Steffen Liebergeld, Taavi Eomäe,
|
||||
Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -685,6 +686,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction("android.intent.action.PHONE_STATE");
|
||||
filter.addAction("android.intent.action.NEW_OUTGOING_CALL");
|
||||
filter.addAction("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL");
|
||||
registerReceiver(mPhoneCallReceiver, filter);
|
||||
}
|
||||
if (mSMSReceiver == null) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Böhler, Andreas Shimokawa,
|
||||
Carsten Pfeiffer, Daniele Gobbetti, João Paulo Barraca, José Rebelo, Kranz,
|
||||
ladbsoft, maxirnilian, protomors, Quallenauge, Sami Alaoui, Sergey Trofimov,
|
||||
tiparega, Vadim Kaushan
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, criogenic, Daniele Gobbetti, Jean-François Greffier, João Paulo
|
||||
Barraca, José Rebelo, Kranz, ladbsoft, maxirnilian, protomors, Quallenauge,
|
||||
Sami Alaoui, Sergey Trofimov, Sophanimus, tiparega, Vadim Kaushan
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -35,11 +35,15 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2.AmazfitCor2Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd02.MijiaLywsd02Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||
@ -54,13 +58,13 @@ public class DeviceSupportFactory {
|
||||
private final BluetoothAdapter mBtAdapter;
|
||||
private final Context mContext;
|
||||
|
||||
public DeviceSupportFactory(Context context) {
|
||||
DeviceSupportFactory(Context context) {
|
||||
mContext = context;
|
||||
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
}
|
||||
|
||||
public synchronized DeviceSupport createDeviceSupport(GBDevice device) throws GBException {
|
||||
DeviceSupport deviceSupport = null;
|
||||
DeviceSupport deviceSupport;
|
||||
String deviceAddress = device.getAddress();
|
||||
int indexFirstColon = deviceAddress.indexOf(":");
|
||||
if (indexFirstColon > 0) {
|
||||
@ -126,12 +130,18 @@ public class DeviceSupportFactory {
|
||||
case MIBAND3:
|
||||
deviceSupport = new ServiceDeviceSupport(new MiBand3Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case MIBAND4:
|
||||
deviceSupport = new ServiceDeviceSupport(new MiBand4Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case AMAZFITBIP:
|
||||
deviceSupport = new ServiceDeviceSupport(new AmazfitBipSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case AMAZFITCOR:
|
||||
deviceSupport = new ServiceDeviceSupport(new AmazfitCorSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case AMAZFITCOR2:
|
||||
deviceSupport = new ServiceDeviceSupport(new AmazfitCor2Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case VIBRATISSIMO:
|
||||
deviceSupport = new ServiceDeviceSupport(new VibratissimoSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
@ -177,9 +187,14 @@ public class DeviceSupportFactory {
|
||||
case CASIOGB6900:
|
||||
deviceSupport = new ServiceDeviceSupport(new CasioGB6900DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case MISCALE2:
|
||||
case MISCALE2:
|
||||
deviceSupport = new ServiceDeviceSupport(new MiScale2DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case BFH16:
|
||||
deviceSupport = new ServiceDeviceSupport(new BFH16DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case MIJIA_LYWSD02:
|
||||
deviceSupport = new ServiceDeviceSupport(new MijiaLywsd02Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
}
|
||||
if (deviceSupport != null) {
|
||||
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Julien Pivotto, Kasha, Steffen Liebergeld
|
||||
Gobbetti, José Rebelo, Julien Pivotto, Kasha, Sebastian Kranz, Steffen
|
||||
Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, JohnnySun, José Rebelo
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti, JohnnySun, José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Andreas Boehler
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -40,22 +40,7 @@ public class BLETypeConversions {
|
||||
* @return
|
||||
* @see GattCharacteristic#UUID_CHARACTERISTIC_CURRENT_TIME
|
||||
*/
|
||||
public static byte[] calendarToRawBytes(Calendar timestamp, boolean honorDeviceTimeOffset) {
|
||||
|
||||
// The mi-band device currently records sleep
|
||||
// only if it happens after 10pm and before 7am.
|
||||
// The offset is used to trick the device to record sleep
|
||||
// in non-standard hours.
|
||||
// If you usually sleep, say, from 6am to 2pm, set the
|
||||
// shift to -8, so at 6am the device thinks it's still 10pm
|
||||
// of the day before.
|
||||
if (honorDeviceTimeOffset) {
|
||||
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
|
||||
if (offsetInHours != 0) {
|
||||
timestamp.add(Calendar.HOUR_OF_DAY, offsetInHours);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] calendarToRawBytes(Calendar timestamp) {
|
||||
// MiBand2:
|
||||
// year,year,month,dayofmonth,hour,minute,second,dayofweek,0,0,tz
|
||||
|
||||
@ -78,25 +63,9 @@ public class BLETypeConversions {
|
||||
/**
|
||||
* Similar to calendarToRawBytes, but only up to (and including) the MINUTES.
|
||||
* @param timestamp
|
||||
* @param honorDeviceTimeOffset
|
||||
* @return
|
||||
*/
|
||||
public static byte[] shortCalendarToRawBytes(Calendar timestamp, boolean honorDeviceTimeOffset) {
|
||||
|
||||
// The mi-band device currently records sleep
|
||||
// only if it happens after 10pm and before 7am.
|
||||
// The offset is used to trick the device to record sleep
|
||||
// in non-standard hours.
|
||||
// If you usually sleep, say, from 6am to 2pm, set the
|
||||
// shift to -8, so at 6am the device thinks it's still 10pm
|
||||
// of the day before.
|
||||
if (honorDeviceTimeOffset) {
|
||||
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
|
||||
if (offsetInHours != 0) {
|
||||
timestamp.add(Calendar.HOUR_OF_DAY, offsetInHours);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] shortCalendarToRawBytes(Calendar timestamp) {
|
||||
// MiBand2:
|
||||
// year,year,month,dayofmonth,hour,minute
|
||||
|
||||
@ -136,7 +105,7 @@ public class BLETypeConversions {
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static GregorianCalendar rawBytesToCalendar(byte[] value, boolean honorDeviceTimeOffset) {
|
||||
public static GregorianCalendar rawBytesToCalendar(byte[] value) {
|
||||
if (value.length >= 7) {
|
||||
int year = toUint16(value[0], value[1]);
|
||||
GregorianCalendar timestamp = new GregorianCalendar(
|
||||
@ -153,14 +122,6 @@ public class BLETypeConversions {
|
||||
timeZone.setRawOffset(value[7] * 15 * 60 * 1000);
|
||||
timestamp.setTimeZone(timeZone);
|
||||
}
|
||||
|
||||
if (honorDeviceTimeOffset) {
|
||||
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
|
||||
if (offsetInHours != 0) {
|
||||
timestamp.add(Calendar.HOUR_OF_DAY,-offsetInHours);
|
||||
}
|
||||
}
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Sergey Trofimov, Uwe Hermann
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti, Sergey Trofimov, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Uwe Hermann
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,3 +1,19 @@
|
||||
/* Copyright (C) 2019 Andreas Böhler
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2019 Andreas Böhler
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Uwe Hermann
|
||||
/* Copyright (C) 2019 Andreas Böhler
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,228 +0,0 @@
|
||||
/* Copyright (C) 2018-2019 Andreas Böhler, Daniele Gobbetti
|
||||
based on code from BlueWatcher, https://github.com/masterjc/bluewatcher
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattServer;
|
||||
import android.bluetooth.BluetoothGattServerCallback;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||
|
||||
class CasioGATTServer extends BluetoothGattServerCallback {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CasioGATTServer.class);
|
||||
|
||||
private Context mContext;
|
||||
private BluetoothGattServer mBluetoothGattServer;
|
||||
private CasioGB6900DeviceSupport mDeviceSupport = null;
|
||||
private final GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl();
|
||||
|
||||
CasioGATTServer(Context context, CasioGB6900DeviceSupport deviceSupport) {
|
||||
mContext = context;
|
||||
mDeviceSupport = deviceSupport;
|
||||
}
|
||||
|
||||
public void setContext(Context ctx) {
|
||||
mContext = ctx;
|
||||
}
|
||||
|
||||
boolean initialize() {
|
||||
if(mContext == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (bluetoothManager == null) {
|
||||
return false;
|
||||
}
|
||||
mBluetoothGattServer = bluetoothManager.openGattServer(mContext, this);
|
||||
if (mBluetoothGattServer == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BluetoothGattService casioGATTService = new BluetoothGattService(CasioGB6900Constants.WATCH_CTRL_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
|
||||
BluetoothGattCharacteristic bluetoothgGATTCharacteristic = new BluetoothGattCharacteristic(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE);
|
||||
bluetoothgGATTCharacteristic.setValue(new byte[0]);
|
||||
|
||||
BluetoothGattCharacteristic bluetoothgGATTCharacteristic2 = new BluetoothGattCharacteristic(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
|
||||
bluetoothgGATTCharacteristic2.setValue(CasioGB6900Constants.MUSIC_MESSAGE.getBytes());
|
||||
|
||||
BluetoothGattDescriptor bluetoothGattDescriptor = new BluetoothGattDescriptor(CasioGB6900Constants.CCC_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
|
||||
bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||
|
||||
bluetoothgGATTCharacteristic2.addDescriptor(bluetoothGattDescriptor);
|
||||
|
||||
casioGATTService.addCharacteristic(bluetoothgGATTCharacteristic);
|
||||
casioGATTService.addCharacteristic(bluetoothgGATTCharacteristic2);
|
||||
mBluetoothGattServer.addService(casioGATTService);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
|
||||
|
||||
if (!characteristic.getUuid().equals(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID)) {
|
||||
LOG.warn("unexpected read request");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("will send response to read request from device: " + device.getAddress());
|
||||
|
||||
if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, CasioGB6900Constants.MUSIC_MESSAGE.getBytes())) {
|
||||
LOG.warn("error sending response");
|
||||
}
|
||||
}
|
||||
private GBDeviceEventMusicControl.Event parse3Button(int button) {
|
||||
GBDeviceEventMusicControl.Event event;
|
||||
switch(button) {
|
||||
case 3:
|
||||
event = GBDeviceEventMusicControl.Event.NEXT;
|
||||
break;
|
||||
case 2:
|
||||
event = GBDeviceEventMusicControl.Event.PREVIOUS;
|
||||
break;
|
||||
case 1:
|
||||
event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unhandled button received: " + button);
|
||||
event = GBDeviceEventMusicControl.Event.UNKNOWN;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
private GBDeviceEventMusicControl.Event parse2Button(int button) {
|
||||
GBDeviceEventMusicControl.Event event;
|
||||
switch(button) {
|
||||
case 2:
|
||||
event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
|
||||
break;
|
||||
case 1:
|
||||
event = GBDeviceEventMusicControl.Event.NEXT;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unhandled button received: " + button);
|
||||
event = GBDeviceEventMusicControl.Event.UNKNOWN;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
|
||||
boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
||||
|
||||
if (!characteristic.getUuid().equals(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID)) {
|
||||
LOG.warn("unexpected write request");
|
||||
return;
|
||||
}
|
||||
|
||||
if(mDeviceSupport == null) {
|
||||
LOG.warn("mDeviceSupport is null, did initialization complete?");
|
||||
return;
|
||||
}
|
||||
|
||||
if((value[0] & 0x03) == 0) {
|
||||
int button = value[1] & 0x0f;
|
||||
LOG.info("Button pressed: " + button);
|
||||
switch(mDeviceSupport.getModel())
|
||||
{
|
||||
case MODEL_CASIO_5600B:
|
||||
musicCmd.event = parse2Button(button);
|
||||
break;
|
||||
case MODEL_CASIO_6900B:
|
||||
musicCmd.event = parse3Button(button);
|
||||
break;
|
||||
case MODEL_CASIO_GENERIC:
|
||||
musicCmd.event = parse3Button(button);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unhandled device");
|
||||
return;
|
||||
}
|
||||
mDeviceSupport.evaluateGBDeviceEvent(musicCmd);
|
||||
mDeviceSupport.evaluateGBDeviceEvent(musicCmd);
|
||||
}
|
||||
else {
|
||||
LOG.info("received from device: " + value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
|
||||
|
||||
LOG.info("Connection state change for device: " + device.getAddress() + " status = " + status + " newState = " + newState);
|
||||
if (newState == BluetoothGattServer.STATE_DISCONNECTED) {
|
||||
LOG.info("CASIO GATT server noticed disconnect.");
|
||||
}
|
||||
if (newState == BluetoothGattServer.STATE_CONNECTED) {
|
||||
GBDevice.State devState = mDeviceSupport.getDevice().getState();
|
||||
Intent deviceCommunicationServiceIntent = new Intent(mContext, DeviceCommunicationService.class);
|
||||
if (devState.equals(GBDevice.State.WAITING_FOR_RECONNECT) || devState.equals(GBDevice.State.NOT_CONNECTED)) {
|
||||
LOG.info("Forcing re-connect because GATT server has been reconnected.");
|
||||
deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT);
|
||||
deviceCommunicationServiceIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
LocalBroadcastManager.getInstance(mContext).sendBroadcast(deviceCommunicationServiceIntent);
|
||||
//PendingIntent reconnectPendingIntent = PendingIntent.getService(mContext, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
//builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor,
|
||||
boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
||||
|
||||
LOG.info("onDescriptorWriteRequest() notifications enabled = " + (value[0] == 1));
|
||||
if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value)) {
|
||||
LOG.warn("onDescriptorWriteRequest() error sending response!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceAdded(int status, BluetoothGattService service) {
|
||||
LOG.info("onServiceAdded() status = " + status + " service = " + service.getUuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationSent(BluetoothDevice bluetoothDevice, int status) {
|
||||
LOG.info("onNotificationSent() status = " + status + " to device " + bluetoothDevice.getAddress());
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (mBluetoothGattServer != null) {
|
||||
mBluetoothGattServer.clearServices();
|
||||
mBluetoothGattServer.close();
|
||||
mBluetoothGattServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/* Copyright (C) 2018-2019 Andreas Böhler
|
||||
based on code from BlueWatcher, https://github.com/masterjc/bluewatcher
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900;
|
||||
import android.content.Context;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CasioGATTThread extends Thread {
|
||||
CasioGATTServer mServer = null;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CasioGATTThread.class);
|
||||
private boolean mStopFlag = false;
|
||||
private final Object waitObject = new Object();
|
||||
|
||||
public CasioGATTThread(Context context, CasioGB6900DeviceSupport deviceSupport)
|
||||
{
|
||||
mServer = new CasioGATTServer(context, deviceSupport);
|
||||
}
|
||||
|
||||
public void setContext(Context ctx) {
|
||||
mServer.setContext(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!mServer.initialize()) {
|
||||
LOG.error("Error initializing CasioGATTServer. Has the context been set?");
|
||||
return;
|
||||
}
|
||||
|
||||
long waitTime = 60 * 1000;
|
||||
|
||||
while (!mStopFlag) {
|
||||
synchronized (waitObject) {
|
||||
try {
|
||||
waitObject.wait(waitTime);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (mStopFlag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mServer.close();
|
||||
}
|
||||
|
||||
public void quit() {
|
||||
mStopFlag = true;
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2018-2019 Andreas Böhler
|
||||
/* Copyright (C) 2018-2019 Andreas Böhler, Sebastian Kranz
|
||||
based on code from BlueWatcher, https://github.com/masterjc/bluewatcher
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -17,12 +17,13 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -34,7 +35,9 @@ import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
@ -48,22 +51,24 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.operations.InitOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CasioGB6900DeviceSupport.class);
|
||||
|
||||
private ArrayList<BluetoothGattCharacteristic> mCasioCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
|
||||
private CasioGATTThread mThread;
|
||||
private CasioHandlerThread mHandlerThread = null;
|
||||
private MusicSpec mBufferMusicSpec = null;
|
||||
private MusicStateSpec mBufferMusicStateSpec = null;
|
||||
private BluetoothGatt mBtGatt = null;
|
||||
private CasioGB6900Constants.Model mModel = CasioGB6900Constants.Model.MODEL_CASIO_GENERIC;
|
||||
private byte[] mBleSettings = null;
|
||||
private boolean mFirstConnect = false;
|
||||
|
||||
private static final int mCasioSleepTime = 80;
|
||||
private static final int mCasioSleepTime = 50;
|
||||
|
||||
public CasioGB6900DeviceSupport() {
|
||||
super(LOG);
|
||||
@ -79,14 +84,30 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
addSupportedService(CasioGB6900Constants.TX_POWER_SERVICE_UUID);
|
||||
addSupportedService(CasioGB6900Constants.LINK_LOSS_SERVICE);
|
||||
addSupportedService(CasioGB6900Constants.IMMEDIATE_ALERT_SERVICE_UUID);
|
||||
mThread = new CasioGATTThread(getContext(), this);
|
||||
|
||||
BluetoothGattService casioGATTService = new BluetoothGattService(CasioGB6900Constants.WATCH_CTRL_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
|
||||
BluetoothGattCharacteristic bluetoothGATTCharacteristic = new BluetoothGattCharacteristic(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE);
|
||||
bluetoothGATTCharacteristic.setValue(new byte[0]);
|
||||
|
||||
BluetoothGattCharacteristic bluetoothGATTCharacteristic2 = new BluetoothGattCharacteristic(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
|
||||
bluetoothGATTCharacteristic2.setValue(CasioGB6900Constants.MUSIC_MESSAGE.getBytes());
|
||||
|
||||
BluetoothGattDescriptor bluetoothGattDescriptor = new BluetoothGattDescriptor(CasioGB6900Constants.CCC_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
|
||||
bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||
|
||||
bluetoothGATTCharacteristic2.addDescriptor(bluetoothGattDescriptor);
|
||||
|
||||
casioGATTService.addCharacteristic(bluetoothGATTCharacteristic);
|
||||
casioGATTService.addCharacteristic(bluetoothGATTCharacteristic2);
|
||||
|
||||
addSupportedServerService(casioGATTService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
|
||||
super.setContext(gbDevice, btAdapter, context);
|
||||
mThread.setContext(context);
|
||||
mThread.start();
|
||||
public boolean connectFirstTime() {
|
||||
GB.toast(getContext(), "After first connect, disable and enable bluetooth on your Casio watch to really connect", Toast.LENGTH_SHORT, GB.INFO);
|
||||
mFirstConnect = true;
|
||||
return super.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -103,12 +124,6 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
mHandlerThread.interrupt();
|
||||
mHandlerThread = null;
|
||||
}
|
||||
|
||||
if(mThread != null) {
|
||||
mThread.quit();
|
||||
mThread.interrupt();
|
||||
mThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,6 +136,14 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
LOG.info("Initializing");
|
||||
|
||||
if(mFirstConnect) {
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
return builder;
|
||||
}
|
||||
|
||||
String name = gbDevice.getName();
|
||||
|
||||
if(name.contains("5600B")) {
|
||||
@ -131,17 +154,27 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
mModel = CasioGB6900Constants.Model.MODEL_CASIO_GENERIC;
|
||||
}
|
||||
|
||||
try {
|
||||
new InitOperation(this, builder).perform();
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Initializing Casio watch failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||
}
|
||||
/*
|
||||
gbDevice.setState(GBDevice.State.INITIALIZING);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
*/
|
||||
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
|
||||
addCharacteristics();
|
||||
|
||||
builder.setGattCallback(this);
|
||||
|
||||
enableNotifications(builder, true);
|
||||
|
||||
configureWatch(builder);
|
||||
|
||||
addCharacteristics();
|
||||
enableNotifications(builder, true);
|
||||
|
||||
LOG.info("Initialization Done");
|
||||
|
||||
return builder;
|
||||
@ -251,52 +284,22 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void readBleSettings() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("readBleSettings");
|
||||
builder.read(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID));
|
||||
builder.queue(getQueue());
|
||||
} catch(IOException e) {
|
||||
LOG.error("Error reading BLE settings: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void configureBleSettings() {
|
||||
// These values seem to improve connection stability _on my phone_
|
||||
// Maybe they should be configurable?
|
||||
int slaveLatency = 2;
|
||||
int connInterval = 300;
|
||||
|
||||
mBleSettings[5] = (byte)(connInterval & 0xff);
|
||||
mBleSettings[6] = (byte)((connInterval >> 8) & 0xff);
|
||||
mBleSettings[7] = (byte)(slaveLatency & 0xff);
|
||||
mBleSettings[8] = (byte)((slaveLatency >> 8) & 0xff);
|
||||
|
||||
mBleSettings[9] = 0; // Setting for Disconnect!?
|
||||
}
|
||||
|
||||
private void writeBleSettings() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("writeBleSettings");
|
||||
builder.write(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID), mBleSettings);
|
||||
builder.queue(getQueue());
|
||||
} catch(IOException e) {
|
||||
LOG.error("Error writing BLE settings: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
private boolean handleInitResponse(byte data) {
|
||||
boolean handled = false;
|
||||
switch(data)
|
||||
{
|
||||
case (byte) 1:
|
||||
LOG.info("Initialization done, setting state to INITIALIZED");
|
||||
if(mHandlerThread == null) {
|
||||
mHandlerThread = new CasioHandlerThread(getDevice(), getContext(), this);
|
||||
if(mHandlerThread != null) {
|
||||
if(mHandlerThread.isAlive()) {
|
||||
mHandlerThread.quit();
|
||||
mHandlerThread.interrupt();
|
||||
}
|
||||
}
|
||||
mHandlerThread = new CasioHandlerThread(getDevice(), getContext(), this);
|
||||
mHandlerThread.start();
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
readBleSettings();
|
||||
handled = true;
|
||||
break;
|
||||
default:
|
||||
@ -351,7 +354,7 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleCasioCom(byte[] data) {
|
||||
private boolean handleCasioCom(byte[] data, boolean handleTime) {
|
||||
boolean handled = false;
|
||||
|
||||
if(data.length < 3) {
|
||||
@ -365,7 +368,11 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
handled = handleInitResponse(data[2]);
|
||||
break;
|
||||
case 2:
|
||||
handled = handleTimeRequests(data[2]);
|
||||
if(handleTime) {
|
||||
handled = handleTimeRequests(data[2]);
|
||||
} else {
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
handled = handleServerFeatureRequests(data[2]);
|
||||
@ -376,7 +383,7 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
byte[] data = characteristic.getValue();
|
||||
@ -391,63 +398,6 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
LOG.info(str);
|
||||
}
|
||||
else if(characteristicUUID.equals(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID)) {
|
||||
mBleSettings = data;
|
||||
String str = "Read Casio Setting for BLE: ";
|
||||
for(int i=0; i<data.length; i++) {
|
||||
str += String.format("0x%1x ", data[i]);
|
||||
}
|
||||
/* Definition of parameters - for future reference */
|
||||
|
||||
// data[0]; // BLE alert for call, mail and other
|
||||
// data[1]; // BLE alert for Calendar
|
||||
// data[2]; // BLE alert for SNS
|
||||
// data[3]; // BLE alert for vibration and alarm
|
||||
// data[4]; // BLE alert for animation
|
||||
// data[5]; // Connection Interval
|
||||
// data[6]; // Connection Interval
|
||||
// data[7]; // Slave Latency
|
||||
// data[8]; // Slave Latency
|
||||
|
||||
// Alert definitions:
|
||||
// 0 = Off
|
||||
// 1 = Sound
|
||||
// 2 = Vibration
|
||||
// 3 = Sound and Vibration
|
||||
//int callAlert = (data[0] >> 6) & 0x03;
|
||||
//LOG.info("Call Alert: " + callAlert);
|
||||
//int mailAlert = (data[0] >> 2) & 0x03;
|
||||
//LOG.info("Mail Alert: " + mailAlert);
|
||||
//int snsAlert = (data[2] >> 4) & 0x03;
|
||||
//LOG.info("SNS Alert: " + snsAlert);
|
||||
//int calAlert = (data[1] >> 6) & 0x03;
|
||||
//LOG.info("Calendart Alert: " + calAlert);
|
||||
//int otherAlert = (data[0] & 0x03);
|
||||
//LOG.info("Other Alert: " + otherAlert);
|
||||
//int vibrationValue = (data[3] & 0x0f);
|
||||
//LOG.info("Vibration Value: " + vibrationValue);
|
||||
//int alarmValue = (data[3] >> 4) & 0x0f;
|
||||
// Vibration pattern; A = 0, B = 1, C = 2
|
||||
//LOG.info("Alarm Value: " + alarmValue);
|
||||
//int animationValue = data[4] & 0x40;
|
||||
// Length of Alarm, only 2, 5 and 10 possible
|
||||
//LOG.info("Animation Value: " + animationValue);
|
||||
// 0 = on
|
||||
// 64 = off
|
||||
//int useDisableMtuReqBit = data[4] & 0x08;
|
||||
// 8 = on
|
||||
// 0 = off!?
|
||||
//LOG.info("useDisableMtuReqBit: " + useDisableMtuReqBit);
|
||||
|
||||
//int slaveLatency = ((data[7] & 0xff) | ((data[8] & 0xff) << 8));
|
||||
//int connInterval = ((data[5] & 0xff) | ((data[6] & 0xff) << 8));
|
||||
//LOG.info("Slave Latency: " + slaveLatency);
|
||||
//LOG.info("Connection Interval: " + connInterval);
|
||||
//LOG.info(str);
|
||||
|
||||
configureBleSettings();
|
||||
writeBleSettings();
|
||||
}
|
||||
else {
|
||||
return super.onCharacteristicRead(gatt, characteristic, status);
|
||||
}
|
||||
@ -466,11 +416,11 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
return true;
|
||||
|
||||
if(characteristicUUID.equals(CasioGB6900Constants.CASIO_A_NOT_W_REQ_NOT)) {
|
||||
handled = handleCasioCom(data);
|
||||
handled = handleCasioCom(data, true);
|
||||
}
|
||||
|
||||
if(characteristicUUID.equals(CasioGB6900Constants.CASIO_A_NOT_COM_SET_NOT)) {
|
||||
handled = handleCasioCom(data);
|
||||
handled = handleCasioCom(data, false);
|
||||
}
|
||||
|
||||
if(characteristicUUID.equals(CasioGB6900Constants.ALERT_LEVEL_CHARACTERISTIC_UUID)) {
|
||||
@ -481,14 +431,16 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
else {
|
||||
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
|
||||
}
|
||||
evaluateGBDeviceEvent(findPhoneEvent);
|
||||
evaluateGBDeviceEvent(findPhoneEvent);
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if(characteristicUUID.equals(CasioGB6900Constants.RINGER_CONTROL_POINT)) {
|
||||
if(data[0] == 0x02)
|
||||
{
|
||||
LOG.info("Mute/ignore call event not yet supported by GB");
|
||||
GBDeviceEventCallControl callControlEvent = new GBDeviceEventCallControl();
|
||||
callControlEvent.event = GBDeviceEventCallControl.Event.IGNORE;
|
||||
evaluateGBDeviceEvent(callControlEvent);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
@ -501,6 +453,8 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private void showNotification(byte icon, String title, String message) {
|
||||
if(!isConnected())
|
||||
return;
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("showNotification");
|
||||
int len;
|
||||
@ -558,6 +512,10 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
int alarmOffset = 4;
|
||||
byte[] data = new byte[20];
|
||||
|
||||
if(!isConnected())
|
||||
return;
|
||||
|
||||
for(int i=0; i<alarms.size(); i++)
|
||||
{
|
||||
Alarm alm = alarms.get(i);
|
||||
@ -584,6 +542,9 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
if(!isConnected())
|
||||
return;
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("SetTime");
|
||||
writeCasioLocalTimeInformation(builder);
|
||||
@ -622,6 +583,9 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private void sendMusicInfo()
|
||||
{
|
||||
if(!isConnected())
|
||||
return;
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("sendMusicInfo");
|
||||
String info = "";
|
||||
@ -716,6 +680,9 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
if(!isConnected())
|
||||
return;
|
||||
|
||||
if(start) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("findDevice");
|
||||
@ -781,4 +748,95 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
|
||||
|
||||
if (!characteristic.getUuid().equals(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID)) {
|
||||
LOG.warn("unexpected read request");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG.info("will send response to read request from device: " + device.getAddress());
|
||||
|
||||
try {
|
||||
ServerTransactionBuilder builder = performServer("sendNameOfApp");
|
||||
builder.writeServerResponse(device, requestId, 0, offset, CasioGB6900Constants.MUSIC_MESSAGE.getBytes());
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("sendMusicInfo failed: " + e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private GBDeviceEventMusicControl.Event parse3Button(int button) {
|
||||
GBDeviceEventMusicControl.Event event;
|
||||
switch(button) {
|
||||
case 3:
|
||||
event = GBDeviceEventMusicControl.Event.NEXT;
|
||||
break;
|
||||
case 2:
|
||||
event = GBDeviceEventMusicControl.Event.PREVIOUS;
|
||||
break;
|
||||
case 1:
|
||||
event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unhandled button received: " + button);
|
||||
event = GBDeviceEventMusicControl.Event.UNKNOWN;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
private GBDeviceEventMusicControl.Event parse2Button(int button) {
|
||||
GBDeviceEventMusicControl.Event event;
|
||||
switch(button) {
|
||||
case 2:
|
||||
event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
|
||||
break;
|
||||
case 1:
|
||||
event = GBDeviceEventMusicControl.Event.NEXT;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unhandled button received: " + button);
|
||||
event = GBDeviceEventMusicControl.Event.UNKNOWN;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
|
||||
boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
||||
|
||||
GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl();
|
||||
if (!characteristic.getUuid().equals(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID)) {
|
||||
LOG.warn("unexpected write request");
|
||||
return false;
|
||||
}
|
||||
|
||||
if((value[0] & 0x03) == 0) {
|
||||
int button = value[1] & 0x0f;
|
||||
LOG.info("Button pressed: " + button);
|
||||
switch(getModel())
|
||||
{
|
||||
case MODEL_CASIO_5600B:
|
||||
musicCmd.event = parse2Button(button);
|
||||
break;
|
||||
case MODEL_CASIO_6900B:
|
||||
musicCmd.event = parse3Button(button);
|
||||
break;
|
||||
case MODEL_CASIO_GENERIC:
|
||||
musicCmd.event = parse3Button(button);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unhandled device");
|
||||
return false;
|
||||
}
|
||||
evaluateGBDeviceEvent(musicCmd);
|
||||
}
|
||||
else {
|
||||
LOG.info("received from device: " + value.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ public class CasioHandlerThread extends GBDeviceIoThread {
|
||||
private boolean mQuit = false;
|
||||
private CasioGB6900DeviceSupport mDeviceSupport;
|
||||
private final Object waitObject = new Object();
|
||||
//private CasioGATTServer mServer = null;
|
||||
|
||||
private int TX_PERIOD = 60;
|
||||
|
||||
@ -43,7 +42,6 @@ public class CasioHandlerThread extends GBDeviceIoThread {
|
||||
super(gbDevice, context);
|
||||
LOG.info("Initializing Casio Handler Thread");
|
||||
mQuit = false;
|
||||
//mServer = new CasioGATTServer(context, deviceSupport);
|
||||
mDeviceSupport = deviceSupport;
|
||||
}
|
||||
|
||||
@ -51,13 +49,6 @@ public class CasioHandlerThread extends GBDeviceIoThread {
|
||||
public void run() {
|
||||
mQuit = false;
|
||||
|
||||
/*
|
||||
if(!mServer.initialize()) {
|
||||
LOG.error("Error initializing CasioGATTServer. Has the context been set?");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
long waitTime = TX_PERIOD * 1000;
|
||||
while (!mQuit) {
|
||||
|
||||
|
@ -0,0 +1,168 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Böhler
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.operations;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.CasioGB6900DeviceSupport;
|
||||
|
||||
public class InitOperation extends AbstractBTLEOperation<CasioGB6900DeviceSupport> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
|
||||
|
||||
private final TransactionBuilder builder;
|
||||
private byte[] mBleSettings = null;
|
||||
|
||||
|
||||
public InitOperation(CasioGB6900DeviceSupport support, TransactionBuilder builder) {
|
||||
super(support);
|
||||
this.builder = builder;
|
||||
builder.setGattCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
TransactionBuilder builder = getSupport().createTransactionBuilder("readBleSettings");
|
||||
builder.setGattCallback(this);
|
||||
builder.read(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID));
|
||||
getSupport().performImmediately(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionBuilder performInitialized(String taskName) throws IOException {
|
||||
throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||
return super.onCharacteristicChanged(gatt, characteristic);
|
||||
}
|
||||
|
||||
private void configureBleSettings() {
|
||||
// These values seem to improve connection stability _on my phone_
|
||||
// Maybe they should be configurable?
|
||||
int slaveLatency = 2;
|
||||
int connInterval = 300;
|
||||
|
||||
mBleSettings[5] = (byte)(connInterval & 0xff);
|
||||
mBleSettings[6] = (byte)((connInterval >> 8) & 0xff);
|
||||
mBleSettings[7] = (byte)(slaveLatency & 0xff);
|
||||
mBleSettings[8] = (byte)((slaveLatency >> 8) & 0xff);
|
||||
|
||||
mBleSettings[9] = 0; // Setting for Disconnect!?
|
||||
}
|
||||
|
||||
private void writeBleSettings() {
|
||||
try {
|
||||
TransactionBuilder builder = getSupport().createTransactionBuilder("writeBleInit");
|
||||
builder.setGattCallback(this);
|
||||
builder.write(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID), mBleSettings);
|
||||
getSupport().performImmediately(builder);
|
||||
} catch(IOException e) {
|
||||
LOG.error("Error writing BLE settings: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
byte[] data = characteristic.getValue();
|
||||
|
||||
if(data.length == 0)
|
||||
return true;
|
||||
|
||||
if(characteristicUUID.equals(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID)) {
|
||||
mBleSettings = data;
|
||||
String str = "Read Casio Setting for BLE: ";
|
||||
for(int i=0; i<data.length; i++) {
|
||||
str += String.format("0x%1x ", data[i]);
|
||||
}
|
||||
/* Definition of parameters - for future reference */
|
||||
|
||||
// data[0]; // BLE alert for call, mail and other
|
||||
// data[1]; // BLE alert for Calendar
|
||||
// data[2]; // BLE alert for SNS
|
||||
// data[3]; // BLE alert for vibration and alarm
|
||||
// data[4]; // BLE alert for animation
|
||||
// data[5]; // Connection Interval
|
||||
// data[6]; // Connection Interval
|
||||
// data[7]; // Slave Latency
|
||||
// data[8]; // Slave Latency
|
||||
|
||||
// Alert definitions:
|
||||
// 0 = Off
|
||||
// 1 = Sound
|
||||
// 2 = Vibration
|
||||
// 3 = Sound and Vibration
|
||||
//int callAlert = (data[0] >> 6) & 0x03;
|
||||
//LOG.info("Call Alert: " + callAlert);
|
||||
//int mailAlert = (data[0] >> 2) & 0x03;
|
||||
//LOG.info("Mail Alert: " + mailAlert);
|
||||
//int snsAlert = (data[2] >> 4) & 0x03;
|
||||
//LOG.info("SNS Alert: " + snsAlert);
|
||||
//int calAlert = (data[1] >> 6) & 0x03;
|
||||
//LOG.info("Calendart Alert: " + calAlert);
|
||||
//int otherAlert = (data[0] & 0x03);
|
||||
//LOG.info("Other Alert: " + otherAlert);
|
||||
//int vibrationValue = (data[3] & 0x0f);
|
||||
//LOG.info("Vibration Value: " + vibrationValue);
|
||||
//int alarmValue = (data[3] >> 4) & 0x0f;
|
||||
// Vibration pattern; A = 0, B = 1, C = 2
|
||||
//LOG.info("Alarm Value: " + alarmValue);
|
||||
//int animationValue = data[4] & 0x40;
|
||||
// Length of Alarm, only 2, 5 and 10 possible
|
||||
//LOG.info("Animation Value: " + animationValue);
|
||||
// 0 = on
|
||||
// 64 = off
|
||||
//int useDisableMtuReqBit = data[4] & 0x08;
|
||||
// 8 = on
|
||||
// 0 = off!?
|
||||
//LOG.info("useDisableMtuReqBit: " + useDisableMtuReqBit);
|
||||
|
||||
//int slaveLatency = ((data[7] & 0xff) | ((data[8] & 0xff) << 8));
|
||||
//int connInterval = ((data[5] & 0xff) | ((data[6] & 0xff) << 8));
|
||||
//LOG.info("Slave Latency: " + slaveLatency);
|
||||
//LOG.info("Connection Interval: " + connInterval);
|
||||
//LOG.info(str);
|
||||
|
||||
configureBleSettings();
|
||||
writeBleSettings();
|
||||
}
|
||||
else {
|
||||
return super.onCharacteristicRead(gatt, characteristic, status);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2019 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
ivanovlev, João Paulo Barraca, Pavel Motyrev, Quallenauge
|
||||
ivanovlev, João Paulo Barraca, Pavel Motyrev, Quallenauge, Sebastian Kranz
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -33,43 +33,35 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class ActivityDetailsParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityDetailsParser.class);
|
||||
public class HuamiActivityDetailsParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuamiActivityDetailsParser.class);
|
||||
|
||||
private static final byte TYPE_GPS = 0;
|
||||
private static final byte TYPE_HR = 1;
|
||||
private static final byte TYPE_UNKNOWN2 = 2;
|
||||
private static final byte TYPE_PAUSE = 3;
|
||||
private static final byte TYPE_PAUSE = 2;
|
||||
private static final byte TYPE_RESUME = 3;
|
||||
private static final byte TYPE_SPEED4 = 4;
|
||||
private static final byte TYPE_SPEED5 = 5;
|
||||
private static final byte TYPE_GPS_SPEED6 = 6;
|
||||
private static final byte TYPE_SPEED6 = 6;
|
||||
private static final byte TYPE_SWIMMING = 8;
|
||||
|
||||
public static final BigDecimal HUAMI_TO_DECIMAL_DEGREES_DIVISOR = new BigDecimal(3000000.0);
|
||||
private final BaseActivitySummary summary;
|
||||
private static final BigDecimal HUAMI_TO_DECIMAL_DEGREES_DIVISOR = new BigDecimal(3000000.0);
|
||||
private final ActivityTrack activityTrack;
|
||||
// private final int version;
|
||||
private final Date baseDate;
|
||||
private long baseLongitude;
|
||||
private long baseLatitude;
|
||||
private int baseAltitude;
|
||||
private ActivityPoint lastActivityPoint;
|
||||
|
||||
public boolean getSkipCounterByte() {
|
||||
return skipCounterByte;
|
||||
}
|
||||
|
||||
public void setSkipCounterByte(boolean skipCounterByte) {
|
||||
this.skipCounterByte = skipCounterByte;
|
||||
}
|
||||
|
||||
private boolean skipCounterByte;
|
||||
|
||||
public ActivityDetailsParser(BaseActivitySummary summary) {
|
||||
this.summary = summary;
|
||||
// this.version = version;
|
||||
// this.baseDate = baseDate;
|
||||
//
|
||||
public HuamiActivityDetailsParser(BaseActivitySummary summary) {
|
||||
this.baseLongitude = summary.getBaseLongitude();
|
||||
this.baseLatitude = summary.getBaseLatitude();
|
||||
this.baseAltitude = summary.getBaseAltitude();
|
||||
@ -109,21 +101,24 @@ public class ActivityDetailsParser {
|
||||
case TYPE_HR:
|
||||
i += consumeHeartRate(bytes, i, totalTimeOffset);
|
||||
break;
|
||||
case TYPE_UNKNOWN2:
|
||||
i += consumeUnknown2(bytes, i);
|
||||
break;
|
||||
case TYPE_PAUSE:
|
||||
i += consumePause(bytes, i);
|
||||
break;
|
||||
case TYPE_RESUME:
|
||||
i += consumeResume(bytes, i);
|
||||
break;
|
||||
case TYPE_SPEED4:
|
||||
i += consumeSpeed4(bytes, i);
|
||||
break;
|
||||
case TYPE_SPEED5:
|
||||
i += consumeSpeed5(bytes, i);
|
||||
break;
|
||||
case TYPE_GPS_SPEED6:
|
||||
case TYPE_SPEED6:
|
||||
i += consumeSpeed6(bytes, i);
|
||||
break;
|
||||
case TYPE_SWIMMING:
|
||||
i += consumeSwimming(bytes, i);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("unknown packet type" + type);
|
||||
i+=6;
|
||||
@ -213,7 +208,6 @@ public class ActivityDetailsParser {
|
||||
|
||||
if (v2 == 0 && v3 == 0 && v4 == 0 && v5 == 0 && v6 == 0) {
|
||||
// new version
|
||||
// LOG.info("detected heart rate in 'new' version, where version is: " + summary.getVersion());
|
||||
LOG.info("detected heart rate in 'new' version format");
|
||||
ActivityPoint ap = getActivityPointFor(timeOffsetSeconds);
|
||||
ap.setHeartRate(v1);
|
||||
@ -270,23 +264,33 @@ public class ActivityDetailsParser {
|
||||
}
|
||||
}
|
||||
|
||||
private int consumeUnknown2(byte[] bytes, int offset) {
|
||||
return 6; // just guessing...
|
||||
private int consumePause(byte[] bytes, int offset) {
|
||||
LOG.debug("got pause packet: " + GB.hexdump(bytes, offset, 6));
|
||||
return 6;
|
||||
}
|
||||
|
||||
private int consumePause(byte[] bytes, int i) {
|
||||
return 6; // just guessing...
|
||||
private int consumeResume(byte[] bytes, int offset) {
|
||||
LOG.debug("got resume package: " + GB.hexdump(bytes, offset, 6));
|
||||
return 6;
|
||||
}
|
||||
|
||||
private int consumeSpeed4(byte[] bytes, int offset) {
|
||||
LOG.debug("got packet type 4 (speed): " + GB.hexdump(bytes, offset, 6));
|
||||
return 6;
|
||||
}
|
||||
|
||||
private int consumeSpeed5(byte[] bytes, int offset) {
|
||||
LOG.debug("got packet type 5 (speed): " + GB.hexdump(bytes, offset, 6));
|
||||
return 6;
|
||||
}
|
||||
|
||||
private int consumeSpeed6(byte[] bytes, int offset) {
|
||||
LOG.debug("got packet type 6 (speed): " + GB.hexdump(bytes, offset, 6));
|
||||
return 6;
|
||||
}
|
||||
|
||||
private int consumeSwimming(byte[] bytes, int offset) {
|
||||
LOG.debug("got packet type 8 (swimming?): " + GB.hexdump(bytes, offset, 6));
|
||||
return 6;
|
||||
}
|
||||
}
|
@ -95,7 +95,7 @@ public class HuamiBatteryInfo extends AbstractInfo {
|
||||
if (mData.length >= 18) {
|
||||
lastCharge = BLETypeConversions.rawBytesToCalendar(new byte[]{
|
||||
mData[10], mData[11], mData[12], mData[13], mData[14], mData[15], mData[16], mData[17]
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
return lastCharge;
|
||||
|
@ -49,8 +49,10 @@ public abstract class HuamiFirmwareInfo {
|
||||
};
|
||||
|
||||
protected static final int FONT_TYPE_OFFSET = 0x9;
|
||||
protected static final int COMPRESSED_RES_HEADER_OFFSET = 0x9;
|
||||
protected static final int COMPRESSED_RES_HEADER_OFFSET_NEW = 0xd;
|
||||
|
||||
private HuamiFirmwareType firmwareType = HuamiFirmwareType.FIRMWARE;
|
||||
private HuamiFirmwareType firmwareType;
|
||||
|
||||
public String toVersion(int crc16) {
|
||||
String version = getCrcMap().get(crc16);
|
||||
@ -60,16 +62,16 @@ public abstract class HuamiFirmwareInfo {
|
||||
version = searchFirmwareVersion(bytes);
|
||||
break;
|
||||
case RES:
|
||||
version = "RES " + Integer.toString(bytes[5]);
|
||||
version = "RES " + bytes[5];
|
||||
break;
|
||||
case RES_COMPRESSED:
|
||||
version = "RES " + Integer.toString(bytes[14]);
|
||||
version = "RES " + bytes[14];
|
||||
break;
|
||||
case FONT:
|
||||
version = "FONT " + Integer.toString(bytes[4]);
|
||||
version = "FONT " + bytes[4];
|
||||
break;
|
||||
case FONT_LATIN:
|
||||
version = "FONT LATIN " + Integer.toString(bytes[4]);
|
||||
version = "FONT LATIN " + bytes[4];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -104,12 +106,14 @@ public abstract class HuamiFirmwareInfo {
|
||||
}
|
||||
|
||||
private final int crc16;
|
||||
private final int crc32;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public HuamiFirmwareInfo(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
crc16 = CheckSums.getCRC16(bytes);
|
||||
crc32 = CheckSums.getCRC32(bytes);
|
||||
firmwareType = determineFirmwareType(bytes);
|
||||
}
|
||||
|
||||
@ -138,6 +142,9 @@ public abstract class HuamiFirmwareInfo {
|
||||
public int getCrc16() {
|
||||
return crc16;
|
||||
}
|
||||
public int getCrc32() {
|
||||
return crc32;
|
||||
}
|
||||
|
||||
public int getFirmwareVersion() {
|
||||
return getCrc16(); // HACK until we know how to determine the version from the fw bytes
|
||||
@ -163,7 +170,7 @@ public abstract class HuamiFirmwareInfo {
|
||||
if (word == 0x642e2564) {
|
||||
word = buf.getInt();
|
||||
if (word == 0x00000000) {
|
||||
byte version[] = new byte[8];
|
||||
byte[] version = new byte[8];
|
||||
buf.get(version);
|
||||
return new String(version);
|
||||
}
|
||||
|
@ -14,20 +14,20 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public enum BipActivityType {
|
||||
public enum HuamiSportsActivityType {
|
||||
Outdoor(1),
|
||||
Treadmill(2),
|
||||
Walking(3),
|
||||
Cycling(4),
|
||||
Exercise(5);
|
||||
|
||||
Exercise(5),
|
||||
Swimming(6);
|
||||
private final int code;
|
||||
|
||||
BipActivityType(final int code) {
|
||||
HuamiSportsActivityType(final int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@ -43,20 +43,22 @@ public enum BipActivityType {
|
||||
return ActivityKind.TYPE_WALKING;
|
||||
case Exercise:
|
||||
return ActivityKind.TYPE_EXERCISE;
|
||||
case Swimming:
|
||||
return ActivityKind.TYPE_SWIMMING;
|
||||
}
|
||||
throw new RuntimeException("Not mapped activity kind for: " + this);
|
||||
}
|
||||
|
||||
public static BipActivityType fromCode(int bipCode) {
|
||||
for (BipActivityType type : values()) {
|
||||
if (type.code == bipCode) {
|
||||
public static HuamiSportsActivityType fromCode(int huamiCode) {
|
||||
for (HuamiSportsActivityType type : values()) {
|
||||
if (type.code == huamiCode) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("No matching BipActivityType for code: " + bipCode);
|
||||
throw new RuntimeException("No matching HuamiSportsActivityType for code: " + huamiCode);
|
||||
}
|
||||
|
||||
public static BipActivityType fromActivityKind(int activityKind) {
|
||||
public static HuamiSportsActivityType fromActivityKind(int activityKind) {
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_RUNNING:
|
||||
return Outdoor;
|
||||
@ -68,6 +70,8 @@ public enum BipActivityType {
|
||||
return Walking;
|
||||
case ActivityKind.TYPE_EXERCISE:
|
||||
return Exercise;
|
||||
case ActivityKind.TYPE_SWIMMING:
|
||||
return Swimming;
|
||||
}
|
||||
throw new RuntimeException("No matching activity activityKind: " + activityKind);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, JohnnySun, José Rebelo, Julien Pivotto, Kasha,
|
||||
Michal Novotny, Sergey Trofimov, Steffen Liebergeld
|
||||
Michal Novotny, Sebastian Kranz, Sergey Trofimov, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -26,6 +26,8 @@ import android.net.Uri;
|
||||
import android.text.format.DateFormat;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -38,13 +40,13 @@ import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -66,6 +68,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Service;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
|
||||
@ -86,6 +90,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -98,6 +103,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
|
||||
@ -107,7 +113,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions.StopNo
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2NotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.InitOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||
@ -203,15 +208,16 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
try {
|
||||
heartRateNotifyEnabled = false;
|
||||
boolean authenticate = needsAuth;
|
||||
needsAuth = false;
|
||||
byte authFlags = getAuthFlags();
|
||||
new InitOperation(authenticate, authFlags, this, builder).perform();
|
||||
byte cryptFlags = getCryptFlags();
|
||||
heartRateNotifyEnabled = false;
|
||||
boolean authenticate = needsAuth && (cryptFlags == 0x00);
|
||||
needsAuth = false;
|
||||
new InitOperation(authenticate, authFlags, cryptFlags, this, builder).perform();
|
||||
characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
|
||||
characteristicChunked = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER);
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Initializing Mi Band 2 failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||
GB.toast(getContext(), "Initializing Huami device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
@ -220,6 +226,10 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
return HuamiService.AUTH_BYTE;
|
||||
}
|
||||
|
||||
public byte getCryptFlags() {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given date/time (calendar) as a byte sequence, suitable for sending to the
|
||||
* Mi Band 2 (or derivative). The band appears to not handle DST offsets, so we simply add this
|
||||
@ -231,9 +241,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
public byte[] getTimeBytes(Calendar calendar, TimeUnit precision) {
|
||||
byte[] bytes;
|
||||
if (precision == TimeUnit.MINUTES) {
|
||||
bytes = BLETypeConversions.shortCalendarToRawBytes(calendar, true);
|
||||
bytes = BLETypeConversions.shortCalendarToRawBytes(calendar);
|
||||
} else if (precision == TimeUnit.SECONDS) {
|
||||
bytes = BLETypeConversions.calendarToRawBytes(calendar, true);
|
||||
bytes = BLETypeConversions.calendarToRawBytes(calendar);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported precision, only MINUTES and SECONDS are supported till now");
|
||||
}
|
||||
@ -245,7 +255,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
public Calendar fromTimeBytes(byte[] bytes) {
|
||||
GregorianCalendar timestamp = BLETypeConversions.rawBytesToCalendar(bytes, true);
|
||||
GregorianCalendar timestamp = BLETypeConversions.rawBytesToCalendar(bytes);
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@ -274,7 +284,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
* @param builder
|
||||
*/
|
||||
public void setInitialized(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZED, getContext()));
|
||||
builder.add(new SetDeviceStateAction(gbDevice, State.INITIALIZED, getContext()));
|
||||
}
|
||||
|
||||
// MB2: AVL
|
||||
@ -291,6 +301,8 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
|
||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
|
||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
|
||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable);
|
||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIODATA), enable);
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -333,14 +345,14 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
public NotificationStrategy getNotificationStrategy() {
|
||||
String firmwareVersion = getDevice().getFirmwareVersion();
|
||||
String firmwareVersion = gbDevice.getFirmwareVersion();
|
||||
if (firmwareVersion != null) {
|
||||
Version ver = new Version(firmwareVersion);
|
||||
if (MiBandConst.MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS.compareTo(ver) > 0) {
|
||||
return new Mi2NotificationStrategy(this);
|
||||
}
|
||||
}
|
||||
if (GBApplication.getPrefs().getBoolean(MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS, true)) {
|
||||
if (GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS, true)) {
|
||||
return new Mi2TextNotificationStrategy(this);
|
||||
}
|
||||
return new Mi2NotificationStrategy(this);
|
||||
@ -461,7 +473,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
|
||||
if (characteristic != null) {
|
||||
builder.notify(characteristic, true);
|
||||
int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
|
||||
int location = MiBandCoordinator.getWearLocation(gbDevice.getAddress());
|
||||
switch (location) {
|
||||
case 0: // left hand
|
||||
builder.write(characteristic, HuamiService.WEAR_LOCATION_LEFT_WRIST);
|
||||
@ -516,7 +528,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
* @param builder
|
||||
*/
|
||||
private HuamiSupport setHeartrateSleepSupport(TransactionBuilder builder) {
|
||||
final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(getDevice().getAddress());
|
||||
final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
|
||||
if (characteristicHRControlPoint != null) {
|
||||
builder.notify(characteristicHRControlPoint, true);
|
||||
if (enableHrSleepSupport) {
|
||||
@ -672,7 +684,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
sendCalendarEvents(builder);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to set time on MI device", ex);
|
||||
LOG.error("Unable to set time on Huami device", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -868,7 +880,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
builder.write(characteristicHRControlPoint, startHeartMeasurementManual);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to read heart rate with MI2", ex);
|
||||
LOG.error("Unable to read heart rate from Huami device", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -929,7 +941,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
try {
|
||||
new FetchActivityOperation(this).perform();
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to fetch MI activity data", ex);
|
||||
LOG.error("Unable to fetch activity data", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -989,7 +1001,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
try {
|
||||
new UpdateFirmwareOperation(uri, this).perform();
|
||||
createUpdateFirmwareOperation(uri).perform();
|
||||
} catch (IOException ex) {
|
||||
GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
@ -1050,7 +1062,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
currentButtonPressTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void handleDeviceEvent(byte[] value) {
|
||||
private void handleDeviceEvent(byte[] value) {
|
||||
if (value == null || value.length == 0) {
|
||||
return;
|
||||
}
|
||||
@ -1058,13 +1070,14 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
switch (value[0]) {
|
||||
case HuamiDeviceEvent.CALL_REJECT:
|
||||
LOG.info("call rejected");
|
||||
callCmd.event = GBDeviceEventCallControl.Event.REJECT;
|
||||
evaluateGBDeviceEvent(callCmd);
|
||||
break;
|
||||
case HuamiDeviceEvent.CALL_IGNORE:
|
||||
LOG.info("ignore call (not yet supported)");
|
||||
//callCmd.event = GBDeviceEventCallControl.Event.IGNORE;
|
||||
//evaluateGBDeviceEvent(callCmd);
|
||||
LOG.info("call ignored");
|
||||
callCmd.event = GBDeviceEventCallControl.Event.IGNORE;
|
||||
evaluateGBDeviceEvent(callCmd);
|
||||
break;
|
||||
case HuamiDeviceEvent.BUTTON_PRESSED:
|
||||
LOG.info("button pressed");
|
||||
@ -1352,7 +1365,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
|
||||
Device device = DBHelper.getDevice(getDevice(), session);
|
||||
Device device = DBHelper.getDevice(gbDevice, session);
|
||||
User user = DBHelper.getUser(session);
|
||||
int ts = (int) (System.currentTimeMillis() / 1000);
|
||||
MiBand2SampleProvider provider = new MiBand2SampleProvider(gbDevice, session);
|
||||
@ -1517,7 +1530,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
case HuamiConst.PREF_DISCONNECT_NOTIFICATION_END:
|
||||
setDisconnectNotification(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_DISPLAY_ITEMS:
|
||||
case HuamiConst.PREF_DISPLAY_ITEMS:
|
||||
setDisplayItems(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO:
|
||||
@ -1526,9 +1539,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
case ActivityUser.PREF_USER_STEPS_GOAL:
|
||||
setFitnessGoal(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB:
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB_START:
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB_END:
|
||||
case MiBandConst.PREF_DO_NOT_DISTURB:
|
||||
case MiBandConst.PREF_DO_NOT_DISTURB_START:
|
||||
case MiBandConst.PREF_DO_NOT_DISTURB_END:
|
||||
setDoNotDisturb(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS:
|
||||
@ -1543,6 +1556,15 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
|
||||
setDistanceUnit(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_SWIPE_UNLOCK:
|
||||
setBandScreenUnlock(builder);
|
||||
break;
|
||||
case HuamiConst.PREF_DATEFORMAT:
|
||||
setDateFormat(builder);
|
||||
break;
|
||||
case HuamiConst.PREF_LANGUAGE:
|
||||
setLanguage(builder);
|
||||
break;
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
@ -1557,11 +1579,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
try {
|
||||
new FetchSportsSummaryOperation(this).perform();
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to fetch MI activity data", ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1570,7 +1588,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HuamiSupport setDateDisplay(TransactionBuilder builder) {
|
||||
DateTimeDisplay dateTimeDisplay = HuamiCoordinator.getDateDisplay(getContext());
|
||||
DateTimeDisplay dateTimeDisplay = HuamiCoordinator.getDateDisplay(getContext(), gbDevice.getAddress());
|
||||
LOG.info("Setting date display to " + dateTimeDisplay);
|
||||
switch (dateTimeDisplay) {
|
||||
case TIME:
|
||||
@ -1583,6 +1601,26 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected HuamiSupport setDateFormat(TransactionBuilder builder) {
|
||||
String dateFormat = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("dateformat", "MM/dd/yyyy");
|
||||
if (dateFormat == null) {
|
||||
return null;
|
||||
}
|
||||
switch (dateFormat) {
|
||||
case "MM/dd/yyyy":
|
||||
case "dd.MM.yyyy":
|
||||
case "dd/MM/yyyy":
|
||||
byte[] command = HuamiService.DATEFORMAT_DATE_MM_DD_YYYY;
|
||||
System.arraycopy(dateFormat.getBytes(), 0, command, 3, 10);
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), command);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("unsupported date format " + dateFormat);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private HuamiSupport setTimeFormat(TransactionBuilder builder) {
|
||||
boolean is24Format = DateFormat.is24HourFormat(getContext());
|
||||
LOG.info("Setting 24h time format to " + is24Format);
|
||||
@ -1606,7 +1644,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HuamiSupport setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
|
||||
ActivateDisplayOnLift displayOnLift = HuamiCoordinator.getActivateDisplayOnLiftWrist(getContext());
|
||||
ActivateDisplayOnLift displayOnLift = HuamiCoordinator.getActivateDisplayOnLiftWrist(getContext(), gbDevice.getAddress());
|
||||
LOG.info("Setting activate display on lift wrist to " + displayOnLift);
|
||||
|
||||
switch (displayOnLift) {
|
||||
@ -1621,12 +1659,12 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
Date start = HuamiCoordinator.getDisplayOnLiftStart();
|
||||
Date start = HuamiCoordinator.getDisplayOnLiftStart(gbDevice.getAddress());
|
||||
calendar.setTime(start);
|
||||
cmd[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
cmd[5] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
Date end = HuamiCoordinator.getDisplayOnLiftEnd();
|
||||
Date end = HuamiCoordinator.getDisplayOnLiftEnd(gbDevice.getAddress());
|
||||
calendar.setTime(end);
|
||||
cmd[6] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
cmd[7] = (byte) calendar.get(Calendar.MINUTE);
|
||||
@ -1637,7 +1675,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
protected HuamiSupport setDisplayItems(TransactionBuilder builder) {
|
||||
Set<String> pages = HuamiCoordinator.getDisplayItems();
|
||||
Set<String> pages = HuamiCoordinator.getDisplayItems(gbDevice.getAddress());
|
||||
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
|
||||
|
||||
byte[] data = HuamiService.COMMAND_CHANGE_SCREENS.clone();
|
||||
@ -1665,7 +1703,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HuamiSupport setRotateWristToSwitchInfo(TransactionBuilder builder) {
|
||||
boolean enable = HuamiCoordinator.getRotateWristToSwitchInfo();
|
||||
boolean enable = HuamiCoordinator.getRotateWristToSwitchInfo(gbDevice.getAddress());
|
||||
LOG.info("Setting rotate wrist to cycle info to " + enable);
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
@ -1681,7 +1719,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HuamiSupport setDoNotDisturb(TransactionBuilder builder) {
|
||||
DoNotDisturb doNotDisturb = HuamiCoordinator.getDoNotDisturb(getContext());
|
||||
DoNotDisturb doNotDisturb = HuamiCoordinator.getDoNotDisturb(gbDevice.getAddress());
|
||||
LOG.info("Setting do not disturb to " + doNotDisturb);
|
||||
switch (doNotDisturb) {
|
||||
case OFF:
|
||||
@ -1695,12 +1733,12 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
Date start = HuamiCoordinator.getDoNotDisturbStart();
|
||||
Date start = HuamiCoordinator.getDoNotDisturbStart(gbDevice.getAddress());
|
||||
calendar.setTime(start);
|
||||
data[HuamiService.DND_BYTE_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[HuamiService.DND_BYTE_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
Date end = HuamiCoordinator.getDoNotDisturbEnd();
|
||||
Date end = HuamiCoordinator.getDoNotDisturbEnd(gbDevice.getAddress());
|
||||
calendar.setTime(end);
|
||||
data[HuamiService.DND_BYTE_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[HuamiService.DND_BYTE_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
@ -1768,7 +1806,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HuamiSupport setDisconnectNotification(TransactionBuilder builder) {
|
||||
DisconnectNotificationSetting disconnectNotificationSetting = HuamiCoordinator.getDisconnectNotificationSetting(getContext());
|
||||
DisconnectNotificationSetting disconnectNotificationSetting = HuamiCoordinator.getDisconnectNotificationSetting(getContext(), gbDevice.getAddress());
|
||||
LOG.info("Setting disconnect notification to " + disconnectNotificationSetting);
|
||||
|
||||
switch (disconnectNotificationSetting) {
|
||||
@ -1783,12 +1821,12 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
Date start = HuamiCoordinator.getDisconnectNotificationStart();
|
||||
Date start = HuamiCoordinator.getDisconnectNotificationStart(gbDevice.getAddress());
|
||||
calendar.setTime(start);
|
||||
cmd[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
cmd[5] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
Date end = HuamiCoordinator.getDisconnectNotificationEnd();
|
||||
Date end = HuamiCoordinator.getDisconnectNotificationEnd(gbDevice.getAddress());
|
||||
calendar.setTime(end);
|
||||
cmd[6] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
cmd[7] = (byte) calendar.get(Calendar.MINUTE);
|
||||
@ -1809,6 +1847,68 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected HuamiSupport setBandScreenUnlock(TransactionBuilder builder) {
|
||||
boolean enable = MiBand3Coordinator.getBandScreenUnlock(gbDevice.getAddress());
|
||||
LOG.info("Setting band screen unlock to " + enable);
|
||||
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_ENABLE_BAND_SCREEN_UNLOCK);
|
||||
} else {
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_DISABLE_BAND_SCREEN_UNLOCK);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
protected HuamiSupport setLanguage(TransactionBuilder builder) {
|
||||
String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
|
||||
if (localeString == null || localeString.equals("auto")) {
|
||||
String language = Locale.getDefault().getLanguage();
|
||||
String country = Locale.getDefault().getCountry();
|
||||
|
||||
if (country == null) {
|
||||
// sometimes country is null, no idea why, guess it.
|
||||
country = language;
|
||||
}
|
||||
localeString = language + "_" + country.toUpperCase();
|
||||
}
|
||||
LOG.info("Setting device to locale: " + localeString);
|
||||
final byte[] command_new = HuamiService.COMMAND_SET_LANGUAGE_NEW_TEMPLATE.clone();
|
||||
System.arraycopy(localeString.getBytes(), 0, command_new, 3, localeString.getBytes().length);
|
||||
|
||||
byte[] command_old;
|
||||
switch (localeString.substring(0, 2)) {
|
||||
case "es":
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SPANISH;
|
||||
break;
|
||||
case "zh":
|
||||
if (localeString.equals("zh_CN")) {
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE;
|
||||
} else {
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE;
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
|
||||
}
|
||||
final byte[] finalCommand_old = command_old;
|
||||
builder.add(new ConditionalWriteAction(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION)) {
|
||||
@Override
|
||||
protected byte[] checkCondition() {
|
||||
if ((gbDevice.getType() == DeviceType.AMAZFITBIP && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("0.1.0.77")) < 0) ||
|
||||
(gbDevice.getType() == DeviceType.AMAZFITCOR && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("1.0.7.23")) < 0)) {
|
||||
return finalCommand_old;
|
||||
} else {
|
||||
return command_new;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
|
||||
final int MAX_CHUNKLENGTH = 17;
|
||||
int remaining = data.length;
|
||||
@ -1869,4 +1969,8 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
|
||||
return new MiBand2FWHelper(uri, context);
|
||||
}
|
||||
|
||||
public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
|
||||
return new UpdateFirmwareOperation(uri, this);
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,11 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(60002, "1.1.5.04");
|
||||
crcToVersion.put(5229, "1.1.5.12");
|
||||
crcToVersion.put(32576, "1.1.5.16");
|
||||
crcToVersion.put(28893, "1.1.5.24");
|
||||
crcToVersion.put(61710, "1.1.5.56");
|
||||
|
||||
// Latin Firmware
|
||||
crcToVersion.put(52828, "1.1.5.36 (Latin)");
|
||||
|
||||
// resources
|
||||
crcToVersion.put(12586, "0.0.8.74");
|
||||
@ -130,7 +135,9 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(23073, "0.1.1.45");
|
||||
crcToVersion.put(59245, "1.0.2.00");
|
||||
crcToVersion.put(20591, "1.1.2.05");
|
||||
crcToVersion.put(5341, "1.1.5.02-16");
|
||||
crcToVersion.put(5341, "1.1.5.02-24");
|
||||
crcToVersion.put(22662, "1.1.5.36");
|
||||
crcToVersion.put(24045, "1.1.5.56");
|
||||
|
||||
// gps
|
||||
crcToVersion.put(61520, "9367,8f79a91,0,0,");
|
||||
|
@ -30,13 +30,11 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import cyanogenmod.weather.util.WeatherUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
@ -52,17 +50,15 @@ import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations.AmazfitBipFetchLogsOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
@ -137,16 +133,25 @@ public class AmazfitBipSupport extends HuamiSupport {
|
||||
appSuffix = appName.getBytes();
|
||||
suffixlength = appSuffix.length;
|
||||
}
|
||||
if (gbDevice.getType() == DeviceType.MIBAND4) {
|
||||
prefixlength += 4;
|
||||
}
|
||||
|
||||
byte[] rawmessage = message.getBytes();
|
||||
int length = Math.min(rawmessage.length, maxLength - prefixlength);
|
||||
|
||||
byte[] command = new byte[length + prefixlength + suffixlength];
|
||||
|
||||
command[0] = (byte) alertCategory.getId();
|
||||
command[1] = 1;
|
||||
int pos = 0;
|
||||
command[pos++] = (byte) alertCategory.getId();
|
||||
if (gbDevice.getType() == DeviceType.MIBAND4) {
|
||||
command[pos++] = 0; // TODO
|
||||
command[pos++] = 0;
|
||||
command[pos++] = 0;
|
||||
command[pos++] = 0;
|
||||
}
|
||||
command[pos++] = 1;
|
||||
if (alertCategory == AlertCategory.CustomHuami) {
|
||||
command[2] = customIconId;
|
||||
command[pos] = customIconId;
|
||||
}
|
||||
|
||||
System.arraycopy(rawmessage, 0, command, prefixlength, length);
|
||||
@ -185,14 +190,7 @@ public class AmazfitBipSupport extends HuamiSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
Version version = new Version(gbDevice.getFirmwareVersion());
|
||||
if (version.compareTo(new Version("0.1.1.14")) < 0) {
|
||||
LOG.warn("Won't set menu items since firmware is too low to be safe");
|
||||
return this;
|
||||
}
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
Set<String> pages = prefs.getStringSet("bip_display_items", null);
|
||||
Set<String> pages = HuamiCoordinator.getDisplayItems(gbDevice.getAddress());
|
||||
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
|
||||
byte[] command = AmazfitBipService.COMMAND_CHANGE_SCREENS.clone();
|
||||
|
||||
@ -440,7 +438,7 @@ public class AmazfitBipSupport extends HuamiSupport {
|
||||
} else if (dataTypes == RecordedDataTypes.TYPE_GPS_TRACKS) {
|
||||
new FetchSportsSummaryOperation(this).perform();
|
||||
} else if (dataTypes == RecordedDataTypes.TYPE_DEBUGLOGS) {
|
||||
new AmazfitBipFetchLogsOperation(this).perform();
|
||||
new HuamiFetchDebugLogsOperation(this).perform();
|
||||
}
|
||||
else {
|
||||
LOG.warn("fetching multiple data types at once is not supported yet");
|
||||
@ -483,82 +481,6 @@ public class AmazfitBipSupport extends HuamiSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected AmazfitBipSupport setLanguage(TransactionBuilder builder) {
|
||||
|
||||
String language = Locale.getDefault().getLanguage();
|
||||
String country = Locale.getDefault().getCountry();
|
||||
|
||||
LOG.info("Setting watch language, phone language = " + language + " country = " + country);
|
||||
|
||||
final byte[] command_new;
|
||||
final byte[] command_old;
|
||||
String localeString;
|
||||
|
||||
switch (GBApplication.getPrefs().getInt("amazfitbip_language", -1)) {
|
||||
case 0:
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE;
|
||||
localeString = "zh_CN";
|
||||
break;
|
||||
case 1:
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE;
|
||||
localeString = "zh_TW";
|
||||
break;
|
||||
case 2:
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
|
||||
localeString = "en_US";
|
||||
break;
|
||||
case 3:
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SPANISH;
|
||||
localeString = "es_ES";
|
||||
break;
|
||||
case 4:
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
|
||||
localeString = "ru_RU";
|
||||
break;
|
||||
default:
|
||||
switch (language) {
|
||||
case "zh":
|
||||
if (country.equals("TW") || country.equals("HK") || country.equals("MO")) { // Taiwan, Hong Kong, Macao
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE;
|
||||
localeString = "zh_TW";
|
||||
} else {
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE;
|
||||
localeString = "zh_CN";
|
||||
}
|
||||
break;
|
||||
case "es":
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SPANISH;
|
||||
localeString = "es_ES";
|
||||
break;
|
||||
case "ru":
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
|
||||
localeString = "ru_RU";
|
||||
break;
|
||||
default:
|
||||
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
|
||||
localeString = "en_US";
|
||||
break;
|
||||
}
|
||||
}
|
||||
command_new = HuamiService.COMMAND_SET_LANGUAGE_NEW_TEMPLATE.clone();
|
||||
System.arraycopy(localeString.getBytes(), 0, command_new, 3, localeString.getBytes().length);
|
||||
|
||||
builder.add(new ConditionalWriteAction(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION)) {
|
||||
@Override
|
||||
protected byte[] checkCondition() {
|
||||
if ((gbDevice.getType() == DeviceType.AMAZFITBIP && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("0.1.0.77")) >= 0) ||
|
||||
(gbDevice.getType() == DeviceType.AMAZFITCOR && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("1.0.7.23")) >= 0)) {
|
||||
return command_new;
|
||||
} else {
|
||||
return command_old;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void phase2Initialize(TransactionBuilder builder) {
|
||||
super.phase2Initialize(builder);
|
||||
|
@ -51,8 +51,8 @@ public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(64977, "RES 1.0.6.76");
|
||||
crcToVersion.put(60501, "RES 1.0.7.52-71");
|
||||
crcToVersion.put(31263, "RES 1.0.7.77-91");
|
||||
crcToVersion.put(20920, "RES 1.2.5.00-65");
|
||||
crcToVersion.put(25397, "RES 1.2.7.20-69");
|
||||
crcToVersion.put(20920, "RES 1.2.5.00-69");
|
||||
crcToVersion.put(25397, "RES 1.2.7.20");
|
||||
|
||||
// font
|
||||
crcToVersion.put(61054, "8");
|
||||
@ -65,12 +65,6 @@ public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
|
||||
|
||||
@Override
|
||||
protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
|
||||
if (bytes.length < 700000) { // dont know how to distinguish from Bip .res
|
||||
return HuamiFirmwareType.INVALID;
|
||||
}
|
||||
return HuamiFirmwareType.RES;
|
||||
}
|
||||
if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
|
||||
return HuamiFirmwareType.RES_COMPRESSED;
|
||||
}
|
||||
@ -90,6 +84,9 @@ public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
|
||||
return HuamiFirmwareType.FONT_LATIN;
|
||||
}
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
|
||||
return HuamiFirmwareType.RES;
|
||||
}
|
||||
return HuamiFirmwareType.INVALID;
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,13 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor.AmazfitCorFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor.AmazfitCorService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class AmazfitCorSupport extends AmazfitBipSupport {
|
||||
|
||||
@ -41,8 +40,7 @@ public class AmazfitCorSupport extends AmazfitBipSupport {
|
||||
@Override
|
||||
protected AmazfitCorSupport setDisplayItems(TransactionBuilder builder) {
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
Set<String> pages = prefs.getStringSet("cor_display_items", null);
|
||||
Set<String> pages = HuamiCoordinator.getDisplayItems(getDevice().getAddress());
|
||||
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
|
||||
byte[] command = AmazfitCorService.COMMAND_CHANGE_SCREENS.clone();
|
||||
|
||||
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||
|
||||
public class AmazfitCor2FirmwareInfo extends HuamiFirmwareInfo {
|
||||
// this is the same as Bip
|
||||
private static final byte[] FW_HEADER = new byte[]{
|
||||
0x00, (byte) 0x98, 0x00, 0x20, (byte) 0xA5, 0x04, 0x00, 0x20, (byte) 0xAD, 0x04, 0x00, 0x20, (byte) 0xC5, 0x04, 0x00, 0x20
|
||||
};
|
||||
|
||||
private static Map<Integer, String> crcToVersion = new HashMap<>();
|
||||
|
||||
static {
|
||||
// font
|
||||
crcToVersion.put(61054, "8");
|
||||
crcToVersion.put(62291, "9 (Latin)");
|
||||
}
|
||||
|
||||
public AmazfitCor2FirmwareInfo(byte[] bytes) {
|
||||
super(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
|
||||
return HuamiFirmwareType.RES_COMPRESSED;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, FW_HEADER)) {
|
||||
// FIXME: It would certainly better if we could check for "Cor 2" when the device name is "Cor 2" and for "Band 2" when it is "Band 2"
|
||||
if (searchString32BitAligned(bytes, "Amazfit Cor 2")) {
|
||||
return HuamiFirmwareType.FIRMWARE;
|
||||
}
|
||||
if (searchString32BitAligned(bytes, "Amazfit Band 2")) {
|
||||
return HuamiFirmwareType.FIRMWARE;
|
||||
}
|
||||
return HuamiFirmwareType.INVALID;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
|
||||
return HuamiFirmwareType.WATCHFACE;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
|
||||
if (bytes[10] == 0x01) {
|
||||
return HuamiFirmwareType.FONT;
|
||||
} else if (bytes[10] == 0x02) {
|
||||
return HuamiFirmwareType.FONT_LATIN;
|
||||
}
|
||||
}
|
||||
// somebody might have unpacked the compressed res
|
||||
if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
|
||||
return HuamiFirmwareType.RES;
|
||||
}
|
||||
return HuamiFirmwareType.INVALID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerallyCompatibleWith(GBDevice device) {
|
||||
return isHeaderValid() && device.getType() == DeviceType.AMAZFITCOR2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<Integer, String> getCrcMap() {
|
||||
return crcToVersion;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2.AmazfitCor2FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport;
|
||||
|
||||
public class AmazfitCor2Support extends AmazfitCorSupport {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitCor2Support.class);
|
||||
|
||||
|
||||
@Override
|
||||
public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitCor2FWHelper(uri, context);
|
||||
}
|
||||
}
|
@ -57,6 +57,13 @@ public class MiBand3FirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(40344, "2.2.0.14");
|
||||
crcToVersion.put(4467, "2.2.0.42");
|
||||
crcToVersion.put(61657, "2.3.0.2");
|
||||
crcToVersion.put(62735, "2.3.0.6");
|
||||
crcToVersion.put(40949, "2.3.0.28");
|
||||
crcToVersion.put(59213, "2.4.0.12");
|
||||
crcToVersion.put(10810, "2.4.0.20");
|
||||
|
||||
// firmware (Mi Band 3 NFC)
|
||||
crcToVersion.put(46724, "1.7.0.4");
|
||||
|
||||
// resources
|
||||
crcToVersion.put(54724, "1.2.0.8");
|
||||
@ -65,10 +72,14 @@ public class MiBand3FirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(25278, "1.4.0.12-1.6.0.16");
|
||||
crcToVersion.put(23249, "1.8.0.0");
|
||||
crcToVersion.put(1815, "2.0.0.4");
|
||||
crcToVersion.put(7225, "2.2.0.12-2.3.0.2");
|
||||
crcToVersion.put(7225, "2.2.0.12-2.3.0.6");
|
||||
crcToVersion.put(52754, "2.3.0.28");
|
||||
crcToVersion.put(17930, "2.4.0.12-20");
|
||||
|
||||
// font
|
||||
crcToVersion.put(19775, "1");
|
||||
crcToVersion.put(42959, "2 (old Jap/Kor)");
|
||||
crcToVersion.put(12052, "1 (Jap/Kor)");
|
||||
}
|
||||
|
||||
public MiBand3FirmwareInfo(byte[] bytes) {
|
||||
@ -78,7 +89,7 @@ public class MiBand3FirmwareInfo extends HuamiFirmwareInfo {
|
||||
@Override
|
||||
protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.startsWith(bytes, FT_HEADER)) {
|
||||
if (bytes[FONT_TYPE_OFFSET] == 0x03 || bytes[FONT_TYPE_OFFSET] == 0x04) {
|
||||
if (bytes[FONT_TYPE_OFFSET] >= 0x03 && bytes[FONT_TYPE_OFFSET] <= 0x05) {
|
||||
return HuamiFirmwareType.FONT;
|
||||
}
|
||||
return HuamiFirmwareType.INVALID;
|
||||
|
@ -27,21 +27,18 @@ import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Service;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class MiBand3Support extends AmazfitBipSupport {
|
||||
|
||||
@ -49,16 +46,13 @@ public class MiBand3Support extends AmazfitBipSupport {
|
||||
|
||||
@Override
|
||||
protected byte getAuthFlags() {
|
||||
if (gbDevice.getType() == DeviceType.MIBAND3) {
|
||||
return 0x00;
|
||||
}
|
||||
return super.getAuthFlags();
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MiBand3Support setDisplayItems(TransactionBuilder builder) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
Set<String> pages = prefs.getStringSet("miband3_display_items", null);
|
||||
Set<String> pages = HuamiCoordinator.getDisplayItems(gbDevice.getAddress());
|
||||
|
||||
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
|
||||
byte[] command = MiBand3Service.COMMAND_CHANGE_SCREENS.clone();
|
||||
|
||||
@ -88,9 +82,17 @@ public class MiBand3Support extends AmazfitBipSupport {
|
||||
command[1] |= 0x40;
|
||||
command[9] = pos++;
|
||||
}
|
||||
if (pages.contains("timer")) {
|
||||
command[1] |= 0x80;
|
||||
command[10] = pos++;
|
||||
}
|
||||
if (pages.contains("nfc")) {
|
||||
command[2] |= 0x01;
|
||||
command[11] = pos++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 4; i <= 9; i++) {
|
||||
for (int i = 4; i <= 11; i++) {
|
||||
if (command[i] == 0) {
|
||||
command[i] = pos++;
|
||||
}
|
||||
@ -107,12 +109,9 @@ public class MiBand3Support extends AmazfitBipSupport {
|
||||
try {
|
||||
builder = performInitialized("Sending configuration for option: " + config);
|
||||
switch (config) {
|
||||
case MiBandConst.PREF_MI3_BAND_SCREEN_UNLOCK:
|
||||
setBandScreenUnlock(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI3_NIGHT_MODE:
|
||||
case MiBandConst.PREF_MI3_NIGHT_MODE_START:
|
||||
case MiBandConst.PREF_MI3_NIGHT_MODE_END:
|
||||
case MiBandConst.PREF_NIGHT_MODE:
|
||||
case MiBandConst.PREF_NIGHT_MODE_START:
|
||||
case MiBandConst.PREF_NIGHT_MODE_END:
|
||||
setNightMode(builder);
|
||||
break;
|
||||
default:
|
||||
@ -125,64 +124,28 @@ public class MiBand3Support extends AmazfitBipSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MiBand3Support setLanguage(TransactionBuilder builder) {
|
||||
String localeString = GBApplication.getPrefs().getString("miband3_language", "auto");
|
||||
|
||||
if (localeString.equals("auto")) {
|
||||
String language = Locale.getDefault().getLanguage();
|
||||
String country = Locale.getDefault().getCountry();
|
||||
|
||||
if (country == null) {
|
||||
// sometimes country is null, no idea why, guess it.
|
||||
country = language;
|
||||
}
|
||||
localeString = language + "_" + country.toUpperCase();
|
||||
}
|
||||
LOG.info("Setting device to locale: " + localeString);
|
||||
byte[] command_new = HuamiService.COMMAND_SET_LANGUAGE_NEW_TEMPLATE.clone();
|
||||
System.arraycopy(localeString.getBytes(), 0, command_new, 3, localeString.getBytes().length);
|
||||
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), command_new);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand3Support setBandScreenUnlock(TransactionBuilder builder) {
|
||||
boolean enable = MiBand3Coordinator.getBandScreenUnlock();
|
||||
LOG.info("Setting band screen unlock to " + enable);
|
||||
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_ENABLE_BAND_SCREEN_UNLOCK);
|
||||
} else {
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_DISABLE_BAND_SCREEN_UNLOCK);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand3Support setNightMode(TransactionBuilder builder) {
|
||||
String nightMode = MiBand3Coordinator.getNightMode();
|
||||
String nightMode = MiBand3Coordinator.getNightMode(gbDevice.getAddress());
|
||||
LOG.info("Setting night mode to " + nightMode);
|
||||
|
||||
switch (nightMode) {
|
||||
case MiBandConst.PREF_MI3_NIGHT_MODE_SUNSET:
|
||||
case MiBandConst.PREF_NIGHT_MODE_SUNSET:
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_NIGHT_MODE_SUNSET);
|
||||
break;
|
||||
case MiBandConst.PREF_MI3_NIGHT_MODE_OFF:
|
||||
case MiBandConst.PREF_NIGHT_MODE_OFF:
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_NIGHT_MODE_OFF);
|
||||
break;
|
||||
case MiBandConst.PREF_MI3_NIGHT_MODE_SCHEDULED:
|
||||
case MiBandConst.PREF_NIGHT_MODE_SCHEDULED:
|
||||
byte[] cmd = MiBand3Service.COMMAND_NIGHT_MODE_SCHEDULED.clone();
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
Date start = MiBand3Coordinator.getNightModeStart();
|
||||
Date start = MiBand3Coordinator.getNightModeStart(gbDevice.getAddress());
|
||||
calendar.setTime(start);
|
||||
cmd[2] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
cmd[3] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
Date end = MiBand3Coordinator.getNightModeEnd();
|
||||
Date end = MiBand3Coordinator.getNightModeEnd(gbDevice.getAddress());
|
||||
calendar.setTime(end);
|
||||
cmd[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
cmd[5] = (byte) calendar.get(Calendar.MINUTE);
|
||||
@ -201,8 +164,10 @@ public class MiBand3Support extends AmazfitBipSupport {
|
||||
public void phase2Initialize(TransactionBuilder builder) {
|
||||
super.phase2Initialize(builder);
|
||||
LOG.info("phase2Initialize...");
|
||||
setLanguage(builder);
|
||||
setBandScreenUnlock(builder);
|
||||
setNightMode(builder);
|
||||
setDateFormat(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,89 @@
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||
|
||||
public class MiBand4FirmwareInfo extends HuamiFirmwareInfo {
|
||||
|
||||
private static final byte[] FW_HEADER = new byte[]{
|
||||
0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x9c, (byte) 0xe3, 0x7d, 0x5c, 0x00, 0x04
|
||||
};
|
||||
|
||||
private static final int FW_HEADER_OFFSET = 16;
|
||||
|
||||
private static Map<Integer, String> crcToVersion = new HashMap<>();
|
||||
|
||||
static {
|
||||
// firmware
|
||||
crcToVersion.put(8969, "1.0.5.22");
|
||||
|
||||
// resources
|
||||
crcToVersion.put(27412, "1.0.5.22");
|
||||
|
||||
// font
|
||||
crcToVersion.put(31978, "1");
|
||||
}
|
||||
|
||||
public MiBand4FirmwareInfo(byte[] bytes) {
|
||||
super(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
|
||||
return HuamiFirmwareType.RES_COMPRESSED;
|
||||
}
|
||||
if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
|
||||
if (searchString32BitAligned(bytes, "Mi Smart Band 4")) {
|
||||
return HuamiFirmwareType.FIRMWARE;
|
||||
}
|
||||
return HuamiFirmwareType.INVALID;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
|
||||
return HuamiFirmwareType.WATCHFACE;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
|
||||
if (bytes[10] == 0x03) {
|
||||
return HuamiFirmwareType.FONT;
|
||||
}
|
||||
}
|
||||
// somebody might have unpacked the compressed res
|
||||
if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
|
||||
return HuamiFirmwareType.RES;
|
||||
}
|
||||
return HuamiFirmwareType.INVALID;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isGenerallyCompatibleWith(GBDevice device) {
|
||||
return isHeaderValid() && device.getType() == DeviceType.MIBAND4;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<Integer, String> getCrcMap() {
|
||||
return crcToVersion;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4.MiBand4FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperationNew;
|
||||
|
||||
public class MiBand4Support extends MiBand3Support {
|
||||
|
||||
@Override
|
||||
public byte getCryptFlags() {
|
||||
return (byte) 0x80;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
|
||||
return new MiBand4FWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateFirmwareOperationNew createUpdateFirmwareOperation(Uri uri) {
|
||||
return new UpdateFirmwareOperationNew(uri, this);
|
||||
}
|
||||
}
|
@ -22,6 +22,9 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -33,8 +36,6 @@ import java.util.GregorianCalendar;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -57,11 +58,10 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractFetchOperation.class);
|
||||
|
||||
protected byte lastPacketCounter;
|
||||
protected int fetchCount;
|
||||
int fetchCount;
|
||||
protected BluetoothGattCharacteristic characteristicActivityData;
|
||||
protected BluetoothGattCharacteristic characteristicFetch;
|
||||
protected Calendar startTimestamp;
|
||||
protected int expectedDataLength;
|
||||
Calendar startTimestamp;
|
||||
|
||||
public AbstractFetchOperation(HuamiSupport support) {
|
||||
super(support);
|
||||
@ -123,7 +123,7 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
|
||||
@CallSuper
|
||||
protected void handleActivityFetchFinish(boolean success) {
|
||||
GB.updateTransferNotification(null,"",false,100,getContext());
|
||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||
operationFinished();
|
||||
unsetBusy();
|
||||
}
|
||||
@ -175,13 +175,13 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
});
|
||||
}
|
||||
|
||||
protected void handleActivityMetadata(byte[] value) {
|
||||
private void handleActivityMetadata(byte[] value) {
|
||||
if (value.length == 15) {
|
||||
// first two bytes are whether our request was accepted
|
||||
if (ArrayUtils.equals(value, HuamiService.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
|
||||
// the third byte (0x01 on success) = ?
|
||||
// the 4th - 7th bytes epresent the number of bytes/packets to expect, excluding the counter bytes
|
||||
expectedDataLength = BLETypeConversions.toUint32(Arrays.copyOfRange(value, 3, 7));
|
||||
//int expectedDataLength = BLETypeConversions.toUint32(Arrays.copyOfRange(value, 3, 7));
|
||||
|
||||
// last 8 bytes are the start date
|
||||
Calendar startTimestamp = getSupport().fromTimeBytes(Arrays.copyOfRange(value, 7, value.length));
|
||||
@ -189,7 +189,7 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
|
||||
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data),
|
||||
getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since,
|
||||
DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), true, 0, getContext());;
|
||||
DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), true, 0, getContext());
|
||||
} else {
|
||||
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
@ -207,30 +207,30 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
}
|
||||
}
|
||||
|
||||
protected void setStartTimestamp(Calendar startTimestamp) {
|
||||
private void setStartTimestamp(Calendar startTimestamp) {
|
||||
this.startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
protected Calendar getLastStartTimestamp() {
|
||||
Calendar getLastStartTimestamp() {
|
||||
return startTimestamp;
|
||||
}
|
||||
|
||||
protected void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) {
|
||||
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
|
||||
void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) {
|
||||
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
|
||||
editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis());
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
|
||||
protected GregorianCalendar getLastSuccessfulSyncTime() {
|
||||
long timeStampMillis = GBApplication.getPrefs().getLong(getLastSyncTimeKey(), 0);
|
||||
long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0);
|
||||
if (timeStampMillis != 0) {
|
||||
GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||
calendar.setTimeInMillis(timeStampMillis);
|
||||
return calendar;
|
||||
}
|
||||
GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, - 100);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -100);
|
||||
return calendar;
|
||||
}
|
||||
}
|
||||
|
@ -203,6 +203,6 @@ public class FetchActivityOperation extends AbstractFetchOperation {
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return getDevice().getAddress() + "_" + "lastSyncTimeMillis";
|
||||
return "lastSyncTimeMillis";
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.ActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -56,7 +56,7 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation {
|
||||
|
||||
private ByteArrayOutputStream buffer;
|
||||
|
||||
public FetchSportsDetailsOperation(@NonNull BaseActivitySummary summary, @NonNull HuamiSupport support, @NonNull String lastSyncTimeKey) {
|
||||
FetchSportsDetailsOperation(@NonNull BaseActivitySummary summary, @NonNull HuamiSupport support, @NonNull String lastSyncTimeKey) {
|
||||
super(support);
|
||||
setName("fetching sport details");
|
||||
this.summary = summary;
|
||||
@ -86,7 +86,7 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation {
|
||||
|
||||
|
||||
if (success) {
|
||||
ActivityDetailsParser parser = new ActivityDetailsParser(summary);
|
||||
HuamiActivityDetailsParser parser = new HuamiActivityDetailsParser(summary);
|
||||
parser.setSkipCounterByte(false); // is already stripped
|
||||
try {
|
||||
ActivityTrack track = parser.parse(buffer.toByteArray());
|
||||
@ -131,7 +131,7 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation {
|
||||
super.handleActivityFetchFinish(success);
|
||||
}
|
||||
|
||||
protected ActivityTrackExporter createExporter() {
|
||||
private ActivityTrackExporter createExporter() {
|
||||
GPXExporter exporter = new GPXExporter();
|
||||
exporter.setCreator(GBApplication.app().getNameAndVersion());
|
||||
return exporter;
|
||||
@ -169,7 +169,6 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation {
|
||||
} else {
|
||||
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
|
||||
handleActivityFetchFinish(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,8 +43,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSportsActivityType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.BipActivityType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -141,7 +141,7 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((byte) (lastPacketCounter + 1) == value[0] ) {
|
||||
if ((byte) (lastPacketCounter + 1) == value[0]) {
|
||||
lastPacketCounter++;
|
||||
bufferActivityData(value);
|
||||
} else {
|
||||
@ -154,6 +154,7 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
/**
|
||||
* Buffers the given activity summary data. If the total size is reached,
|
||||
* it is converted to an object and saved in the database.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Override
|
||||
@ -166,14 +167,14 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(stream.toByteArray()).order(ByteOrder.LITTLE_ENDIAN);
|
||||
// summary.setVersion(BLETypeConversions.toUnsigned(buffer.getShort()));
|
||||
short version = buffer.getShort(); // version
|
||||
LOG.debug("Got verison " + version);
|
||||
LOG.debug("Got sport summary version " + version + "total bytes=" + buffer.capacity());
|
||||
int activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
try {
|
||||
int rawKind = BLETypeConversions.toUnsigned(buffer.getShort());
|
||||
BipActivityType activityType = BipActivityType.fromCode(rawKind);
|
||||
HuamiSportsActivityType activityType = HuamiSportsActivityType.fromCode(rawKind);
|
||||
activityKind = activityType.toActivityKind();
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Error mapping acivity kind: " + ex.getMessage(), ex);
|
||||
LOG.error("Error mapping activity kind: " + ex.getMessage(), ex);
|
||||
}
|
||||
summary.setActivityKind(activityKind);
|
||||
|
||||
@ -197,50 +198,123 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
summary.setBaseLongitude(baseLongitude);
|
||||
summary.setBaseLatitude(baseLatitude);
|
||||
summary.setBaseAltitude(baseAltitude);
|
||||
|
||||
// unused data (for now)
|
||||
float distanceMeters = buffer.getFloat();
|
||||
float ascentMeters = buffer.getFloat();
|
||||
float descentMeters = buffer.getFloat();
|
||||
float maxAltitude = buffer.getFloat();
|
||||
float minAltitude = buffer.getFloat();
|
||||
int maxLatitude = buffer.getInt(); // format?
|
||||
int minLatitude = buffer.getInt(); // format?
|
||||
int maxLongitude = buffer.getInt(); // format?
|
||||
int minLongitude = buffer.getInt(); // format?
|
||||
int steps = buffer.getInt();
|
||||
int activeSeconds = buffer.getInt();
|
||||
float caloriesBurnt = buffer.getFloat();
|
||||
float maxSpeed = buffer.getFloat();
|
||||
float minPace = buffer.getFloat(); // format?
|
||||
float maxPace = buffer.getFloat(); // format?
|
||||
float totalStride = buffer.getFloat();
|
||||
|
||||
buffer.getInt(); // unknown
|
||||
|
||||
if (activityKind == ActivityKind.TYPE_SWIMMING) {
|
||||
// 28 bytes
|
||||
float averageStrokeDistance = buffer.getFloat();
|
||||
float averageStrokesPerSecond = buffer.getFloat();
|
||||
float averageLapPace = buffer.getFloat();
|
||||
short strokes = buffer.getShort();
|
||||
short swolfIndex = buffer.getShort();
|
||||
byte swimStyle = buffer.get();
|
||||
byte laps = buffer.get();
|
||||
buffer.getInt(); // unknown
|
||||
buffer.getInt(); // unknown
|
||||
buffer.getShort(); // unknown
|
||||
|
||||
LOG.debug("unused swim data:" +
|
||||
"\naverageStrokeDistance=" + averageStrokeDistance +
|
||||
"\naverageStrokesPerSecond=" + averageStrokesPerSecond +
|
||||
"\naverageLapPace" + averageLapPace +
|
||||
"\nstrokes=" + strokes +
|
||||
"\nswolfIndex=" + swolfIndex +
|
||||
"\nswimStyle=" + swimStyle + // 1 = breast, 2 = freestyle
|
||||
"\nlaps=" + laps +
|
||||
""
|
||||
);
|
||||
} else {
|
||||
// 28 bytes
|
||||
buffer.getInt(); // unknown
|
||||
buffer.getInt(); // unknown
|
||||
int ascentSeconds = buffer.getInt() / 1000; //ms?
|
||||
buffer.getInt(); // unknown;
|
||||
int descentSeconds = buffer.getInt() / 1000; //ms?
|
||||
buffer.getInt(); // unknown;
|
||||
int flatSeconds = buffer.getInt() / 1000; // ms?
|
||||
LOG.debug("unused non-swim data:" +
|
||||
"\nascentSeconds=" + ascentSeconds +
|
||||
"\ndescentSeconds=" + descentSeconds +
|
||||
"\nflatSeconds=" + flatSeconds +
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
short averageHR = buffer.getShort();
|
||||
short averageKMPaceSeconds = buffer.getShort();
|
||||
short averageStride = buffer.getShort();
|
||||
|
||||
LOG.debug("unused common:" +
|
||||
"\ndistanceMeters=" + distanceMeters +
|
||||
"\nascentMeters=" + ascentMeters +
|
||||
"\ndescentMeters=" + descentMeters +
|
||||
"\nmaxAltitude=" + maxAltitude +
|
||||
"\nminAltitude=" + minAltitude +
|
||||
//"\nmaxLatitude=" + maxLatitude + // not useful
|
||||
//"\nminLatitude=" + minLatitude + // not useful
|
||||
//"\nmaxLongitude=" + maxLongitude + // not useful
|
||||
//"\nminLongitude=" + minLongitude + // not useful
|
||||
"\nsteps=" + steps +
|
||||
"\nactiveSeconds=" + activeSeconds +
|
||||
"\ncaloriesBurnt=" + caloriesBurnt +
|
||||
"\nmaxSpeed=" + maxSpeed +
|
||||
"\nminPace=" + minPace +
|
||||
"\nmaxPace=" + maxPace +
|
||||
"\ntotalStride=" + totalStride +
|
||||
"\naverageHR=" + averageHR +
|
||||
"\naverageKMPaceSeconds=" + averageKMPaceSeconds +
|
||||
"\naverageStride=" + averageStride +
|
||||
""
|
||||
);
|
||||
|
||||
// summary.setBaseCoordinate(new GPSCoordinate(baseLatitude, baseLongitude, baseAltitude));
|
||||
|
||||
// summary.setDistanceMeters(Float.intBitsToFloat(buffer.getInt()));
|
||||
// summary.setAscentMeters(Float.intBitsToFloat(buffer.getInt()));
|
||||
// summary.setDescentMeters(Float.intBitsToFloat(buffer.getInt()));
|
||||
//
|
||||
// summary.setMinAltitude(Float.intBitsToFloat(buffer.getInt()));
|
||||
// summary.setMaxAltitude(Float.intBitsToFloat(buffer.getInt()));
|
||||
// summary.setMinLatitude(buffer.getInt());
|
||||
// summary.setMaxLatitude(buffer.getInt());
|
||||
// summary.setMinLongitude(buffer.getInt());
|
||||
// summary.setMaxLongitude(buffer.getInt());
|
||||
//
|
||||
// summary.setSteps(BLETypeConversions.toUnsigned(buffer.getInt()));
|
||||
// summary.setActiveTimeSeconds(BLETypeConversions.toUnsigned(buffer.getInt()));
|
||||
//
|
||||
// summary.setCaloriesBurnt(Float.intBitsToFloat(buffer.get()));
|
||||
// summary.setMaxSpeed(Float.intBitsToFloat(buffer.get()));
|
||||
// summary.setMinPace(Float.intBitsToFloat(buffer.get()));
|
||||
// summary.setMaxPace(Float.intBitsToFloat(buffer.get()));
|
||||
// summary.setTotalStride(Float.intBitsToFloat(buffer.get()));
|
||||
|
||||
buffer.getInt(); //
|
||||
buffer.getInt(); //
|
||||
buffer.getInt(); //
|
||||
|
||||
// summary.setTimeAscent(BLETypeConversions.toUnsigned(buffer.getInt()));
|
||||
// buffer.getInt(); //
|
||||
// summary.setTimeDescent(BLETypeConversions.toUnsigned(buffer.getInt()));
|
||||
// buffer.getInt(); //
|
||||
// summary.setTimeFlat(BLETypeConversions.toUnsigned(buffer.getInt()));
|
||||
//
|
||||
// summary.setAverageHR(BLETypeConversions.toUnsigned(buffer.getShort()));
|
||||
//
|
||||
// summary.setAveragePace(BLETypeConversions.toUnsigned(buffer.getShort()));
|
||||
// summary.setAverageStride(BLETypeConversions.toUnsigned(buffer.getShort()));
|
||||
|
||||
buffer.getShort(); //
|
||||
// summary.setDistanceMeters(distanceMeters);
|
||||
// summary.setAscentMeters(ascentMeters);
|
||||
// summary.setDescentMeters(descentMeters);
|
||||
// summary.setMinAltitude(maxAltitude);
|
||||
// summary.setMaxAltitude(maxAltitude);
|
||||
// summary.setMinLatitude(minLatitude);
|
||||
// summary.setMaxLatitude(maxLatitude);
|
||||
// summary.setMinLongitude(minLatitude);
|
||||
// summary.setMaxLongitude(maxLatitude);
|
||||
// summary.setSteps(steps);
|
||||
// summary.setActiveTimeSeconds(secondsActive);
|
||||
// summary.setCaloriesBurnt(caloriesBurnt);
|
||||
// summary.setMaxSpeed(maxSpeed);
|
||||
// summary.setMinPace(minPace);
|
||||
// summary.setMaxPace(maxPace);
|
||||
// summary.setTotalStride(totalStride);
|
||||
// summary.setTimeAscent(BLETypeConversions.toUnsigned(ascentSeconds);
|
||||
// summary.setTimeDescent(BLETypeConversions.toUnsigned(descentSeconds);
|
||||
// summary.setTimeFlat(BLETypeConversions.toUnsigned(flatSeconds);
|
||||
// summary.setAverageHR(BLETypeConversions.toUnsigned(averageHR);
|
||||
// summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace);
|
||||
// summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride);
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return getDevice().getAddress() + "_" + "lastSportsActivityTimeMillis";
|
||||
return "lastSportsActivityTimeMillis";
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -36,18 +36,17 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipS
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.AbstractFetchOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipFetchLogsOperation.class);
|
||||
public class HuamiFetchDebugLogsOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuamiFetchDebugLogsOperation.class);
|
||||
|
||||
private FileOutputStream logOutputStream;
|
||||
|
||||
public AmazfitBipFetchLogsOperation(AmazfitBipSupport support) {
|
||||
public HuamiFetchDebugLogsOperation(AmazfitBipSupport support) {
|
||||
super(support);
|
||||
setName("fetch logs");
|
||||
setName("fetch debug logs");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -60,7 +59,7 @@ public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
|
||||
}
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
|
||||
String filename = "amazfitbip_" + dateFormat.format(new Date()) + ".log";
|
||||
String filename = "huamidebug_" + dateFormat.format(new Date()) + ".log";
|
||||
|
||||
File outputFile = new File(dir, filename );
|
||||
try {
|
@ -16,16 +16,16 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
@ -36,6 +36,7 @@ import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
|
||||
@ -50,21 +51,25 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
||||
private final TransactionBuilder builder;
|
||||
private final boolean needsAuth;
|
||||
private final byte authFlags;
|
||||
private final byte cryptFlags;
|
||||
private final HuamiSupport huamiSupport;
|
||||
|
||||
public InitOperation(boolean needsAuth, byte authFlags, HuamiSupport support, TransactionBuilder builder) {
|
||||
public InitOperation(boolean needsAuth, byte authFlags, byte cryptFlags, HuamiSupport support, TransactionBuilder builder) {
|
||||
super(support);
|
||||
this.huamiSupport = support;
|
||||
this.needsAuth = needsAuth;
|
||||
this.authFlags = authFlags;
|
||||
this.cryptFlags = cryptFlags;
|
||||
this.builder = builder;
|
||||
builder.setGattCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
getSupport().enableNotifications(builder, true);
|
||||
protected void doPerform() {
|
||||
huamiSupport.enableNotifications(builder, true);
|
||||
if (needsAuth) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
|
||||
// write key to miband2
|
||||
// write key to device
|
||||
byte[] sendKey = org.apache.commons.lang3.ArrayUtils.addAll(new byte[]{HuamiService.AUTH_SEND_KEY, authFlags}, getSecretKey());
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), sendKey);
|
||||
} else {
|
||||
@ -75,15 +80,32 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
||||
}
|
||||
|
||||
private byte[] requestAuthNumber() {
|
||||
return new byte[]{HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER, authFlags};
|
||||
if (cryptFlags == 0x00) {
|
||||
return new byte[]{HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER, authFlags};
|
||||
} else {
|
||||
return new byte[]{(byte) (cryptFlags | HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER), authFlags, 0x02};
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getSecretKey() {
|
||||
return new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45};
|
||||
byte[] authKeyBytes = new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45};
|
||||
|
||||
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
||||
|
||||
String authKey = sharedPrefs.getString("authkey", null);
|
||||
if (authKey != null && !authKey.isEmpty()) {
|
||||
byte[] srcBytes = authKey.getBytes();
|
||||
if (authKey.length() == 34 && authKey.substring(0, 2).equals("0x")) {
|
||||
srcBytes = GB.hexStringToByteArray(authKey.substring(2));
|
||||
}
|
||||
System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
|
||||
}
|
||||
|
||||
return authKeyBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionBuilder performInitialized(String taskName) throws IOException {
|
||||
public TransactionBuilder performInitialized(String taskName) {
|
||||
throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
|
||||
}
|
||||
|
||||
@ -94,41 +116,40 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
||||
if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
|
||||
try {
|
||||
byte[] value = characteristic.getValue();
|
||||
getSupport().logMessageContent(value);
|
||||
huamiSupport.logMessageContent(value);
|
||||
if (value[0] == HuamiService.AUTH_RESPONSE &&
|
||||
value[1] == HuamiService.AUTH_SEND_KEY &&
|
||||
value[2] == HuamiService.AUTH_SUCCESS) {
|
||||
TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the band");
|
||||
TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the device");
|
||||
builder.write(characteristic, requestAuthNumber());
|
||||
getSupport().performImmediately(builder);
|
||||
huamiSupport.performImmediately(builder);
|
||||
} else if (value[0] == HuamiService.AUTH_RESPONSE &&
|
||||
value[1] == HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER &&
|
||||
(value[1] & 0x0f) == HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER &&
|
||||
value[2] == HuamiService.AUTH_SUCCESS) {
|
||||
// md5??
|
||||
byte[] eValue = handleAESAuth(value, getSecretKey());
|
||||
byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll(
|
||||
new byte[]{HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER, authFlags}, eValue);
|
||||
new byte[]{(byte) (HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER | cryptFlags), authFlags}, eValue);
|
||||
|
||||
TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the band");
|
||||
TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the device");
|
||||
builder.write(characteristic, responseValue);
|
||||
getSupport().setCurrentTimeWithService(builder);
|
||||
getSupport().performImmediately(builder);
|
||||
huamiSupport.setCurrentTimeWithService(builder);
|
||||
huamiSupport.performImmediately(builder);
|
||||
} else if (value[0] == HuamiService.AUTH_RESPONSE &&
|
||||
value[1] == HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER &&
|
||||
(value[1] & 0x0f) == HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER &&
|
||||
value[2] == HuamiService.AUTH_SUCCESS) {
|
||||
TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
getSupport().requestDeviceInfo(builder);
|
||||
getSupport().enableFurtherNotifications(builder, true);
|
||||
getSupport().phase2Initialize(builder);
|
||||
getSupport().phase3Initialize(builder);
|
||||
getSupport().setInitialized(builder);
|
||||
getSupport().performImmediately(builder);
|
||||
huamiSupport.requestDeviceInfo(builder);
|
||||
huamiSupport.enableFurtherNotifications(builder, true);
|
||||
huamiSupport.phase2Initialize(builder);
|
||||
huamiSupport.phase3Initialize(builder);
|
||||
huamiSupport.setInitialized(builder);
|
||||
huamiSupport.performImmediately(builder);
|
||||
} else {
|
||||
return super.onCharacteristicChanged(gatt, characteristic);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GB.toast(getContext(), "Error authenticating Mi Band 2", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
GB.toast(getContext(), "Error authenticating Huami device", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@ -137,17 +158,11 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getMD5(byte[] message) throws NoSuchAlgorithmException {
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
return md5.digest(message);
|
||||
}
|
||||
|
||||
private byte[] handleAESAuth(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
|
||||
byte[] mValue = Arrays.copyOfRange(value, 3, 19);
|
||||
Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
@SuppressLint("GetInstance") Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES");
|
||||
ecipher.init(Cipher.ENCRYPT_MODE, newKey);
|
||||
byte[] enc = ecipher.doFinal(mValue);
|
||||
return enc;
|
||||
return ecipher.doFinal(mValue);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user