Merge branch 'master' into background-javascript

This commit is contained in:
Andreas Shimokawa 2017-03-16 18:01:51 +01:00
commit 06c89b508e
47 changed files with 849 additions and 199 deletions

View File

@ -6,10 +6,15 @@
* Add Czech translation
* Add Hebrew translation and transliteration
* Consistently display device specific icons already during discovery
* Add sleep chart diplaying the last week of sleep
* Add sleep chart displaying the last week of sleep
* Huge speedup for weekly charts when changing days
* Drop support for pre Gadgetbride 0.12.0 database
* Pebble: allow configuration webpages (clay) to access device location
* Drop support for importing pre Gadgetbridge 0.12.0 database
* Pebble: allow configuration web pages (clay) to access device location
* Mi2: Initial support for text notifications, caller ID, and icons (requires font installation) (#560)
* Mi2: Support for flashing Mili_pro.ft* font files
* Mi2: Improved firmware/font updated
* Mi2: Set 12h/24h time format, following the Android configuration (#573)
* Improved BLE discovery and connectivity
####Version 0.17.5
* Automatically start the service on boot (can be turned off)

View File

@ -80,7 +80,6 @@ dependencies {
compile 'org.apache.commons:commons-lang3:3.4'
// compile project(":DaoCore")
compile 'com.android.support.constraint:constraint-layout:1.0.2'
}
preBuild.dependsOn(":GBDaoGenerator:genSources")

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.Manifest;
@ -10,6 +27,7 @@ import android.content.pm.PackageManager;
import android.graphics.Canvas;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView;
import android.support.v4.app.ActivityCompat;
@ -196,7 +214,7 @@ public class ControlCenterv2 extends AppCompatActivity
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
@ -274,8 +292,6 @@ public class ControlCenterv2 extends AppCompatActivity
wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CALENDAR);
if (ContextCompat.checkSelfPermission(this, "com.fsck.k9.permission.READ_MESSAGES") == PackageManager.PERMISSION_DENIED)
wantedPermissions.add("com.fsck.k9.permission.READ_MESSAGES");
if (!wantedPermissions.isEmpty())
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);

View File

