From a37f0c89bbe67e3415132744b001c9e8a8aeda85 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Fri, 5 Apr 2024 19:54:58 +0200 Subject: [PATCH] Nothing: add adjustable delay for auto-pick-up of calls - Also add a (basic) Application wide TextToSpeech helper - use the TextToSpeech to announce the caller name or number --- .../DeviceSettingsPreferenceConst.java | 1 + .../nothing/EarSettingsCustomizer.java | 42 +++++++++---- .../service/devices/nothing/Ear1Support.java | 62 ++++++++++++++++--- .../gadgetbridge/util/GBTextToSpeech.java | 61 ++++++++++++++++++ app/src/main/res/values/strings.xml | 2 + .../res/xml/devicesettings_nothing_ear1.xml | 11 +++- 6 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 21c78ab9e..f385d72c3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -438,4 +438,5 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_FORCE_CONNECTION_TYPE = "pref_force_connection_type"; public static final String PREF_AUTO_REPLY_INCOMING_CALL = "pref_auto_reply_phonecall"; + public static final String PREF_AUTO_REPLY_INCOMING_CALL_DELAY = "pref_auto_reply_phonecall_delay"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java index 3e898c4ab..0e5d19409 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java @@ -17,7 +17,9 @@ package nodomain.freeyourgadget.gadgetbridge.devices.nothing; import android.os.Parcel; +import android.text.InputType; +import androidx.preference.EditTextPreference; import androidx.preference.ListPreference; import androidx.preference.Preference; @@ -32,6 +34,18 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpec import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class EarSettingsCustomizer implements DeviceSpecificSettingsCustomizer { + public static final Creator CREATOR = new Creator() { + @Override + public EarSettingsCustomizer createFromParcel(final Parcel in) { + return new EarSettingsCustomizer(); + } + + @Override + public EarSettingsCustomizer[] newArray(final int size) { + return new EarSettingsCustomizer[size]; + } + }; + @Override public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) { } @@ -62,6 +76,22 @@ public class EarSettingsCustomizer implements DeviceSpecificSettingsCustomizer { ((ListPreference) audioModePref).setEntryValues(entryValues.toArray(new CharSequence[0])); } } + final Preference autoReplyPref = handler.findPreference(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL); + final Preference autoReplyDelay = handler.findPreference(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY); + + if (autoReplyPref != null && autoReplyDelay != null) { + + autoReplyDelay.setEnabled(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL, false)); + + autoReplyPref.setOnPreferenceChangeListener((preference, newValue) -> { + autoReplyDelay.setEnabled((Boolean) newValue); + return true; + }); + + ((EditTextPreference) autoReplyDelay).setOnBindEditTextListener(editText -> editText.setInputType(InputType.TYPE_CLASS_NUMBER)); + + } + } @Override @@ -69,18 +99,6 @@ public class EarSettingsCustomizer implements DeviceSpecificSettingsCustomizer { return Collections.emptySet(); } - public static final Creator CREATOR = new Creator() { - @Override - public EarSettingsCustomizer createFromParcel(final Parcel in) { - return new EarSettingsCustomizer(); - } - - @Override - public EarSettingsCustomizer[] newArray(final int size) { - return new EarSettingsCustomizer[size]; - } - }; - @Override public int describeContents() { return 0; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java index 2f5157661..780e346a0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java @@ -30,10 +30,11 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; +import nodomain.freeyourgadget.gadgetbridge.util.GBTextToSpeech; public class Ear1Support extends AbstractSerialDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(Ear1Support.class); - + private GBTextToSpeech gbTextToSpeech; @Override public void onSetCallState(CallSpec callSpec) { @@ -42,19 +43,30 @@ public class Ear1Support extends AbstractSerialDeviceSupport { if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL, false)) return; - if(CallSpec.CALL_INCOMING != callSpec.command) + final int delayMillis = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, "15")) * 1000; + + if (CallSpec.CALL_INCOMING != callSpec.command) return; - LOG.debug("Incoming call, scheduling auto answer in 10 seconds."); - Looper mainLooper = Looper.getMainLooper(); - new Handler(mainLooper).postDelayed(new Runnable() { - @Override - public void run() { + 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 + } + String speechText = callSpec.name; + if (callSpec.name.equals(callSpec.number)) { + StringBuilder numberSpeller = new StringBuilder(); + for (char c : callSpec.number.toCharArray()) { + numberSpeller.append(c).append(" "); } - }, 15000); //15s + speechText = numberSpeller.toString(); + } + gbTextToSpeech.speak(speechText); } @@ -71,6 +83,7 @@ public class Ear1Support extends AbstractSerialDeviceSupport { @Override public boolean connect() { getDeviceIOThread().start(); + gbTextToSpeech = new GBTextToSpeech(getContext(), new UtteranceProgressListener()); return true; } @@ -93,4 +106,37 @@ public class Ear1Support extends AbstractSerialDeviceSupport { return new NothingIOThread(getDevice(), getContext(), (NothingProtocol) getDeviceProtocol(), Ear1Support.this, getBluetoothAdapter()); } + + @Override + public void dispose() { + gbTextToSpeech.shutdown(); + 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."); + 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."); + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java new file mode 100644 index 000000000..11b120bfd --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java @@ -0,0 +1,61 @@ +package nodomain.freeyourgadget.gadgetbridge.util; + +import android.content.Context; +import android.media.AudioManager; +import android.os.Bundle; +import android.speech.tts.TextToSpeech; +import android.speech.tts.UtteranceProgressListener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Locale; + + +public class GBTextToSpeech { + private static final Logger LOG = LoggerFactory.getLogger(GBTextToSpeech.class); + private final Context context; + private TextToSpeech textToSpeech; + private boolean isConnected = false; + + public GBTextToSpeech(Context context, UtteranceProgressListener callback) { + this.context = context; + initializeTTS(callback); + } + + public boolean isConnected() { + return isConnected; + } + + private void initializeTTS(UtteranceProgressListener callback) { + textToSpeech = new TextToSpeech(context, status -> { + if (status == TextToSpeech.SUCCESS) { + int result = textToSpeech.setLanguage(Locale.getDefault()); + if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { + LOG.error("TTS returned error: Language not supported."); + } else { + this.isConnected = true; + textToSpeech.setOnUtteranceProgressListener(callback); + } + } else { + LOG.error("TTS returned error: Initialization failed."); + } + }); + } + + public void speak(String text) { + Bundle params = new Bundle(); + // Put the audio stream type into the Bundle + params.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, AudioManager.STREAM_RING); + textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params, "utteranceId"); + } + + public void shutdown() { + if (textToSpeech != null) { + textToSpeech.stop(); + textToSpeech.shutdown(); + } + } + + +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8cbc46f30..602862a4c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2776,4 +2776,6 @@ The amount of minutes the chart shows \'worn\' after each successful heart rate measurement The phone will automatically pick-up incoming phonecalls Automatically answer phone calls + Number of seconds after which the call is automatically picked up + Automatic Answer Delay diff --git a/app/src/main/res/xml/devicesettings_nothing_ear1.xml b/app/src/main/res/xml/devicesettings_nothing_ear1.xml index f32b9c7a8..f0b682585 100644 --- a/app/src/main/res/xml/devicesettings_nothing_ear1.xml +++ b/app/src/main/res/xml/devicesettings_nothing_ear1.xml @@ -1,5 +1,6 @@ - + +