diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 023dcdfc1f..2cb94117c2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -91,6 +91,8 @@
+
+
{
case EntityRule.TYPE_ANSWER:
tvAction.setText(R.string.title_rule_answer);
break;
+ case EntityRule.TYPE_TTS:
+ tvAction.setText(R.string.title_rule_tts);
+ break;
case EntityRule.TYPE_AUTOMATION:
tvAction.setText(R.string.title_rule_automation);
break;
diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java
index e19e86b625..990452a3a8 100644
--- a/app/src/main/java/eu/faircode/email/EntityRule.java
+++ b/app/src/main/java/eu/faircode/email/EntityRule.java
@@ -98,6 +98,7 @@ public class EntityRule {
static final int TYPE_KEYWORD = 11;
static final int TYPE_HIDE = 12;
static final int TYPE_IMPORTANCE = 13;
+ static final int TYPE_TTS = 14;
static final String ACTION_AUTOMATION = BuildConfig.APPLICATION_ID + ".AUTOMATION";
static final String EXTRA_RULE = "rule";
@@ -323,6 +324,8 @@ public class EntityRule {
return onActionCopy(context, message, jaction);
case TYPE_ANSWER:
return onActionAnswer(context, message, jaction);
+ case TYPE_TTS:
+ return onActionTts(context, message, jaction);
case TYPE_AUTOMATION:
return onActionAutomation(context, message, jaction);
default:
@@ -480,6 +483,24 @@ public class EntityRule {
return true;
}
+ private boolean onActionTts(Context context, EntityMessage message, JSONObject jargs) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(context.getString(R.string.title_rule_tts_prefix));
+
+ if (message.from != null && message.from.length > 0)
+ sb.append(' ').append(context.getString(R.string.title_from))
+ .append(' ').append(MessageHelper.formatAddressesShort(message.from));
+
+ if (!TextUtils.isEmpty(message.subject))
+ sb.append(' ').append(context.getString(R.string.title_subject))
+ .append(' ').append(message.subject);
+
+ EntityLog.log(context, "TTS queued language=" + message.language + " text=" + sb.toString());
+ TTSHelper.speak(context, "rule:" + message.id, sb.toString(), message.language);
+
+ return true;
+ }
+
private boolean onActionSnooze(Context context, EntityMessage message, JSONObject jargs) throws JSONException {
int duration = jargs.getInt("duration");
boolean schedule_end = jargs.optBoolean("schedule_end", false);
diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java
index b5213f9c33..a5f79950a4 100644
--- a/app/src/main/java/eu/faircode/email/FragmentRule.java
+++ b/app/src/main/java/eu/faircode/email/FragmentRule.java
@@ -31,6 +31,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
+import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
@@ -133,6 +134,8 @@ public class FragmentRule extends FragmentBase {
private Spinner spAnswer;
private CheckBox cbCc;
+ private Button btnTts;
+
private TextView tvAutomation;
private BottomNavigationView bottom_navigation;
@@ -146,6 +149,7 @@ public class FragmentRule extends FragmentBase {
private Group grpMove;
private Group grpMoveProp;
private Group grpAnswer;
+ private Group grpTts;
private Group grpAutomation;
private ArrayAdapter adapterDay;
@@ -168,6 +172,7 @@ public class FragmentRule extends FragmentBase {
private final static int REQUEST_DELETE = 4;
private final static int REQUEST_SCHEDULE_START = 5;
private final static int REQUEST_SCHEDULE_END = 6;
+ private final static int REQUEST_TTS = 7;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -247,6 +252,7 @@ public class FragmentRule extends FragmentBase {
spAnswer = view.findViewById(R.id.spAnswer);
cbCc = view.findViewById(R.id.cbCc);
+ btnTts = view.findViewById(R.id.btnTts);
tvAutomation = view.findViewById(R.id.tvAutomation);
bottom_navigation = view.findViewById(R.id.bottom_navigation);
@@ -261,6 +267,7 @@ public class FragmentRule extends FragmentBase {
grpMove = view.findViewById(R.id.grpMove);
grpMoveProp = view.findViewById(R.id.grpMoveProp);
grpAnswer = view.findViewById(R.id.grpAnswer);
+ grpTts = view.findViewById(R.id.grpTts);
grpAutomation = view.findViewById(R.id.grpAutomation);
ibSender.setOnClickListener(new View.OnClickListener() {
@@ -391,6 +398,7 @@ public class FragmentRule extends FragmentBase {
actions.add(new Action(EntityRule.TYPE_COPY, getString(R.string.title_rule_copy)));
}
actions.add(new Action(EntityRule.TYPE_ANSWER, getString(R.string.title_rule_answer)));
+ actions.add(new Action(EntityRule.TYPE_TTS, getString(R.string.title_rule_tts)));
actions.add(new Action(EntityRule.TYPE_AUTOMATION, getString(R.string.title_rule_automation)));
adapterAction.addAll(actions);
@@ -454,6 +462,15 @@ public class FragmentRule extends FragmentBase {
spIdent.setOnItemSelectedListener(onItemSelectedListener);
spAnswer.setOnItemSelectedListener(onItemSelectedListener);
+ btnTts.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent tts = new Intent();
+ tts.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
+ startActivityForResult(tts, REQUEST_TTS);
+ }
+ });
+
tvAutomation.setText(getString(R.string.title_rule_automation_hint,
EntityRule.ACTION_AUTOMATION,
TextUtils.join(",", new String[]{
@@ -491,6 +508,7 @@ public class FragmentRule extends FragmentBase {
grpMove.setVisibility(View.GONE);
grpMoveProp.setVisibility(View.GONE);
grpAnswer.setVisibility(View.GONE);
+ grpTts.setVisibility(View.GONE);
grpAutomation.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
@@ -634,6 +652,15 @@ public class FragmentRule extends FragmentBase {
if (resultCode == RESULT_OK)
onScheduleEnd(data.getBundleExtra("args"));
break;
+ case REQUEST_TTS:
+ if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS)
+ ToastEx.makeText(getContext(), android.R.string.ok, Toast.LENGTH_LONG).show();
+ else {
+ Intent tts = new Intent();
+ tts.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
+ startActivity(tts);
+ }
+ break;
}
} catch (Throwable ex) {
Log.e(ex);
@@ -881,6 +908,7 @@ public class FragmentRule extends FragmentBase {
grpMove.setVisibility(type == EntityRule.TYPE_MOVE || type == EntityRule.TYPE_COPY ? View.VISIBLE : View.GONE);
grpMoveProp.setVisibility(type == EntityRule.TYPE_MOVE ? View.VISIBLE : View.GONE);
grpAnswer.setVisibility(type == EntityRule.TYPE_ANSWER ? View.VISIBLE : View.GONE);
+ grpTts.setVisibility(type == EntityRule.TYPE_TTS ? View.VISIBLE : View.GONE);
grpAutomation.setVisibility(type == EntityRule.TYPE_AUTOMATION ? View.VISIBLE : View.GONE);
}
diff --git a/app/src/main/java/eu/faircode/email/TTSHelper.java b/app/src/main/java/eu/faircode/email/TTSHelper.java
new file mode 100644
index 0000000000..5ebf1e05a6
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/TTSHelper.java
@@ -0,0 +1,44 @@
+package eu.faircode.email;
+
+import android.content.Context;
+import android.speech.tts.TextToSpeech;
+
+import java.util.Locale;
+
+public class TTSHelper {
+ private static boolean initialized;
+ private static TextToSpeech instance;
+
+ static void speak(Context context, final String utteranceId, final String text, final String language) {
+ // https://developer.android.com/reference/android/speech/tts/TextToSpeech
+ // https://android-developers.googleblog.com/2009/09/introduction-to-text-to-speech-in.html
+
+ final Runnable speak = new Runnable() {
+ @Override
+ public void run() {
+ if (language != null) {
+ Locale loc = new Locale(language);
+ if (instance.setLanguage(loc) < 0)
+ EntityLog.log(context, "TTS unavailable language=" + loc);
+ }
+
+ instance.speak(text, TextToSpeech.QUEUE_ADD, null, utteranceId);
+ }
+ };
+
+ if (initialized) {
+ speak.run();
+ return;
+ }
+
+ instance = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
+ @Override
+ public void onInit(int status) {
+ initialized = (status == TextToSpeech.SUCCESS);
+ Log.i("TTS status=" + status + " ok=" + initialized);
+ if (initialized)
+ speak.run();
+ }
+ });
+ }
+}
diff --git a/app/src/main/res/layout/fragment_rule.xml b/app/src/main/res/layout/fragment_rule.xml
index f7729b2f6c..6774c85c56 100644
--- a/app/src/main/res/layout/fragment_rule.xml
+++ b/app/src/main/res/layout/fragment_rule.xml
@@ -689,6 +689,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbCc" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/btnTts" />
+
+
Move
Copy (label)
Reply
+ Text to speech
Automation
Edit rule
@@ -1095,6 +1096,9 @@
Matching messages
No matching messages
+ Check setup
+ New message
+
Synchronize
Folders
Messages