1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-26 16:26:52 +02:00

Merge branch 'master' into fossil-q-hybrid

This commit is contained in:
Andreas Shimokawa 2019-10-12 20:47:19 +02:00 committed by GitHub
commit 074994d24b
51 changed files with 1429 additions and 190 deletions

View File

@ -1,7 +1,18 @@
### Changelog
#### Version 0.36.3
* Basic Makibes HR3 support
#### Version 0.37.0
* Initial Makibes HR3 support
* Amazfit Bip Lite: Inittal working support, firmware update is disabled for now (we do not have any firmware for testing)
* Amazfit Cor 2: Enable Emoji Font setting and 3rd party HR access
* Find Phone now also vibration in addition to playing the ring tone
* ID115: All settings are now per-device
* Time format settings are now per-device for all supported devices
* Wrist location settings are now per-device for all supported devices
* Work around broken layout in database management activity
* Show toast in case no app is installed which can handle GPX files
* Mi Band 4/Amazfit Bip Lite: Trim white spaces and new lines from auth key
* Mi Band 4/Amazfit Bip Lite: Display a toast and do not try to pair if there was no auth key supplied
* Skip service scan if supported device could be recognized without uuids during discovery
#### Version 0.36.2
* Amazfit Bip: Untested support for Lite variant

View File