@ -224,6 +224,11 @@ public class FwAppInstallerActivity extends GBActivity implements InstallActivit
fwAppInstallTextView.setText(text);
}
@Override
public CharSequence getInfoText() {
return fwAppInstallTextView.getText();
}
@Override
public void setInstallEnabled(boolean enable) {
boolean enabled = device != null && device.isConnected() && enable;

View File

@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
public interface InstallActivity {
CharSequence getInfoText();
void setInfoText(String text);
void setInstallEnabled(boolean enable);
@ -26,4 +28,5 @@ public interface InstallActivity {
void clearInstallItems();
void setInstallItem(ItemWithDetails item);
}

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, 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.adapter;
import android.app.Activity;
@ -27,11 +44,13 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -61,7 +80,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final GBDevice device = deviceList.get(position);
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
holder.container.setOnClickListener(new View.OnClickListener() {
@ -79,7 +98,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
holder.container.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (device.isInitialized() || device.isConnected()) {
if (device.getState() != GBDevice.State.NOT_CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
GBApplication.deviceService().disconnect();
}
@ -218,6 +237,13 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
{
@Override
public void onClick(View v) {
if (device.getType() == DeviceType.VIBRATISSIMO) {
Intent startIntent;
startIntent = new Intent(context, VibrationActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
return;
}
GBApplication.deviceService().onFindDevice(true);
//TODO: extract string resource if we like this solution.
Snackbar.make(parent, R.string.control_center_find_lost_device, Snackbar.LENGTH_INDEFINITE).setAction("Found it!", new View.OnClickListener() {

View File

@ -48,6 +48,14 @@ public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
}
}
public Context getContext() {
return mContext;
}
public AbstractMiBandFWHelper getHelper() {
return helper;
}
protected abstract AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException;

View File

@ -53,6 +53,7 @@ public class MiBand2Service {
public static final int ALERT_LEVEL_MESSAGE = 1;
public static final int ALERT_LEVEL_PHONE_CALL = 2;
public static final int ALERT_LEVEL_VIBRATE_ONLY = 3;
public static final int ALERT_LEVEL_CUSTOM = 0xfa; // followed by another uin8 to select the actual icon
// set metric distance
// set 12 hour time mode
@ -109,6 +110,41 @@ public class MiBand2Service {
public static final byte[] COMMAND_SET_FITNESS_GOAL_START = new byte[] { 0x10, 0x0, 0x0 };
public static final byte[] COMMAND_SET_FITNESS_GOAL_END = new byte[] { 0, 0 };
public static final byte ICON_CHAT = 0x00;
public static final byte ICON_PENGUIN = 0x01;
public static final byte ICON_CHAT_MI = 0x02;
public static final byte ICON_FB = 0x03;
public static final byte ICON_TWITTER = 0x04;
public static final byte ICON_MIBAND = 0x05;
public static final byte ICON_SNAPCHAT = 0x06;
public static final byte ICON_WHATSAPP = 0x07;
public static final byte ICON_MANTA = 0x08;
public static final byte ICON_XX0 = 0x09;
public static final byte ICON_ALARM = 0x10;
public static final byte ICON_SHATTERED_GLASS = 0x11;
public static final byte ICON_INSTAGRAM = 0x12;
public static final byte ICON_CHAT_GHOST = 0x13;
public static final byte ICON_COW = 0x14;
public static final byte ICON_XX2 = 0x15;
public static final byte ICON_XX3 = 0x16;
public static final byte ICON_XX4 = 0x17;
public static final byte ICON_XX5 = 0x18;
public static final byte ICON_XX6 = 0x19;
public static final byte ICON_EGALE = 0x1a;
public static final byte ICON_CALENDAR = 0x1b;
public static final byte ICON_XX7 = 0x1c;
public static final byte ICON_PHONE_CALL = 0x1d;
public static final byte ICON_CHAT_LINE = 0x1e;
public static final byte ICON_TELEGRAM = 0x1f;
public static final byte ICON_CHAT_TALK = 0x20;
public static final byte ICON_SKYPE = 0x21;
public static final byte ICON_VK = 0x22;
public static final byte ICON_CIRCLES = 0x23;
public static final byte ICON_HANGOUTS = 0x24;
public static final byte ICON_MI = 0x25;
public static final byte ICON_HIGH_PRIORITY = 0x7;
public static byte ENDPOINT_DISPLAY = 0x06;
@ -119,7 +155,7 @@ public class MiBand2Service {
public static final byte[] COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01};
public static final byte[] COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x00};
public static final byte[] DISPLAY_XXX = new byte[] {ENDPOINT_DISPLAY, 0x03, 0x0, 0x0 };
public static final byte[] DISPLAY_YYY = new byte[] {ENDPOINT_DISPLAY, 0x10, 0x0, 0x1, 0x0 };
public static final byte[] DISPLAY_YYY = new byte[] {ENDPOINT_DISPLAY, 0x10, 0x0, 0x1, 0x1 };
public static final byte RESPONSE = 0x10;
@ -148,7 +184,6 @@ public class MiBand2Service {
public static final byte[] COMMAND_DISABLE_HR_SLEEP_MEASUREMENT = new byte[]{0x15, 0x00, 0x00};
public static final byte[] COMMAND_TEXT_NOTIFICATION = new byte[] {0x05, 0x01};
public static final byte COMMAND_ALERT_CATEGORY_CHAT = (byte) 0xfa;
static {
MIBAND_DEBUG = new HashMap<>();

View File

@ -21,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.Version;
public final class MiBandConst {
private static final Logger LOG = LoggerFactory.getLogger(MiBandConst.class);
@ -35,6 +36,7 @@ public final class MiBandConst {
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours";
public static final String PREF_MI2_DATEFORMAT = "mi2_dateformat";
public static final String PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT = "mi2_activate_display_on_lift_wrist";
public static final String PREF_MI2_ENABLE_TEXT_NOTIFICATIONS = "mi2_enable_text_notifications";
public static final String PREF_MIBAND_SETUP_BT_PAIRING = "mi_setup_bt_pairing";
@ -48,6 +50,9 @@ public final class MiBandConst {
public static final String MI_AMAZFIT = "Amazfit";
public static final String MI_PRO = "2";
public static final Version MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS = new Version("1.0.1.28");
public static final Version MI2_FW_VERSION_INTERMEDIATE_UPGRADE_53 = new Version("1.0.0.53");
public static int getNotificationPrefIntValue(String pref, String origin, Prefs prefs, int defaultValue) {
String key = getNotificationPrefKey(pref, origin);
return prefs.getInt(key, defaultValue);

View File

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

View File

@ -39,6 +39,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.OR
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
@ -163,6 +164,7 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
prefKeys.add(ActivityUser.PREF_USER_STEPS_GOAL);
prefKeys.add(PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR);
prefKeys.add(PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
prefKeys.add(PREF_MI2_ENABLE_TEXT_NOTIFICATIONS);
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_ALARM_CLOCK));
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL));

View File

@ -24,10 +24,15 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.FirmwareType;
import nodomain.freeyourgadget.gadgetbridge.util.Version;
public class MiBand2FWInstallHandler extends AbstractMiBandFWInstallHandler {
private static final Logger LOG = LoggerFactory.getLogger(MiBand2FWInstallHandler.class);
@ -36,6 +41,64 @@ public class MiBand2FWInstallHandler extends AbstractMiBandFWInstallHandler {
super(uri, context);
}
@Override
public void validateInstallation(InstallActivity installActivity, GBDevice device) {
super.validateInstallation(installActivity, device);
maybeAddFw53Hint(installActivity, device);
maybeAddFontHint(installActivity);
}
private void maybeAddFontHint(InstallActivity installActivity) {
FirmwareType type = getFirmwareType();
if (type == FirmwareType.FIRMWARE) {
String newInfoText = installActivity.getInfoText() + "\n\n" + "Note: you may install Mili_pro.ft or Mili_pro.ft.en to enable text notifications.";
installActivity.setInfoText(newInfoText);
}
}
private void maybeAddFw53Hint(InstallActivity installActivity, GBDevice device) {
FirmwareType type = getFirmwareType();
if (type != FirmwareType.FIRMWARE) {
return;
}
Version deviceVersion = getFirmwareVersionOf(device);
if (deviceVersion != null) {
Version v53 = MiBandConst.MI2_FW_VERSION_INTERMEDIATE_UPGRADE_53;
if (deviceVersion.compareTo(v53) < 0) {
String vInstall = getHelper().format(getHelper().getFirmwareVersion());
if (vInstall == null || new Version(vInstall).compareTo(v53) > 0) {
String newInfoText = getContext().getString(R.string.mi2_fw_installhandler_fw53_hint, v53.get()) + "\n\n" + installActivity.getInfoText();
installActivity.setInfoText(newInfoText);
}
}
}
}
private Version getFirmwareVersionOf(GBDevice device) {
String version = device.getFirmwareVersion();
if (version == null || version.length() == 0) {
return null;
}
if (version.charAt(0) == 'V') {
version = version.substring(1);
}
try {
return new Version(version);
} catch (Exception ex) {
LOG.error("Unable to parse version: " + version);
return null;
}
}
private FirmwareType getFirmwareType() {
AbstractMiBandFWHelper helper = getHelper();
if (helper instanceof MiBand2FWHelper) {
return ((MiBand2FWHelper) helper).getFirmwareInfo().getFirmwareType();
}
return FirmwareType.INVALID;
}
@Override
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
return new MiBand2FWHelper(uri, context);

View File

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

View File

@ -265,7 +265,7 @@ public class NotificationListener extends NotificationListenerService {
notificationSpec.type = AppNotificationType.getInstance().get(source);
if (source.equals("com.fsck.k9")) {
if (source.startsWith("com.fsck.k9")) {
// we dont want group summaries at all for k9
if ((notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) {
return;

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, ivanovlev,
Julien Pivotto, Kasha, Steffen Liebergeld
/* Copyright (C) 2015-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
ivanovlev, Julien Pivotto, Kasha, Steffen Liebergeld
This file is part of Gadgetbridge.
@ -73,9 +73,9 @@ public class GBDeviceService implements DeviceService {
}
protected void invokeService(Intent intent) {
if(LanguageUtils.transliterate()){
for (String extra: transliterationExtras) {
if (intent.hasExtra(extra)){
if (LanguageUtils.transliterate()) {
for (String extra : transliterationExtras) {
if (intent.hasExtra(extra)) {
intent.putExtra(extra, LanguageUtils.transliterate(intent.getStringExtra(extra)));
}
}
@ -172,12 +172,15 @@ public class GBDeviceService implements DeviceService {
String currentPrivacyMode = GBApplication.getPrefs().getString("pref_call_privacy_mode", GBApplication.getContext().getString(R.string.p_call_privacy_mode_off));
if (context.getString(R.string.p_call_privacy_mode_name).equals(currentPrivacyMode)) {
callSpec.name = callSpec.number;
}
else if (context.getString(R.string.p_call_privacy_mode_complete).equals(currentPrivacyMode)) {
} else if (context.getString(R.string.p_call_privacy_mode_complete).equals(currentPrivacyMode)) {
callSpec.number = null;
callSpec.name = null;
}
else {
} else if (context.getString(R.string.pref_call_privacy_mode_number).equals(currentPrivacyMode)) {
callSpec.name = coalesce(callSpec.name, getContactDisplayNameByNumber(callSpec.number));
if (callSpec.name != null && !callSpec.name.equals(callSpec.number)) {
callSpec.number = null;
}
} else {
callSpec.name = coalesce(callSpec.name, getContactDisplayNameByNumber(callSpec.number));
}
@ -372,6 +375,7 @@ public class GBDeviceService implements DeviceService {
/**
* Returns contact DisplayName by call number
*
* @param number contact number
* @return contact DisplayName, if found it
*/

View File

@ -33,6 +33,7 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
private AppNotificationType() {
// Generic Email
put("com.fsck.k9", NotificationType.GENERIC_EMAIL);
put("com.fsck.k9.material", NotificationType.GENERIC_EMAIL);
put("com.imaeses.squeaky", NotificationType.GENERIC_EMAIL);
put("com.android.email", NotificationType.GENERIC_EMAIL);

View File

@ -33,9 +33,11 @@ public enum AlertCategory {
VoiceMail(6),
Schedule(7),
HighPriorityAlert(8),
InstantMessage(9);
InstantMessage(9),
// 10-250 reserved for future use
// 251-255 defined by service specification
Any(255),
Custom(-1);
private final int id;
@ -91,5 +93,4 @@ public enum AlertCategory {
return null;
}
}

View File

@ -16,9 +16,39 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
/**
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_notification_control_point.xml
*/
public class AlertNotificationControl {
private AlertCategory category;
private Command command;
public void setCategory(AlertCategory category) {
this.category = category;
}
public void setCommand(Command command) {
this.command = command;
}
public AlertCategory getCategory() {
return category;
}
public Command getCommand() {
return command;
}
/**
* Returns the formatted message to be written to the alert notification control point
* characteristic
*/
public byte[] getControlMessage() {
return new byte[] {
BLETypeConversions.fromUint8(command.getId()),
BLETypeConversions.fromUint8(category.getId())
};
}
}

View File

@ -39,10 +39,24 @@ public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> exten
super(support);
}
public void configure(TransactionBuilder builder, AlertNotificationControl control) {
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT);
if (characteristic != null) {
builder.write(characteristic, control.getControlMessage());
}
}
public void updateAlertLevel(TransactionBuilder builder, AlertLevel level) {
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
if (characteristic != null) {
builder.write(characteristic, new byte[] {BLETypeConversions.fromUint8(level.getId())});
}
}
public void newAlert(TransactionBuilder builder, NewAlert alert, OverflowStrategy strategy) {
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
if (characteristic != null) {
String message = alert.getMessage();
String message = StringUtils.ensureNotNull(alert.getMessage());
if (message.length() > MAX_MSG_LENGTH && strategy == OverflowStrategy.TRUNCATE) {
message = StringUtils.truncate(message, MAX_MSG_LENGTH);
}
@ -52,31 +66,42 @@ public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> exten
numChunks++;
}
for (int i = 0; i < numChunks; i++) {
int offset = i * MAX_MSG_LENGTH;
int restLength = message.length() - offset;
message = message.substring(offset, offset + Math.min(MAX_MSG_LENGTH, restLength));
if (message.length() == 0) {
break;
try {
boolean hasAlerted = false;
for (int i = 0; i < numChunks; i++) {
int offset = i * MAX_MSG_LENGTH;
int restLength = message.length() - offset;
message = message.substring(offset, offset + Math.min(MAX_MSG_LENGTH, restLength));
if (hasAlerted && message.length() == 0) {
// no need to do it again when there is no text content
break;
}
builder.write(characteristic, getAlertMessage(alert, message, 1));
hasAlerted = true;
}
writeAlertMessage(builder, characteristic, alert, message, i);
if (!hasAlerted) {
builder.write(characteristic, getAlertMessage(alert, "", 1));
}
} catch (IOException ex) {
// ain't gonna happen
LOG.error("Error writing alert message to ByteArrayOutputStream");
}
} else {
LOG.warn("NEW_ALERT characteristic not available");
}
}
protected void writeAlertMessage(TransactionBuilder builder, BluetoothGattCharacteristic characteristic, NewAlert alert, String message, int chunk) {
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream(100);
stream.write(alert.getCategory().getId());
stream.write(alert.getNumAlerts());
stream.write(BLETypeConversions.toUtf8s(message));
protected byte[] getAlertMessage(NewAlert alert, String message, int chunk) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream(100);
stream.write(BLETypeConversions.fromUint8(alert.getCategory().getId()));
stream.write(BLETypeConversions.fromUint8(alert.getNumAlerts()));
builder.write(characteristic, stream.toByteArray());
} catch (IOException ex) {
// aint gonna happen
LOG.error("Error writing alert message to ByteArrayOutputStream");
if (message.length() > 0) {
stream.write(BLETypeConversions.toUtf8s(message));
} else {
// some write a null byte instead of leaving out this optional value
// stream.write(new byte[] {0});
}
return stream.toByteArray();
}
}

View File

@ -0,0 +1,40 @@
/* 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.btle.profiles.alertnotification;
/**
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_notification_control_point.xml
*/
public enum Command {
EnableNewIncomingAlertNotification(0),
EnableUnreadCategoryStatusNotification(1),
DisableNewIncomingAlertNotification(2),
DisbleUnreadCategoryStatusNotification(3),
NotifyNewIncomingAlertImmediately(4),
NotifyUnreadCategoryStatusImmediately(5),;
// 6-255 reserved for future use
private final int id;
Command(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, ivanovlev, João Paulo Barraca
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, ivanovlev, João
Paulo Barraca
This file is part of Gadgetbridge.
@ -669,18 +670,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte[] msg = new byte[13];
//Show call number
for (int i = 0; i < msg.length; i++)
msg[i] = ' ';
for (int i = 0; i < number.length() && i < (msg.length - 1); i++)
msg[i + 1] = (byte) number.charAt(i);
msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
builder.write(ctrlCharacteristic, msg);
builder.wait(200);
msg = msg.clone();
//Show call name
@ -697,6 +686,20 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME_CN;
builder.write(ctrlCharacteristic, msg);
builder.wait(200);
msg = msg.clone();
//Show call number
for (int i = 0; i < msg.length; i++)
msg[i] = ' ';
for (int i = 0; i < number.length() && i < (msg.length - 1); i++)
msg[i + 1] = (byte) number.charAt(i);
msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue());
} catch (IOException e) {
GB.toast(getContext(), "Error showing incoming call: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);

View File

@ -22,6 +22,7 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
@ -72,7 +73,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
@ -248,7 +248,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
* @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example.
* @param builder
*/
private MiBandSupport sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
private MiBandSupport sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
getNotificationStrategy().sendCustomNotification(vibrationProfile, simpleNotification, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder);
LOG.info("Sending notification to MiBand");
return this;
@ -487,7 +487,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
}
private void performPreferredNotification(String task, SimpleNotification simpleNotification, String notificationOrigin, BtLEAction extraAction) {
private void performPreferredNotification(String task, @Nullable SimpleNotification simpleNotification, String notificationOrigin, BtLEAction extraAction) {
try {
TransactionBuilder builder = performInitialized(task);
Prefs prefs = GBApplication.getPrefs();
@ -572,11 +572,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
return;
}
String message = NotificationUtils.getPreferredTextFor(notificationSpec, 40, 40, getContext()).trim();
SimpleNotification simpleNotification = new SimpleNotification(message, BLETypeConversions.toAlertCategory(notificationSpec.type));
String origin = notificationSpec.type.getGenericType();
performPreferredNotification(origin + " received", simpleNotification, origin, null);
performPreferredNotification(origin + " received", null, origin, null);
}
private void onAlarmClock(NotificationSpec notificationSpec) {

View File

@ -39,4 +39,9 @@ public class NoNotificationStrategy implements NotificationStrategy {
public void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
LOG.info("dummy notification stragegy: custom notification: " + simpleNotification);
}
@Override
public void stopCurrentNotification(TransactionBuilder builder) {
LOG.info("dummy notification stragegy: stop notification");
}
}

View File

@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
@ -27,7 +29,7 @@ public interface NotificationStrategy {
/**
* Adds a custom notification to the given transaction builder
* @param vibrationProfile specifies how and how often the Band shall vibrate.
* @param simpleNotification
* @param simpleNotification an optional notification containing a type and text message
* @param flashTimes
* @param flashColour
* @param originalColour
@ -35,5 +37,11 @@ public interface NotificationStrategy {
* @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example.
* @param builder
*/
void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder);
void sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder);
/**
* Stops any current notification.
* @param builder
*/
void stopCurrentNotification(TransactionBuilder builder);
}

View File

@ -103,6 +103,12 @@ public class V1NotificationStrategy implements NotificationStrategy {
}
}
@Override
public void stopCurrentNotification(TransactionBuilder builder) {
BluetoothGattCharacteristic controlPoint = support.getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
builder.write(controlPoint, stopVibrate);
}
// private void sendCustomNotification(int vibrateDuration, int vibrateTimes, int pause, int flashTimes, int flashColour, int originalColour, long flashDuration, TransactionBuilder builder) {
// BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
// int vDuration = Math.min(500, vibrateDuration); // longer than 500ms is not possible

View File

@ -17,25 +17,23 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.bluetooth.BluetoothGattCharacteristic;
import android.support.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
public class V2NotificationStrategy implements NotificationStrategy {
private final AbstractBTLEDeviceSupport support;
public class V2NotificationStrategy<T extends AbstractBTLEDeviceSupport> implements NotificationStrategy {
private final T support;
public V2NotificationStrategy(AbstractBTLEDeviceSupport support) {
public V2NotificationStrategy(T support) {
this.support = support;
}
protected AbstractBTLEDeviceSupport getSupport() {
protected T getSupport() {
return support;
}
@ -45,7 +43,7 @@ public class V2NotificationStrategy implements NotificationStrategy {
sendCustomNotification(profile, simpleNotification, extraAction, builder);
}
protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
protected void sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
//use the new alert characteristic
BluetoothGattCharacteristic alert = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
for (short i = 0; i < vibrationProfile.getRepeat(); i++) {
@ -69,13 +67,6 @@ public class V2NotificationStrategy implements NotificationStrategy {
}
}
}
// sendAlert(simpleNotification, builder);
}
protected void sendAlert(SimpleNotification simpleNotification, TransactionBuilder builder) {
AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(getSupport());
NewAlert alert = new NewAlert(simpleNotification.getAlertCategory(), 1, simpleNotification.getMessage());
profile.newAlert(builder, alert, OverflowStrategy.MAKE_MULTIPLE);
}
@Override
@ -83,4 +74,10 @@ public class V2NotificationStrategy implements NotificationStrategy {
// all other parameters are unfortunately not supported anymore ;-(
sendCustomNotification(vibrationProfile, simpleNotification, extraAction, builder);
}
@Override
public void stopCurrentNotification(TransactionBuilder builder) {
BluetoothGattCharacteristic alert = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
builder.write(alert, new byte[]{GattCharacteristic.NO_ALERT});
}
}

View File

@ -55,7 +55,16 @@ public class Mi2FirmwareInfo {
private static Map<Integer,String> crcToVersion = new HashMap<>();
static {
// firmware
crcToVersion.put(41899, "1.0.0.39");
crcToVersion.put(49197, "1.0.0.53");
crcToVersion.put(32450, "1.0.1.28");
crcToVersion.put(51770, "1.0.1.34");
crcToVersion.put(3929, "1.0.1.39");
// fonts
crcToVersion.put(45624, "Font");
crcToVersion.put(6377, "Font (En)");
}
private FirmwareType firmwareType = FirmwareType.FIRMWARE;

View File

@ -17,34 +17,34 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
import android.bluetooth.BluetoothGattCharacteristic;
import android.support.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.V2NotificationStrategy;
public class Mi2NotificationStrategy extends V2NotificationStrategy {
public class Mi2NotificationStrategy extends V2NotificationStrategy<MiBand2Support> {
public Mi2NotificationStrategy(AbstractBTLEDeviceSupport support) {
private final BluetoothGattCharacteristic alertLevelCharacteristic;
public Mi2NotificationStrategy(MiBand2Support support) {
super(support);
alertLevelCharacteristic = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
}
@Override
protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
//use the new alert characteristic
BluetoothGattCharacteristic alert = getSupport().getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
for (short i = 0; i < vibrationProfile.getRepeat(); i++) {
int[] onOffSequence = vibrationProfile.getOnOffSequence();
for (int j = 0; j < onOffSequence.length; j++) {
int on = onOffSequence[j];
on = Math.min(500, on); // longer than 500ms is not possible
builder.write(alert, new byte[]{(byte) vibrationProfile.getAlertLevel()});
startNotify(builder, vibrationProfile.getAlertLevel(), simpleNotification);
builder.wait(on);
builder.write(alert, new byte[]{GattCharacteristic.NO_ALERT});
stopNotify(builder);
if (++j < onOffSequence.length) {
int off = Math.max(onOffSequence[j], 25); // wait at least 25ms
@ -56,12 +56,19 @@ public class Mi2NotificationStrategy extends V2NotificationStrategy {
}
}
}
}
sendAlert(simpleNotification, builder);
protected void startNotify(TransactionBuilder builder, int alertLevel, @Nullable SimpleNotification simpleNotification) {
builder.write(alertLevelCharacteristic, new byte[] {(byte) alertLevel});
}
protected void stopNotify(TransactionBuilder builder) {
builder.write(alertLevelCharacteristic, new byte[]{GattCharacteristic.NO_ALERT});
}
@Override
public void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
public void sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
// all other parameters are unfortunately not supported anymore ;-(
sendCustomNotification(vibrationProfile, simpleNotification, extraAction, builder);
}

View File

@ -0,0 +1,90 @@
/* 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;
import android.bluetooth.BluetoothGattCharacteristic;
import android.support.annotation.NonNull;
import android.util.Log;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class Mi2TextNotificationStrategy extends Mi2NotificationStrategy {
private final BluetoothGattCharacteristic newAlertCharacteristic;
public Mi2TextNotificationStrategy(MiBand2Support support) {
super(support);
newAlertCharacteristic = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
}
@Override
protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
if (simpleNotification != null && simpleNotification.getAlertCategory() == AlertCategory.IncomingCall) {
// incoming calls are notified solely via NewAlert including caller ID
sendAlert(simpleNotification, builder);
return;
}
// announce text messages with configured alerts first
super.sendCustomNotification(vibrationProfile, simpleNotification, extraAction, builder);
// and finally send the text message, if any
if (simpleNotification != null && !StringUtils.isEmpty(simpleNotification.getMessage())) {
sendAlert(simpleNotification, builder);
}
}
@Override
protected void startNotify(TransactionBuilder builder, int alertLevel, SimpleNotification simpleNotification) {
builder.write(newAlertCharacteristic, getNotifyMessage(simpleNotification));
}
protected byte[] getNotifyMessage(SimpleNotification simpleNotification) {
int numAlerts = 1;
if (simpleNotification != null) {
switch (simpleNotification.getAlertCategory()) {
case Email:
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_MESSAGE), BLETypeConversions.fromUint8(numAlerts)};
case InstantMessage:
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_CUSTOM), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_CHAT};
case News:
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_CUSTOM), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_PENGUIN};
}
}
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.SMS.getId()), BLETypeConversions.fromUint8(numAlerts)};
}
protected void sendAlert(@NonNull SimpleNotification simpleNotification, TransactionBuilder builder) {
AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(getSupport());
// override the alert category, since only SMS and incoming call support text notification
AlertCategory category = AlertCategory.SMS;
if (simpleNotification.getAlertCategory() == AlertCategory.IncomingCall) {
category = simpleNotification.getAlertCategory();
}
NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage());
profile.newAlert(builder, alert, OverflowStrategy.MAKE_MULTIPLE);
}
}

View File

@ -84,6 +84,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
@ -96,6 +99,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.U
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.Version;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT;
@ -301,6 +305,16 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
}
private NotificationStrategy getNotificationStrategy() {
String firmwareVersion = getDevice().getFirmwareVersion();
if (firmwareVersion != null) {
Version ver = new Version(firmwareVersion);
if (MiBandConst.MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS.compareTo(ver) > 0) {
return new Mi2NotificationStrategy(this);
}
}
if (GBApplication.getPrefs().getBoolean(MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS, true)) {
return new Mi2TextNotificationStrategy(this);
}
return new Mi2NotificationStrategy(this);
}
@ -440,6 +454,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
int flashDuration = getPreferredFlashDuration(notificationOrigin, prefs);
sendCustomNotification(profile, simpleNotification, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder);
// sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder);
builder.queue(getQueue());
} catch (IOException ex) {
@ -563,6 +578,17 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, simpleNotification, MiBand2Service.ALERT_LEVEL_PHONE_CALL, abortAction);
} else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
telephoneRinging = false;
stopCurrentNotification();
}
}
private void stopCurrentNotification() {
try {
TransactionBuilder builder = performInitialized("stop notification");
getNotificationStrategy().stopCurrentNotification(builder);
builder.queue(getQueue());
} catch (IOException e) {
LOG.error("Error stopping notification");
}
}
@ -642,7 +668,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
return !isLocatingDevice;
}
};
SimpleNotification simpleNotification = new SimpleNotification(getContext().getString(R.string.find_device_you_found_it), AlertCategory.HighPriorityAlert.HighPriorityAlert);
SimpleNotification simpleNotification = new SimpleNotification(getContext().getString(R.string.find_device_you_found_it), AlertCategory.HighPriorityAlert);
performDefaultNotification("locating device", simpleNotification, (short) 255, abortAction);
}
}
@ -974,6 +1000,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
versionCmd.hwVersion = info.getHardwareRevision();
// versionCmd.fwVersion = info.getFirmwareRevision(); // always null
versionCmd.fwVersion = info.getSoftwareRevision();
if (versionCmd.fwVersion != null && versionCmd.fwVersion.length() > 0 && versionCmd.fwVersion.charAt(0) == 'V') {
versionCmd.fwVersion = versionCmd.fwVersion.substring(1);
}
handleGBDeviceEvent(versionCmd);
}
@ -1044,11 +1073,12 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
@Override
public void onTestNewFunction() {
try {
performInitialized("read characteristic 10")
.read(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_10_BUTTON))
.queue(getQueue());
TransactionBuilder builder = performInitialized("incoming call from peter");
NewAlert alert = new NewAlert(AlertCategory.Custom, 1, new String(new byte[] {0x19}));
AlertNotificationProfile<MiBand2Support> profile = new AlertNotificationProfile<>(this);
profile.newAlert(builder, alert, OverflowStrategy.MAKE_MULTIPLE);
builder.queue(getQueue());
} catch (IOException e) {
e.printStackTrace();
}
}

View File

@ -16,6 +16,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class CheckSums {
public static int getCRC8(byte[] seq) {
int len = seq.length;
@ -51,4 +58,31 @@ public class CheckSums {
crc &= 0xffff;
return crc;
}
public static void main(String[] args) throws IOException {
if (args == null || args.length == 0) {
throw new IllegalArgumentException("Pass the files to be checksummed as arguments");
}
for (String name : args) {
try (FileInputStream in = new FileInputStream(name)) {
byte[] bytes = readAll(in, 1000 * 1000);
System.out.println(name + " : " + getCRC16(bytes));
}
}
}
public static byte[] readAll(InputStream in, long maxLen) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(8192, in.available()));
byte[] buf = new byte[8192];
int read = 0;
long totalRead = 0;
while ((read = in.read(buf)) > 0) {
out.write(buf, 0, read);
totalRead += read;
if (totalRead > maxLen) {
throw new IOException("Too much data to read into memory. Got already " + totalRead + buf);
}
}
return out.toByteArray();
}
}

View File

@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.util;
import android.content.Context;
import android.support.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -47,13 +48,22 @@ public class NotificationUtils {
}
@NonNull
public static String formatText(String sender, String subject, String body, int lengthBody, int lengthSubject, Context context) {
StringBuilder builder = new StringBuilder();
builder.append(StringUtils.truncate(body, lengthBody));
builder.append(StringUtils.truncate(subject, lengthSubject));
builder.append(StringUtils.formatSender(sender, context));
public static String formatSender(String sender, Context context) {
if (sender == null || sender.length() == 0) {
return "";
}
return context.getString(R.string.StringUtils_sender, sender);
}
return builder.toString();
@NonNull
public static String formatText(String sender, String subject, String body, int lengthBody, int lengthSubject, Context context) {
String fBody = StringUtils.truncate(body, lengthBody);
String fSubject = StringUtils.truncate(subject, lengthSubject);
String fSender = formatSender(sender, context);
StringBuilder builder = StringUtils.join(" ", fBody, fSubject, fSender);
return builder.toString().trim();
}
public static String getPreferredTextFor(CallSpec callSpec) {

View File

@ -16,11 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util;
import android.content.Context;
import android.support.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.R;
public class StringUtils {
public static String truncate(String s, int maxLength){
@ -47,12 +44,31 @@ public class StringUtils {
return s;
}
/**
* Joins the given elements and adds a separator between each element in the resulting string.
* There will be no separator at the start or end of the string. There will be no consecutive
* separators (even in case an element is null or empty).
* @param separator the separator string
* @param elements the elements to concatenate to a new string
* @return the joined strings, separated by the separator
*/
@NonNull
public static String formatSender(String sender, Context context) {
if (sender == null || sender.length() == 0) {
return "";
public static StringBuilder join(String separator, String... elements) {
StringBuilder builder = new StringBuilder();
if (elements == null) {
return builder;
}
return context.getString(R.string.StringUtils_sender, sender);
boolean hasAdded = false;
for (String element : elements) {
if (element != null && element.length() > 0) {
if (hasAdded) {
builder.append(separator);
}
builder.append(element);
hasAdded = true;
}
}
return builder;
}
@NonNull
@ -65,4 +81,15 @@ public class StringUtils {
}
return "";
}
public static boolean isEmpty(String string) {
return string != null && string.length() == 0;
}
public static String ensureNotNull(String message) {
if (message != null) {
return message;
}
return "";
}
}

View File

@ -0,0 +1,64 @@
/* 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.util;
// http://stackoverflow.com/questions/198431/how-do-you-compare-two-version-strings-in-java
public class Version implements Comparable<Version> {
private String version;
public final String get() {
return this.version;
}
public Version(String version) {
if(version == null)
throw new IllegalArgumentException("Version can not be null");
if(!version.matches("[0-9]+(\\.[0-9]+)*"))
throw new IllegalArgumentException("Invalid version format");
this.version = version;
}
@Override public int compareTo(Version that) {
if(that == null)
return 1;
String[] thisParts = this.get().split("\\.");
String[] thatParts = that.get().split("\\.");
int length = Math.max(thisParts.length, thatParts.length);
for(int i = 0; i < length; i++) {
int thisPart = i < thisParts.length ?
Integer.parseInt(thisParts[i]) : 0;
int thatPart = i < thatParts.length ?
Integer.parseInt(thatParts[i]) : 0;
if(thisPart < thatPart)
return -1;
if(thisPart > thatPart)
return 1;
}
return 0;
}
@Override public boolean equals(Object that) {
if(this == that)
return true;
if(that == null)
return false;
if(this.getClass() != that.getClass())
return false;
return this.compareTo((Version) that) == 0;
}
}

View File

@ -1,9 +1,10 @@
<!-- drawable/watch-vibrate.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FF000000"
android:pathData="M19,2L5,2c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM13,18h-2v-2h2v2zM15.07,10.25l-0.9,0.92C13.45,11.9 13,12.5 13,14h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,8c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
android:fillColor="#000"
android:pathData="M3,17V7H5V17H3M19,17V7H21V17H19M22,9H24V15H22V9M0,15V9H2V15H0M17.96,11.97C17.96,13.87 17.07,15.57 15.68,16.67L14.97,20.95H9L8.27,16.67C6.88,15.57 6,13.87 6,11.97C6,10.07 6.88,8.37 8.27,7.28L9,3H14.97L15.68,7.28C17.07,8.37 17.96,10.07 17.96,11.97M7.5,11.97C7.5,14.45 9.5,16.46 11.97,16.46A4.5,4.5 0 0,0 16.46,11.97C16.46,9.5 14.45,7.5 11.97,7.5A4.47,4.47 0 0,0 7.5,11.97Z" />
</vector>

View File

@ -15,20 +15,19 @@
card_view:cardElevation="4dp"
card_view:contentPadding="8dp">
<android.support.constraint.ConstraintLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/device_item_infos_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="8dp"
android:focusable="false"
android:visibility="gone"
card_view:layout_constraintLeft_toLeftOf="parent"
card_view:layout_constraintRight_toRightOf="parent">
android:visibility="gone">
<ListView
android:id="@+id/device_item_infos"
@ -58,70 +57,66 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentStart="true"
android:layout_marginStart="8dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:layout_below="@id/device_item_infos_box"
android:contentDescription="@string/candidate_item_device_image"
android:clickable="true"
android:longClickable="true"
card_view:layout_constraintLeft_toLeftOf="parent"
android:background="?android:attr/selectableItemBackground"
tools:src="@drawable/ic_device_pebble"
android:layout_marginTop="8dp"
card_view:layout_constraintTop_toBottomOf="@+id/device_item_infos_box" />
android:layout_marginTop="8dp" />
<TextView
android:id="@+id/device_name"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@id/device_image"
android:layout_alignParentEnd="true"
android:layout_marginEnd="48dp"
android:layout_below="@id/device_item_infos_box"
android:gravity="center"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
card_view:layout_constraintLeft_toRightOf="@+id/device_image"
card_view:layout_constraintRight_toLeftOf="@+id/device_info_image"
tools:text="My Pebble Watch"
android:layout_marginTop="8dp"
card_view:layout_constraintTop_toBottomOf="@+id/device_item_infos_box" />
tools:text="My Pebble Watch" />
<TextView
android:id="@+id/device_status"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/device_name"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@id/device_image"
android:layout_alignParentEnd="true"
android:layout_marginEnd="48dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
card_view:layout_constraintLeft_toRightOf="@+id/device_image"
card_view:layout_constraintRight_toLeftOf="@+id/device_info_image"
card_view:layout_constraintTop_toBottomOf="@+id/device_name"
tools:text="@string/connecting" />
<ImageView
android:id="@+id/device_info_image"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/selectableItemBackground"
android:layout_alignParentEnd="true"
android:layout_below="@id/device_item_infos_box"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="0dp"
android:layout_marginTop="8dp"
android:clickable="true"
android:contentDescription="@string/candidate_item_device_image"
android:tint="@color/secondarytext"
card_view:layout_constraintRight_toRightOf="parent"
card_view:srcCompat="@drawable/ic_more_vert"
android:layout_marginTop="12dp"
card_view:layout_constraintTop_toBottomOf="@+id/device_item_infos_box" />
android:background="?android:attr/selectableItemBackground"
card_view:srcCompat="@drawable/ic_more_vert" />
<LinearLayout
android:id="@+id/device_battery_status_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/device_image"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="0dp"
android:layout_marginTop="8dp"
android:minWidth="48dp"
android:orientation="vertical"
card_view:layout_constraintLeft_toLeftOf="parent"
card_view:layout_constraintTop_toBottomOf="@+id/device_image">
android:orientation="vertical">
<ImageView
android:id="@+id/device_battery_status"
@ -146,22 +141,21 @@
android:id="@+id/device_action_fetch_activity_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_below="@id/device_image"
android:layout_margin="8dp"
android:layout_toEndOf="@id/device_battery_status_box"
android:gravity="center_vertical"
android:minWidth="36dp"
android:orientation="vertical"
card_view:layout_constraintLeft_toRightOf="@+id/device_battery_status_box"
card_view:layout_constraintTop_toBottomOf="@+id/device_image">
android:orientation="vertical">
<ImageView
android:id="@+id/device_action_fetch_activity"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:layout_width="36dp"
android:layout_height="36dp"
android:clickable="true"
android:contentDescription="@string/controlcenter_fetch_activity_data"
android:tint="@color/secondarytext"
android:background="?android:attr/selectableItemBackground"
card_view:srcCompat="@drawable/ic_action_fetch_activity_data" />
<ProgressBar
@ -177,75 +171,69 @@
<ImageView
android:id="@+id/device_action_take_screenshot"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/selectableItemBackground"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_below="@id/device_image"
android:layout_margin="8dp"
android:layout_toEndOf="@id/device_action_fetch_activity_box"
android:clickable="true"
android:contentDescription="@string/controlcenter_take_screenshot"
android:tint="@color/secondarytext"
card_view:layout_constraintLeft_toRightOf="@+id/device_action_fetch_activity_box"
card_view:layout_constraintTop_toBottomOf="@+id/device_image"
android:background="?android:attr/selectableItemBackground"
card_view:srcCompat="@drawable/ic_screenshot" />
<ImageView
android:id="@+id/device_action_manage_apps"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/selectableItemBackground"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_below="@id/device_image"
android:layout_margin="8dp"
android:layout_toEndOf="@id/device_action_take_screenshot"
android:clickable="true"
android:contentDescription="@string/title_activity_appmanager"
android:tint="@color/secondarytext"
card_view:layout_constraintLeft_toRightOf="@+id/device_action_take_screenshot"
card_view:layout_constraintTop_toBottomOf="@+id/device_image"
android:background="?android:attr/selectableItemBackground"
card_view:srcCompat="@drawable/ic_action_manage_apps" />
<ImageView
android:id="@+id/device_action_set_alarms"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/selectableItemBackground"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_below="@id/device_image"
android:layout_margin="8dp"
android:layout_toEndOf="@id/device_action_manage_apps"
android:clickable="true"
android:contentDescription="@string/controlcenter_start_configure_alarms"
android:tint="@color/secondarytext"
card_view:layout_constraintLeft_toRightOf="@+id/device_action_manage_apps"
card_view:layout_constraintTop_toBottomOf="@+id/device_image"
android:background="?android:attr/selectableItemBackground"
card_view:srcCompat="@drawable/ic_device_set_alarms" />
<ImageView
android:id="@+id/device_action_show_activity_graphs"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/selectableItemBackground"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_below="@id/device_image"
android:layout_margin="8dp"
android:layout_toEndOf="@id/device_action_set_alarms"
android:clickable="true"
android:contentDescription="@string/controlcenter_start_activitymonitor"
android:tint="@color/secondarytext"
card_view:layout_constraintLeft_toRightOf="@+id/device_action_set_alarms"
card_view:layout_constraintTop_toBottomOf="@+id/device_image"
card_view:srcCompat="@drawable/ic_activity_graphs" />
card_view:srcCompat="@drawable/ic_activity_graphs"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/controlcenter_start_activitymonitor" />
<ImageView
android:id="@+id/device_action_find"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/selectableItemBackground"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_below="@id/device_image"
android:layout_margin="8dp"
android:layout_toEndOf="@id/device_action_show_activity_graphs"
android:clickable="true"
android:contentDescription="@string/controlcenter_find_device"
android:tint="@color/secondarytext"
card_view:layout_constraintLeft_toRightOf="@+id/device_action_show_activity_graphs"
card_view:layout_constraintTop_toBottomOf="@+id/device_image"
card_view:srcCompat="@drawable/ic_action_find_lost_device" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -13,6 +13,12 @@
<string name="controlcenter_delete_device">Gerät löschen</string>
<string name="controlcenter_delete_device_name">%1$s löschen</string>
<string name="controlcenter_delete_device_dialogmessage">Das wird das Gerät und alle zugehörigen Daten löschen!</string>
<string name="controlcenter_navigation_drawer_open">Navigations-Menü öffnen</string>
<string name="controlcenter_navigation_drawer_close">Navigations-Menü schließen</string>
<string name="controlcenter_snackbar_need_longpress">Halte die Karte lange gedrückt, um die Verbindung zu trennen.</string>
<string name="controlcenter_snackbar_disconnecting">Trenne ...</string>
<string name="controlcenter_snackbar_connecting">Verbinde ...</string>
<string name="controlcenter_snackbar_requested_screenshot">Screenshot des Gerätes wird erstellt.</string>
<string name="title_activity_debug">Debug</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">App Manager</string>
@ -181,6 +187,7 @@
<string name="miband_pairing_using_dummy_userdata">Keine gültigen Benutzerinformationen angegeben, verwende Dummy-Daten für\'s Erste.</string>
<string name="miband_pairing_tap_hint">Wenn Dein Mi Band vibriert und blinkt, tippe ein paar Mal schnell hintereinander darauf.</string>
<string name="appinstaller_install">Installieren</string>
<string name="discovery_connected_devices_hint">Mache dein Gerät auffindbar. Derzeit verbundene Geräte werden wahrscheinlich nicht erkannt. Aktiviere die Standortbestimmung (zum Beispiel GPS) in Android 6+. Deaktiviere den Privatsphäreschutz für Gadgetbridge, da er zu Abstürzen und Neustarts deines Telefons führen kann. Wenn nach einigen Minuten kein Gerät erkannt wird, versuche es nach einem Neustart deines Telefons erneut.</string>
<string name="discovery_note">Tipp:</string>
<string name="candidate_item_device_image">Bild des Geräts</string>
<string name="miband_prefs_alias">Name/Alias</string>

View File

@ -13,6 +13,12 @@
<string name="controlcenter_delete_device">Borrar Dispositivo</string>
<string name="controlcenter_delete_device_name">Borrar %1$s</string>
<string name="controlcenter_delete_device_dialogmessage">¡Esta acción borrará el dispositivo y toda su información asociada!</string>
<string name="controlcenter_navigation_drawer_open">Abrir el cajón de navegación</string>
<string name="controlcenter_navigation_drawer_close">Cerrar el cajón de navegación</string>
<string name="controlcenter_snackbar_need_longpress">Mantener pulsado el icono para desconectar</string>
<string name="controlcenter_snackbar_disconnecting">Desconectando</string>
<string name="controlcenter_snackbar_connecting">Conectando</string>
<string name="controlcenter_snackbar_requested_screenshot">Captura de pantalla del dispositivo</string>
<string name="title_activity_debug">Depuración</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestor de app</string>

View File

@ -13,6 +13,12 @@
<string name="controlcenter_delete_device">Supprimer lappareil</string>
<string name="controlcenter_delete_device_name">Supprimer %1$s</string>
<string name="controlcenter_delete_device_dialogmessage">Ceci va supprimer lappareil et toutes les données associées !</string>
<string name="controlcenter_navigation_drawer_open">Ouvrir le tiroir de navigation</string>
<string name="controlcenter_navigation_drawer_close">Fermer le tiroir de navigation</string>
<string name="controlcenter_snackbar_need_longpress">Presser longuement l\'icône pour déconnecter</string>
<string name="controlcenter_snackbar_disconnecting">Déconnexion</string>
<string name="controlcenter_snackbar_connecting">Connexion</string>
<string name="controlcenter_snackbar_requested_screenshot">Capture d\'écran de l\'appareil</string>
<string name="title_activity_debug">Déboguer</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestionnaire d\'application</string>

View File

@ -181,6 +181,7 @@
<string name="miband_pairing_using_dummy_userdata">לא ניתנו נתוני משתמש, נעשה שימוש בנתוני דמה לבינתיים.</string>
<string name="miband_pairing_tap_hint">כאשר ה־Mi Band שלך רוטט ומהבהב, יש לגעת בו מספר פעמים ברצף.</string>
<string name="appinstaller_install">התקנה</string>
<string name="discovery_connected_devices_hint">יש להפעיל את האפשרות לאיתור ההתקן שלך. התקנים שכבר מחוברים כעת לא יתגלו. יש להפעיל מיקום (GPS) באנדרואיד 6+. יש לנטרל את שומר הפרטיות עבור Gadgetbridge כיוון שתכונה זו עשויה להקריס ולהפעיל מחדש את הטלפון שלך. אם לא נמצא אף התקן לאחר מספר דקות יש לנסות שוב לאחר הפעלת הטלפון הנייד שלך מחדש. </string>
<string name="discovery_note">לתשומת לבך:</string>
<string name="candidate_item_device_image">תמונת ההתקן</string>
<string name="miband_prefs_alias">שם/כינוי</string>

View File

@ -13,6 +13,8 @@
<string name="controlcenter_delete_device">Rimuovi dispositivo</string>
<string name="controlcenter_delete_device_name">Rimuovi %1$s</string>
<string name="controlcenter_delete_device_dialogmessage">Il dispositivo verrà rimosso e tutti i dati ad esso associati verranno cancellati!</string>
<string name="controlcenter_snackbar_disconnecting">Disconnessione</string>
<string name="controlcenter_snackbar_connecting">Connessione</string>
<string name="title_activity_debug">Debug</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestione app</string>

View File

@ -13,6 +13,12 @@
<string name="controlcenter_delete_device">デバイスを削除</string>
<string name="controlcenter_delete_device_name">%1$s を削除</string>
<string name="controlcenter_delete_device_dialogmessage">これにより、デバイスと関連するデータを削除します!</string>
<string name="controlcenter_navigation_drawer_open">ナビゲーションドロワーを開く</string>
<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_requested_screenshot">デバイスのスクリーンショットを取得中</string>
<string name="title_activity_debug">デバッグ</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">アプリ管理画面</string>

View File

@ -158,12 +158,14 @@
<string-array name="pref_call_privacy_mode">
<item name="off">@string/pref_call_privacy_mode_off</item>
<item name="name">@string/pref_call_privacy_mode_name</item>
<item name="number">@string/pref_call_privacy_mode_number</item>
<item name="complete">@string/pref_call_privacy_mode_complete</item>
</string-array>
<string-array name="pref_call_privacy_mode_values">
<item>@string/p_call_privacy_mode_off</item>
<item>@string/p_call_privacy_mode_name</item>
<item>@string/pref_call_privacy_mode_number</item>
<item>@string/p_call_privacy_mode_complete</item>
</string-array>

View File

@ -99,8 +99,10 @@
<string name="pref_title_call_privacy_mode">Call Privacy Mode</string>
<string name="pref_call_privacy_mode_off">Display name and number</string>
<string name="pref_call_privacy_mode_name">Hide name but display number</string>
<string name="pref_call_privacy_mode_number">Hide number but display name</string>
<string name="pref_call_privacy_mode_complete">Hide name and number</string>
<string name="pref_blacklist">Blacklist Apps</string>
<string name="pref_header_cannned_messages">Canned Messages</string>
@ -407,4 +409,8 @@
<string name="StringUtils_sender"> (%1$s)</string>
<string name="find_device_you_found_it">You found it!</string>
<string name="miband2_prefs_timeformat">Mi2: Time Format</string>
<string name="mi2_fw_installhandler_fw53_hint">You need to install version %1$s before installing this firmware!</string>
<string name="mi2_enable_text_notifications">Text notifications</string>
<string name="mi2_enable_text_notifications_summary"><![CDATA[Needs firmware >= 1.0.1.28 and Mili_pro.ft* installed.]]></string>
<string name="off">off</string>
</resources>

View File

@ -10,6 +10,11 @@
<change>Huge speedup for weekly charts when changing days</change>
<change>Drop support for pre Gadgetbride 0.12.0 database</change>
<change>Pebble: allow configuration webpages (clay) to access device location</change>
<change>Mi2: Initial support for text notifications, caller ID, and icons (requires font installation) (#560)</change>
<change>Mi2: Support for flashing Mili_pro.ft* font files</change>
<change>Mi2: Improved firmware/font updated</change>
<change>Mi2: Set 12h/24h time format, following the Android configuration (#573)</change>
<change>Improved BLE discovery and connectivity</change>
</release>
<release version="0.17.5" versioncode="86">
<change>Automatically start the service on boot (can be turned off)</change>

View File

@ -48,6 +48,13 @@
android:key="mi2_activate_display_on_lift_wrist"
android:title="@string/mi2_prefs_activate_display_on_lift" />
<CheckBoxPreference
android:defaultValue="true"
android:key="mi2_enable_text_notifications"
android:summaryOn="@string/mi2_enable_text_notifications_summary"
android:summaryOff="@string/off"
android:title="@string/mi2_enable_text_notifications" />
<ListPreference
android:defaultValue="@string/p_dateformat_time"
android:entries="@array/mi2_dateformats"

View File

@ -0,0 +1,56 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import org.junit.Test;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import static org.junit.Assert.assertEquals;
public class StringUtilsTest extends TestBase {
private static final String SEP = ":";
private static final String E1 = "e1";
private static final String E2 = "e2";
private static final String E3 = "e3";
@Test
public void testJoinNull() {
StringBuilder result = StringUtils.join(SEP, (String[]) null);
assertEquals("", result.toString());
}
@Test
public void testJoinNullElement() {
StringBuilder result = StringUtils.join(SEP, (String) null);
assertEquals("", result.toString());
}
@Test
public void testJoinSingleElement() {
StringBuilder result = StringUtils.join(SEP, E1);
assertEquals(E1, result.toString());
}
@Test
public void testJoinSingleAndNullElement() {
StringBuilder result = StringUtils.join(SEP, E1, null);
assertEquals(E1, result.toString());
}
@Test
public void testJoinTwoElements() {
StringBuilder result = StringUtils.join(SEP, E1, E2);
assertEquals(E1 + SEP + E2, result.toString());
}
@Test
public void testJoinTwoElementsAndNull() {
StringBuilder result = StringUtils.join(SEP, E1, null, E2);
assertEquals(E1 + SEP + E2, result.toString());
}
@Test
public void testJoinThreeElements() {
StringBuilder result = StringUtils.join(SEP, E1, E2, E3);
assertEquals(E1 + SEP + E2 + SEP + E3, result.toString());
}
}