1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-26 10:35:50 +01:00

Merge branch 'master' into background-javascript

This commit is contained in:
Andreas Shimokawa 2017-09-13 21:51:55 +02:00
commit 99d873a178
48 changed files with 905 additions and 237 deletions

View File

@ -1,5 +1,14 @@
### Changelog
#### Version 0.21.1
* Initial support for EXRIZU K8 (HPLus variant)
* Amazfit Bip: fix long messages not being displayed at all
* Mi Band 2: Support multiple button actions
* NO.1 F1: Fetch sleep data
* NO.1 F1: Heart rate support
* Pebble: Support controlling the current active media playback application
* Fix suspended activities coming to front when rotating the screen
#### Version 0.21.0
* Initial NO.1 F1 support
* Initial Teclast H30 support

View File

@ -42,7 +42,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
Schema schema = new Schema(16, MAIN_PACKAGE + ".entities");
Schema schema = new Schema(17, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -265,6 +265,7 @@ public class GBDaoGenerator {
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
return activitySample;
}

View File

@ -26,8 +26,8 @@ android {
targetSdkVersion 25
// note: always bump BOTH versionCode and versionName!
versionName "0.21.0"
versionCode 101
versionName "0.21.1"
versionCode 102
vectorDrawables.useSupportLibrary = true
}
buildTypes {

View File

@ -33,6 +33,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
public abstract class AbstractGBActivity extends AppCompatActivity implements GBActivity {
private boolean isLanguageInvalid = false;
public static final int NONE = 0;
public static final int NO_ACTIONBAR = 1;
@ -52,8 +53,11 @@ public abstract class AbstractGBActivity extends AppCompatActivity implements GB
}
};
public void setLanguage(Locale language, boolean recreate) {
AndroidUtils.setLanguage(this, language, recreate);
public void setLanguage(Locale language, boolean invalidateLanguage) {
if (invalidateLanguage) {
isLanguageInvalid = true;
}
AndroidUtils.setLanguage(this, language);
}
public static void init(GBActivity activity) {
@ -88,6 +92,15 @@ public abstract class AbstractGBActivity extends AppCompatActivity implements GB
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
if (isLanguageInvalid) {
isLanguageInvalid = false;
recreate();
}
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);

View File

@ -49,6 +49,8 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
private static final Logger LOG = LoggerFactory.getLogger(AbstractSettingsActivity.class);
private boolean isLanguageInvalid = false;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -152,6 +154,15 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
}
}
@Override
protected void onResume() {
super.onResume();
if (isLanguageInvalid) {
isLanguageInvalid = false;
recreate();
}
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
@ -210,7 +221,10 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
return super.onOptionsItemSelected(item);
}
public void setLanguage(Locale language, boolean recreate) {
AndroidUtils.setLanguage(this, language, recreate);
public void setLanguage(Locale language, boolean invalidateLanguage) {
if (invalidateLanguage) {
isLanguageInvalid = true;
}
AndroidUtils.setLanguage(this, language);
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Daniele Gobbetti
/* Copyright (C) 2017 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge.

View File

@ -78,6 +78,8 @@ public class ControlCenterv2 extends AppCompatActivity
private GBDeviceAdapterv2 mGBDeviceAdapter;
private RecyclerView deviceListView;
private boolean isLanguageInvalid = false;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -206,6 +208,15 @@ public class ControlCenterv2 extends AppCompatActivity
}
}
@Override
protected void onResume() {
super.onResume();
if (isLanguageInvalid) {
isLanguageInvalid = false;
recreate();
}
}
@Override
protected void onDestroy() {
unregisterForContextMenu(deviceListView);
@ -311,7 +322,10 @@ public class ControlCenterv2 extends AppCompatActivity
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);
}
public void setLanguage(Locale language, boolean recreate) {
AndroidUtils.setLanguage(this, language, recreate);
public void setLanguage(Locale language, boolean invalidateLanguage) {
if (invalidateLanguage) {
isLanguageInvalid = true;
}
AndroidUtils.setLanguage(this, language);
}
}

View File

@ -1,9 +1,25 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import java.util.Locale;
public interface GBActivity {
void setLanguage(Locale language, boolean recreate);
void setLanguage(Locale language, boolean invalidateLanguage);
void setTheme(int themeId);
}

View File

@ -159,6 +159,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
String newLang = newVal.toString();
try {
GBApplication.setLanguage(newLang);
recreate();
} catch (Exception ex) {
GB.toast(getApplicationContext(),
"Error setting language: " + ex.getLocalizedMessage(),

View File

@ -0,0 +1,38 @@
/* Copyright (C) 2017 protomors
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.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
public class GadgetbridgeUpdate_17 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
if (!DBHelper.existsColumn(No1F1ActivitySampleDao.TABLENAME, No1F1ActivitySampleDao.Properties.HeartRate.columnName, db)) {
String ADD_COLUMN_HEART_RATE = "ALTER TABLE " + No1F1ActivitySampleDao.TABLENAME + " ADD COLUMN "
+ No1F1ActivitySampleDao.Properties.HeartRate.columnName + " INTEGER NOT NULL DEFAULT 0;";
db.execSQL(ADD_COLUMN_HEART_RATE);
}
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
}
}

View File

@ -0,0 +1,55 @@
/* Copyright (C) 2017 João Paulo Barraca, Quallenauge
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.hplus;
/*
* @author Quallenauge &lt;Hamsi2k@freenet.de&gt;
*/
import android.support.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
/**
* Pseudo Coordinator for the EXRIZU K8, a sub type of the HPLUS devices
*/
public class EXRIZUK8Coordinator extends HPlusCoordinator {
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String name = candidate.getDevice().getName();
if(name != null && name.startsWith("iRun ")){
return DeviceType.EXRIZUK8;
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.EXRIZUK8;
}
@Override
public String getManufacturer() {
return "EXRIZU";
}
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2017 Sami Alaoui
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
import java.util.UUID;

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, protomors, Sami Alaoui
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
import android.annotation.TargetApi;

View File

@ -207,6 +207,8 @@ public class MiBand2Service {
public static final byte SUCCESS = 0x01;
public static final byte COMMAND_ACTIVITY_DATA_START_DATE = 0x01;
public static final byte COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY = 0x01;
public static final byte COMMAND_ACTIVITY_DATA_TYPE_UNKNOWN_2 = 0x02;
public static final byte COMMAND_ACTIVITY_DATA_XXX_DATE = 0x02; // issued on first connect, followd by COMMAND_XXXX_ACTIVITY_DATA instead of COMMAND_FETCH_ACTIVITY_DATA
public static final byte COMMAND_FIRMWARE_INIT = 0x01; // to UUID_CHARACTERISTIC_FIRMWARE, followed by fw file size in bytes

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
Fischer, Daniele Gobbetti, José Rebelo, Szymon Tomasz Stefanek
Fischer, Daniele Gobbetti, José Rebelo, Michal Novotny, Szymon Tomasz Stefanek
This file is part of Gadgetbridge.
@ -36,6 +36,7 @@ public final class MiBandConst {
public static final String PREF_MIBAND_BUTTON_ACTION_VIBRATE = "mi2_button_action_vibrate";
public static final String PREF_MIBAND_BUTTON_PRESS_COUNT = "mi_button_press_count";
public static final String PREF_MIBAND_BUTTON_PRESS_MAX_DELAY = "mi_button_press_count_max_delay";
public static final String PREF_MIBAND_BUTTON_ACTION_DELAY = "mi_button_press_count_match_delay";
public static final String PREF_MIBAND_BUTTON_PRESS_BROADCAST = "mi_button_press_broadcast";
public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours";

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2017 protomors
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.no1f1;
import java.util.UUID;
@ -18,6 +34,8 @@ public final class No1F1Constants {
public static final byte CMD_REALTIME_STEPS = (byte) 0xb1;
public static final byte CMD_FETCH_STEPS = (byte) 0xb2;
public static final byte CMD_FETCH_SLEEP = (byte) 0xb3;
public static final byte CMD_REALTIME_HEARTRATE = (byte) 0xe5;
public static final byte CMD_FETCH_HEARTRATE = (byte) 0xe6;
public static final byte CMD_NOTIFICATION = (byte) 0xc1;
public static final byte CMD_ICON = (byte) 0xc3;
public static final byte CMD_DEVICE_SETTINGS = (byte) 0xd3;

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, protomors
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.no1f1;
import android.annotation.TargetApi;
@ -108,7 +125,7 @@ public class No1F1Coordinator extends AbstractDeviceCoordinator {
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return false;
return true;
}
@Override

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2017 protomors
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.no1f1;
import android.support.annotation.NonNull;

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Daniele Gobbetti, Kevin Richter
/* Copyright (C) 2016-2017 Andreas Shimokawa, AnthonyDiGirolamo, Daniele
Gobbetti, Kevin Richter
This file is part of Gadgetbridge.

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, João Paulo Barraca
Gobbetti, João Paulo Barraca, protomors, Quallenauge, Sami Alaoui
This file is part of Gadgetbridge.
@ -37,6 +37,7 @@ public enum DeviceType {
LIVEVIEW(30, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled),
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
MAKIBESF68(41, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
EXRIZUK8(42, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
NO1F1(50, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
TECLASTH30(60, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Julien
Pivotto, Kevin Richter
/* Copyright (C) 2015-2017 Andreas Shimokawa, AnthonyDiGirolamo, Carsten
Pfeiffer, Julien Pivotto, Kevin Richter
This file is part of Gadgetbridge.

View File

@ -1,5 +1,6 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, João Paulo Barraca, Sergey Trofimov
Daniele Gobbetti, João Paulo Barraca, protomors, Quallenauge, Sami Alaoui,
Sergey Trofimov
This file is part of Gadgetbridge.
@ -127,6 +128,9 @@ public class DeviceSupportFactory {
case MAKIBESF68:
deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.MAKIBESF68), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case EXRIZUK8:
deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.EXRIZUK8), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case NO1F1:
deviceSupport = new ServiceDeviceSupport(new No1F1Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;

View File

@ -1,19 +1,3 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*
* Copyright (C) 2013 The Android Open Source Project
*

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Carsten Pfeiffer
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.

View File

@ -45,9 +45,11 @@ public class AmazfitBipFirmwareInfo extends Mi2FirmwareInfo {
static {
// firmware
crcToVersion.put(25257, "0.0.8.74");
crcToVersion.put(57724, "0.0.8.88");
// resources
crcToVersion.put(12586, "RES 0.0.8.74");
crcToVersion.put(34068, "RES 0.0.8.88");
// gps
crcToVersion.put(61520, "GPS 9367,8f79a91,0,0,");

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Andreas Shimokawa
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
@ -80,7 +80,7 @@ public class AmazfitBipSupport extends MiBand2Support {
try {
TransactionBuilder builder = performInitialized("new notification");
AlertNotificationProfile<?> profile = new AlertNotificationProfile(this);
profile.setMaxLength(255); // TODO: find out real limit, certainly it is more than 18 which is default
profile.setMaxLength(230);
int customIconId = AmazfitBipIcon.mapToIconId(notificationSpec.type);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Andreas Shimokawa
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
ivanovlev, João Paulo Barraca, Pavel Motyrev
ivanovlev, João Paulo Barraca, Pavel Motyrev, Quallenauge
This file is part of Gadgetbridge.
@ -160,7 +160,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
private HPlusSupport syncPreferences(TransactionBuilder transaction) {
if (deviceType == DeviceType.HPLUS) {
if (deviceType == DeviceType.HPLUS || deviceType == DeviceType.EXRIZUK8) {
setSIT(transaction); //Sync SIT Interval
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2017 Sami Alaoui
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou;
import android.bluetooth.BluetoothGatt;

View File

@ -1,6 +1,6 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
Fischer, Daniele Gobbetti, JohnnySun, José Rebelo, Julien Pivotto, Kasha,
Sergey Trofimov, Steffen Liebergeld
Michal Novotny, Sergey Trofimov, Steffen Liebergeld
This file is part of Gadgetbridge.
@ -24,9 +24,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.text.format.DateFormat;
import android.widget.Toast;
@ -42,6 +40,8 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -126,8 +126,10 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ge
public class MiBand2Support extends AbstractBTLEDeviceSupport {
// We introduce key press counter for notification purposes
private static int currentButtonActionId = 0;
private static int currentButtonPressCount = 0;
private static long currentButtonPressTime = 0;
private static long currentButtonTimerActivationTime = 0;
private static final Logger LOG = LoggerFactory.getLogger(MiBand2Support.class);
private final DeviceInfoProfile<MiBand2Support> deviceInfoProfile;
@ -782,6 +784,80 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
// not supported
}
public void runButtonAction() {
Prefs prefs = GBApplication.getPrefs();
if (currentButtonTimerActivationTime != currentButtonPressTime) {
return;
}
String requiredButtonPressMessage = prefs.getString(MiBandConst.PREF_MIBAND_BUTTON_PRESS_BROADCAST,
this.getContext().getString(R.string.mi2_prefs_button_press_broadcast_default_value));
Intent in = new Intent();
in.setAction(requiredButtonPressMessage);
in.putExtra("button_id", currentButtonActionId);
LOG.info("Sending " + requiredButtonPressMessage + " with button_id " + currentButtonActionId);
this.getContext().getApplicationContext().sendBroadcast(in);
if (prefs.getBoolean(MiBandConst.PREF_MIBAND_BUTTON_ACTION_VIBRATE, false)) {
performPreferredNotification(null, null, null, MiBand2Service.ALERT_LEVEL_VIBRATE_ONLY, null);
}
currentButtonActionId = 0;
currentButtonPressCount = 0;
currentButtonPressTime = System.currentTimeMillis();
}
public void handleButtonPressed(byte[] value) {
LOG.info("Button pressed");
///logMessageContent(value);
// If disabled we return from function immediately
Prefs prefs = GBApplication.getPrefs();
if (!prefs.getBoolean(MiBandConst.PREF_MIBAND_BUTTON_ACTION_ENABLE, false)) {
return;
}
int buttonPressMaxDelay = prefs.getInt(MiBandConst.PREF_MIBAND_BUTTON_PRESS_MAX_DELAY, 2000);
int buttonActionDelay = prefs.getInt(MiBandConst.PREF_MIBAND_BUTTON_ACTION_DELAY, 0);
int requiredButtonPressCount = prefs.getInt(MiBandConst.PREF_MIBAND_BUTTON_PRESS_COUNT, 0);
if (requiredButtonPressCount > 0) {
long timeSinceLastPress = System.currentTimeMillis() - currentButtonPressTime;
if ((currentButtonPressTime == 0) || (timeSinceLastPress < buttonPressMaxDelay)) {
currentButtonPressCount++;
}
else {
currentButtonPressCount = 1;
currentButtonActionId = 0;
}
currentButtonPressTime = System.currentTimeMillis();
if (currentButtonPressCount == requiredButtonPressCount) {
currentButtonTimerActivationTime = currentButtonPressTime;
if (buttonActionDelay > 0) {
LOG.info("Activating timer");
final Timer buttonActionTimer = new Timer("Mi Band Button Action Timer");
buttonActionTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
runButtonAction();
buttonActionTimer.cancel();
}
}, buttonActionDelay, buttonActionDelay);
}
else {
LOG.info("Activating button action");
runButtonAction();
}
currentButtonActionId++;
currentButtonPressCount = 0;
}
}
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
@ -811,54 +887,10 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
logMessageContent(characteristic.getValue());
}
return false;
}
public void handleButtonPressed(byte[] value) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.getContext());
LOG.info("Button pressed");
logMessageContent(value);
Prefs prefs = GBApplication.getPrefs();
// If disabled we return from function immediately
if (!prefs.getBoolean(MiBandConst.PREF_MIBAND_BUTTON_ACTION_ENABLE, false)) {
return;
}
int buttonPressMaxDelay = prefs.getInt(MiBandConst.PREF_MIBAND_BUTTON_PRESS_MAX_DELAY, 2000);
int requiredButtonPressCount = prefs.getInt(MiBandConst.PREF_MIBAND_BUTTON_PRESS_COUNT, 0);
String requiredButtonPressMessage = prefs.getString(MiBandConst.PREF_MIBAND_BUTTON_PRESS_BROADCAST,
this.getContext().getString(R.string.mi2_prefs_button_press_broadcast_default_value));
if (requiredButtonPressCount > 0) {
long timeSinceLastPress = System.currentTimeMillis() - currentButtonPressTime;
if ((currentButtonPressTime == 0) || (timeSinceLastPress < buttonPressMaxDelay)) {
currentButtonPressCount++;
}
else {
currentButtonPressCount = 0;
}
currentButtonPressTime = System.currentTimeMillis();
if (currentButtonPressCount >= requiredButtonPressCount) {
Intent in = new Intent();
in.setAction(requiredButtonPressMessage);
this.getContext().getApplicationContext().sendBroadcast(in);
currentButtonPressCount = 0;
currentButtonPressTime = System.currentTimeMillis();
if (prefs.getBoolean(MiBandConst.PREF_MIBAND_BUTTON_ACTION_VIBRATE, false)) {
performPreferredNotification(null, null, null, MiBand2Service.ALERT_LEVEL_VIBRATE_ONLY, null);
}
}
}
}
private void handleUnknownCharacteristic(byte[] value) {
}
@ -888,6 +920,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
LOG.info("Unhandled characteristic read: " + characteristicUUID);
logMessageContent(characteristic.getValue());
}
return false;
}
@ -1063,6 +1096,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
// getContext().getString(R.string.DEVINFO_HR_VER),
// info.getSoftwareRevision()));
// }
LOG.warn("Device info: " + info);
versionCmd.hwVersion = info.getHardwareRevision();
// versionCmd.fwVersion = info.getFirmwareRevision(); // always null

View File

@ -0,0 +1,193 @@
/* Copyright (C) 2017 Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.SharedPreferences;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.AbstractMiBand2Operation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/**
* An operation that fetches activity data. For every fetch, a new operation must
* be created, i.e. an operation may not be reused for multiple fetches.
*/
public abstract class AbstractFetchOperation extends AbstractMiBand2Operation {
private static final Logger LOG = LoggerFactory.getLogger(AbstractFetchOperation.class);
protected byte lastPacketCounter;
protected int fetchCount;
protected BluetoothGattCharacteristic characteristicActivityData;
protected BluetoothGattCharacteristic characteristicFetch;
protected Calendar startTimestamp;
public AbstractFetchOperation(MiBand2Support support) {
super(support);
}
@Override
protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
if (!enable) {
// dynamically enabled, but always disabled on finish
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA), enable);
}
}
@Override
protected void doPerform() throws IOException {
startFetching();
}
protected void startFetching() throws IOException {
lastPacketCounter = -1;
TransactionBuilder builder = performInitialized("fetching activity data");
getSupport().setLowLatency(builder);
if (fetchCount == 0) {
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
}
fetchCount++;
characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
builder.notify(characteristicActivityData, false);
characteristicFetch = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4);
builder.notify(characteristicFetch, true);
startFetching(builder);
builder.queue(getQueue());
}
protected abstract void startFetching(TransactionBuilder builder);
protected abstract String getLastSyncTimeKey();
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
if (MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA.equals(characteristicUUID)) {
handleActivityNotif(characteristic.getValue());
return true;
} else if (MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
handleActivityMetadata(characteristic.getValue());
return true;
} else {
return super.onCharacteristicChanged(gatt, characteristic);
}
}
@CallSuper
protected void handleActivityFetchFinish() {
operationFinished();
unsetBusy();
}
/**
* Method to handle the incoming activity data.
* There are two kind of messages we currently know:
* - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
* - the second one is 20 bytes long and contains the actual activity data
* <p/>
* The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
*
* @param value
*/
protected abstract void handleActivityNotif(byte[] value);
/**
* Creates samples from the given 17-length array
* @param value
*/
protected abstract void bufferActivityData(byte[] value);
protected void handleActivityMetadata(byte[] value) {
if (value.length == 15) {
// first two bytes are whether our request was accepted
if (ArrayUtils.equals(value, MiBand2Service.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
// the third byte (0x01 on success) = ?
// the 4th - 7th bytes probably somehow represent the number of bytes/packets to expect
// last 8 bytes are the start date
Calendar startTimestamp = getSupport().fromTimeBytes(org.apache.commons.lang3.ArrayUtils.subarray(value, 7, value.length));
setStartTimestamp(startTimestamp);
GB.toast(getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since,
DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), Toast.LENGTH_LONG, GB.INFO);
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
handleActivityFetchFinish();
}
} else if (value.length == 3) {
if (Arrays.equals(MiBand2Service.RESPONSE_FINISH_SUCCESS, value)) {
handleActivityFetchFinish();
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
handleActivityFetchFinish();
}
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
handleActivityFetchFinish();
}
}
protected void setStartTimestamp(Calendar startTimestamp) {
this.startTimestamp = startTimestamp;
}
protected void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) {
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis());
editor.apply();
}
protected GregorianCalendar getLastSuccessfulSyncTime() {
long timeStampMillis = GBApplication.getPrefs().getLong(getLastSyncTimeKey(), 0);
if (timeStampMillis != 0) {
GregorianCalendar calendar = BLETypeConversions.createCalendar();
calendar.setTimeInMillis(timeStampMillis);
return calendar;
}
GregorianCalendar calendar = BLETypeConversions.createCalendar();
calendar.add(Calendar.DAY_OF_MONTH, -10);
return calendar;
}
}