@ -45,7 +45,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
Schema schema = new Schema(20, MAIN_PACKAGE + ".entities");
Schema schema = new Schema(21, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -60,6 +60,7 @@ public class GBDaoGenerator {
Entity tag = addTag(schema);
Entity userDefinedActivityOverlay = addActivityDescription(schema, tag, user);
addMakibesHR3ActivitySample(schema, user, device);
addMiBandActivitySample(schema, user, device);
addPebbleHealthActivitySample(schema, user, device);
addPebbleHealthActivityKindOverlay(schema, user, device);
@ -186,6 +187,16 @@ public class GBDaoGenerator {
return deviceAttributes;
}
private static Entity addMakibesHR3ActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "MakibesHR3ActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
return activitySample;
}
private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "MiBandActivitySample");
activitySample.implementsSerializable();
@ -363,7 +374,7 @@ public class GBDaoGenerator {
alarm.addBooleanProperty("smartWakeup").notNull();
alarm.addIntProperty("repetition").notNull().codeBeforeGetter(
"public boolean isRepetitive() { return getRepetition() != ALARM_ONCE; } " +
"public boolean getRepetition(int dow) { return (this.repetition & dow) > 0; }"
"public boolean getRepetition(int dow) { return (this.repetition & dow) > 0; }"
);
alarm.addIntProperty("hour").notNull();
alarm.addIntProperty("minute").notNull();

View File

@ -30,28 +30,30 @@ vendor's servers.
[List of changes](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/master/CHANGELOG.md)
## Supported Devices
## Supported Devices (Some of them WIP and some of them without maintainer)
* Amazfit Bip [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
* Amazfit Bip Lite (NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT AND ROOT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip-Lite)
* 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)
* Casio GB-6900B
* HPlus Devices (e.g. ZeBand) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/HPlus)
* ID115 (WIP)
* Lenovo Watch 9 (WIP)
* Liveview (WIP)
* ID115
* Lenovo Watch 9
* Liveview
* Makibes HR3
* 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 Band 4 (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)
* NO.1 F1
* 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)
* Teclast H10, H30 (WIP)
* Teclast H10, H30
* XWatch (Affordable Chinese Casio-like smartwatches)
* Vibratissimo (experimental)
* ZeTime (WIP) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime)
* ZeTime [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime)
* Fossil Q Hybrid
* Skagen Connected
@ -113,6 +115,7 @@ Please [this wiki article](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki
* Andreas Böhler (Casio GB-6900B)
* Jean-François Greffier (Mi Scale 2)
* Johannes Schmitt (BFH-16)
* Lukas Schwichtenberg (Makibes HR3)
## Contribute

View File

@ -25,7 +25,7 @@ android {
targetSdkVersion 27
// Note: always bump BOTH versionCode and versionName!
versionName "0.36.3"
versionName "0.37.0"
versionCode 158
vectorDrawables.useSupportLibrary = true
}

View File

@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.VIBRATE" /> <!-- Used for reverse find device -->
<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />

View File

@ -80,6 +80,7 @@ public class ConfigureAlarms extends AbstractGBActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQ_CONFIGURE_ALARM) {
avoidSendAlarmsToDevice = false;
updateAlarmsFromDB();

View File

@ -35,6 +35,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
@ -195,8 +196,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
private final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
LOG.warn(device.getName() + ": " + ((scanRecord != null) ? scanRecord.length : -1));
logMessageContent(scanRecord);
//logMessageContent(scanRecord);
handleDeviceFound(device, (short) rssi);
}
};
@ -338,6 +338,12 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
}
private void handleDeviceFound(BluetoothDevice device, short rssi) {
if (device.getName() != null) {
if (handleDeviceFound(device,rssi, null)) {
LOG.info("found supported device " + device.getName() + " without scanning services, skipping service scan.");
return;
}
}
ParcelUuid[] uuids = device.getUuids();
if (uuids == null) {
if (device.fetchUuidsWithSdp()) {
@ -349,7 +355,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
}
private void handleDeviceFound(BluetoothDevice device, short rssi, ParcelUuid[] uuids) {
private boolean handleDeviceFound(BluetoothDevice device, short rssi, ParcelUuid[] uuids) {
LOG.debug("found device: " + device.getName() + ", " + device.getAddress());
if (LOG.isDebugEnabled()) {
if (uuids != null && uuids.length > 0) {
@ -359,7 +365,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
}
}
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
return; // ignore already bonded devices
return true; // ignore already bonded devices
}
GBDeviceCandidate candidate = new GBDeviceCandidate(device, rssi, uuids);
@ -374,7 +380,9 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
deviceCandidates.add(candidate);
}
cadidateListAdapter.notifyDataSetChanged();
return true;
}
return false;
}
/**
@ -620,6 +628,17 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
stopDiscovery();
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
LOG.info("Using device candidate " + deviceCandidate + " with coordinator: " + coordinator.getClass());
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceCandidate.getMacAddress());
String authKey = sharedPrefs.getString("authkey", null);
if (authKey == null || authKey.isEmpty() || authKey.getBytes().length < 34 || !authKey.substring(0, 2).equals("0x")) {
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_need_to_enter_authkey), Toast.LENGTH_LONG, GB.WARN);
return;
}
}
Class<? extends Activity> pairingActivity = coordinator.getPairingActivity();
if (pairingActivity != null) {
Intent intent = new Intent(this, pairingActivity);
@ -627,7 +646,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
startActivity(intent);
} else {
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
int bondingStyle = coordinator.getBondingStyle(device);
int bondingStyle = coordinator.getBondingStyle();
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE) {
LOG.info("No bonding needed, according to coordinator, so connecting right away");
connectAndFinish(device);

View File

@ -25,7 +25,10 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.view.View;
import android.widget.Button;
@ -58,6 +61,7 @@ public class FindPhoneActivity extends AbstractGBActivity {
}
};
Vibrator mVibrator;
AudioManager mAudioManager;
int userVolume;
MediaPlayer mp;
@ -79,10 +83,26 @@ public class FindPhoneActivity extends AbstractGBActivity {
finish();
}
});
vibrate();
playRingtone();
}
public void playRingtone(){
private void vibrate(){
mVibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
long[] vibrationPattern = new long[]{ 1000, 1000 };
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
VibrationEffect vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, 0);
mVibrator.vibrate(vibrationEffect);
} else {
mVibrator.vibrate(vibrationPattern, 0);
}
}
private void playRingtone(){
mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager != null) {
userVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
@ -107,7 +127,11 @@ public class FindPhoneActivity extends AbstractGBActivity {
}
}
public void stopSound() {
private void stopVibration() {
mVibrator.cancel();
}
private void stopSound() {
mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND);
mp.stop();
mp.reset();
@ -116,7 +140,10 @@ public class FindPhoneActivity extends AbstractGBActivity {
@Override
protected void onDestroy() {
super.onDestroy();
stopVibration();
stopSound();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
unregisterReceiver(mReceiver);
}

View File

@ -346,7 +346,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
renderCharts();
// have to enable it again and again to keep it measureing
// have to enable it again and again to keep it measuring
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
}

View File

@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
@ -369,15 +370,25 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
});
}
EditTextPreference pref = findPreference(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
if (pref != null) {
pref.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
EditTextPreference mibandTimeOffset = findPreference(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
if (mibandTimeOffset != null) {
mibandTimeOffset.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
@Override
public void onBindEditText(@NonNull EditText editText) {
editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
});
}
EditTextPreference findPhoneDuration = findPreference(MakibesHR3Constants.PREF_FIND_PHONE_DURATION);
if (findPhoneDuration != null) {
findPhoneDuration.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
@Override
public void onBindEditText(@NonNull EditText editText) {
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
}
});
}
}
static DeviceSpecificSettingsFragment newInstance(String settingsFileSuffix, @NonNull int[] supportedSettings) {

View File

@ -136,7 +136,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice device) {
public int getBondingStyle() {
return BONDING_STYLE_ASK;
}
@ -159,6 +159,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false;
}
@NonNull
@Override
public int[] getColorPresets() {
return new int[0];

View File

@ -29,7 +29,6 @@ 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;
@ -63,6 +62,11 @@ public interface DeviceCoordinator {
*/
int BONDING_STYLE_ASK = 2;
/**
* A secret key has to be entered before connecting
*/
int BONDING_STYLE_REQUIRE_KEY = 3;
/**
* Checks whether this coordinator handles the given candidate.
* Returns the supported device type for the given candidate or
@ -224,9 +228,8 @@ public interface DeviceCoordinator {
/**
* Returns how/if the given device should be bonded before connecting to it.
* @param device
*/
int getBondingStyle(GBDevice device);
int getBondingStyle();
/**
* Indicates whether the device has some kind of calender we can sync to.

View File

@ -93,6 +93,7 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
sampleProvider = new UnknownSampleProvider();
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
return DeviceType.UNKNOWN;
@ -197,6 +198,7 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
return false;
}
@NonNull
@Override
public int[] getColorPresets() {
return new int[0];

View File

@ -58,7 +58,7 @@ public class CasioGB6900DeviceCoordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice deviceCandidate){
public int getBondingStyle(){
return BONDING_STYLE_BOND;
}

View File

@ -84,7 +84,7 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice deviceCandidate){
public int getBondingStyle(){
return BONDING_STYLE_NONE;
}

View File

@ -57,4 +57,9 @@ public class AmazfitBipLiteCoordinator extends AmazfitBipCoordinator {
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_REQUIRE_KEY;
}
}

View File

@ -87,8 +87,11 @@ public class AmazfitCor2Coordinator extends HuamiCoordinator {
return new int[]{
R.xml.devicesettings_amazfitcor,
R.xml.devicesettings_wearlocation,
R.xml.devicesettings_custom_emoji_font,
R.xml.devicesettings_liftwrist_display,
R.xml.devicesettings_disconnectnotification,
R.xml.devicesettings_pairingkey};
R.xml.devicesettings_expose_hr_thirdparty,
R.xml.devicesettings_pairingkey
};
}
}

View File

@ -46,11 +46,6 @@ public class MiBand2Coordinator extends HuamiCoordinator {
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
if (candidate.supportsService(HuamiService.UUID_SERVICE_MIBAND2_SERVICE)) {
return DeviceType.MIBAND2;
}
// and a heuristic for now
try {
BluetoothDevice device = candidate.getDevice();
String name = device.getName();

View File

@ -97,4 +97,9 @@ public class MiBand4Coordinator extends HuamiCoordinator {
R.xml.devicesettings_pairingkey
};
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_REQUIRE_KEY;
}
}

View File

@ -66,7 +66,7 @@ public class ID115Coordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice deviceCandidate){
public int getBondingStyle(){
return BONDING_STYLE_NONE;
}

View File

@ -70,6 +70,7 @@ public class BFH16DeviceCoordinator extends AbstractDeviceCoordinator
return Collections.singletonList(filter);
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
@ -85,7 +86,7 @@ public class BFH16DeviceCoordinator extends AbstractDeviceCoordinator
}
@Override
public int getBondingStyle(GBDevice deviceCandidate){
public int getBondingStyle(){
return BONDING_STYLE_NONE;
}

View File

@ -82,7 +82,7 @@ public class TeclastH30Coordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice deviceCandidate){
public int getBondingStyle(){
return BONDING_STYLE_NONE;
}

View File

@ -21,18 +21,19 @@ import java.util.UUID;
public final class MakibesHR3Constants {
// TODO: This doesn't belong here, but I don't want to touch other files to avoid
// TODO: breaking someone else's device support.
public static final String PREF_HEADS_UP_SCREEN = "activate_display_on_lift_wrist";
public static final String PREF_LOST_REMINDER = "disconnect_notification";
public static final String PREF_DO_NOT_DISTURB = "do_not_disturb_no_auto";
public static final String PREF_DO_NOT_DISTURB_START = "do_not_disturb_no_auto_start";
public static final String PREF_DO_NOT_DISTURB_END = "do_not_disturb_no_auto_end";
public static final String PREF_FIND_PHONE = "prefs_find_phone";
public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration";
public static final UUID UUID_SERVICE = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
// time
// mode ab:00:04:ff:7c:80:** (00: 24h, 01: 12h)
// confirm write?
// ab:00:09:ff:52:80:00:13:06:09:0f:0b
// disconnect?
// ab:00:03:ff:ff:80
public static final UUID UUID_CHARACTERISTIC_REPORT = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
// Services and Characteristics
// 00001801-0000-1000-8000-00805f9b34fb
@ -44,8 +45,8 @@ public final class MakibesHR3Constants {
// 00002a04-0000-1000-8000-00805f9b34fb
// 00002aa6-0000-1000-8000-00805f9b34fb
// 6e400001-b5a3-f393-e0a9-e50e24dcca9e // Nordic UART Service
// 6e400002-b5a3-f393-e0a9-e50e24dcca9e // control
// 6e400003-b5a3-f393-e0a9-e50e24dcca9e
// 6e400002-b5a3-f393-e0a9-e50e24dcca9e // control (RX)
// 6e400003-b5a3-f393-e0a9-e50e24dcca9e // report
// 0000fee7-0000-1000-8000-00805f9b34fb
// 0000fec9-0000-1000-8000-00805f9b34fb
// 0000fea1-0000-1000-8000-00805f9b34fb
@ -54,11 +55,8 @@ public final class MakibesHR3Constants {
// Command structure
// ab 00 [argument_count] ff [command] 80 [arguments]
// where [argument_count] is [arguments].length + 3
// 80 might by different.
// refresh sends
// 51
// 52
// 93 (CMD_SET_DATE_TIME)
public static final byte[] DATA_TEMPLATE = {
(byte) 0xab,
@ -74,37 +72,137 @@ public final class MakibesHR3Constants {
public static final int DATA_COMMAND_INDEX = 4;
public static final int DATA_ARGUMENTS_INDEX = 6;
// blood oxygen percentage
public static final byte[] RPRT_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x12 };
// blood oxygen percentage
// blood oxygen percentage
public static final byte[] RPRT_SINGLE_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x11 };
// steps might take up more bytes. I don't know which ones and I won't walk that much.
// Only sent after we send CMD_51
// 00 (maybe also used for steps)
// [steps hi]
// [steps lo]
// 00
// 00
// 01 (also was 0b. Maybe minutes of activity.)
// 00
// 00
// 00
// 00
// 00
public static final byte[] RPRT_FITNESS = new byte[]{ (byte) 0x51, 0x08 };
// year (+2000)
// month
// day
// hour
// minute
// heart rate
// heart rate
public static final byte[] RPRT_HEART_RATE_SAMPLE = new byte[]{ (byte) 0x51, (byte) 0x11 };
// WearFit says "walking" in the step details. This is probably also in here, but
// I don't run :O
// year (+2000)
// month
// day
// hour (start of measurement. interval is 1h. Might be longer when running.)
// 00 (either used for steps or minute)
// accumulated steps (hi)
// accumulated steps (lo)
// 00
// 00
// ?? (changes whenever steps change. Ranges from 00 to 16.)
// 00
// 00
// 00
// 00
public static final byte[] RPRT_STEPS_SAMPLE = new byte[]{ (byte) 0x51, (byte) 0x20 };
// enable (00/01)
public static final byte RPRT_REVERSE_FIND_DEVICE = (byte) 0x7d;
// The proximity sensor sees air..
public static final byte ARG_HEARTRATE_NO_TARGET = (byte) 0xff;
// The hr sensor didn't find the heart rate yet.
public static final byte ARG_HEARTRATE_NO_READING = (byte) 0x00;
// heart rate
public static final byte RPRT_HEARTRATE = (byte) 0x84;
// charging (00/01)
// battery percentage (step size is 20).
public static final byte RPRT_BATTERY = (byte) 0x91;
// firmware_major
// firmware_minor
// 37
// 00
// 00
// 00
// 00
// 00
// 00
// 20
// 0e
public static final byte RPRT_SOFTWARE = (byte) 0x92;
// 00
public static final byte CMD_FACTORY_RESET = (byte) 0x23;
// 00
// year (+2000)
// month
// day
// 0b
// 00
// year (+2000)
// month
// day
// 0b
// 19
public static final byte CMD_UNKNOWN_51 = (byte) 0x51;
// enable (00/01)
public static final byte[] CMD_SET_REAL_TIME_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x12 };
// this is the last command sent on sync
// After disabling, the watch replies with RPRT_SINGLE_BLOOD_OXYGEN
// enable (00/01)
public static final byte[] CMD_SET_SINGLE_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x11 };
// device replies with
// {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE}
// {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE} (Only if steps are non-zero)
// {@link MakibesHR3Constants#RPRT_FITNESS}
// there are also multiple 6 * 00 reports
// 00
// year (+2000)
// month
// 14 this isn't the current day
// year (+2000) steps after
// month steps after
// day steps after
// hour steps after
// minute steps after
// year (+2000) heart rate after
// month heart rate after
// day heart rate after
// hour heart rate after
// minute heart rate after
public static final byte CMD_REQUEST_FITNESS = (byte) 0x51;
// Manually sending this doesn't yield a reply. The heart rate history is sent in response to
// CMD_CMD_REQUEST_FITNESS.
// 00
// year (+2000) (probably not current)
// month (not current!)
// day (not current!)
// hour (current)
// minute (current)
public static final byte CMD_UNKNOWN_52 = (byte) 0x52;
public static final byte CMD_52 = (byte) 0x52;
// vibrates 6 times
public static final byte CMD_FIND_DEVICE = (byte) 0x71;
// WearFit writes uses other sources as well. They don't do anything though.
public static final byte ARG_SEND_NOTIFICATION_SOURCE_CALL = (byte) 0x01;
public static final byte ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL = (byte) 0x02;
public static final byte ARG_SEND_NOTIFICATION_SOURCE_MESSAGE = (byte) 0x03;
@ -118,27 +216,33 @@ public final class MakibesHR3Constants {
public static final byte ARG_SEND_NOTIFICATION_SOURCE_WEIBO = (byte) 0x13;
public static final byte ARG_SEND_NOTIFICATION_SOURCE_KAKOTALK = (byte) 0x14;
// ARG_SET_NOTIFICATION_SOURCE_*
// 02
// 02 (This is 00 and 01 during connection. I don't know what it does. Maybe clears notifications?)
// ASCII
public static final byte CMD_SEND_NOTIFICATION = (byte) 0x72;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_WEEKDAY = (byte) 0x1F;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_CUSTOM = (byte) 0x40;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_EVERY_DAY = (byte) 0x7F;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_ONE_TIME = (byte) 0x80;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_MONDAY = (byte) 0x01;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_TUESDAY = (byte) 0x02;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_WEDNESDAY = (byte) 0x04;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_THURSDAY = (byte) 0x08;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_FRIDAY = (byte) 0x10;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SATURDAY = (byte) 0x20;
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SUNDAY = (byte) 0x40;
// reminder id starting at 0
// enable (00/01)
// hour
// minute
// ARG_SET_ALARM_REMINDER_REPEAT_*
// bit field of ARG_SET_ALARM_REMINDER_REPEAT_*
public static final byte CMD_SET_ALARM_REMINDER = (byte) 0x73;
public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_MILES = (byte) 0x00;
public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_KILOMETERS = (byte) 0x01;
public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_LENGTH_INCHES = (byte) 0x00;
public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_LENGTH_CENTIMETERS = (byte) 0x01;
// step length (in/cm)
// step length (in/cm)
// age (years)
// height (in/cm)
@ -177,6 +281,10 @@ public final class MakibesHR3Constants {
public static final byte CMD_SET_HEADS_UP_SCREEN = (byte) 0x77;
// Looks like enable/disable.
public static final byte CMD_78 = (byte) 0x78;
// The watch enters photograph mode, but doesn't appear to send a trigger signal.
// enable (00/01)
public static final byte CMD_SET_PHOTOGRAPH_MODE = (byte) 0x79;
@ -188,14 +296,32 @@ public final class MakibesHR3Constants {
// 7b has 1 argument. Looks like enable/disable.
// 7e has 14 arguments.
public static final byte ARG_SET_TIMEMODE_24H = 0x00;
public static final byte ARG_SET_TIMEMODE_12H = 0x01;
// ARG_SET_TIMEMODE_*
public static final byte CMD_SET_TIMEMODE = (byte) 0x7c;
// 14 arguments. Watch might reply with RPRT_BATTERY.
public static final byte CMD_7e = (byte) 0x7e;
// 01
// fall hour
// fall minute
// awake hour
// awake minute
public static final byte CMD_SET_SLEEP_TIME = (byte) 0x7f;
// enable (00/01)
public static final byte CMD_SET_REAL_TIME_HEART_RATE = (byte) 0x84;
// looks like enable/disable.
public static final byte CMD_85 = (byte) 0x85;
// 00
// year hi
// year lo
@ -206,6 +332,16 @@ public final class MakibesHR3Constants {
// second
public static final byte CMD_SET_DATE_TIME = (byte) 0x93;
// 3 arguments. Sent when saving personal information.
public static final byte CMD_95 = (byte) 0x95;
// looks like enable/disable.
public static final byte CMD_96 = (byte) 0x96;
// looks like enable/disable.
public static final byte CMD_e5 = (byte) 0xe5;
// If this is sent after {@link CMD_FACTORY_RESET}, it's a shutdown, not a reboot.
// Rebooting resets the watch face and wallpaper.

View File

@ -16,6 +16,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
@ -27,7 +31,7 @@ import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
@ -36,6 +40,7 @@ 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.entities.MakibesHR3ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -46,28 +51,101 @@ import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext;
public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
public static final int FindPhone_ON = -1;
public static final int FindPhone_OFF = 0;
private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3Coordinator.class);
public static byte getTimeMode(String deviceAddress) {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
String tmode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h));
public static boolean shouldEnableHeadsUpScreen(SharedPreferences sharedPrefs) {
String liftMode = sharedPrefs.getString(MakibesHR3Constants.PREF_HEADS_UP_SCREEN, getContext().getString(R.string.p_on));
LOG.debug("tmode is " + tmode);
// Makibes HR3 doesn't support scheduled intervals. Treat it as "on".
return !liftMode.equals(getContext().getString(R.string.p_off));
}
if (getContext().getString(R.string.p_timeformat_24h).equals(tmode)) {
public static boolean shouldEnableLostReminder(SharedPreferences sharedPrefs) {
String lostReminder = sharedPrefs.getString(MakibesHR3Constants.PREF_LOST_REMINDER, getContext().getString(R.string.p_on));
// Makibes HR3 doesn't support scheduled intervals. Treat it as "on".
return !lostReminder.equals(getContext().getString(R.string.p_off));
}
public static byte getTimeMode(SharedPreferences sharedPrefs) {
String timeMode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h));
if (timeMode.equals(getContext().getString(R.string.p_timeformat_24h))) {
return MakibesHR3Constants.ARG_SET_TIMEMODE_24H;
} else {
return MakibesHR3Constants.ARG_SET_TIMEMODE_12H;
}
}
/**
* @param startOut out Only hour/minute are used.
* @param endOut out Only hour/minute are used.
* @return True if quite hours are enabled.
*/
public static boolean getQuiteHours(SharedPreferences sharedPrefs, Calendar startOut, Calendar endOut) {
String doNotDisturb = sharedPrefs.getString(MakibesHR3Constants.PREF_DO_NOT_DISTURB, getContext().getString(R.string.p_off));
if (doNotDisturb.equals(getContext().getString(R.string.p_off))) {
return false;
} else {
String start = sharedPrefs.getString(MakibesHR3Constants.PREF_DO_NOT_DISTURB_START, "00:00");
String end = sharedPrefs.getString(MakibesHR3Constants.PREF_DO_NOT_DISTURB_END, "00:00");
DateFormat df = new SimpleDateFormat("HH:mm");
try {
startOut.setTime(df.parse(start));
endOut.setTime(df.parse(end));
return true;
} catch (Exception e) {
LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
return false;
}
}
}
/**
* @return {@link #FindPhone_OFF}, {@link #FindPhone_ON}, or the duration
*/
public static int getFindPhone(SharedPreferences sharedPrefs) {
String findPhone = sharedPrefs.getString(MakibesHR3Constants.PREF_FIND_PHONE, getContext().getString(R.string.p_off));
if (findPhone.equals(getContext().getString(R.string.p_off))) {
return FindPhone_OFF;
} else if (findPhone.equals(getContext().getString(R.string.p_on))) {
return FindPhone_ON;
} else { // Duration
String duration = sharedPrefs.getString(MakibesHR3Constants.PREF_FIND_PHONE_DURATION, "0");
try {
int iDuration;
try {
iDuration = Integer.valueOf(duration);
} catch (Exception ex) {
LOG.warn(ex.getMessage());
iDuration = 60;
}
return iDuration;
} catch (Exception e) {
LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
return FindPhone_ON;
}
}
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String name = candidate.getDevice().getName();
// TODO:
// TODO: Device discovery
if ((name != null) && name.equals("Y808")) {
return DeviceType.MAKIBESHR3;
}
@ -77,11 +155,13 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
Long deviceId = device.getId();
QueryBuilder<?> qb = session.getMakibesHR3ActivitySampleDao().queryBuilder();
qb.where(MakibesHR3ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
}
@Override
public int getBondingStyle(GBDevice deviceCandidate) {
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}
@ -92,7 +172,7 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
@Override
public boolean supportsRealtimeData() {
return false;
return true;
}
@Override
@ -123,12 +203,12 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
@Override
public boolean supportsActivityTracking() {
return false;
return true;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null;
return new MakibesHR3SampleProvider(device, session);
}
@Override
@ -143,8 +223,7 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
@Override
public int getAlarmSlotCount() {
// TODO:
return 5;
return 8;
}
@Override
@ -175,7 +254,11 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{
R.xml.devicesettings_timeformat
R.xml.devicesettings_timeformat,
R.xml.devicesettings_liftwrist_display,
R.xml.devicesettings_disconnectnotification,
R.xml.devicesettings_donotdisturb_no_auto,
R.xml.devicesettings_find_phone
};
}
}

