mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 01:55:50 +01:00
Huawei: Initial Freebuds (5i) support
This commit is contained in:
parent
55238f4ee9
commit
c5714746fb
@ -334,6 +334,10 @@ public class DeviceSettingsPreferenceConst {
|
|||||||
public static final String PREF_NOTHING_EAR1_INEAR = "pref_nothing_inear_detection";
|
public static final String PREF_NOTHING_EAR1_INEAR = "pref_nothing_inear_detection";
|
||||||
public static final String PREF_NOTHING_EAR1_AUDIOMODE = "pref_nothing_audiomode";
|
public static final String PREF_NOTHING_EAR1_AUDIOMODE = "pref_nothing_audiomode";
|
||||||
|
|
||||||
|
|
||||||
|
public static final String PREF_HUAWEI_FREEBUDS_INEAR = "pref_freebuds_inear_detection";
|
||||||
|
public static final String PREF_HUAWEI_FREEBUDS_AUDIOMODE = "pref_freebuds_audiomode";
|
||||||
|
|
||||||
public static final String PREF_GALAXY_BUDS_AMBIENT_MODE = "pref_galaxy_buds_ambient_mode";
|
public static final String PREF_GALAXY_BUDS_AMBIENT_MODE = "pref_galaxy_buds_ambient_mode";
|
||||||
public static final String PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS = "pref_galaxy_buds_ambient_voice_focus";
|
public static final String PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS = "pref_galaxy_buds_ambient_voice_focus";
|
||||||
public static final String PREF_GALAXY_BUDS_AMBIENT_VOLUME = "pref_galaxy_buds_ambient_volume";
|
public static final String PREF_GALAXY_BUDS_AMBIENT_VOLUME = "pref_galaxy_buds_ambient_volume";
|
||||||
|
@ -640,6 +640,9 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
|||||||
addPreferenceHandlerFor(PREF_NOTHING_EAR1_INEAR);
|
addPreferenceHandlerFor(PREF_NOTHING_EAR1_INEAR);
|
||||||
addPreferenceHandlerFor(PREF_NOTHING_EAR1_AUDIOMODE);
|
addPreferenceHandlerFor(PREF_NOTHING_EAR1_AUDIOMODE);
|
||||||
|
|
||||||
|
addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_INEAR);
|
||||||
|
addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_AUDIOMODE);
|
||||||
|
|
||||||
addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS);
|
addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS);
|
||||||
addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOLUME);
|
addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOLUME);
|
||||||
addPreferenceHandlerFor(PREF_GALAXY_BUDS_LOCK_TOUCH);
|
addPreferenceHandlerFor(PREF_GALAXY_BUDS_LOCK_TOUCH);
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||||
|
|
||||||
|
public abstract class HuaweiFreebudsCoordinator extends AbstractBLClassicDeviceCoordinator implements HuaweiCoordinatorSupplier {
|
||||||
|
|
||||||
|
private final HuaweiCoordinator huaweiCoordinator = new HuaweiCoordinator(this);
|
||||||
|
private GBDevice gbDevice;
|
||||||
|
|
||||||
|
public HuaweiFreebudsCoordinator() {
|
||||||
|
huaweiCoordinator.setTransactionCrypted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||||
|
// TODO: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturer() {
|
||||||
|
return "Huawei";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefaultIconResource() {
|
||||||
|
return R.drawable.ic_device_nothingear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDisabledIconResource() {
|
||||||
|
return R.drawable.ic_device_nothingear_disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HuaweiCoordinator getHuaweiCoordinator() {
|
||||||
|
return huaweiCoordinator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HuaweiDeviceType getHuaweiType() {
|
||||||
|
return HuaweiDeviceType.BR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDevice(GBDevice gbDevice) {
|
||||||
|
this.gbDevice = gbDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GBDevice getDevice() {
|
||||||
|
return this.gbDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBondingStyle() {
|
||||||
|
// TODO: Check if correct
|
||||||
|
return BONDING_STYLE_ASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBatteryCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatteryConfig[] getBatteryConfig(GBDevice device) {
|
||||||
|
BatteryConfig battery1 = new BatteryConfig(2, R.drawable.ic_tws_case, R.string.battery_case);
|
||||||
|
BatteryConfig battery2 = new BatteryConfig(0, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
|
||||||
|
BatteryConfig battery3 = new BatteryConfig(1, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
|
||||||
|
return new BatteryConfig[]{battery1, battery2, battery3};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceSpecificSettings getDeviceSpecificSettings(GBDevice device) {
|
||||||
|
DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
|
||||||
|
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_huawei_freebuds);
|
||||||
|
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_headphones);
|
||||||
|
return deviceSpecificSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addBatteryPollingSettings() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Contacts;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Contacts;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
||||||
@ -446,6 +447,7 @@ public class HuaweiPacket {
|
|||||||
case DeviceConfig.Auth.id:
|
case DeviceConfig.Auth.id:
|
||||||
return new DeviceConfig.Auth.Response(paramsProvider).fromPacket(this);
|
return new DeviceConfig.Auth.Response(paramsProvider).fromPacket(this);
|
||||||
case DeviceConfig.BatteryLevel.id:
|
case DeviceConfig.BatteryLevel.id:
|
||||||
|
case DeviceConfig.BatteryLevel.id_change:
|
||||||
return new DeviceConfig.BatteryLevel.Response(paramsProvider).fromPacket(this);
|
return new DeviceConfig.BatteryLevel.Response(paramsProvider).fromPacket(this);
|
||||||
case DeviceConfig.DeviceStatus.id:
|
case DeviceConfig.DeviceStatus.id:
|
||||||
return new DeviceConfig.DeviceStatus.Response(paramsProvider).fromPacket(this);
|
return new DeviceConfig.DeviceStatus.Response(paramsProvider).fromPacket(this);
|
||||||
@ -652,6 +654,13 @@ public class HuaweiPacket {
|
|||||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
case Earphones.id:
|
||||||
|
switch (this.commandId) {
|
||||||
|
case Earphones.InEarStateResponse.id:
|
||||||
|
return new Earphones.InEarStateResponse(paramsProvider).fromPacket(this);
|
||||||
|
case Earphones.GetAudioModeRequest.id:
|
||||||
|
return new Earphones.GetAudioModeRequest.Response(paramsProvider).fromPacket(this);
|
||||||
|
}
|
||||||
case FileDownloadService2C.id:
|
case FileDownloadService2C.id:
|
||||||
switch (this.commandId) {
|
switch (this.commandId) {
|
||||||
case FileDownloadService2C.FileDownloadInit.id:
|
case FileDownloadService2C.FileDownloadInit.id:
|
||||||
|
@ -219,6 +219,14 @@ public class HuaweiTLV {
|
|||||||
throw new HuaweiPacket.MissingTagException(tag);
|
throw new HuaweiPacket.MissingTagException(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes(int tag, byte[] defaultValue) {
|
||||||
|
try {
|
||||||
|
return getBytes(tag);
|
||||||
|
} catch (HuaweiPacket.MissingTagException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Byte getByte(int tag) throws HuaweiPacket.MissingTagException {
|
public Byte getByte(int tag) throws HuaweiPacket.MissingTagException {
|
||||||
return getBytes(tag)[0];
|
return getBytes(tag)[0];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.huawei.freebuds5i;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiFreebudsCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFreebudsSupport;
|
||||||
|
|
||||||
|
public class HuaweiFreebuds5iCoordinator extends HuaweiFreebudsCoordinator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Pattern getSupportedDeviceName() {
|
||||||
|
return Pattern.compile("huawei freebuds 5i.*", Pattern.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Class<? extends DeviceSupport> getDeviceSupportClass() {
|
||||||
|
return HuaweiFreebudsSupport.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.HUAWEI_FREEBUDS5I;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDeviceNameResource() {
|
||||||
|
return R.string.devicetype_huawei_freebuds_5i;
|
||||||
|
}
|
||||||
|
}
|
@ -666,6 +666,7 @@ public class DeviceConfig {
|
|||||||
|
|
||||||
public static class BatteryLevel {
|
public static class BatteryLevel {
|
||||||
public static final byte id = 0x08;
|
public static final byte id = 0x08;
|
||||||
|
public static final byte id_change = 0x27; // Same format, async (receive) only
|
||||||
|
|
||||||
public static class Request extends HuaweiPacket {
|
public static class Request extends HuaweiPacket {
|
||||||
public Request(ParamsProvider paramsProvider) {
|
public Request(ParamsProvider paramsProvider) {
|
||||||
@ -683,6 +684,9 @@ public class DeviceConfig {
|
|||||||
public static class Response extends HuaweiPacket {
|
public static class Response extends HuaweiPacket {
|
||||||
public byte level;
|
public byte level;
|
||||||
|
|
||||||
|
public byte[] multi_level;
|
||||||
|
public byte[] status; // TODO: enum
|
||||||
|
|
||||||
public Response(ParamsProvider paramsProvider) {
|
public Response(ParamsProvider paramsProvider) {
|
||||||
super(paramsProvider);
|
super(paramsProvider);
|
||||||
|
|
||||||
@ -693,6 +697,8 @@ public class DeviceConfig {
|
|||||||
@Override
|
@Override
|
||||||
public void parseTlv() throws ParseException {
|
public void parseTlv() throws ParseException {
|
||||||
this.level = this.tlv.getByte(0x01);
|
this.level = this.tlv.getByte(0x01);
|
||||||
|
this.multi_level = this.tlv.getBytes(0x02, null);
|
||||||
|
this.status = this.tlv.getBytes(0x03, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: implement parsing this request for the log parser support
|
// TODO: implement parsing this request for the log parser support
|
||||||
@ -958,6 +964,9 @@ public class DeviceConfig {
|
|||||||
// TODO: implement parsing this request for the log parser support
|
// TODO: implement parsing this request for the log parser support
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: set (earphone) double tap action 0x1f
|
||||||
|
// TODO: get (earphone) double tap action 0x20
|
||||||
|
|
||||||
public static class HiChain {
|
public static class HiChain {
|
||||||
public static final int id = 0x28;
|
public static final int id = 0x28;
|
||||||
|
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
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.huawei.packets;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
|
||||||
|
|
||||||
|
// Information from:
|
||||||
|
// https://mmk.pw/en/posts/freebuds-4i-proto/ and
|
||||||
|
// https://github.com/TheLastGimbus/FreeBuddy/blob/master/notes/mbb-protocol-wiki.md and
|
||||||
|
// https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/4325/
|
||||||
|
|
||||||
|
public class Earphones {
|
||||||
|
public static final byte id = 0x2b;
|
||||||
|
|
||||||
|
public static class InEarStateResponse extends HuaweiPacket {
|
||||||
|
public static final byte id = 0x03;
|
||||||
|
|
||||||
|
public byte leftState;
|
||||||
|
public byte rightState;
|
||||||
|
|
||||||
|
public InEarStateResponse(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Earphones.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseTlv() throws ParseException {
|
||||||
|
this.leftState = this.tlv.getByte(0x08, (byte) -1);
|
||||||
|
this.rightState = this.tlv.getByte(0x09, (byte) -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SetAudioModeRequest extends HuaweiPacket {
|
||||||
|
public static final byte id = 0x04;
|
||||||
|
|
||||||
|
// TODO: enum for new state
|
||||||
|
public SetAudioModeRequest(ParamsProvider paramsProvider, byte newState) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Earphones.id;
|
||||||
|
this.commandId = id;
|
||||||
|
|
||||||
|
byte[] data = {newState, newState == 0 ? 0x00 : (byte) 0xff};
|
||||||
|
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01, data);
|
||||||
|
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SetPauseWhenRemovedFromEar extends HuaweiPacket {
|
||||||
|
public static final byte id = 0x10;
|
||||||
|
|
||||||
|
// TODO: enum for new state
|
||||||
|
public SetPauseWhenRemovedFromEar(ParamsProvider paramsProvider, boolean newState) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Earphones.id;
|
||||||
|
this.commandId = id;
|
||||||
|
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01, newState);
|
||||||
|
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: get pause when removed from ear 0x11
|
||||||
|
// TODO: set long tap action 0x16
|
||||||
|
// TODO: get long tap action 0x17
|
||||||
|
// TODO: Audio mode cycle 0x19
|
||||||
|
|
||||||
|
public static class GetAudioModeRequest {
|
||||||
|
public static final byte id = 0x2a;
|
||||||
|
|
||||||
|
public static class Request extends HuaweiPacket {
|
||||||
|
public Request(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Earphones.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response extends HuaweiPacket {
|
||||||
|
public short fullState;
|
||||||
|
|
||||||
|
public byte state; // TODO: enum
|
||||||
|
|
||||||
|
public Response(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Earphones.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseTlv() throws ParseException {
|
||||||
|
this.fullState = this.tlv.getShort(0x01);
|
||||||
|
this.state = (byte) this.fullState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -171,6 +171,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5.MiBand5Coordin
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6.MiBand6Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6.MiBand6Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband7.MiBand7Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband7.MiBand7Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe.ZeppECoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe.ZeppECoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.freebuds5i.HuaweiFreebuds5iCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband3.HonorBand3Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband3.HonorBand3Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband4.HonorBand4Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband4.HonorBand4Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband5.HonorBand5Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband5.HonorBand5Coordinator;
|
||||||
@ -549,6 +550,7 @@ public enum DeviceType {
|
|||||||
HUAWEIWATCHULTIMATE(HuaweiWatchUltimateCoordinator.class),
|
HUAWEIWATCHULTIMATE(HuaweiWatchUltimateCoordinator.class),
|
||||||
HUAWEIWATCH3(HuaweiWatch3Coordinator.class),
|
HUAWEIWATCH3(HuaweiWatch3Coordinator.class),
|
||||||
HUAWEIWATCH4PRO(HuaweiWatch4ProCoordinator.class),
|
HUAWEIWATCH4PRO(HuaweiWatch4ProCoordinator.class),
|
||||||
|
HUAWEI_FREEBUDS5I(HuaweiFreebuds5iCoordinator.class),
|
||||||
VESC(VescCoordinator.class),
|
VESC(VescCoordinator.class),
|
||||||
BINARY_SENSOR(BinarySensorCoordinator.class),
|
BINARY_SENSOR(BinarySensorCoordinator.class),
|
||||||
FLIPPER_ZERO(FlipperZeroCoordinator.class),
|
FLIPPER_ZERO(FlipperZeroCoordinator.class),
|
||||||
|
@ -16,142 +16,50 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service;
|
package nodomain.freeyourgadget.gadgetbridge.service;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.media.AudioManager;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GBTextToSpeech;
|
|
||||||
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE;
|
public abstract class AbstractHeadphoneDeviceSupport extends AbstractSerialDeviceSupport implements HeadphoneHelper.Callback {
|
||||||
|
|
||||||
public abstract class AbstractHeadphoneDeviceSupport extends AbstractSerialDeviceSupport {
|
private HeadphoneHelper headphoneHelper;
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractHeadphoneDeviceSupport.class);
|
|
||||||
private GBTextToSpeech gbTextToSpeech;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSetCallState(CallSpec callSpec) {
|
public void onSetCallState(CallSpec callSpec) {
|
||||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
headphoneHelper.onSetCallState(callSpec);
|
||||||
|
|
||||||
if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL, false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
final int delayMillis = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, "15")) * 1000;
|
|
||||||
|
|
||||||
if (CallSpec.CALL_INCOMING != callSpec.command)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!gbTextToSpeech.isConnected()) { // schedule the automatic reply here, if the speech to text is not connected. Else it's done by the callback, and the timeout starts after the name or number have been spoken
|
|
||||||
Looper mainLooper = Looper.getMainLooper();
|
|
||||||
LOG.debug("Incoming call, scheduling auto answer in {} seconds.", delayMillis / 1000);
|
|
||||||
|
|
||||||
new Handler(mainLooper).postDelayed(() -> {
|
|
||||||
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
|
|
||||||
callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
|
|
||||||
evaluateGBDeviceEvent(callCmd);
|
|
||||||
}, delayMillis); //15s
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String speechText = callSpec.name;
|
|
||||||
if (callSpec.name.equals(callSpec.number)) {
|
|
||||||
StringBuilder numberSpeller = new StringBuilder();
|
|
||||||
for (char c : callSpec.number.toCharArray()) {
|
|
||||||
numberSpeller.append(c).append(" ");
|
|
||||||
}
|
|
||||||
speechText = numberSpeller.toString();
|
|
||||||
}
|
|
||||||
gbTextToSpeech.speak(speechText);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNotification(NotificationSpec notificationSpec) {
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
headphoneHelper.onNotification(notificationSpec);
|
||||||
|
|
||||||
if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPEAK_NOTIFICATIONS_ALOUD, false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (gbTextToSpeech.isConnected()) {
|
|
||||||
|
|
||||||
String notificationSpeller = new StringBuilder()
|
|
||||||
.append(notificationSpec.sourceName == null ? "" : notificationSpec.sourceName).append(". ")
|
|
||||||
.append(notificationSpec.title == null ? "" : notificationSpec.title).append(": ")
|
|
||||||
.append(notificationSpec.body == null ? "" : notificationSpec.body).toString();
|
|
||||||
|
|
||||||
|
|
||||||
gbTextToSpeech.speakNotification(notificationSpeller);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
|
||||||
|
super.setContext(gbDevice, btAdapter, context);
|
||||||
|
headphoneHelper = new HeadphoneHelper(getContext(), getDevice(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean connect() {
|
public boolean connect() {
|
||||||
getDeviceIOThread().start();
|
getDeviceIOThread().start();
|
||||||
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
|
||||||
gbTextToSpeech = new GBTextToSpeech(getContext(), new UtteranceProgressListener(),
|
|
||||||
prefs.getBoolean(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE, false) ?
|
|
||||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE :
|
|
||||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendConfiguration(String config) {
|
public void onSendConfiguration(String config) {
|
||||||
if (PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE.equals(config)) {
|
if (!headphoneHelper.onSendConfiguration(config))
|
||||||
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
|
||||||
gbTextToSpeech.setAudioFocus(prefs.getBoolean(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE, false) ?
|
|
||||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE :
|
|
||||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
|
||||||
} else {
|
|
||||||
super.onSendConfiguration(config);
|
super.onSendConfiguration(config);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
gbTextToSpeech.shutdown();
|
if (headphoneHelper != null)
|
||||||
|
headphoneHelper.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UtteranceProgressListener extends android.speech.tts.UtteranceProgressListener {
|
|
||||||
@Override
|
|
||||||
public void onStart(String utteranceId) {
|
|
||||||
// LOG.debug("UtteranceProgressListener onStart.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDone(String utteranceId) {
|
|
||||||
// LOG.debug("UtteranceProgressListener onDone.");
|
|
||||||
|
|
||||||
gbTextToSpeech.abandonFocus();
|
|
||||||
if (utteranceId.equals("call")) {
|
|
||||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
|
||||||
final int delayMillis = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, "15")) * 1000;
|
|
||||||
|
|
||||||
|
|
||||||
Looper mainLooper = Looper.getMainLooper();
|
|
||||||
new Handler(mainLooper).postDelayed(() -> {
|
|
||||||
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
|
|
||||||
callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
|
|
||||||
evaluateGBDeviceEvent(callCmd);
|
|
||||||
}, delayMillis); //15s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(String utteranceId) {
|
|
||||||
LOG.error("UtteranceProgressListener returned error.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,161 @@
|
|||||||
|
/* Copyright (C) 2024 Arjan Schrijver, Daniele Gobbetti, Martin.JM
|
||||||
|
|
||||||
|
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.service;
|
||||||
|
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GBTextToSpeech;
|
||||||
|
|
||||||
|
public class HeadphoneHelper {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HeadphoneHelper.class);
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void evaluateGBDeviceEvent(GBDeviceEvent event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final GBDevice device;
|
||||||
|
private final GBTextToSpeech gbTextToSpeech;
|
||||||
|
private final Callback callback;
|
||||||
|
|
||||||
|
public HeadphoneHelper(Context context, GBDevice device, Callback callback) {
|
||||||
|
this.device = device;
|
||||||
|
this.callback = callback;
|
||||||
|
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(this.device.getAddress());
|
||||||
|
gbTextToSpeech = new GBTextToSpeech(context, new UtteranceProgressListener(),
|
||||||
|
prefs.getBoolean(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE, false) ?
|
||||||
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE :
|
||||||
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
gbTextToSpeech.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSetCallState(CallSpec callSpec) {
|
||||||
|
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
|
||||||
|
|
||||||
|
if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
final int delayMillis = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, "15")) * 1000;
|
||||||
|
|
||||||
|
if (CallSpec.CALL_INCOMING != callSpec.command)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!gbTextToSpeech.isConnected()) { // schedule the automatic reply here, if the speech to text is not connected. Else it's done by the callback, and the timeout starts after the name or number have been spoken
|
||||||
|
Looper mainLooper = Looper.getMainLooper();
|
||||||
|
LOG.debug("Incoming call, scheduling auto answer in {} seconds.", delayMillis / 1000);
|
||||||
|
|
||||||
|
new Handler(mainLooper).postDelayed(() -> {
|
||||||
|
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
|
||||||
|
callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
|
||||||
|
callback.evaluateGBDeviceEvent(callCmd);
|
||||||
|
}, delayMillis); //15s
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String speechText = callSpec.name;
|
||||||
|
if (callSpec.name.equals(callSpec.number)) {
|
||||||
|
StringBuilder numberSpeller = new StringBuilder();
|
||||||
|
for (char c : callSpec.number.toCharArray()) {
|
||||||
|
numberSpeller.append(c).append(" ");
|
||||||
|
}
|
||||||
|
speechText = numberSpeller.toString();
|
||||||
|
}
|
||||||
|
gbTextToSpeech.speak(speechText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
|
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
|
||||||
|
|
||||||
|
if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPEAK_NOTIFICATIONS_ALOUD, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (gbTextToSpeech.isConnected()) {
|
||||||
|
String notificationSpeller = new StringBuilder()
|
||||||
|
.append(notificationSpec.sourceName == null ? "" : notificationSpec.sourceName).append(". ")
|
||||||
|
.append(notificationSpec.title == null ? "" : notificationSpec.title).append(": ")
|
||||||
|
.append(notificationSpec.body == null ? "" : notificationSpec.body).toString();
|
||||||
|
|
||||||
|
gbTextToSpeech.speakNotification(notificationSpeller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @return True if handled, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean onSendConfiguration(String config) {
|
||||||
|
if (PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE.equals(config)) {
|
||||||
|
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
|
||||||
|
gbTextToSpeech.setAudioFocus(prefs.getBoolean(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE, false) ?
|
||||||
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE :
|
||||||
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UtteranceProgressListener extends android.speech.tts.UtteranceProgressListener {
|
||||||
|
@Override
|
||||||
|
public void onStart(String utteranceId) {
|
||||||
|
// LOG.debug("UtteranceProgressListener onStart.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDone(String utteranceId) {
|
||||||
|
// LOG.debug("UtteranceProgressListener onDone.");
|
||||||
|
|
||||||
|
gbTextToSpeech.abandonFocus();
|
||||||
|
if (utteranceId.equals("call")) {
|
||||||
|
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(HeadphoneHelper.this.device.getAddress());
|
||||||
|
final int delayMillis = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, "15")) * 1000;
|
||||||
|
|
||||||
|
|
||||||
|
Looper mainLooper = Looper.getMainLooper();
|
||||||
|
new Handler(mainLooper).postDelayed(() -> {
|
||||||
|
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
|
||||||
|
callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
|
||||||
|
callback.evaluateGBDeviceEvent(callCmd);
|
||||||
|
}, delayMillis); //15s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(String utteranceId) {
|
||||||
|
LOG.error("UtteranceProgressListener returned error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
|
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||||
@ -59,6 +60,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBatteryLevelRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadComplete;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadComplete;
|
||||||
@ -123,6 +126,7 @@ public class AsynchronousResponse {
|
|||||||
handleP2p(response);
|
handleP2p(response);
|
||||||
handleEphemeris(response);
|
handleEphemeris(response);
|
||||||
handleEphemerisUploadService(response);
|
handleEphemerisUploadService(response);
|
||||||
|
handleAsyncBattery(response);
|
||||||
} catch (Request.ResponseParseException e) {
|
} catch (Request.ResponseParseException e) {
|
||||||
LOG.error("Response parse exception", e);
|
LOG.error("Response parse exception", e);
|
||||||
}
|
}
|
||||||
@ -679,4 +683,46 @@ public class AsynchronousResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleAsyncBattery(HuaweiPacket response) {
|
||||||
|
if (response.serviceId == DeviceConfig.id && response.commandId == DeviceConfig.BatteryLevel.id_change) {
|
||||||
|
if (!(response instanceof DeviceConfig.BatteryLevel.Response)) {
|
||||||
|
// TODO: exception?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceConfig.BatteryLevel.Response resp = (DeviceConfig.BatteryLevel.Response) response;
|
||||||
|
|
||||||
|
if (resp.multi_level == null) {
|
||||||
|
byte batteryLevel = resp.level;
|
||||||
|
this.support.getDevice().setBatteryLevel(batteryLevel);
|
||||||
|
|
||||||
|
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
||||||
|
batteryInfo.state = BatteryState.BATTERY_NORMAL;
|
||||||
|
batteryInfo.level = (int) batteryLevel & 0xff;
|
||||||
|
this.support.evaluateGBDeviceEvent(batteryInfo);
|
||||||
|
} else {
|
||||||
|
// Handle multiple batteries
|
||||||
|
for (int i = 0; i < resp.multi_level.length; i++) {
|
||||||
|
int level = (int) resp.multi_level[i] & 0xff;
|
||||||
|
this.support.getDevice().setBatteryLevel(level, i);
|
||||||
|
|
||||||
|
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
||||||
|
batteryInfo.batteryIndex = i;
|
||||||
|
batteryInfo.state = resp.status != null && resp.status.length > i ?
|
||||||
|
GetBatteryLevelRequest.byteToBatteryState(resp.status[i]) :
|
||||||
|
BatteryState.BATTERY_NORMAL;
|
||||||
|
batteryInfo.level = level;
|
||||||
|
this.support.evaluateGBDeviceEvent(batteryInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GBApplication.getDevicePrefs(this.support.getDevice()).getBatteryPollingEnabled()) {
|
||||||
|
if (!this.support.startBatteryRunnerDelayed()) {
|
||||||
|
GB.toast(this.support.getContext(), R.string.battery_polling_failed_start, Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
LOG.error("Failed to start the battery polling");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,10 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
|
|||||||
addSupportedService(HuaweiConstants.UUID_SERVICE_HUAWEI_SDP);
|
addSupportedService(HuaweiConstants.UUID_SERVICE_HUAWEI_SDP);
|
||||||
setBufferSize(1032);
|
setBufferSize(1032);
|
||||||
supportProvider = new HuaweiSupportProvider(this);
|
supportProvider = new HuaweiSupportProvider(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HuaweiSupportProvider getSupportProvider() {
|
||||||
|
return supportProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.HeadphoneHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetProductInformationRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAudioModeRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetPauseWhenRemovedFromEarRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
// TODO: Move from HuaweiBRSupport to AbstractBTBRDeviceSupport
|
||||||
|
public class HuaweiFreebudsSupport extends HuaweiBRSupport implements HeadphoneHelper.Callback {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HuaweiFreebudsSupport.class);
|
||||||
|
|
||||||
|
private HeadphoneHelper headphoneHelper;
|
||||||
|
|
||||||
|
public HuaweiFreebudsSupport() {
|
||||||
|
super();
|
||||||
|
addSupportedService(UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"));
|
||||||
|
setBufferSize(1032);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
|
||||||
|
super.setContext(gbDevice, btAdapter, context);
|
||||||
|
headphoneHelper = new HeadphoneHelper(getContext(), getDevice(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||||
|
LOG.info("Huawei Freebuds init" );
|
||||||
|
|
||||||
|
super.getSupportProvider().setup(getDevice(), getContext());
|
||||||
|
|
||||||
|
builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||||
|
try {
|
||||||
|
builder.setCallback(this);
|
||||||
|
final GetProductInformationRequest deviceProductReq = new GetProductInformationRequest(super.getSupportProvider());
|
||||||
|
deviceProductReq.setFinalizeReq(new Request.RequestCallback(getSupportProvider()) {
|
||||||
|
@Override
|
||||||
|
public void call() {
|
||||||
|
// This also (optionally) starts the battery polling
|
||||||
|
getSupportProvider().getBatteryLevel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
deviceProductReq.doPerform();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Connection failed", e);
|
||||||
|
GB.toast("Connection failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
}
|
||||||
|
builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
if (headphoneHelper != null)
|
||||||
|
headphoneHelper.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSocketRead(byte[] data) {
|
||||||
|
super.getSupportProvider().onSocketRead(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetCallState(CallSpec callSpec) {
|
||||||
|
headphoneHelper.onSetCallState(callSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
|
headphoneHelper.onNotification(notificationSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFetchRecordedData(int dataTypes) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendConfiguration(String config) {
|
||||||
|
if (headphoneHelper.onSendConfiguration(config))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (config) {
|
||||||
|
case DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_INEAR:
|
||||||
|
new SetPauseWhenRemovedFromEarRequest(getSupportProvider()).doPerform();
|
||||||
|
break;
|
||||||
|
case DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_AUDIOMODE:
|
||||||
|
new SetAudioModeRequest(getSupportProvider()).doPerform();
|
||||||
|
break;
|
||||||
|
case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_ENABLE:
|
||||||
|
if (!GBApplication.getDevicePrefs(gbDevice).getBatteryPollingEnabled()) {
|
||||||
|
getSupportProvider().stopBatteryRunnerDelayed();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Fall through if enabled
|
||||||
|
case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_INTERVAL:
|
||||||
|
if (!getSupportProvider().startBatteryRunnerDelayed()) {
|
||||||
|
GB.toast(getContext(), R.string.battery_polling_failed_start, Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
LOG.error("Failed to start the battery polling");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
GB.toast(getContext(), "Configuration of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
LOG.error("Configuration of Huawei device failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -376,11 +376,15 @@ public class HuaweiSupportProvider {
|
|||||||
this.gpsParametersResponse = response;
|
this.gpsParametersResponse = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder) {
|
public void setup(GBDevice device, Context context) {
|
||||||
this.gbDevice = leSupport.getDevice();
|
this.gbDevice = device;
|
||||||
this.context = leSupport.getContext();
|
this.context = context;
|
||||||
this.huaweiType = getCoordinator().getHuaweiType();
|
this.huaweiType = getCoordinator().getHuaweiType();
|
||||||
this.paramsProvider.setTransactionsCrypted(this.getHuaweiCoordinator().isTransactionCrypted());
|
this.paramsProvider.setTransactionsCrypted(this.getHuaweiCoordinator().isTransactionCrypted());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder) {
|
||||||
|
setup(leSupport.getDevice(), leSupport.getContext());
|
||||||
builder.setCallback(leSupport);
|
builder.setCallback(leSupport);
|
||||||
final BluetoothGattCharacteristic characteristicRead = leSupport.getCharacteristic(HuaweiConstants.UUID_CHARACTERISTIC_HUAWEI_READ);
|
final BluetoothGattCharacteristic characteristicRead = leSupport.getCharacteristic(HuaweiConstants.UUID_CHARACTERISTIC_HUAWEI_READ);
|
||||||
if (characteristicRead == null) {
|
if (characteristicRead == null) {
|
||||||
@ -397,10 +401,7 @@ public class HuaweiSupportProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
|
protected nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
|
||||||
this.gbDevice = brSupport.getDevice();
|
setup(brSupport.getDevice(), brSupport.getContext());
|
||||||
this.context = brSupport.getContext();
|
|
||||||
this.huaweiType = getCoordinator().getHuaweiType();
|
|
||||||
this.paramsProvider.setTransactionsCrypted(this.getHuaweiCoordinator().isTransactionCrypted());
|
|
||||||
builder.setCallback(brSupport);
|
builder.setCallback(brSupport);
|
||||||
builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
|
builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
|
||||||
final GetLinkParamsRequest linkParamsReq = new GetLinkParamsRequest(this, builder);
|
final GetLinkParamsRequest linkParamsReq = new GetLinkParamsRequest(this, builder);
|
||||||
|
@ -50,6 +50,12 @@ public class GetBatteryLevelRequest extends Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static BatteryState byteToBatteryState(byte state) {
|
||||||
|
if (state == 1)
|
||||||
|
return BatteryState.BATTERY_CHARGING;
|
||||||
|
return BatteryState.BATTERY_NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void processResponse() throws ResponseParseException {
|
protected void processResponse() throws ResponseParseException {
|
||||||
LOG.debug("handle Battery Level");
|
LOG.debug("handle Battery Level");
|
||||||
@ -57,13 +63,31 @@ public class GetBatteryLevelRequest extends Request {
|
|||||||
if (!(receivedPacket instanceof DeviceConfig.BatteryLevel.Response))
|
if (!(receivedPacket instanceof DeviceConfig.BatteryLevel.Response))
|
||||||
throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.BatteryLevel.Response.class);
|
throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.BatteryLevel.Response.class);
|
||||||
|
|
||||||
byte batteryLevel = ((DeviceConfig.BatteryLevel.Response) receivedPacket).level;
|
DeviceConfig.BatteryLevel.Response response = (DeviceConfig.BatteryLevel.Response) receivedPacket;
|
||||||
|
|
||||||
|
if (response.multi_level == null) {
|
||||||
|
byte batteryLevel = response.level;
|
||||||
getDevice().setBatteryLevel(batteryLevel);
|
getDevice().setBatteryLevel(batteryLevel);
|
||||||
|
|
||||||
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
||||||
batteryInfo.state = BatteryState.BATTERY_NORMAL;
|
batteryInfo.state = BatteryState.BATTERY_NORMAL;
|
||||||
batteryInfo.level = (int)batteryLevel & 0xff;
|
batteryInfo.level = (int) batteryLevel & 0xff;
|
||||||
this.supportProvider.evaluateGBDeviceEvent(batteryInfo);
|
this.supportProvider.evaluateGBDeviceEvent(batteryInfo);
|
||||||
|
} else {
|
||||||
|
// Handle multiple batteries
|
||||||
|
for (int i = 0; i < response.multi_level.length; i++) {
|
||||||
|
int level = (int) response.multi_level[i] & 0xff;
|
||||||
|
getDevice().setBatteryLevel(level, i);
|
||||||
|
|
||||||
|
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
||||||
|
batteryInfo.batteryIndex = i;
|
||||||
|
batteryInfo.state = response.status != null && response.status.length > i ?
|
||||||
|
byteToBatteryState(response.status[i]) :
|
||||||
|
BatteryState.BATTERY_NORMAL;
|
||||||
|
batteryInfo.level = level;
|
||||||
|
this.supportProvider.evaluateGBDeviceEvent(batteryInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (GBApplication.getDevicePrefs(getDevice()).getBatteryPollingEnabled()) {
|
if (GBApplication.getDevicePrefs(getDevice()).getBatteryPollingEnabled()) {
|
||||||
if (!this.supportProvider.startBatteryRunnerDelayed()) {
|
if (!this.supportProvider.startBatteryRunnerDelayed()) {
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
|
||||||
|
public class SetAudioModeRequest extends Request {
|
||||||
|
|
||||||
|
public SetAudioModeRequest(HuaweiSupportProvider supportProvider) {
|
||||||
|
super(supportProvider);
|
||||||
|
this.serviceId = Earphones.id;
|
||||||
|
this.commandId = Earphones.SetAudioModeRequest.id;
|
||||||
|
this.addToResponse = false; // Response with different command ID
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
String audioMode = GBApplication
|
||||||
|
.getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
|
||||||
|
.getString(DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_AUDIOMODE, "off");
|
||||||
|
byte mode = 0; // Off by default
|
||||||
|
switch (audioMode) {
|
||||||
|
case "anc":
|
||||||
|
mode = 1;
|
||||||
|
break;
|
||||||
|
case "transparency":
|
||||||
|
mode = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return new Earphones.SetAudioModeRequest(this.paramsProvider, mode).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
|
||||||
|
public class SetPauseWhenRemovedFromEarRequest extends Request {
|
||||||
|
|
||||||
|
public SetPauseWhenRemovedFromEarRequest(HuaweiSupportProvider supportProvider) {
|
||||||
|
super(supportProvider);
|
||||||
|
this.serviceId = Earphones.id;
|
||||||
|
this.commandId = Earphones.SetAudioModeRequest.id;
|
||||||
|
this.addToResponse = false; // Response with different command ID
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
boolean newState = GBApplication
|
||||||
|
.getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
|
||||||
|
.getBoolean(DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_INEAR, false);
|
||||||
|
return new Earphones.SetPauseWhenRemovedFromEar(this.paramsProvider, newState).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4473,4 +4473,16 @@
|
|||||||
<item>calories_active</item>
|
<item>calories_active</item>
|
||||||
<item>calories_segmented</item>
|
<item>calories_segmented</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="huawei_freebuds_audio_mode_names">
|
||||||
|
<item>@string/prefs_active_noise_cancelling</item>
|
||||||
|
<item>@string/prefs_active_noise_cancelling_transparency</item>
|
||||||
|
<item>@string/off</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="huawei_freebuds_audio_mode_values">
|
||||||
|
<item>anc</item>
|
||||||
|
<item>transparency</item>
|
||||||
|
<item>off</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1881,6 +1881,7 @@
|
|||||||
<string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
|
<string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
|
||||||
<string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
|
<string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
|
||||||
<string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
|
<string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
|
||||||
|
<string name="devicetype_huawei_freebuds_5i">Huawei FreeBuds 5i</string>
|
||||||
<string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
|
<string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
|
||||||
<string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
|
<string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
|
||||||
<string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
|
<string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
|
||||||
|
18
app/src/main/res/xml/devicesettings_huawei_freebuds.xml
Normal file
18
app/src/main/res/xml/devicesettings_huawei_freebuds.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:icon="@drawable/ic_extension"
|
||||||
|
android:key="pref_freebuds_inear_detection"
|
||||||
|
android:layout="@layout/preference_checkbox"
|
||||||
|
android:summary="@string/nothing_prefs_inear_summary"
|
||||||
|
android:title="@string/nothing_prefs_inear_title" />
|
||||||
|
<ListPreference
|
||||||
|
android:entries="@array/huawei_freebuds_audio_mode_names"
|
||||||
|
android:entryValues="@array/huawei_freebuds_audio_mode_values"
|
||||||
|
android:icon="@drawable/ic_extension"
|
||||||
|
android:key="pref_freebuds_audiomode"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/nothing_prefs_audiomode_title" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user