1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-26 16:26:52 +02:00
This commit is contained in:
ksiwczynski 2019-08-17 02:07:55 +02:00
commit 0f6ef140e2
213 changed files with 8181 additions and 3292 deletions

View File

@ -1,3 +1,6 @@
os: linux
dist: trusty
language: android
jdk:

View File

@ -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

View File

@ -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?

View File

@ -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")
}
}
}

View File

@ -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"

View File

@ -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;

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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.

View File

@ -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",
};
}

View File

@ -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);

View File

@ -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();

View File

@ -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:

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
});
}
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -28,5 +28,6 @@ public class GBDeviceEventCallControl extends GBDeviceEvent {
OUTGOING,
REJECT,
START,
IGNORE,
}
}

View File

@ -152,4 +152,10 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
@Override
public boolean supportsUnicodeEmojis() { return false; }
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return null;
}
}

View File

@ -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);
}

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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;
}

View File

@ -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};

View File

@ -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
};
}
}

View File

@ -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};
}
}

View File

@ -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};
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -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
};
}
}

View File

@ -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
};
}
}

View File

@ -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};

View File

@ -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
};
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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";

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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));

View File

@ -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
}
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2017-2019 AndrewH, Carsten Pfeiffer, Daniele Gobbetti,
Dikay900
Dikay900, Nick Spacek
This file is part of Gadgetbridge.

View File

@ -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) {

View File

@ -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("")) {

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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());

View File

@ -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) {

View File

@ -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);

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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;
}

View File

@ -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.

View File

@ -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.

View File

@ -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;

View File

@ -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.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
/* Copyright (C) 2019 Andreas Böhler
This file is part of Gadgetbridge.

View File

@ -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.

View File

@ -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.

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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,");

View File

@ -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);

View File

@ -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;
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -203,6 +203,6 @@ public class FetchActivityOperation extends AbstractFetchOperation {
@Override
protected String getLastSyncTimeKey() {
return getDevice().getAddress() + "_" + "lastSyncTimeMillis";
return "lastSyncTimeMillis";
}
}

View File

@ -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;
}
}

View File

@ -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";
}
}

View File

@ -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 {

View File

@ -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