View File

@ -0,0 +1,84 @@
/* Copyright (C) 2018-2019 Daniele Gobbetti, 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.makibeshr3;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class MakibesHR3SampleProvider extends AbstractSampleProvider<MakibesHR3ActivitySample> {
private GBDevice mDevice;
private DaoSession mSession;
public MakibesHR3SampleProvider(GBDevice device, DaoSession session) {
super(device, session);
mSession = session;
mDevice = device;
}
@Override
public int normalizeType(int rawType) {
return rawType;
}
@Override
public int toRawActivityKind(int activityKind) {
return activityKind;
}
@Override
public float normalizeIntensity(int rawIntensity) {
return rawIntensity;
}
@Override
public MakibesHR3ActivitySample createActivitySample() {
return new MakibesHR3ActivitySample();
}
@Override
public AbstractDao<MakibesHR3ActivitySample, ?> getSampleDao() {
return getSession().getMakibesHR3ActivitySampleDao();
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return MakibesHR3ActivitySampleDao.Properties.RawKind;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return MakibesHR3ActivitySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return MakibesHR3ActivitySampleDao.Properties.DeviceId;
}
}

View File

@ -49,7 +49,7 @@ public class MijiaLywsd02Coordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice deviceCandidate) {
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}

View File

@ -85,7 +85,7 @@ public class MiScale2DeviceCoordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice device) {
public int getBondingStyle() {
return super.BONDING_STYLE_NONE;
}

View File

@ -71,7 +71,7 @@ public class No1F1Coordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice deviceCandidate) {
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}

View File

@ -44,7 +44,7 @@ public abstract class RoidmiCoordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice device) {
public int getBondingStyle() {
return BONDING_STYLE_BOND;
}
@ -133,6 +133,7 @@ public abstract class RoidmiCoordinator extends AbstractDeviceCoordinator {
return true;
}
@NonNull
@Override
public int[] getColorPresets() {
return RoidmiConst.COLOR_PRESETS;

View File

@ -77,7 +77,7 @@ public class Watch9DeviceCoordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice deviceCandidate) {
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}

View File

@ -153,7 +153,7 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
}
@Override
public int getBondingStyle(GBDevice device) {
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}

View File

@ -61,7 +61,7 @@ public class BluetoothPairingRequestReceiver extends BroadcastReceiver {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
try {
if (coordinator.getBondingStyle(gbDevice) == DeviceCoordinator.BONDING_STYLE_NONE) {
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_NONE) {
LOG.info("Aborting unwanted pairing request");
abortBroadcast();
}

View File

@ -242,6 +242,7 @@ public final class BtLEQueue {
mBluetoothGattServer.addService(service);
}
}
synchronized (mGattMonitor) {
// connectGatt with true doesn't really work ;( too often connection problems
if (GBApplication.isRunningMarshmallowOrLater()) {
@ -259,6 +260,7 @@ public final class BtLEQueue {
private void setDeviceConnectionState(State newState) {
LOG.debug("new device connection state: " + newState);
mGbDevice.setState(newState);
mGbDevice.sendDeviceUpdateIntent(mContext);
if (mConnectionLatch != null && newState == State.CONNECTED) {

View File

@ -20,8 +20,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
import android.content.Context;
import android.net.Uri;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
public class AmazfitBipLiteSupport extends AmazfitBipSupport {
@ -37,7 +35,7 @@ public class AmazfitBipLiteSupport extends AmazfitBipSupport {
}
@Override
public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
public HuamiFWHelper createFWHelper(Uri uri, Context context) {
return null;
}
}

View File

@ -94,7 +94,7 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
String authKey = sharedPrefs.getString("authkey", null);
if (authKey != null && !authKey.isEmpty()) {
byte[] srcBytes = authKey.getBytes();
byte[] srcBytes = authKey.trim().getBytes();
if (authKey.length() == 34 && authKey.substring(0, 2).equals("0x")) {
srcBytes = GB.hexStringToByteArray(authKey.substring(2));
}

View File

@ -1,22 +1,66 @@
// TODO: GB sometimes fails to connect until a connection with WearFit was made. This must be caused
// TODO: by GB, not by Makibes hr3 support. Charging the watch, attempting to pair, delete and
// TODO: re-add, scan for devices and go back, might also help. This needs further research.
// TODO: All the commands that aren't supported by GB should be added to device specific settings.
// TODO: It'd be cool if we could change the language. There's no official way to do so, but the
// TODO: watch is sold as chinese/english. Screen-on-time would be nice too.
// TODO: Firmware upgrades. WearFit tries to connect to Wake up Technology at
// TODO: http://47.112.119.52/app.php/Api/hardUpdate/type/55
// TODO: But that server resets the connection.
// TODO: The host is supposed to be www.iwhop.com, but that domain no longer exists.
// TODO: I think /app.php is missing a closing php tag.
package nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.CountDownTimer;
import android.os.Handler;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -24,12 +68,32 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3DeviceSupport.class);
private BluetoothGattCharacteristic ctrlCharacteristic = null;
// The delay must be at least as long as it takes the watch to respond.
// Reordering the requests could maybe reduce the delay, but this works fine too.
private CountDownTimer mFetchCountDown = new CountDownTimer(2000, 2000) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
LOG.debug("download finished");
GB.updateTransferNotification(null, "", false, 100, getContext());
}
};
private Handler mFindPhoneHandler = new Handler();
private BluetoothGattCharacteristic mControlCharacteristic = null;
private BluetoothGattCharacteristic mReportCharacteristic = null;
public MakibesHR3DeviceSupport() {
super(LOG);
@ -37,11 +101,82 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
addSupportedService(MakibesHR3Constants.UUID_SERVICE);
}
/**
* Called whenever data is received to postpone the removing of the progress notification.
*
* @param start Start showing the notification
*/
private void fetch(boolean start) {
if (start) {
// We don't know how long the watch is going to take to reply. Keep progress at 0.
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 0, getContext());
}
this.mFetchCountDown.cancel();
this.mFetchCountDown.start();
}
@Override
public boolean useAutoConnect() {
return false;
}
/**
* @param timeStamp seconds
*/
private void getDayStartEnd(int timeStamp, Calendar start, Calendar end) {
final int DAY = (24 * 60 * 60);
int timeStampStart = ((timeStamp / DAY) * DAY);
int timeStampEnd = (timeStampStart + DAY);
start.setTimeInMillis(timeStampStart * 1000L);
end.setTimeInMillis(timeStampEnd * 1000L);
}
/**
* @param timeStamp Time stamp at some point during the requested day.
*/
private int getStepsOnDay(int timeStamp) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
Calendar dayStart = new GregorianCalendar();
Calendar dayEnd = new GregorianCalendar();
this.getDayStartEnd(timeStamp, dayStart, dayEnd);
MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
List<MakibesHR3ActivitySample> samples = provider.getAllActivitySamples(
(int) (dayStart.getTimeInMillis() / 1000L),
(int) (dayEnd.getTimeInMillis() / 1000L));
int totalSteps = 0;
for (MakibesHR3ActivitySample sample : samples) {
totalSteps += sample.getSteps();
}
return totalSteps;
} catch (Exception ex) {
LOG.error(ex.getMessage());
return 0;
}
}
public MakibesHR3ActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
sample.setDevice(device);
sample.setUser(user);
sample.setTimestamp(timestampInSeconds);
sample.setProvider(provider);
return sample;
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
TransactionBuilder transactionBuilder = this.createTransactionBuilder("onnotificaiton");
@ -77,8 +212,16 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
break;
}
String message = "";
if (notificationSpec.title != null) {
message += (notificationSpec.title + ": ");
}
message += notificationSpec.body;
this.sendNotification(transactionBuilder,
sender, notificationSpec.title + ": " + notificationSpec.body);
sender, message);
try {
this.performConnected(transactionBuilder.getTransaction());
@ -113,6 +256,34 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
for (int i = 0; i < alarms.size(); ++i) {
Alarm alarm = alarms.get(i);
byte repetition = 0x00;
switch (alarm.getRepetition()) {
case Alarm.ALARM_ONCE:
repetition = MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_ONE_TIME;
break;
case Alarm.ALARM_MON:
repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_MONDAY;
case Alarm.ALARM_TUE:
repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_TUESDAY;
case Alarm.ALARM_WED:
repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_WEDNESDAY;
case Alarm.ALARM_THU:
repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_THURSDAY;
case Alarm.ALARM_FRI:
repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_FRIDAY;
case Alarm.ALARM_SAT:
repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_SATURDAY;
case Alarm.ALARM_SUN:
repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_SUNDAY;
break;
default:
LOG.warn("invalid alarm repetition " + alarm.getRepetition());
break;
}
// Should we use @alarm.getPosition() rather than @i?
this.setAlarmReminder(
transactionBuilder,
@ -120,7 +291,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
alarm.getEnabled(),
alarm.getHour(),
alarm.getMinute(),
MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_CUSTOM);
repetition);
}
try {
@ -137,7 +308,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
if (callSpec.command == CallSpec.CALL_INCOMING) {
this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_CALL, callSpec.name);
} else {
this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL, callSpec.name);
this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL, "");
}
try {
@ -164,32 +335,26 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onEnableRealtimeSteps(boolean enable) {
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public void onAppInfoReq() {
}
@Override
public void onAppStart(UUID uuid, boolean start) {
}
@Override
public void onAppDelete(UUID uuid) {
}
@Override
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
}
@Override
@ -199,7 +364,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onFetchRecordedData(int dataTypes) {
// what is this?
}
@Override
@ -228,12 +393,52 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onHeartRateTest() {
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
TransactionBuilder transactionBuilder = this.createTransactionBuilder("finddevice");
this.setEnableRealTimeHeartRate(transactionBuilder, enable);
try {
this.performConnected(transactionBuilder.getTransaction());
} catch (Exception e) {
LOG.debug("ERROR");
}
}
private void onReverseFindDevice(boolean start) {
if (start) {
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(
this.getDevice().getAddress());
int findPhone = MakibesHR3Coordinator.getFindPhone(sharedPreferences);
if (findPhone != MakibesHR3Coordinator.FindPhone_OFF) {
GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
evaluateGBDeviceEvent(findPhoneEvent);
if (findPhone > 0) {
this.mFindPhoneHandler.postDelayed(new Runnable() {
@Override
public void run() {
onReverseFindDevice(false);
}
}, findPhone * 1000);
}
}
} else {
// Always send stop, ignore preferences.
GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
evaluateGBDeviceEvent(findPhoneEvent);
}
}
@Override
@ -303,51 +508,102 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
}
private MakibesHR3DeviceSupport sendUserInfo(TransactionBuilder builder) {
// builder.write(ctrlCharacteristic, MakibesHR3Constants.CMD_SET_PREF_START);
// builder.write(ctrlCharacteristic, MakibesHR3Constants.CMD_SET_PREF_START1);
private void syncPreferences(TransactionBuilder transaction) {
syncPreferences(builder);
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
// builder.write(ctrlCharacteristic, new byte[]{MakibesHR3Constants.CMD_SET_CONF_END});
return this;
this.setTimeMode(transaction, sharedPreferences);
this.setDateTime(transaction);
this.setQuiteHours(transaction, sharedPreferences);
this.setHeadsUpScreen(transaction, sharedPreferences);
this.setLostReminder(transaction, sharedPreferences);
ActivityUser activityUser = new ActivityUser();
this.setPersonalInformation(transaction,
(byte) Math.round(activityUser.getHeightCm() * 0.43), // Thanks no1f1
activityUser.getAge(),
activityUser.getHeightCm(),
activityUser.getWeightKg(),
activityUser.getStepsGoal() / 1000);
this.fetch(true);
}
private MakibesHR3DeviceSupport syncPreferences(TransactionBuilder transaction) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
LOG.debug(key + " changed");
this.setTimeMode(transaction);
this.setDateTime(transaction);
// setDayOfWeek(transaction);
// setTimeMode(transaction);
if (!this.isConnected()) {
LOG.debug("ignoring change, we're disconnected");
return;
}
// setGender(transaction);
// setAge(transaction);
// setWeight(transaction);
// setHeight(transaction);
TransactionBuilder transactionBuilder = this.createTransactionBuilder("onSharedPreferenceChanged");
// setGoal(transaction);
// setLanguage(transaction);
// setScreenTime(transaction);
// setUnit(transaction);
// setAllDayHeart(transaction);
if (key.equals(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT)) {
this.setTimeMode(transactionBuilder, sharedPreferences);
} else if (key.equals(MakibesHR3Constants.PREF_HEADS_UP_SCREEN)) {
this.setHeadsUpScreen(transactionBuilder, sharedPreferences);
} else if (key.equals(MakibesHR3Constants.PREF_LOST_REMINDER)) {
this.setLostReminder(transactionBuilder, sharedPreferences);
} else if (key.equals(MakibesHR3Constants.PREF_DO_NOT_DISTURB) ||
key.equals(MakibesHR3Constants.PREF_DO_NOT_DISTURB_START) ||
key.equals(MakibesHR3Constants.PREF_DO_NOT_DISTURB_END)) {
this.setQuiteHours(transactionBuilder, sharedPreferences);
} else if (key.equals(MakibesHR3Constants.PREF_FIND_PHONE) ||
key.equals(MakibesHR3Constants.PREF_FIND_PHONE_DURATION)) {
// No action, we check the shared preferences when the device tries to ring the phone.
} else {
return;
}
return this;
try {
this.performConnected(transactionBuilder.getTransaction());
} catch (Exception ex) {
LOG.warn(ex.getMessage());
}
}
/**
* Use to show the battery icon in the device card.
* If the icon shows up later, the user might be trying to tap one thing but the battery icon
* will shift everything.
* This is hacky. There should be a "supportsBattery" function in the coordinator that displays
* the battery icon before the battery level is received.
*/
private void fakeBattery() {
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
batteryInfo.level = 100;
batteryInfo.state = BatteryState.UNKNOWN;
this.handleGBDeviceEvent(batteryInfo);
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
this.fakeBattery();
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 0, getContext());
gbDevice.setState(GBDevice.State.INITIALIZING);
gbDevice.sendDeviceUpdateIntent(getContext());
this.ctrlCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_CONTROL);
this.mControlCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_CONTROL);
this.mReportCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_REPORT);
builder.notify(this.mReportCharacteristic, true);
builder.setGattCallback(this);
// Allow modifications
builder.write(this.ctrlCharacteristic, new byte[]{0x01, 0x00});
builder.write(this.mControlCharacteristic, new byte[]{0x01, 0x00});
// Initialize device
sendUserInfo(builder); //Sync preferences
this.syncPreferences(builder);
this.requestFitness(builder);
gbDevice.setState(GBDevice.State.INITIALIZED);
gbDevice.sendDeviceUpdateIntent(getContext());
@ -355,27 +611,233 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
getDevice().setFirmwareVersion("N/A");
getDevice().setFirmwareVersion2("N/A");
SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
preferences.registerOnSharedPreferenceChangeListener(this);
return builder;
}
private void addGBActivitySamples(MakibesHR3ActivitySample[] samples) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
User user = DBHelper.getUser(dbHandler.getDaoSession());
Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
for (MakibesHR3ActivitySample sample : samples) {
sample.setDevice(device);
sample.setUser(user);
sample.setProvider(provider);
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
provider.addGBActivitySample(sample);
}
} catch (Exception ex) {
// Why is this a toast? The user doesn't care about the error.
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
LOG.error(ex.getMessage());
}
}
private void addGBActivitySample(MakibesHR3ActivitySample sample) {
this.addGBActivitySamples(new MakibesHR3ActivitySample[]{sample});
}
/**
* @param command
* @param data
* @return
* Should only be called after the sample has been populated by
* {@link MakibesHR3DeviceSupport#addGBActivitySample} or
* {@link MakibesHR3DeviceSupport#addGBActivitySamples}
*/
private byte[] craftData(byte command, byte[] data) {
private void broadcastSample(MakibesHR3ActivitySample sample) {
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
.putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
private void onReceiveFitness(int steps) {
LOG.info("steps: " + steps);
this.onReceiveStepsSample(steps);
}
private void onReceiveHeartRate(int heartRate) {
LOG.info("heart rate: " + heartRate);
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
if (heartRate > 0) {
sample.setHeartRate(heartRate);
sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
} else {
if (heartRate == MakibesHR3Constants.ARG_HEARTRATE_NO_TARGET) {
sample.setRawKind(ActivityKind.TYPE_NOT_WORN);
} else if (heartRate == MakibesHR3Constants.ARG_HEARTRATE_NO_READING) {
sample.setRawKind(ActivityKind.TYPE_NOT_MEASURED);
} else {
LOG.warn("invalid heart rate reading: " + heartRate);
return;
}
}
this.addGBActivitySample(sample);
this.broadcastSample(sample);
}
private void onReceiveHeartRateSample(int year, int month, int day, int hour, int minute, int heartRate) {
LOG.debug("received heart rate sample " + year + "-" + month + "-" + day + " " + hour + ":" + minute + " " + heartRate);
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
Calendar calendar = new GregorianCalendar(year, month - 1, day, hour, minute);
int timeStamp = (int) (calendar.getTimeInMillis() / 1000);
sample.setHeartRate(heartRate);
sample.setTimestamp(timeStamp);
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
this.addGBActivitySample(sample);
}
private void onReceiveStepsSample(int timeStamp, int steps) {
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
// We need to subtract the day's total step count thus far.
int dayStepCount = this.getStepsOnDay(timeStamp);
int newSteps = (steps - dayStepCount);
if (newSteps > 0) {
LOG.debug("adding " + newSteps + " steps");
sample.setSteps(steps - dayStepCount);
sample.setTimestamp(timeStamp);
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
this.addGBActivitySample(sample);
}
}
/**
* The time is the start of the measurement. Each measurement lasts 1h.
*/
private void onReceiveStepsSample(int year, int month, int day, int hour, int minute, int steps) {
LOG.debug("received steps sample " + year + "-" + month + "-" + day + " " + hour + ":" + minute + " " + steps);
Calendar calendar = new GregorianCalendar(year, month - 1, day, hour + 1, minute);
int timeStamp = (int) (calendar.getTimeInMillis() / 1000);
this.onReceiveStepsSample(timeStamp, steps);
}
private void onReceiveStepsSample(int steps) {
this.onReceiveStepsSample((int) (Calendar.getInstance().getTimeInMillis() / 1000l), steps);
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (super.onCharacteristicChanged(gatt, characteristic)) {
return true;
}
byte[] data = characteristic.getValue();
if (data.length < 6)
return true;
this.fetch(false);
UUID characteristicUuid = characteristic.getUuid();
if (characteristicUuid.equals(mReportCharacteristic.getUuid())) {
byte[] value = characteristic.getValue();
byte[] arguments = new byte[value.length - 6];
for (int i = 0; i < arguments.length; ++i) {
arguments[i] = value[i + 6];
}
byte[] report = new byte[]{value[4], value[5]};
switch (report[0]) {
case MakibesHR3Constants.RPRT_REVERSE_FIND_DEVICE:
this.onReverseFindDevice(arguments[0] == 0x01);
break;
case MakibesHR3Constants.RPRT_HEARTRATE:
if (value.length == 7) {
this.onReceiveHeartRate((int) arguments[0]);
}
break;
case MakibesHR3Constants.RPRT_BATTERY:
if (arguments.length == 2) {
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
batteryInfo.level = (short) (arguments[1] & 0xff);
batteryInfo.state = ((arguments[0] == 0x01) ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL);
this.handleGBDeviceEvent(batteryInfo);
}
break;
case MakibesHR3Constants.RPRT_SOFTWARE:
if (arguments.length == 11) {
this.getDevice().setFirmwareVersion(((int) (arguments[0] & 0xff)) + "." + (arguments[1] & 0xff));
}
break;
default: // Non-80 reports
if (Arrays.equals(report, MakibesHR3Constants.RPRT_FITNESS)) {
int steps = (arguments[1] & 0xff) * 0x100 + (arguments[2] & 0xff);
this.onReceiveFitness(
steps
);
} else if (Arrays.equals(report, MakibesHR3Constants.RPRT_HEART_RATE_SAMPLE)) {
this.onReceiveHeartRateSample(
(arguments[0] & 0xff) + 2000, (arguments[1] & 0xff), (arguments[2] & 0xff),
(arguments[3] & 0xff), (arguments[4] & 0xff),
(arguments[5] & 0xff));
} else if (Arrays.equals(report, MakibesHR3Constants.RPRT_STEPS_SAMPLE)) {
this.onReceiveStepsSample(
(arguments[0] & 0xff) + 2000, (arguments[1] & 0xff), (arguments[2] & 0xff),
(arguments[3] & 0xff), 0,
((arguments[5] & 0xff) * 0x100) + (arguments[6] & 0xff));
}
break;
}
}
return false;
}
private byte[] craftData(byte[] command, byte[] data) {
byte[] result = new byte[MakibesHR3Constants.DATA_TEMPLATE.length + data.length];
System.arraycopy(MakibesHR3Constants.DATA_TEMPLATE, 0, result, 0, MakibesHR3Constants.DATA_TEMPLATE.length);
result[MakibesHR3Constants.DATA_ARGUMENT_COUNT_INDEX] = (byte) (data.length + 3);
result[MakibesHR3Constants.DATA_COMMAND_INDEX] = command;
for (int i = 0; i < command.length; ++i) {
result[MakibesHR3Constants.DATA_COMMAND_INDEX + i] = command[i];
}
System.arraycopy(data, 0, result, 6, data.length);
return result;
}
private byte[] craftData(byte command, byte[] data) {
return this.craftData(new byte[]{command}, data);
}
private byte[] craftData(byte command) {
return this.craftData(command, new byte[]{});
@ -385,7 +847,11 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
final int maxMessageLength = 20;
// For every split, we need 1 byte extra.
int extraBytes = (((data.length - maxMessageLength) / maxMessageLength) + 1);
int extraBytes = 0;
if (data.length > 20) {
extraBytes = (((data.length - maxMessageLength) / maxMessageLength) + 1);
}
int totalDataLength = (data.length + extraBytes);
@ -422,13 +888,88 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
}
private MakibesHR3DeviceSupport factoryReset(TransactionBuilder transaction) {
transaction.write(this.ctrlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FACTORY_RESET));
transaction.write(this.mControlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FACTORY_RESET));
return this.reboot(transaction);
}
/**
* Ugly because I don't like Date.
* All non-zero records after the given times will be returned via
* {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE},
* {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE},
* {@link MakibesHR3Constants#RPRT_FITNESS}
*/
private MakibesHR3DeviceSupport requestFitness(TransactionBuilder transaction,
int yearStepsAfter, int monthStepsAfter, int dayStepsAfter,
int hourStepsAfter, int minuteStepsAfter,
int yearHeartRateAfter, int monthHeartRateAfter, int dayHeartRateAfter,
int hourHeartRateAfter, int minuteHeartRateAfter) {
byte[] data = this.craftData(MakibesHR3Constants.CMD_REQUEST_FITNESS,
new byte[]{
(byte) 0x00,
(byte) (yearStepsAfter - 2000),
(byte) monthStepsAfter,
(byte) dayStepsAfter,
(byte) hourStepsAfter,
(byte) minuteStepsAfter,
(byte) (yearHeartRateAfter - 2000),
(byte) monthHeartRateAfter,
(byte) dayHeartRateAfter,
(byte) hourHeartRateAfter,
(byte) minuteHeartRateAfter
});
transaction.write(this.mControlCharacteristic, data);
this.fetch(true);
return this;
}
/**
* Ugly because I don't like Date.
* All non-zero records after the given times will be returned via
* {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE},
* {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE},
* {@link MakibesHR3Constants#RPRT_FITNESS}
*/
private MakibesHR3DeviceSupport requestFitness(TransactionBuilder transaction) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
MakibesHR3ActivitySample latestSample = provider.getLatestActivitySample();
if (latestSample == null) {
this.requestFitness(transaction,
2000, 0, 0, 0, 0,
2000, 0, 0, 0, 0);
} else {
Calendar calendar = new GregorianCalendar();
calendar.setTime(new Date(latestSample.getTimestamp() * 1000l));
int year = calendar.get(Calendar.YEAR);
int month = (calendar.get(Calendar.MONTH) + 1);
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
this.requestFitness(transaction,
year, month, day, hour, minute,
year, month, day, hour, minute);
}
} catch (Exception ex) {
LOG.error(ex.getMessage());
}
return this;
}
private MakibesHR3DeviceSupport findDevice(TransactionBuilder transaction) {
transaction.write(this.ctrlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FIND_DEVICE));
transaction.write(this.mControlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FIND_DEVICE));
return this;
}
@ -444,7 +985,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
}
this.writeSafe(
this.ctrlCharacteristic,
this.mControlCharacteristic,
transaction,
this.craftData(MakibesHR3Constants.CMD_SEND_NOTIFICATION, data));
@ -453,7 +994,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
private MakibesHR3DeviceSupport setAlarmReminder(TransactionBuilder transaction,
int id, boolean enable, int hour, int minute, byte repeat) {
transaction.write(this.ctrlCharacteristic,
transaction.write(this.mControlCharacteristic,
this.craftData(MakibesHR3Constants.CMD_SET_ALARM_REMINDER, new byte[]{
(byte) id,
(byte) (enable ? 0x01 : 0x00),
@ -465,12 +1006,112 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
return this;
}
private MakibesHR3DeviceSupport setTimeMode(TransactionBuilder transaction) {
byte value = MakibesHR3Coordinator.getTimeMode(getDevice().getAddress());
/**
* @param transactionBuilder
* @param stepLength cm
* @param age years
* @param height cm
* @param weight kg
* @param stepGoal kilo
*/
private MakibesHR3DeviceSupport setPersonalInformation(TransactionBuilder transactionBuilder,
int stepLength, int age, int height, int weight, int stepGoal) {
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_PERSONAL_INFORMATION,
new byte[]{
(byte) stepLength,
(byte) age,
(byte) height,
(byte) weight,
MakibesHR3Constants.ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_KILOMETERS,
(byte) stepGoal,
(byte) 0x5a,
(byte) 0x82,
(byte) 0x3c,
(byte) 0x5a,
(byte) 0x28,
(byte) 0xb4,
(byte) 0x5d,
(byte) 0x64,
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_TIMEMODE, new byte[]{value});
});
transaction.write(this.ctrlCharacteristic, data);
transactionBuilder.write(this.mControlCharacteristic, data);
return this;
}
private MakibesHR3DeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, boolean enable) {
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_HEADS_UP_SCREEN,
new byte[]{(byte) (enable ? 0x01 : 0x00)});
transactionBuilder.write(this.mControlCharacteristic, data);
return this;
}
private MakibesHR3DeviceSupport setQuiteHours(TransactionBuilder transactionBuilder,
boolean enable,
int hourStart, int minuteStart,
int hourEnd, int minuteEnd) {
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_QUITE_HOURS, new byte[]{
(byte) (enable ? 0x01 : 0x00),
(byte) hourStart, (byte) minuteStart,
(byte) hourEnd, (byte) minuteEnd
});
transactionBuilder.write(this.mControlCharacteristic, data);
return this;
}
private MakibesHR3DeviceSupport setQuiteHours(TransactionBuilder transactionBuilder,
SharedPreferences sharedPreferences) {
Calendar start = new GregorianCalendar();
Calendar end = new GregorianCalendar();
boolean enable = MakibesHR3Coordinator.getQuiteHours(sharedPreferences, start, end);
return this.setQuiteHours(transactionBuilder, enable,
start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE),
end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE));
}
private MakibesHR3DeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) {
return this.setHeadsUpScreen(transactionBuilder,
MakibesHR3Coordinator.shouldEnableHeadsUpScreen(sharedPreferences));
}
private MakibesHR3DeviceSupport setLostReminder(TransactionBuilder transactionBuilder, boolean enable) {
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_LOST_REMINDER,
new byte[]{(byte) (enable ? 0x01 : 0x00)});
transactionBuilder.write(this.mControlCharacteristic, data);
return this;
}
private MakibesHR3DeviceSupport setLostReminder(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) {
return this.setLostReminder(transactionBuilder,
MakibesHR3Coordinator.shouldEnableLostReminder(sharedPreferences));
}
private MakibesHR3DeviceSupport setTimeMode(TransactionBuilder transactionBuilder, byte timeMode) {
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_TIMEMODE, new byte[]{timeMode});
transactionBuilder.write(this.mControlCharacteristic, data);
return this;
}
private MakibesHR3DeviceSupport setTimeMode(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) {
return this.setTimeMode(transactionBuilder,
MakibesHR3Coordinator.getTimeMode(sharedPreferences));
}
private MakibesHR3DeviceSupport setEnableRealTimeHeartRate(TransactionBuilder transaction, boolean enable) {
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_REAL_TIME_HEART_RATE, new byte[]{(byte) (enable ? 0x01 : 0x00)});
transaction.write(this.mControlCharacteristic, data);
return this;
}
@ -486,7 +1127,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_DATE_TIME,
new byte[]{
(byte) 0x00,
(byte) (year & 0xff00),
(byte) ((year & 0xff00) >> 8),
(byte) (year & 0x00ff),
(byte) month,
(byte) day,
@ -495,7 +1136,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
(byte) second
});
transaction.write(this.ctrlCharacteristic, data);
transaction.write(this.mControlCharacteristic, data);
return this;
}
@ -515,7 +1156,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport {
}
private MakibesHR3DeviceSupport reboot(TransactionBuilder transaction) {
transaction.write(this.ctrlCharacteristic, this.craftData(MakibesHR3Constants.CMD_REBOOT));
transaction.write(this.mControlCharacteristic, this.craftData(MakibesHR3Constants.CMD_REBOOT));
return this;
}

