Ultrahuman optimizations

- fetch only new records instead of all records while the device is connected
- added PREF_POWER_SAVING support (disables / enables SpO2 sensing)
- added HRV visualization
- unified and secured Intent handling
- added BATTERY_CHARGING_FULL support
- honour PREF_TIME_SYNC in onSetTime
- various clean-ups
This commit is contained in:
Thomas Kuehne
2025-04-18 17:44:48 +02:00
parent 956e9cc4ba
commit ff761cf1e4
7 changed files with 184 additions and 70 deletions

View File

@@ -56,7 +56,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(101, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(102, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -193,6 +193,7 @@ public class GBDaoGenerator {
addGenericHeartRateSample(schema, user, device);
addGenericSpo2Sample(schema, user, device);
addGenericStressSample(schema, user, device);
addGenericHrvSummarySample(schema, user, device);
addGenericHrvValueSample(schema, user, device);
addGenericTemperatureSample(schema, user, device);
@@ -1773,6 +1774,19 @@ public class GBDaoGenerator {
return heartRateSample;
}
private static Entity addGenericHrvSummarySample(Schema schema, Entity user, Entity device) {
Entity hrvSummarySample = addEntity(schema, "GenericHrvSummarySample");
addCommonTimeSampleProperties("AbstractHrvSummarySample", hrvSummarySample, user, device);
hrvSummarySample.addIntProperty(SAMPLE_HRV_WEEKLY_AVERAGE).codeBeforeGetter(OVERRIDE);
hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_AVERAGE).codeBeforeGetter(OVERRIDE);
hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH).codeBeforeGetter(OVERRIDE);
hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_LOW_UPPER).codeBeforeGetter(OVERRIDE);
hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_LOWER).codeBeforeGetter(OVERRIDE);
hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_UPPER).codeBeforeGetter(OVERRIDE);
hrvSummarySample.addIntProperty(SAMPLE_HRV_STATUS_NUM).codeBeforeGetter(OVERRIDE);
return hrvSummarySample;
}
private static Entity addGenericHrvValueSample(Schema schema, Entity user, Entity device) {
Entity hrvValueSample = addEntity(schema, "GenericHrvValueSample");
addCommonTimeSampleProperties("AbstractHrvValueSample", hrvValueSample, user, device);

View File

@@ -0,0 +1,56 @@
/* Copyright (C) 2025 Thomas Kuehne
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.GenericHrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.GenericHrvSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GenericHrvSummarySampleProvider extends AbstractTimeSampleProvider<GenericHrvSummarySample> {
public GenericHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<GenericHrvSummarySample, ?> getSampleDao() {
return getSession().getGenericHrvSummarySampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return GenericHrvSummarySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return GenericHrvSummarySampleDao.Properties.DeviceId;
}
@Override
public GenericHrvSummarySample createSample() {
return new GenericHrvSummarySample();
}
}

View File

@@ -36,4 +36,6 @@ public class UltrahumanConstants {
public static final byte OPERATION_PING = 0x59;
public static final byte OPERATION_ACTIVATE_AIRPLANE_MODE = 0x70;
public static final byte OPERATION_RESET = (byte) 0x98;
public static final byte OPERATION_DISABLE_POWERSAVE = (byte) 0xD1;
public static final byte OPERATION_ENABLE_POWERSAVE = (byte) 0xD2;
}

View File

@@ -22,6 +22,7 @@ import android.content.Intent;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCardAction;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@@ -58,6 +59,7 @@ public class UltrahumanDeviceCardAction implements DeviceCardAction {
.setIcon(Icon)
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
final Intent intent = new Intent(Action);
intent.setPackage(BuildConfig.APPLICATION_ID);
intent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.sendBroadcast(intent);
})

View File

@@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCardAction;
import nodomain.freeyourgadget.gadgetbridge.devices.GenericHeartRateSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.GenericHrvSummarySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.GenericHrvValueSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.GenericSpo2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.GenericStressSampleProvider;
@@ -51,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.UltrahumanDeviceStateSample
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
@@ -71,7 +73,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
final Long deviceId = device.getId();
final Map<AbstractDao<?, ?>, Property> daoMap = new HashMap<AbstractDao<?, ?>, Property>() {{
final Map<AbstractDao<?, ?>, Property> daoMap = new HashMap<>() {{
put(session.getGenericHeartRateSampleDao(), GenericHeartRateSampleDao.Properties.DeviceId);
put(session.getGenericHrvValueSampleDao(), GenericHrvValueSampleDao.Properties.DeviceId);
put(session.getGenericSpo2SampleDao(), GenericSpo2SampleDao.Properties.DeviceId);
@@ -114,6 +116,11 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
return "Ultrahuman Healthcare Pvt Ltd";
}
@Override
public TimeSampleProvider<? extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session) {
return new GenericHrvSummarySampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(GBDevice device, DaoSession session) {
return new GenericHrvValueSampleProvider(device, session);
@@ -147,7 +154,8 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{
R.xml.devicesettings_time_sync
R.xml.devicesettings_time_sync,
R.xml.devicesettings_power_saving
};
}
@@ -183,8 +191,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public boolean supportsHrvMeasurement(final GBDevice device) {
// TODO - needs getHrvSummarySampleProvider in addition to the implemented getHrvValueSampleProvider
return false;
return true;
}
@Override

View File

@@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Locale;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@@ -57,7 +58,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.UltrahumanActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.UltrahumanDeviceStateSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
@@ -71,30 +71,30 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(UltrahumanDeviceSupport.class);
private BroadcastReceiver CommandReceiver;
private final UltrahumanBroadcastReceiver CommandReceiver;
private int FetchTo;
private int FetchFrom;
private int FetchCurrent;
public UltrahumanDeviceSupport(DeviceType type) {
public UltrahumanDeviceSupport() {
super(LOG);
addSupportedService(UltrahumanConstants.UUID_SERVICE_COMMAND);
addSupportedService(UltrahumanConstants.UUID_SERVICE_STATE);
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
CommandReceiver = new UltrahumanBroadcastReceiver();
DeviceInfoProfile<UltrahumanDeviceSupport> deviceProfile = new DeviceInfoProfile<>(this);
deviceProfile.addListener(new UltrahumanIntentListener());
deviceProfile.addListener(CommandReceiver);
addSupportedProfile(deviceProfile);
}
@Override
public void dispose() {
BroadcastReceiver receiver = CommandReceiver;
CommandReceiver = null;
if (receiver != null) {
getContext().unregisterReceiver(receiver);
if (CommandReceiver.Registered) {
CommandReceiver.Registered = false;
getContext().unregisterReceiver(CommandReceiver);
}
super.dispose();
@@ -115,11 +115,11 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
if (CommandReceiver == null) {
CommandReceiver = new UltrahumanBroadcastReceiver();
if (!CommandReceiver.Registered) {
IntentFilter filter = new IntentFilter();
filter.addAction(UltrahumanConstants.ACTION_AIRPLANE_MODE);
ContextCompat.registerReceiver(getContext(), CommandReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
CommandReceiver.Registered = true;
}
// trying to read non-existing characteristics sometimes causes odd BLE failures
@@ -148,6 +148,9 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
builder.add(new UltrahumanSetTimeAction(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND)));
}
boolean powerSave = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_POWER_SAVING, true);
builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{powerSave ? UltrahumanConstants.OPERATION_ENABLE_POWERSAVE : UltrahumanConstants.OPERATION_DISABLE_POWERSAVE});
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
return builder;
@@ -162,12 +165,15 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
getDevice().setBusyTask(task);
getDevice().sendDeviceUpdateIntent(getContext());
FetchFrom = -1;
// fetch deltas while the device is connected
FetchFrom = (FetchCurrent > 0 && FetchCurrent < Short.MAX_VALUE - 1000) ? FetchCurrent : -1;
FetchTo = -1;
FetchCurrent = -1;
TransactionBuilder builder = new TransactionBuilder("onFetchRecordedData");
if (FetchFrom <= 0) {
builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{UltrahumanConstants.OPERATION_GET_FIRST_RECORDING_NR});
}
builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{UltrahumanConstants.OPERATION_GET_LAST_RECORDING_NR});
if (isConnected()) {
@@ -209,19 +215,20 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
return false;
}
byte op = raw[0];
byte success = raw[1];
byte result = raw[2];
final byte op = raw[0];
final byte success = raw[1];
final byte result = raw[2];
// ignore checksums for now - algorithm is unknown
//byte chk1 = raw[raw.length-1];
//byte chk2 = raw[raw.length-2];
final String info = String.format(Locale.US, "received BLE command response op=%02x, success=%02x, result=%02x : %s", op, success, result, StringUtils.bytesToHex(raw));
if (success == 0x00) {
LOG.debug("received BLE command response op={}, success={}, result={} : {}", op, success, result, StringUtils.bytesToHex(raw));
LOG.debug(info);
} else if ((0xFF & success) == 0xFF) {
LOG.warn("received BLE command response op={}, success={}, result={} : {}", op, success, result, StringUtils.bytesToHex(raw));
LOG.warn(info);
} else {
LOG.info("received BLE command response op={}, success={}, result={} : {}", op, success, result, StringUtils.bytesToHex(raw));
LOG.info(info);
}
Context context = getContext();
@@ -230,13 +237,12 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
case UltrahumanConstants.OPERATION_GET_RECORDINGS:
return decodeRecordings(raw);
case UltrahumanConstants.OPERATION_PING:
if (raw[1] != 0x00) {
String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]);
GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR);
}
if (success == 0x00) {
return true;
}
break;
case UltrahumanConstants.OPERATION_ACTIVATE_AIRPLANE_MODE:
switch (raw[2]) {
switch (result) {
case 0x01:
GB.toast(context, context.getString(R.string.ultrahuman_airplane_mode_activated), Toast.LENGTH_LONG, GB.INFO);
return true;
@@ -247,54 +253,66 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
GB.toast(context, context.getString(R.string.ultrahuman_airplane_mode_too_full), Toast.LENGTH_LONG, GB.ERROR);
return true;
}
LOG.warn("set airplane mode - unknown error: {} ", StringUtils.bytesToHex(raw));
GB.toast(context, context.getString(R.string.ultrahuman_airplane_mode_unknown), Toast.LENGTH_LONG, GB.ERROR);
String airplaneMessage = getContext().getString(R.string.ultrahuman_airplane_mode_unknown, result);
GB.toast(context, airplaneMessage, Toast.LENGTH_LONG, GB.ERROR);
return false;
case UltrahumanConstants.OPERATION_GET_FIRST_RECORDING_NR:
if (raw[1] == 0x00 || raw[2] == 0x01) {
if (success == 0x00 && result == 0x01) {
FetchFrom = BLETypeConversions.toUint16(raw, 3);
if (FetchTo != -1) {
fetchRecordingActually();
}
return true;
} else {
String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]);
GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR);
fetchRecordedDataFinished();
}
return false;
fetchRecordedDataFinished();
break;
case UltrahumanConstants.OPERATION_GET_LAST_RECORDING_NR:
if (raw[1] == 0x00 && raw[2] == 0x01) {
if (success == 0x00 && result == 0x01) {
FetchTo = BLETypeConversions.toUint16(raw, 3);
if (FetchFrom != -1) {
fetchRecordingActually();
}
return true;
} else {
String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]);
GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR);
}
fetchRecordedDataFinished();
}
return false;
break;
case UltrahumanConstants.OPERATION_DISABLE_POWERSAVE:
case UltrahumanConstants.OPERATION_ENABLE_POWERSAVE:
case UltrahumanConstants.OPERATION_SETTIME:
if (raw[1] != 0x00 || raw[2] != 0x01) {
String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]);
GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR);
}
if (success == 0x00 && result == 0x01) {
return true;
}
break;
default:
LOG.error("unhandled BLE command response: {} ", StringUtils.bytesToHex(raw));
String message = getContext().getString(R.string.ultrahuman_unhandled_operation_response, StringUtils.bytesToHex(raw));
GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR);
return false;
}
String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, op, success, result);
GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR);
return false;
}
LOG.error("unhandled characteristics {} - {} ", characteristicUUID, StringUtils.bytesToHex(raw));
return false;
}
@Override
public void onSendConfiguration(String config) {
if (DeviceSettingsPreferenceConst.PREF_TIME_SYNC.equals(config)) {
onSetTime();
} else if (DeviceSettingsPreferenceConst.PREF_POWER_SAVING.equals(config)) {
onSetPowerSaveMode();
} else {
super.onSendConfiguration(config);
}
}
@Override
public boolean useAutoConnect() {
@@ -310,7 +328,7 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
if ((raw[1] & 0xFF) == 0xEE) {
LOG.warn("no historic data recorded");
} else {
String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]);
String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[0], raw[1], raw[2]);
GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR);
}
fetchRecordedDataFinished();
@@ -439,7 +457,7 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
batteryState = BatteryState.BATTERY_NORMAL;
break;
case 0x03:
batteryState = BatteryState.BATTERY_CHARGING;
batteryState = (batteryLevel > 99) ? BatteryState.BATTERY_CHARGING_FULL : BatteryState.BATTERY_CHARGING;
break;
default:
LOG.warn("DeviceState contains unhandled device state {}: {}", raw[5], StringUtils.bytesToHex(raw));
@@ -488,8 +506,8 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
GB.signalActivityDataFinish(getDevice());
}
public void activateAirplaneMode() {
sendCommand("ActivateAirplaneMode", new byte[]{UltrahumanConstants.OPERATION_ACTIVATE_AIRPLANE_MODE});
private void activateAirplaneMode() {
sendCommand("activateAirplaneMode", new byte[]{UltrahumanConstants.OPERATION_ACTIVATE_AIRPLANE_MODE});
}
@Override
@@ -499,8 +517,22 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
}
}
private void onSetPowerSaveMode() {
boolean powerSave = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_POWER_SAVING, true);
TransactionBuilder builder = new TransactionBuilder("onSetPowerSaveMode");
builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{powerSave ? UltrahumanConstants.OPERATION_ENABLE_POWERSAVE : UltrahumanConstants.OPERATION_DISABLE_POWERSAVE});
if (isConnected()) {
builder.queue(getQueue());
} else {
GB.toast(getContext(), R.string.devicestatus_disconnected, Toast.LENGTH_LONG, GB.ERROR);
}
}
@Override
public void onSetTime() {
boolean timeSync = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_TIME_SYNC, true);
if (timeSync) {
TransactionBuilder builder = new TransactionBuilder("onSetTime");
UltrahumanSetTimeAction action = new UltrahumanSetTimeAction(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND));
builder.add(action);
@@ -511,6 +543,7 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
GB.toast(getContext(), R.string.devicestatus_disconnected, Toast.LENGTH_LONG, GB.ERROR);
}
}
}
private void sendCommand(String taskName, byte[] contents) {
LOG.debug("sendCommand {} : {}", taskName, StringUtils.bytesToHex(contents));
@@ -524,9 +557,16 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
}
}
private class UltrahumanBroadcastReceiver extends BroadcastReceiver {
private class UltrahumanBroadcastReceiver extends BroadcastReceiver implements IntentListener {
boolean Registered;
@Override
public void onReceive(Context context, Intent intent) {
notify(intent);
}
@Override
public void notify(Intent intent) {
final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (device != null && !device.equals(getDevice())) {
// this intent is for another device
@@ -536,15 +576,7 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport {
String action = intent.getAction();
if (UltrahumanConstants.ACTION_AIRPLANE_MODE.equals(action)) {
activateAirplaneMode();
}
}
}
private class UltrahumanIntentListener implements IntentListener {
@Override
public void notify(Intent intent) {
String action = intent.getAction();
if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
} else if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
handleDeviceInfo(intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
}
}

View File

@@ -1189,7 +1189,7 @@
<string name="persian">Persian</string>
<string name="scandinavian">Scandinavian</string>
<string name="serbian">Serbian</string>
<string name="ukranian">Ukranian</string>
<string name="ukranian">Ukrainian</string>
<string name="armenian">Armenian</string>
<string name="italian">Italian</string>
<string name="french">French</string>
@@ -3875,10 +3875,11 @@
<string name="ultrahuman_airplane_mode_activated">airplane mode activated</string>
<string name="ultrahuman_airplane_mode_on_charger">ring is charging - airplane mode NOT activated</string>
<string name="ultrahuman_airplane_mode_too_full">ring is fully charged - airplane mode NOT activated</string>
<string name="ultrahuman_airplane_mode_unknown">unknown error - airplane mode NOT activated</string>
<string name="ultrahuman_airplane_mode_unknown">unknown error %#02x - airplane mode NOT activated</string>
<string name="ultrahuman_airplane_mode_question">Activate airplane mode?</string>
<string name="ultrahuman_airplane_mode_title">airplane mode</string>
<string name="ultrahuman_unhandled_error_response">received unhandled response code 0x%x for operation 0x%x</string>
<string name="ultrahuman_unhandled_error_response">unhandled response %2$#02x %3$#02x for operation %1$#02x</string>
<string name="ultrahuman_unhandled_operation_response">unhandled operation response %s</string>
<string name="huawei_stress_test_enable">Automatic stress test</string>
<string name="huawei_stress_test_enable_summary">Enable or disable automatic stress test</string>
<string name="huawei_stress_no_calibration_data">No initial data - calibrate first</string>