View File

@ -16,11 +16,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.v4.util.TimeUtils;
import android.text.format.DateUtils;
import android.widget.Toast;
@ -28,18 +23,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
@ -51,11 +41,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.AbstractMiBand2Operation;
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -63,95 +50,31 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
* An operation that fetches activity data. For every fetch, a new operation must
* be created, i.e. an operation may not be reused for multiple fetches.
*/
public class FetchActivityOperation extends AbstractMiBand2Operation {
public class FetchActivityOperation extends AbstractFetchOperation {
private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
private List<MiBandActivitySample> samples = new ArrayList<>(60*24); // 1day per default
private byte lastPacketCounter;
private Calendar startTimestamp;
private int fetchCount;
public FetchActivityOperation(MiBand2Support support) {
super(support);
}
@Override
protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
if (!enable) {
// dynamically enabled, but always disabled on finish
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA), enable);
}
protected void startFetching() throws IOException {
samples.clear();
super.startFetching();
}
@Override
protected void doPerform() throws IOException {
startFetching();
}
private void startFetching() throws IOException {
samples.clear();
lastPacketCounter = -1;
TransactionBuilder builder = performInitialized("fetching activity data");
getSupport().setLowLatency(builder);
if (fetchCount == 0) {
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
}
fetchCount++;
BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
builder.notify(characteristicActivityData, false);
BluetoothGattCharacteristic characteristicFetch = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4);
builder.notify(characteristicFetch, true);
protected void startFetching(TransactionBuilder builder) {
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, 0x01 }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, MiBand2Service.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply
builder.notify(characteristicActivityData, true);
builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_ACTIVITY_DATA });
builder.queue(getQueue());
}
private GregorianCalendar getLastSuccessfulSyncTime() {
long timeStampMillis = GBApplication.getPrefs().getLong(getLastSyncTimeKey(), 0);
if (timeStampMillis != 0) {
GregorianCalendar calendar = BLETypeConversions.createCalendar();
calendar.setTimeInMillis(timeStampMillis);
return calendar;
}
GregorianCalendar calendar = BLETypeConversions.createCalendar();
calendar.add(Calendar.DAY_OF_MONTH, -10);
return calendar;
}
private void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) {
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis());
editor.apply();
}
private String getLastSyncTimeKey() {
return getDevice().getAddress() + "_" + "lastSyncTimeMillis";
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
if (MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA.equals(characteristicUUID)) {
handleActivityNotif(characteristic.getValue());
return true;
} else if (MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
handleActivityMetadata(characteristic.getValue());
return true;
} else {
return super.onCharacteristicChanged(gatt, characteristic);
}
}
private void handleActivityFetchFinish() {
protected void handleActivityFetchFinish() {
LOG.info("Fetching activity data has finished round " + fetchCount);
GregorianCalendar lastSyncTimestamp = saveSamples();
if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) {
@ -163,8 +86,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
}
}
operationFinished();
unsetBusy();
super.handleActivityFetchFinish();
}
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
@ -232,7 +154,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
*
* @param value
*/
private void handleActivityNotif(byte[] value) {
protected void handleActivityNotif(byte[] value) {
if (!isOperationRunning()) {
LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
getSupport().logMessageContent(value);
@ -257,7 +179,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
* Creates samples from the given 17-length array
* @param value
*/
private void bufferActivityData(byte[] value) {
protected void bufferActivityData(byte[] value) {
int len = value.length;
if (len % 4 != 1) {
@ -280,34 +202,8 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
return sample;
}
private void handleActivityMetadata(byte[] value) {
if (value.length == 15) {
// first two bytes are whether our request was accepted
if (ArrayUtils.equals(value, MiBand2Service.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
// the third byte (0x01 on success) = ?
// the 4th - 7th bytes probably somehow represent the number of bytes/packets to expect
// last 8 bytes are the start date
Calendar startTimestamp = getSupport().fromTimeBytes(org.apache.commons.lang3.ArrayUtils.subarray(value, 7, value.length));
setStartTimestamp(startTimestamp);
GB.toast(getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since,
DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), Toast.LENGTH_LONG, GB.INFO);
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
handleActivityFetchFinish();
}
} else if (value.length == 3) {
if (Arrays.equals(MiBand2Service.RESPONSE_FINISH_SUCCESS, value)) {
handleActivityFetchFinish();
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
handleActivityFetchFinish();
}
}
}
private void setStartTimestamp(Calendar startTimestamp) {
this.startTimestamp = startTimestamp;
@Override
protected String getLastSyncTimeKey() {
return getDevice().getAddress() + "_" + "lastSyncTimeMillis";
}
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2017 protomors
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1;
import android.bluetooth.BluetoothGatt;
@ -114,6 +130,15 @@ public class No1F1Support extends AbstractBTLEDeviceSupport {
case No1F1Constants.CMD_FETCH_STEPS:
handleStepData(data);
return true;
case No1F1Constants.CMD_FETCH_SLEEP:
handleSleepData(data);
return true;
case No1F1Constants.CMD_FETCH_HEARTRATE:
handleHeartRateData(data);
return true;
case No1F1Constants.CMD_REALTIME_HEARTRATE:
handleRealtimeHeartRateData(data);
return true;
case No1F1Constants.CMD_NOTIFICATION:
case No1F1Constants.CMD_ICON:
case No1F1Constants.CMD_DEVICE_SETTINGS:
@ -237,7 +262,17 @@ public class No1F1Support extends AbstractBTLEDeviceSupport {
@Override
public void onHeartRateTest() {
try {
TransactionBuilder builder = performInitialized("heartRateTest");
byte[] msg = new byte[]{
No1F1Constants.CMD_REALTIME_HEARTRATE,
(byte) 0x11
};
builder.write(ctrlCharacteristic, msg);
performConnected(builder.getTransaction());
} catch (IOException e) {
GB.toast(getContext(), "Error starting heart rate measurement: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
@Override
@ -470,10 +505,18 @@ public class No1F1Support extends AbstractBTLEDeviceSupport {
}
samples.clear();
LOG.info("Steps data saved");
if (getDevice().isBusy()) {
getDevice().unsetBusyTask();
getDevice().sendDeviceUpdateIntent(getContext());
try {
TransactionBuilder builder = performInitialized("fetchSleep");
byte[] msg = new byte[]{
No1F1Constants.CMD_FETCH_SLEEP,
(byte) 0xfa
};
builder.write(ctrlCharacteristic, msg);
performConnected(builder.getTransaction());
} catch (IOException e) {
GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
} catch (Exception ex) {
GB.toast(getContext(), "Error saving step data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
@ -498,4 +541,132 @@ public class No1F1Support extends AbstractBTLEDeviceSupport {
);
}
}
private void handleSleepData(byte[] data) {
if (data[1] == (byte) 0xfd) {
// TODO Check CRC
if (samples.size() > 0) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
No1F1SampleProvider provider = new No1F1SampleProvider(getDevice(), dbHandler.getDaoSession());
for (int i = 0; i < samples.size(); i++) {
samples.get(i).setDeviceId(deviceId);
samples.get(i).setUserId(userId);
if (samples.get(i).getRawIntensity()<7)
samples.get(i).setRawKind(ActivityKind.TYPE_DEEP_SLEEP);
else
samples.get(i).setRawKind(ActivityKind.TYPE_LIGHT_SLEEP);
provider.addGBActivitySample(samples.get(i));
}
samples.clear();
LOG.info("Sleep data saved");
try {
TransactionBuilder builder = performInitialized("fetchHeartRate");
byte[] msg = new byte[]{
No1F1Constants.CMD_FETCH_HEARTRATE,
(byte) 0xfa
};
builder.write(ctrlCharacteristic, msg);
performConnected(builder.getTransaction());
} catch (IOException e) {
GB.toast(getContext(), "Error fetching heart rate data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
} catch (Exception ex) {
GB.toast(getContext(), "Error saving sleep data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
} else {
No1F1ActivitySample sample = new No1F1ActivitySample();
Calendar timestamp = GregorianCalendar.getInstance();
timestamp.set(Calendar.YEAR, data[1] * 256 + (data[2] & 0xff));
timestamp.set(Calendar.MONTH, (data[3] - 1) & 0xff);
timestamp.set(Calendar.DAY_OF_MONTH, data[4] & 0xff);
timestamp.set(Calendar.HOUR_OF_DAY, data[5] & 0xff);
timestamp.set(Calendar.MINUTE, data[6] & 0xff);
timestamp.set(Calendar.SECOND, 0);
sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000L));
sample.setRawIntensity(data[7] * 256 + (data[8] & 0xff));
samples.add(sample);
LOG.info("Received sleep data for " + String.format("%1$TD %1$TT", timestamp) + ": " +
sample.getRawIntensity() + " rolls"
);
}
}
private void handleHeartRateData(byte[] data) {
if (data[1] == (byte) 0xfd) {
// TODO Check CRC
if (samples.size() > 0) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
No1F1SampleProvider provider = new No1F1SampleProvider(getDevice(), dbHandler.getDaoSession());
for (int i = 0; i < samples.size(); i++) {
samples.get(i).setDeviceId(deviceId);
samples.get(i).setUserId(userId);
provider.addGBActivitySample(samples.get(i));
}
samples.clear();
LOG.info("Heart rate data saved");
if (getDevice().isBusy()) {
getDevice().unsetBusyTask();
getDevice().sendDeviceUpdateIntent(getContext());
}
} catch (Exception ex) {
GB.toast(getContext(), "Error saving heart rate data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
} else {
No1F1ActivitySample sample = new No1F1ActivitySample();
Calendar timestamp = GregorianCalendar.getInstance();
timestamp.set(Calendar.YEAR, data[1] * 256 + (data[2] & 0xff));
timestamp.set(Calendar.MONTH, (data[3] - 1) & 0xff);
timestamp.set(Calendar.DAY_OF_MONTH, data[4] & 0xff);
timestamp.set(Calendar.HOUR_OF_DAY, data[5] & 0xff);
timestamp.set(Calendar.MINUTE, data[6] & 0xff);
timestamp.set(Calendar.SECOND, 0);
sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000L));
sample.setHeartRate(data[7] & 0xff);
samples.add(sample);
LOG.info("Received heart rate data for " + String.format("%1$TD %1$TT", timestamp) + ": " +
sample.getHeartRate() + " BPM"
);
}
}
private void handleRealtimeHeartRateData(byte[] data) {
if (data.length==2)
{
if (data[1]==(byte) 0x11)
LOG.info("Heart rate measurement started.");
else
LOG.info("Heart rate measurement stopped.");
return;
}
// Check if data is valid. Otherwise ignore sample.
if (data[2]==0) {
No1F1ActivitySample sample = new No1F1ActivitySample();
sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
sample.setHeartRate(data[3] & 0xff);
LOG.info("Current heart rate is: " + sample.getHeartRate() + " BPM");
try (DBHandler dbHandler = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
No1F1SampleProvider provider = new No1F1SampleProvider(getDevice(), dbHandler.getDaoSession());
sample.setDeviceId(deviceId);
sample.setUserId(userId);
provider.addGBActivitySample(sample);
} catch (Exception ex) {
LOG.warn("Error saving current heart rate: " + ex.getLocalizedMessage());
}
}
}
}

View File

@ -36,6 +36,8 @@ import java.util.Random;
import java.util.SimpleTimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement;
@ -591,9 +593,9 @@ public class PebbleProtocol extends GBDeviceProtocol {
byte actions_count;
short actions_length;
String dismiss_string;
String open_string = "Open on phone";
String mute_string = "Mute";
String reply_string = "Reply";
String open_string = GBApplication.getContext().getString(R.string._pebble_watch_open_on_phone);
String mute_string = GBApplication.getContext().getString(R.string._pebble_watch_mute);
String reply_string = GBApplication.getContext().getString(R.string._pebble_watch_reply);
if (sourceName != null) {
mute_string += " " + sourceName;
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
Gobbetti, Gabe Schrecker
This file is part of Gadgetbridge.
@ -18,17 +18,23 @@
package nodomain.freeyourgadget.gadgetbridge.service.receivers;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.os.SystemClock;
import android.view.KeyEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class GBMusicControlReceiver extends BroadcastReceiver {
@ -73,6 +79,22 @@ public class GBMusicControlReceiver extends BroadcastReceiver {
Prefs prefs = GBApplication.getPrefs();
String audioPlayer = prefs.getString("audio_player", "default");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
MediaSessionManager mediaSessionManager =
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
List<MediaController> controllers = mediaSessionManager.getActiveSessions(
new ComponentName(context, NotificationListener.class));
try {
MediaController controller = controllers.get(0);
audioPlayer = controller.getPackageName();
} catch (IndexOutOfBoundsException e) {
System.err.println("IndexOutOfBoundsException: " + e.getMessage());
}
}
LOG.debug("keypress: " + musicCmd.toString() + " sent to: " + audioPlayer);
long eventtime = SystemClock.uptimeMillis();
Intent downIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Carsten Pfeiffer
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
@ -70,14 +70,6 @@ public class AndroidUtils {
}
}
public static void setLanguage(Activity activity, Locale language, boolean recreate) {
setLanguage(activity.getBaseContext(), language);
if (recreate) {
activity.recreate();
}
}
public static void setLanguage(Context context, Locale language) {
Configuration config = new Configuration();
config.setLocale(language);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, João Paulo Barraca
Daniele Gobbetti, João Paulo Barraca, protomors, Quallenauge, Sami Alaoui
This file is part of Gadgetbridge.
@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipCooordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30Coordinator;
@ -196,6 +197,7 @@ public class DeviceHelper {
result.add(new HPlusCoordinator());
result.add(new No1F1Coordinator());
result.add(new MakibesF68Coordinator());
result.add(new EXRIZUK8Coordinator());
result.add(new TeclastH30Coordinator());
return result;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Carsten Pfeiffer
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer, Michal Novotny
This file is part of Gadgetbridge.
@ -68,4 +68,4 @@ public class Version implements Comparable<Version> {
public int hashCode() {
return version.hashCode();
}
}
}

View File

@ -413,13 +413,14 @@
<string name="mi2_prefs_button_press_count_summary">Definujte počet stisknutí tlačítka k vyslání broadcast zprávy</string>
<string name="mi2_prefs_button_press_broadcast">Zpráva k vyslání (broadcast)</string>
<string name="mi2_prefs_button_press_broadcast_summary">Zpráva k vyslání do systému pokud počtu stisknutí tlačítka (viz výše)</string>
<string name="mi2_prefs_button_press_broadcast_default_value">nodomain.freeyourgadget.gadgetbridge.mibandButtonPressed</string>
<string name="mi2_prefs_button_action">Povolit akci tlačítka</string>
<string name="mi2_prefs_button_action_summary">Povolit akci tlačítka na definovaný počet stisknutí</string>
<string name="mi2_prefs_button_action_vibrate">Povolit vibrace náramku</string>
<string name="mi2_prefs_button_action_vibrate_summary">Povolit vibrace náramku při vyslání broadcast zprávy</string>
<string name="mi2_prefs_button_press_count_max_delay">Maximální prodleva mezi stisky</string>
<string name="mi2_prefs_button_press_count_max_delay_summary">Maximální prodleva mezi stisky tlačítka v milisekundách</string>
<string name="mi2_prefs_button_press_count_match_delay">Prodleva před akcí</string>
<string name="mi2_prefs_button_press_count_match_delay_summary">Prodleva před akcí tlačítka. Umožňuje více průchodů (v extra button_id u Intent) nebo 0 pro okamžitou akci</string>
<string name="fw_upgrade_notice_amazfitbip">Chystáte se nainstalovat firmware %s namísto toho, který je aktuálně na vašem Amazfit Bipu.
\n
\nUjistěte se, že jste nainstalovali firmware .gps, pak soubor .res a nakonec soubor .fw. Hodiny se po instalaci souboru .fw restartují.

View File

@ -289,7 +289,7 @@
<string name="pref_summary_keep_data_on_device">Aktivitätsdaten verbleiben auf dem Mi Band, auch nach der Synchronisierung. Hilfreich, wenn das Mi Band mit weiteren Apps verwendet wird.</string>
<string name="pref_title_low_latency_fw_update">Benutze Modus mit niedriger Latenz für Firmware-Updates</string>
<string name="pref_summary_low_latency_fw_update">Dies kann bei Geräten helfen, bei denen Firmwareupdates fehlschlagen</string>
<string name="live_activity_steps_history">Verlauf Schritte</string>
<string name="live_activity_steps_history">Schritteverlauf</string>
<string name="live_activity_current_steps_per_minute">Akt. Schritte pro Minute</string>
<string name="live_activity_total_steps">Schritte insgesamt</string>
<string name="live_activity_steps_per_minute_history">Verlauf Schritte pro Minute</string>
@ -385,7 +385,7 @@
<string name="pref_screen_notification_profile_alarm_clock">Wecker</string>
<string name="StringUtils_sender"> (%1$s)</string>
<string name="find_device_you_found_it">Gefunden!</string>
<string name="miband2_prefs_timeformat">Mi2: Uhrzeit-Format</string>
<string name="miband2_prefs_timeformat">Mi2: Uhrzeitformat</string>
<string name="mi2_fw_installhandler_fw53_hint">Installiere Version %1$s vor dem Installieren der Firmware!</string>
<string name="mi2_enable_text_notifications">Text Benachrichtigung</string>
<string name="mi2_enable_text_notifications_summary"><![CDATA[Firmware >= 1.0.1.28 und installiertes Mili_pro.ft* benötigt.]]></string>
@ -410,4 +410,19 @@
\nHinweis: Du musst die .res and .gps Dateien nicht installieren, falls diese genau die gleichen Dateien wie die sind, die Du schon mit einer vorigen .fw Datei zusammen installiert hattest.
\n
\nDIES IST EXPERIMENTELL, FAHRE AUF EIGENES RISIKO FORT</string>
</resources>
<string name="amazfitbip_firmware">Amazfit Bip Firmware %1$s</string>
<string name="mi2_prefs_button_actions">Aktion bei Tastendruck</string>
<string name="mi2_prefs_button_actions_summary">Bestimmte Aktion bei Tastendruck auf dem Mi Band 2</string>
<string name="mi2_prefs_button_press_count_summary">Anzahl der Tastendrücke, die einen Broadcast auslöst</string>
<string name="mi2_prefs_button_press_broadcast">Zu Sendende Broadcast-Nachricht</string>
<string name="mi2_prefs_button_action">Aktiviere Tastenaktion</string>
<string name="mi2_prefs_button_action_summary">Aktiviere Aktion bei einer bestimmten Anzahl an Tastendrücken</string>
<string name="mi2_prefs_button_action_vibrate">Aktiviere Vibration</string>
<string name="mi2_prefs_button_action_vibrate_summary">Vibriere, wenn die Tastenaktion ausgelöst wurde</string>
<string name="mi2_prefs_button_press_count_max_delay">Maximale Verzögerung zwischen den Tastendrücken</string>
<string name="mi2_prefs_button_press_count_max_delay_summary">Maximale Verzögerung zwischen den Tastendrücken in Millisekunden</string>
<string name="mi2_prefs_button_press_count_match_delay">Verzögerung nach Tastenaktion</string>
<string name="_pebble_watch_open_on_phone">Auf dem Smartphone öffnen</string>
<string name="_pebble_watch_mute">Stummschalten</string>
<string name="_pebble_watch_reply">Antwort</string>
</resources>

View File

@ -411,4 +411,22 @@
\n
\nEXPERIMENTAL, PROCEDE BAJO TU PROPIA RESPONSABILIDAD</string>
<string name="amazfitbip_firmware">Firmware Amazfit Bip %1$s</string>
</resources>
<string name="mi2_prefs_button_actions">Acciones del botón</string>
<string name="mi2_prefs_button_actions_summary">Especificar acción para pulsación del botón del Mi Band 2</string>
<string name="mi2_prefs_button_press_count">Número de presiones</string>
<string name="mi2_prefs_button_press_count_summary">Número de presiones para activar el envío del mensaje</string>
<string name="mi2_prefs_button_press_broadcast">Mensaje para enviar</string>
<string name="mi2_prefs_button_press_broadcast_summary">Enviar mensaje después de un número definido de pulsaciones</string>
<string name="mi2_prefs_button_press_broadcast_default_value">nodomain.freeyourgadget.gadgetbridge.mibandButtonPressed</string>
<string name="mi2_prefs_button_action">Activar acción por botón</string>
<string name="mi2_prefs_button_action_summary">Activar acción por un número definido de pulsaciones</string>
<string name="mi2_prefs_button_action_vibrate">Activar la vibración de la pulsera</string>
<string name="mi2_prefs_button_action_vibrate_summary">Activar vibración por acción</string>
<string name="mi2_prefs_button_press_count_max_delay">Retardo máximo entre pulsaciones</string>
<string name="mi2_prefs_button_press_count_max_delay_summary">Retardo máximo entre pulsaciones en milisegundos</string>
<string name="mi2_prefs_button_press_count_match_delay">Retardo después de la acción del botón</string>
<string name="mi2_prefs_button_press_count_match_delay_summary">Retardo después de una acción del botón (el número está en button_id intent extra) o bien 0 para efecto inmediato</string>
<string name="_pebble_watch_open_on_phone">Abrir en el teléfono</string>
<string name="_pebble_watch_mute">Silenciar</string>
<string name="_pebble_watch_reply">Responder</string>
</resources>

View File

@ -415,4 +415,22 @@ NOTE: la base de données sera bien évidement plus grande !</string>
\n
\nEXPÉRIMENTAL, CONTINUEZ À VOS RISQUES</string>
<string name="amazfitbip_firmware">Firmware Amazfit Bip %1$s</string>
</resources>
<string name="mi2_prefs_button_actions">Actions du bouton</string>
<string name="mi2_prefs_button_actions_summary">Spécifier les actions par pression du bouton du Mi Band 2</string>
<string name="mi2_prefs_button_press_count">Nombre de pressions du bouton</string>
<string name="mi2_prefs_button_press_count_summary">Nombre de pressions pour envoyer message</string>
<string name="mi2_prefs_button_press_broadcast">Message à envoyer</string>
<string name="mi2_prefs_button_press_broadcast_summary">Envoyer message après nombre défini de pressions du bouton</string>
<string name="mi2_prefs_button_press_broadcast_default_value">nodomain.freeyourgadget.gadgetbridge.mibandButtonPressed</string>
<string name="mi2_prefs_button_action">Activer action du bouton</string>
<string name="mi2_prefs_button_action_summary">Activer action après nombre spécifié de pressions</string>
<string name="mi2_prefs_button_action_vibrate">Activer la vibration du bracelet</string>
<string name="mi2_prefs_button_action_vibrate_summary">Activer la vibration après déclenchement de l\'action</string>
<string name="mi2_prefs_button_press_count_max_delay">Délai maximum entre pressions</string>
<string name="mi2_prefs_button_press_count_max_delay_summary">Délai maximum entre pressions en millisecondes</string>
<string name="mi2_prefs_button_press_count_match_delay">Délai après action du bouton</string>
<string name="mi2_prefs_button_press_count_match_delay_summary">Délai après une pression de bouton (le nombre est dans button_id intent extra) ou 0 pour immédiatement</string>
<string name="_pebble_watch_open_on_phone">Ouvrir sur le téléphone</string>
<string name="_pebble_watch_mute">Silencieux</string>
<string name="_pebble_watch_reply">Répondre</string>
</resources>

View File

@ -409,4 +409,17 @@
\n
\n実験的です。あなた自身の責任で行ってください</string>
<string name="amazfitbip_firmware">Amazfit Bip ファームウェア%1$s</string>
<string name="mi2_prefs_button_actions">ボタンの動作</string>
<string name="mi2_prefs_button_actions_summary">Mi Band 2 ボタンを押した動作を指定します</string>
<string name="mi2_prefs_button_press_count">ボタン押したカウント</string>
<string name="mi2_prefs_button_press_count_summary">ブロードキャスト メッセージをトリガーするボタンを押した数</string>
<string name="mi2_prefs_button_press_broadcast">送信するブロードキャスト メッセージ</string>
<string name="mi2_prefs_button_press_broadcast_summary">ボタンを押した数で定義された数に達したブロードキャスト メッセージ</string>
<string name="mi2_prefs_button_press_broadcast_default_value">nodomain.freeyourgadget.gadgetbridge.mibandButtonPressed</string>
<string name="mi2_prefs_button_action">ボタンの動作を有効にする</string>
<string name="mi2_prefs_button_action_summary">指定したボタンを押した数の動作を有効にします</string>
<string name="mi2_prefs_button_action_vibrate">band の振動を有効にする</string>
<string name="mi2_prefs_button_action_vibrate_summary">ボタンの動作をトリガーに band の振動を有効にします</string>
<string name="mi2_prefs_button_press_count_max_delay">ボタンを押す間の最大遅延時間</string>
<string name="mi2_prefs_button_press_count_max_delay_summary">ボタンを押す間隔の最大遅延時間 (ミリ秒単位)</string>
</resources>

View File

@ -7,7 +7,7 @@
<string name="action_quit">종료</string>
<string name="controlcenter_fetch_activity_data">동기화</string>
<string name="controlcenter_start_sleepmonitor">수면 측정계 (초기 버전)</string>
<string name="controlcenter_find_device">잃어버린 기기 찾기...</string>
<string name="controlcenter_find_device">잃어버린 기기 찾기</string>
<string name="controlcenter_take_screenshot">스크린샷 찍기</string>
<string name="controlcenter_disconnect">연결 해제</string>
<string name="title_activity_debug">디버그</string>
@ -46,7 +46,7 @@
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_pebblemsg">Pebble 메세지</string>
<string name="pref_title_notifications_generic">일반적인 알림 지원</string>
<string name="pref_title_whenscreenon"> 화면이 켜져있을 때도</string>
<string name="pref_title_whenscreenon">…화면이 켜져있을 때도</string>
<string name="always">항상</string>
<string name="when_screen_off">화면이 꺼져 있을 때</string>
<string name="never">하지 않음</string>
@ -56,7 +56,7 @@
<string name="pref_title_development_miaddr">Mi Band 주소</string>
<string name="pref_title_pebble_settings">Pebble 설정</string>
<string name="pref_title_pebble_activitytracker">선호하는 액티비티 트래커</string>
<string name="pref_title_enable_pebblekit">제3자 안드로이드 앱 접근을 허용</string>
<string name="pref_title_enable_pebblekit"> 3자 안드로이드 앱 접근을 허용</string>
<string name="pref_summary_enable_pebblekit">PebbleKit을 사용하는 안드로이드 앱을 실험적으로 지원</string>
<string name="pref_title_pebble_forceprotocol">강제 알림 프로토콜</string>
<string name="pref_summary_pebble_forceprotocol">이 옵션은 펌웨어 버전에 따라 최신 알림 프로토콜을 강제로 사용하도록 합니다. 자세한 내용을 알 경우에만 이 옵션을 사용하세요.</string>

View File

@ -92,7 +92,7 @@
<string name="pref_title_notifications_generic">Generic notification support</string>
<string name="pref_title_whenscreenon">…also when screen is on</string>
<string name="pref_title_notification_filter">Do Not Disturb</string>
<string name="pref_summary_notification_filter">Stop unwanted Notifications from being sent based on the Do Not Disturb mode</string>
<string name="pref_summary_notification_filter">Stop unwanted notifications from being sent based on the Do Not Disturb mode</string>
<string name="pref_title_transliteration">Transliteration</string>
<string name="pref_summary_transliteration">Enable this if your device has no support for your language\'s font</string>
@ -158,7 +158,7 @@
<string name="toast_enable_networklocationprovider">Please enable network location</string>
<string name="toast_aqurired_networklocation">location acquired</string>
<string name="pref_title_pebble_forceprotocol">Force Notification Protocol</string>
<string name="pref_title_pebble_forceprotocol">Force notification protocol</string>
<string name="pref_summary_pebble_forceprotocol">This option forces using the latest notification protocol depending on the firmware version. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!</string>
<string name="pref_title_pebble_forceuntested">Enable untested features</string>
<string name="pref_summary_pebble_forceuntested">Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!</string>
@ -357,13 +357,15 @@
<string name="mi2_prefs_button_press_count_summary">Number of button presses to trigger message broadcast</string>
<string name="mi2_prefs_button_press_broadcast">Broadcast message to send</string>
<string name="mi2_prefs_button_press_broadcast_summary">Broadcast message on defined number of button presses reached</string>
<string name="mi2_prefs_button_press_broadcast_default_value">nodomain.freeyourgadget.gadgetbridge.mibandButtonPressed</string>
<string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.mibandButtonPressed</string>
<string name="mi2_prefs_button_action">Enable button action</string>
<string name="mi2_prefs_button_action_summary">Enable action on specified number of button presses</string>
<string name="mi2_prefs_button_action_vibrate">Enable band vibration</string>
<string name="mi2_prefs_button_action_vibrate_summary">Enable band vibration on button action triggered</string>
<string name="mi2_prefs_button_press_count_max_delay">Maximum delay between presses</string>
<string name="mi2_prefs_button_press_count_max_delay_summary">Maximum delay between button presses in milliseconds</string>
<string name="mi2_prefs_button_press_count_match_delay">Delay after button action</string>
<string name="mi2_prefs_button_press_count_match_delay_summary">Delay after one button action match (number is in button_id intent extra) or 0 for immediately</string>
<string name="mi2_prefs_goal_notification">Goal notification</string>
<string name="mi2_prefs_goal_notification_summary">The band will vibrate when the daily steps goal is reached</string>
<string name="mi2_prefs_display_items">Display items</string>
@ -471,4 +473,9 @@
<string name="discovery_pair_question">Select Pair to pair your devices. If this fails, try again without pairing.</string>
<string name="discovery_yes_pair">Pair</string>
<string name="discovery_dont_pair">Don\'t Pair</string>
<!-- strings sent to pebble watches for quick actions -->
<string name="_pebble_watch_open_on_phone">Open on phone</string>
<string name="_pebble_watch_mute">Mute</string>
<string name="_pebble_watch_reply">Reply</string>
</resources>

View File

@ -1,5 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.21.1" versioncode="102">
<change>Initial support for EXRIZU K8 (HPLus variant)</change>
<change>Amazfit Bip: fix long messages not being displayed at all</change>
<change>Mi Band 2: Support multiple button actions</change>
<change>NO.1 F1: Fetch sleep data</change>
<change>NO.1 F1: Heart rate support</change>
<change>Pebble: Support controlling the current active media playback application</change>
<change>Fix suspended activities coming to front when rotating the screen</change>
</release>
<release version="0.21.0" versioncode="101">
<change>Initial NO.1 F1 support</change>
<change>Initial Teclast H30 support</change>

View File

@ -49,12 +49,14 @@
<CheckBoxPreference
android:defaultValue="false"
android:dependency="mi2_enable_button_action"
android:key="mi2_button_action_vibrate"
android:summary="@string/mi2_prefs_button_action_vibrate_summary"
android:title="@string/mi2_prefs_button_action_vibrate" />
<EditTextPreference
android:defaultValue="6"
android:dependency="mi2_enable_button_action"
android:inputType="number"
android:key="mi_button_press_count"
android:summary="@string/mi2_prefs_button_press_count_summary"
@ -62,16 +64,26 @@
<EditTextPreference
android:defaultValue="@string/mi2_prefs_button_press_broadcast_default_value"
android:dependency="mi2_enable_button_action"
android:key="mi_button_press_broadcast"
android:summary="@string/mi2_prefs_button_press_broadcast_summary"
android:title="@string/mi2_prefs_button_press_broadcast" />
<EditTextPreference
android:defaultValue="2000"
android:dependency="mi2_enable_button_action"
android:inputType="number"
android:key="mi_button_press_count_max_delay"
android:summary="@string/mi2_prefs_button_press_count_max_delay_summary"
android:title="@string/mi2_prefs_button_press_count_max_delay" />
<EditTextPreference
android:defaultValue="0"
android:dependency="mi2_enable_button_action"
android:inputType="number"
android:key="mi_button_press_count_match_delay"
android:summary="@string/mi2_prefs_button_press_count_match_delay_summary"
android:title="@string/mi2_prefs_button_press_count_match_delay" />
</PreferenceScreen>
<EditTextPreference

View File

@ -0,0 +1,7 @@
* Initial support for EXRIZU K8 (HPLus variant)
* Amazfit Bip: fix long messages not being displayed at all
* Mi Band 2: Support multiple button actions
* NO.1 F1: Fetch sleep data
* NO.1 F1: Heart rate support
* Pebble: Support controlling the current active media playback application
* Fix suspended activities coming to front when rotating the screen