View File

@ -83,14 +83,13 @@ public class DeviceHelper {
private static final Logger LOG = LoggerFactory.getLogger(DeviceHelper.class);
private static final DeviceHelper instance = new DeviceHelper();
// lazily created
private List<DeviceCoordinator> coordinators;
public static DeviceHelper getInstance() {
return instance;
}
// lazily created
private List<DeviceCoordinator> coordinators;
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
for (DeviceCoordinator coordinator : getAllCoordinators()) {
DeviceType deviceType = coordinator.getSupportedType(candidate);
@ -204,15 +203,15 @@ public class DeviceHelper {
private List<DeviceCoordinator> createCoordinators() {
List<DeviceCoordinator> result = new ArrayList<>();
result.add(new MiScale2DeviceCoordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new AmazfitBipCoordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new AmazfitBipLiteCoordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new AmazfitCorCoordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new AmazfitCor2Coordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new MiBand3Coordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new MiBand4Coordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new MiBand2HRXCoordinator()); // Note: must come before MiBand2 because detection is hacky, atm
result.add(new MiBand2Coordinator()); // Note: MiBand2 must come before MiBand because detection is hacky, atm
result.add(new MiScale2DeviceCoordinator());
result.add(new AmazfitBipCoordinator());
result.add(new AmazfitBipLiteCoordinator());
result.add(new AmazfitCorCoordinator());
result.add(new AmazfitCor2Coordinator());
result.add(new MiBand3Coordinator());
result.add(new MiBand4Coordinator());
result.add(new MiBand2HRXCoordinator());
result.add(new MiBand2Coordinator()); // Note: MiBand2 and all of the above must come before MiBand because detection is hacky, atm
result.add(new MiBandCoordinator());
result.add(new PebbleCoordinator());
result.add(new VibratissimoCoordinator());

View File

@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#7E7E7E">
<path
android:fillColor="#FF000000"
android:pathData="M3,17V7H5V17H3m16,0V7h2v10h-2m3,-8h2v6H22V9M0,15V9h2v6H0"/>
<path
android:pathData="M6.84,3.615H17.16V20.385H6.84Z"
android:strokeAlpha="1"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:fillAlpha="1"
android:strokeLineCap="butt"/>
<path
android:pathData="m12.75,18.467a0.75,0.75 0,0 1,-0.75 0.75,0.75 0.75,0 0,1 -0.75,-0.75 0.75,0.75 0,0 1,0.75 -0.75,0.75 0.75,0 0,1 0.75,0.75z"
android:strokeAlpha="1"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillAlpha="1"
android:strokeLineCap="round"/>
</vector>

View File

@ -747,4 +747,18 @@
<string name="pref_display_add_device_fab">Tlačítko Připojit nové zařízení</string>
<string name="pref_display_add_device_fab_on">Vždy viditelné</string>
<string name="pref_display_add_device_fab_off">Viditelné pouze pokud není přidáno žádné zařízení</string>
<plurals name="widget_alarm_target_hours">
<item quantity="one">%d hodina</item>
<item quantity="few">%d hodiny</item>
<item quantity="other">%d hodin</item>
</plurals>
<string name="activity_error_no_app_for_gpx">Pro zobrazení trasy aktivity nainstalujte aplikaci, která umí zobrazit GPX soubory.</string>
<string name="preferences_makibes_hr3_settings">Nastavení Makibes HR3</string>
<string name="devicetype_makibes_hr3">Makibes HR3</string>
<string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
<string name="prefs_find_phone">Vyhledat telefon</string>
<string name="prefs_enable_find_phone">Povolit vyhledání telefonu</string>
<string name="prefs_find_phone_summary">Použijte náramek/hodinky k prozvonění telefonu.</string>
<string name="prefs_find_phone_duration">Délka zvonění ve vteřinách</string>
<string name="maximum_duration">Délka</string>
</resources>

View File

@ -770,4 +770,10 @@
</plurals>
<string name="preferences_makibes_hr3_settings">Makibes HR3 Einstellungen</string>
<string name="devicetype_makibes_hr3">Makibes HR3</string>
<string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
<string name="prefs_find_phone">Telefon finden</string>
<string name="prefs_enable_find_phone">Telefon finden aktivieren</string>
<string name="prefs_find_phone_summary">Verwende dein Band, um den Klingelton deines Handys wiederzugeben.</string>
<string name="prefs_find_phone_duration">Klingeldauer in Sekunden</string>
<string name="maximum_duration">Dauer</string>
</resources>

View File

@ -753,4 +753,10 @@
<string name="activity_error_no_app_for_gpx">כדי לצפות בעקבות פעילות, עליך להתקין יישומון שיכול לטפל בקובצי GPX.</string>
<string name="preferences_makibes_hr3_settings">הגדרות של Makibes HR3</string>
<string name="devicetype_makibes_hr3">Makibes HR3</string>
<string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
<string name="prefs_find_phone">איתור הטלפון</string>
<string name="prefs_enable_find_phone">הפעלת איתור הטלפון</string>
<string name="prefs_find_phone_summary">ניתן להשתמש בצמיד כדי שהטלפון שלך ישמיע צלצול.</string>
<string name="prefs_find_phone_duration">משך זמן הצלצול בשניות</string>
<string name="maximum_duration">משך</string>
</resources>

View File

@ -17,7 +17,7 @@
<string name="controlcenter_navigation_drawer_close">ナビゲーションドロワーを閉じる</string>
<string name="controlcenter_snackbar_need_longpress">カードを長押しすると切断します</string>
<string name="controlcenter_snackbar_disconnecting">切断中</string>
<string name="controlcenter_snackbar_connecting">接続中</string>
<string name="controlcenter_snackbar_connecting">接続中</string>
<string name="controlcenter_snackbar_requested_screenshot">デバイスのスクリーンショットを取得中</string>
<string name="title_activity_debug">デバッグ</string>
<!--Strings related to AppManager-->
@ -35,7 +35,7 @@
<string name="appmanager_hrm_deactivate">HRM を非アクティベート</string>
<string name="appmanager_weather_activate">システムの天気アプリを有効にする</string>
<string name="appmanager_weather_deactivate">システムの天気アプリを無効にする</string>
<string name="appmanager_weather_install_provider">天気予報アプリをインストール</string>
<string name="appmanager_weather_install_provider">天気予報通知アプリをインストール</string>
<string name="app_configure">設定</string>
<string name="app_move_to_top">先頭に移動</string>
<!--Strings related to AppBlacklist-->
@ -44,17 +44,17 @@
<string name="title_activity_calblacklist">ブラックリストにしたカレンダー</string>
<!--Strings related to FwAppInstaller-->
<string name="title_activity_fw_app_insaller">ファームウェア・アプリのインストール</string>
<string name="fw_upgrade_notice">お使いのMi Bandに、現在のファームウェアの代わりに%sをインストールしようとしています。</string>
<string name="fw_upgrade_notice">%s をインストールしようとしています。</string>
<string name="fw_multi_upgrade_notice">お使いのMi Bandに、現在のファームウェアの代わりに %1$s および %2$s をインストールしようとしています。</string>
<string name="miband_firmware_known">このファームウェアはテスト済で、ガジェットブリッジと互換性があることがわかっています。</string>
<string name="miband_firmware_unknown_warning">このファームウェアは未テストで、Gadgetbridge と互換性がない可能性があります。
\n
\nお使いのMi Bandにフラッシュすることは推奨されません!</string>
<string name="miband_firmware_suggest_whitelist">それでも続行して、正しく動作した場合は、Gadgetbridge開発者にホワイトリストの %s ファームウェア バージョンを教えてください</string>
<string name="miband_firmware_unknown_warning">このファームウェアは未テストで、Gadgetbridge と互換性がない可能性があります。
\n
\nフラッシュすることは推奨されません!</string>
<string name="miband_firmware_suggest_whitelist">それでも続行して、正しく動作した場合は、Gadgetbridge開発者にホワイトリストの %s ファームウェア バージョンを教えてください</string>
<!--Strings related to Settings-->
<string name="title_activity_settings">設定</string>
<string name="pref_header_general">全般設定</string>
<string name="pref_title_general_autoconnectonbluetooth">Bluetoothがオンになったときにデバイスに接続</string>
<string name="pref_title_general_autoconnectonbluetooth">Bluetoothがオンになったときに Gadgetbridge デバイスに接続</string>
<string name="pref_title_general_autostartonboot">自動的に開始</string>
<string name="pref_title_general_autoreconnect">自動的に再接続</string>
<string name="pref_title_audio_player">お好みのオーディオプレイヤー</string>
@ -427,14 +427,14 @@
<string name="_pebble_watch_open_on_phone">電話で開く</string>
<string name="_pebble_watch_mute">ミュート</string>
<string name="_pebble_watch_reply">返信</string>
<string name="controlcenter_connect">接続</string>
<string name="controlcenter_connect">接続</string>
<string name="fw_upgrade_notice_amazfitcor">Amazfit Cor に %s のファームウェアをインストールしようとしています。
\n
\n.fw ファイルをインストールし、その後 .res ファイルをインストールしください。お使いのウォッチは、.fw ファイルをインストールした後に再起動します。
\n.fw ファイルをインストールし、その後 .res ファイルをインストールしください。お使いの band は、.fw ファイルをインストールした後に再起動します。
\n
\n注: 以前にインストールされたものと同じ場合は、.res をインストールする必要はありません。
\n
\nテストされていません。デバイスが文鎮化する可能性があります。ご自身の責任で行って下さい!</string>
\nご自身の責任で行って下さい!</string>
<string name="pref_title_pebble_enable_bgjs">バックグラウンド JS を有効にします</string>
<string name="pref_summary_pebble_enable_bgjs">有効にすると、ウォッチフェースに天気、バッテリー情報等を表示することができます。</string>
<string name="activity_web_view">Web View アクティビティ</string>
@ -520,13 +520,13 @@
<string name="debugactivity_really_factoryreset">工場出荷値にリセットすると、(サポートされている場合) 接続されているデバイスからすべてのデータを削除します。Xiaomi/Huamiデバイスでは、BluetoothのMACアドレスも変更するので、GadgetBligeに新しいデバイスとして表示されます。</string>
<string name="blacklist_all_for_notifications">すべて通知のブラックリストにする</string>
<string name="whitelist_all_for_notifications">すべて通知のホワイトリストにする</string>
<string name="fw_upgrade_notice_miband3">Mi Band 3 に %s ファームウェアをインストールします。
\n
\n.fw ファイルをインストールして、その後 .res ファイルをインストールするようにしてください。 .fw ファイルをインストールした後、ウォッチは再起動します。
\n
\n注意: .res が以前にインストールしたものと全く同じ場合は、インストールする必要はありません。
\n
\nテストされていません。お使いのデバイスが文鎮化する可能性があります。ご自身の責任で行ってください!</string>
<string name="fw_upgrade_notice_miband3">Mi Band 3 に %s ファームウェアをインストールします。
\n
\n.fw ファイルをインストールして、その後 .res ファイルをインストールするようにしてください。 .fw ファイルをインストールした後、お使いの band は再起動します。
\n
\n注意: .res が以前にインストールしたものと全く同じ場合は、インストールする必要はありません。
\n
\nご自身の責任で行ってください!</string>
<string name="pref_title_notifications_timeout">通知の間の最小時間</string>
<string name="pref_title_rtl">右から左へ</string>
<string name="pref_summary_rtl">お使いのデバイスが右から左の言語を表示できない場合はこれを有効にします</string>
@ -627,4 +627,19 @@
\nあなた自身のリスクで続行してください!
\n 
\n完全にはテストされていません。お使いのデバイス名が \"Amazfit Band 2\" の場合、おそらく BEATS_W ファームウェアをフラッシュする必要があります</string>
<string name="pref_title_support_voip_calls">VoIP アプリの通話を有効にする</string>
<string name="fw_upgrade_notice_miband4">Mi Band 4 に %s ファームウェアをインストールしようとしています。
\n
\n.fw ファイルをインストールしてから、.res ファイルをインストールしてください。 .fw ファイルをインストールすると、お使いの band が再起動します。
\n
\n注: 以前にインストールしたものとまったく同じ場合、.res をインストールする必要はありません。
\n
\nご自身のリスクで進めてください!</string>
<string name="pref_summary_expose_hr">Gadgetbridge の接続中に、他のアプリが HR データにリアルタイムでアクセスできるようにします</string>
<string name="pref_title_expose_hr">サードパーティのリアルタイムHRアクセス</string>
<string name="pref_title_use_custom_font">カスタムフォントを使用する</string>
<string name="pref_summary_use_custom_font">お使いのデバイスで絵文字サポートのカスタムフォントファームウェアがある場合は、これを有効にしてください</string>
<string name="pref_display_add_device_fab">新しいデバイスを接続するボタン</string>
<string name="pref_display_add_device_fab_on">常に表示</string>
<string name="pref_display_add_device_fab_off">デバイスが追加されていない場合にのみ表示</string>
</resources>

View File

@ -347,7 +347,7 @@
<string name="dateformat_time">Tijd</string>
<string name="dateformat_date_time">Tijd &amp; datum</string>
<string name="mi2_prefs_button_actions">Knoppen acties</string>
<string name="mi2_prefs_button_actions_summary">Specificeer actie voor de Mi Band 2 knopdruk</string>
<string name="mi2_prefs_button_actions_summary">Specificeer actie voor de Mi Band knopdruk</string>
<string name="mi2_prefs_button_press_count">Aantal knopdrukken</string>
<string name="mi2_prefs_button_press_count_summary">Aantal knopdrukken nodig om een bericht te sturen</string>
<string name="mi2_prefs_button_press_broadcast">Uit te sturen bericht</string>
@ -754,4 +754,8 @@
<string name="pref_display_add_device_fab">Verbind nieuw apparaat knop</string>
<string name="pref_display_add_device_fab_on">Altijkd zichtbaar</string>
<string name="pref_display_add_device_fab_off">Alleen zichtbaar als er geen apparaat toegevoegd is</string>
<string name="activity_error_no_app_for_gpx">Om de route van de activiteit te zien: installeer een app die GPX bestanden kan tonen.</string>
<string name="preferences_makibes_hr3_settings">Makibes HR3 instellingen</string>
<string name="devicetype_makibes_hr3">Makibes HR3</string>
<string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
</resources>

View File

@ -763,4 +763,10 @@
<string name="activity_error_no_app_for_gpx">Para visualizar o rastreamento de atividade, instale um aplicativo que consegue manipular arquivos GPX.</string>
<string name="preferences_makibes_hr3_settings">Configurações de Makibes HR3</string>
<string name="devicetype_makibes_hr3">Makibes HR3</string>
<string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
<string name="prefs_find_phone">Encontrar telefone</string>
<string name="prefs_enable_find_phone">Ativar encontrar telefone</string>
<string name="prefs_find_phone_summary">Use sua pulseira para reproduzir o toque sonoro do seu celular.</string>
<string name="prefs_find_phone_duration">Duração do toque sonoro em segundos</string>
<string name="maximum_duration">Duração</string>
</resources>

View File

@ -760,4 +760,10 @@
<string name="activity_error_no_app_for_gpx">若需要查看活动轨迹,请安装一个能查看 GPX 文件的应用。</string>
<string name="preferences_makibes_hr3_settings">Makibes HR3 设置</string>
<string name="devicetype_makibes_hr3">Makibes HR3</string>
<string name="devicetype_amazfit_bip_lite">米动手表青春版 Lite</string>
<string name="prefs_find_phone">查找手机</string>
<string name="prefs_enable_find_phone">启用查找手机</string>
<string name="prefs_find_phone_summary">使用您的手环以在手机上播放铃声。</string>
<string name="prefs_find_phone_duration">铃声将持续数秒</string>
<string name="maximum_duration">持续</string>
</resources>

View File

@ -141,6 +141,18 @@
<item>@string/p_pebble_privacy_mode_complete</item>
</string-array>
<string-array name="prefs_find_phone">
<item>@string/off</item>
<item>@string/on</item>
<item>@string/maximum_duration</item>
</string-array>
<string-array name="prefs_find_phone_values">
<item>@string/p_off</item>
<item>@string/p_on</item>
<item>@string/p_scheduled</item>
</string-array>
<string-array name="mi2_dateformats">
<item>@string/dateformat_time</item>
<item>@string/dateformat_date_time</item>
@ -162,6 +174,15 @@
<item>MM/dd/yyyy</item>
</string-array>
<string-array name="do_not_disturb_no_auto">
<item>@string/mi2_dnd_off</item>
<item>@string/mi2_dnd_scheduled</item>
</string-array>
<string-array name="do_not_disturb_no_auto_values">
<item>@string/p_off</item>
<item>@string/p_scheduled</item>
</string-array>
<string-array name="mi2_do_not_disturb">
<item>@string/mi2_dnd_off</item>
<item>@string/mi2_dnd_automatic</item>

View File

@ -300,6 +300,7 @@
<string name="miband_pairing_tap_hint">When your Mi Band vibrates and blinks, tap it a few times in a row.</string>
<string name="appinstaller_install">Install</string>
<string name="discovery_connected_devices_hint">Make your device discoverable. Currently connected devices will likely not be discovered. Activate location (e.g. GPS) on Android 6+. Disable Privacy Guard for Gadgetbridge, because it may crash and reboot your phone. If no device is found after a few minutes, try again after rebooting your mobile device.</string>
<string name="discovery_need_to_enter_authkey">This device needs a secret auth key, long press on the device to enter it. Read the wiki.</string>
<string name="discovery_note">Note:</string>
<string name="candidate_item_device_image">Device image</string>
<string name="miband_prefs_alias">Name/Alias</string>
@ -445,6 +446,10 @@
<string name="miband_prefs_reserve_alarm_calendar">Alarms to reserve for upcoming events</string>
<string name="miband_prefs_hr_sleep_detection">Use heart rate sensor to improve sleep detection</string>
<string name="miband_prefs_device_time_offset_hours">Device time offset in hours (for detecting sleep of shift workers)</string>
<string name="prefs_find_phone">Find phone</string>
<string name="prefs_enable_find_phone">Enable find phone</string>
<string name="prefs_find_phone_summary">Use your band to play your phone\'s ringtone.</string>
<string name="prefs_find_phone_duration">Ring duration in seconds</string>
<string name="miband2_prefs_dateformat">Date format</string>
<string name="dateformat_time">Time</string>
<string name="dateformat_date_time"><![CDATA[Time & date]]></string>
@ -600,6 +605,7 @@
<string name="mi3_night_mode_sunset">At sunset</string>
<string name="mi2_dnd_automatic">Automatic (sleep detection)</string>
<string name="mi2_dnd_scheduled">Scheduled (time interval)</string>
<string name="maximum_duration">Duration</string>
<string name="discovery_attempting_to_pair">Attempting to pair with %1$s</string>
<string name="discovery_bonding_failed_immediately">Bonding with %1$s failed immediately.</string>
<string name="discovery_trying_to_connect_to">Trying to connect to: %1$s</string>

View File

@ -1,5 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.37.0" versioncode="158">
<change>Initial Makibes HR3 support</change>
<change>Amazfit Bip Lite: Inittal working support, firmware update is disabled for now (we do not have any firmware for testing)</change>
<change>Amazfit Cor 2: Enable Emoji Font setting and 3rd party HR access</change>
<change>Find Phone now also vibration in addition to playing the ring tone</change>
<change>ID115: All settings are now per-device</change>
<change>Time format settings are now per-device for all supported devices</change>
<change>Wrist location settings are now per-device for all supported devices</change>
<change>Work around broken layout in database management activity</change>
<change>Show toast in case no app is installed which can handle GPX files</change>
<change>Mi Band 4/Amazfit Bip Lite: Trim white spaces and new lines from auth key</change>
<change>Mi Band 4/Amazfit Bip Lite: Display a toast and do not try to pair if there was no auth key supplied</change>
<change>Skip service scan if supported device could be recognized without uuids during discovery</change>
</release>
<release version="0.36.2" versioncode="157">
<change>Amazfit Bip: Untested support for Lite variant </change>
<change>Force Lineage OS to ask for permission when Trust is used to fix non-working incoming calls</change>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:icon="@drawable/ic_do_not_disturb"
android:key="screen_do_not_disturb_no_auto"
android:persistent="false"
android:summary="@string/mi2_prefs_do_not_disturb_summary"
android:title="@string/mi2_prefs_do_not_disturb">
<!-- workaround for missing toolbar -->
<PreferenceCategory android:title="@string/mi2_prefs_do_not_disturb" />
<ListPreference
android:defaultValue="@string/p_off"
android:entries="@array/do_not_disturb_no_auto"
android:entryValues="@array/do_not_disturb_no_auto_values"
android:key="do_not_disturb_no_auto"
android:summary="%s"
android:title="@string/mi2_prefs_do_not_disturb" />
<nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
android:defaultValue="01:00"
android:key="do_not_disturb_no_auto_start"
android:title="@string/mi2_prefs_do_not_disturb_start" />
<nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
android:defaultValue="06:00"
android:key="do_not_disturb_no_auto_end"
android:title="@string/mi2_prefs_do_not_disturb_end" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:icon="@drawable/ic_find_lost_phone"
android:key="screen_prefs_find_phone"
android:persistent="false"
android:summary="@string/prefs_find_phone_summary"
android:title="@string/prefs_find_phone">
<ListPreference
android:defaultValue="@string/p_off"
android:entries="@array/prefs_find_phone"
android:entryValues="@array/prefs_find_phone_values"
android:key="prefs_find_phone"
android:title="@string/prefs_enable_find_phone" />
<EditTextPreference
android:defaultValue="60"
android:inputType="number"
android:key="prefs_find_phone_duration"
android:title="@string/prefs_find_phone_duration" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,12 @@
* Initial Makibes HR3 support
* Amazfit Bip Lite: Inittal working support, firmware update is disabled for now (we do not have any firmware for testing)
* Amazfit Cor 2: Enable Emoji Font setting and 3rd party HR access
* Find Phone now also vibration in addition to playing the ring tone
* ID115: All settings are now per-device
* Time format settings are now per-device for all supported devices
* Wrist location settings are now per-device for all supported devices
* Work around broken layout in database management activity
* Show toast in case no app is installed which can handle GPX files
* Mi Band 4/Amazfit Bip Lite: Trim white spaces and new lines from auth key
* Mi Band 4/Amazfit Bip Lite: Display a toast and do not try to pair if there was no auth key supplied
* Skip service scan if supported device could be recognized without uuids during discovery