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
This commit is contained in:
Daniele Gobbetti 2024-04-05 19:54:58 +02:00
parent c4747e2e23
commit a37f0c89bb
6 changed files with 158 additions and 21 deletions

View File

@ -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";
}

View File

@ -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<EarSettingsCustomizer> CREATOR = new Creator<EarSettingsCustomizer>() {
@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<EarSettingsCustomizer> CREATOR = new Creator<EarSettingsCustomizer>() {
@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;

View File

@ -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.");
}
}
}

View File

@ -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();
}
}
}

View File

@ -2776,4 +2776,6 @@
<string name="pref_dashboard_widget_today_hr_interval_summary">The amount of minutes the chart shows \'worn\' after each successful heart rate measurement</string>
<string name="pref_auto_reply_calls_summary">The phone will automatically pick-up incoming phonecalls</string>
<string name="pref_auto_reply_calls_title">Automatically answer phone calls</string>
<string name="pref_auto_reply_calls_delay_summary">Number of seconds after which the call is automatically picked up</string>
<string name="pref_auto_reply_calls_delay_title">Automatic Answer Delay</string>
</resources>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<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"
@ -20,4 +21,12 @@
android:key="pref_auto_reply_phonecall"
android:summary="@string/pref_auto_reply_calls_summary"
android:title="@string/pref_auto_reply_calls_title"/>
<EditTextPreference
android:digits="123"
android:enabled="false"
android:inputType="numberDecimal"
android:key="pref_auto_reply_phonecall_delay"
android:summary="@string/pref_auto_reply_calls_delay_summary"
android:title="@string/pref_auto_reply_calls_delay_title"
app:defaultValue="15" />
</androidx.preference.PreferenceScreen>