From 9b6017ac4e0da4b530245616b120a188fc544ed2 Mon Sep 17 00:00:00 2001
From: Josh Ziegler
Date: Fri, 10 Jan 2020 23:08:01 -0500
Subject: [PATCH] Store Android Context directly in TTSManager
---
.../spokestack/spokestack/SpeechOutput.java | 6 +-
.../spokestack/tts/SpokestackTTSOutput.java | 35 ++++++------
.../spokestack/spokestack/tts/TTSManager.java | 55 ++++++++++++++-----
.../tts/SpokestackTTSOutputTest.java | 3 +-
.../spokestack/tts/TTSManagerTest.java | 14 ++---
5 files changed, 71 insertions(+), 42 deletions(-)
diff --git a/src/main/java/io/spokestack/spokestack/SpeechOutput.java b/src/main/java/io/spokestack/spokestack/SpeechOutput.java
index abbdb62..438de9d 100644
--- a/src/main/java/io/spokestack/spokestack/SpeechOutput.java
+++ b/src/main/java/io/spokestack/spokestack/SpeechOutput.java
@@ -52,9 +52,9 @@ public void eventReceived(TTSEvent event) {
public abstract void audioReceived(AudioResponse response);
/**
- * Sets the output's application context.
+ * Sets the output's Android context.
*
- * @param appContext The application context.
+ * @param androidContext The Android context.
*/
- public abstract void setAppContext(Context appContext);
+ public abstract void setAndroidContext(Context androidContext);
}
diff --git a/src/main/java/io/spokestack/spokestack/tts/SpokestackTTSOutput.java b/src/main/java/io/spokestack/spokestack/tts/SpokestackTTSOutput.java
index c624220..b7360d1 100644
--- a/src/main/java/io/spokestack/spokestack/tts/SpokestackTTSOutput.java
+++ b/src/main/java/io/spokestack/spokestack/tts/SpokestackTTSOutput.java
@@ -4,8 +4,6 @@
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
@@ -29,9 +27,9 @@
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
-import io.spokestack.spokestack.util.TaskHandler;
import io.spokestack.spokestack.SpeechConfig;
import io.spokestack.spokestack.SpeechOutput;
+import io.spokestack.spokestack.util.TaskHandler;
import org.jetbrains.annotations.NotNull;
/**
@@ -52,6 +50,14 @@
* playback is desired, consider adding a {@link TTSListener} to the {@code
* TTSManager} and managing audio via its methods.
*
+ *
+ *
+ * Additionally, this component requires an Android {@code Context} to be
+ * attached to the manager that has created it. If the manager is meant to
+ * persist across different {@code Activity}s, the {@code Context} used must
+ * either be the application context, or it must be re-set on the
+ * manager when the Activity context changes.
+ *
*/
public class SpokestackTTSOutput extends SpeechOutput
implements Player.EventListener,
@@ -60,7 +66,6 @@ public class SpokestackTTSOutput extends SpeechOutput
private final int contentType;
private final int usage;
private TaskHandler taskHandler;
- private Handler playerHandler;
private PlayerFactory playerFactory;
private ExoPlayer mediaPlayer;
private ConcatenatingMediaSource mediaSource;
@@ -74,9 +79,11 @@ public class SpokestackTTSOutput extends SpeechOutput
* configuration properties, but this constructor is required
* for participation in the TTS subsystem.
*/
- public SpokestackTTSOutput(@Nullable SpeechConfig config) {
- // the constant value here is the same in ExoPlayer as in
- // AudioAttributesCompat in versions 2.11.0 and v1.1.0, respectively
+ @SuppressWarnings("unused")
+ public SpokestackTTSOutput(SpeechConfig config) {
+ // this constant's value (1) is the same in ExoPlayer as in
+ // AudioAttributesCompat in versions 2.11.0 and v1.1.0, respectively,
+ // which is why it's used as a stand-in for both in this class
this.contentType = C.CONTENT_TYPE_SPEECH;
this.playerState = new PlayerState();
@@ -93,13 +100,6 @@ public SpokestackTTSOutput(@Nullable SpeechConfig config) {
this.mediaSource = new ConcatenatingMediaSource();
}
- private void runOnPlayerThread(Runnable task) {
- if (playerHandler == null) {
- playerHandler = new Handler(Looper.getMainLooper());
- }
- playerHandler.post(task);
- }
-
/**
* Creates a new instance of the audio player with a custom player factory
* and task handler. Used for testing.
@@ -115,8 +115,8 @@ private void runOnPlayerThread(Runnable task) {
}
@Override
- public void setAppContext(@NonNull Context context) {
- this.appContext = context;
+ public void setAndroidContext(@NonNull Context androidContext) {
+ this.appContext = androidContext;
}
/**
@@ -379,6 +379,9 @@ ExoPlayer createPlayer(int usage, int contentType,
@Override public void onTimelineChanged(Timeline timeline, int reason) { }
@Override
+ // it's deprecated, but it's still a default method, so we have to
+ // implement it for older versions of Android
+ @SuppressWarnings("deprecation")
public void onTimelineChanged(Timeline timeline,
@Nullable Object manifest, int reason) { }
diff --git a/src/main/java/io/spokestack/spokestack/tts/TTSManager.java b/src/main/java/io/spokestack/spokestack/tts/TTSManager.java
index 4badb0c..da19209 100644
--- a/src/main/java/io/spokestack/spokestack/tts/TTSManager.java
+++ b/src/main/java/io/spokestack/spokestack/tts/TTSManager.java
@@ -32,7 +32,11 @@
* TTSManager ttsManager = new TTSManager.Builder()
* .setTTSServiceClass("io.spokestack.spokestack.tts.SpokestackTTSService")
* .setOutputClass("io.spokestack.spokestack.tts.SpokestackTTSOutput")
- * .setProperty("spokestack-key", "f854fbf30a5f40c189ecb1b38bc78059")
+ * .setProperty("spokestack-key", "f0bc990c-e9db-4a0c-a2b1-6a6395a3d97e")
+ * .setProperty(
+ * "spokestack-secret",
+ * "5BD5483F573D691A15CFA493C1782F451D4BD666E39A9E7B2EBE287E6A72C6B6")
+ * .setAndroidContext(getApplicationContext())
* .setLifecycle(getLifecycle())
* .build();
* }
@@ -58,8 +62,8 @@ public final class TTSManager implements AutoCloseable {
private final List listeners = new ArrayList<>();
private TTSService ttsService;
private SpeechOutput output;
- private Context context;
private Lifecycle lifecycle;
+ private Context appContext;
/**
* Get the current TTS service.
@@ -89,9 +93,9 @@ private TTSManager(Builder builder) throws Exception {
this.ttsServiceClass = builder.ttsServiceClass;
this.outputClass = builder.outputClass;
this.config = builder.config;
- this.context = builder.context.getApplicationContext();
this.lifecycle = builder.lifecycle;
this.listeners.addAll(builder.listeners);
+ this.appContext = builder.appContext;
prepare();
}
@@ -123,6 +127,22 @@ public void registerLifecycle(@NonNull Lifecycle newLifecycle) {
}
}
+ /**
+ * Sets the android context for the TTS Subsystem. Some components may
+ * require an application context instead of an activity context;
+ * see individual component documentation for details.
+ *
+ * @param androidContext the android context for the TTS subsystem.
+ *
+ * @see io.spokestack.spokestack.tts.SpokestackTTSOutput
+ */
+ public void setAndroidContext(Context androidContext) {
+ this.appContext = androidContext;
+ if (this.output != null) {
+ this.output.setAndroidContext(androidContext);
+ }
+ }
+
@Override
public void close() {
release();
@@ -149,8 +169,8 @@ public void prepare() throws Exception {
}
if (this.outputClass != null && this.output == null) {
this.output = createComponent(this.outputClass, SpeechOutput.class);
+ this.output.setAndroidContext(appContext);
this.ttsService.addListener(this.output);
- this.output.setAppContext(this.context);
if (this.lifecycle != null) {
this.registerLifecycle(this.lifecycle);
}
@@ -205,22 +225,15 @@ private void raiseError(Throwable e) {
public static final class Builder {
private String ttsServiceClass;
private String outputClass;
- private Context context;
+ private Context appContext;
private Lifecycle lifecycle;
private SpeechConfig config = new SpeechConfig();
private List listeners = new ArrayList<>();
/**
* Initializes a new builder with no default configuration.
- *
- * @param appContext The context for the TTS manager. Since the manager
- * is meant to cross activity boundaries, this should
- * be the application context rather than an activity
- * context.
*/
- public Builder(Context appContext) {
- this.context = appContext;
- }
+ public Builder() { }
/**
* Sets the class name of the external TTS service component.
@@ -267,11 +280,25 @@ public Builder setProperty(String key, Object value) {
return this;
}
+ /**
+ * Sets the Android context for the pipeline. Some components may
+ * require an Application context instead of an Activity context;
+ * see individual component documentation for details.
+ *
+ * @param androidContext The Android context for the pipeline.
+ * @return this
+ * @see io.spokestack.spokestack.tts.SpokestackTTSOutput
+ */
+ public Builder setAndroidContext(Context androidContext) {
+ this.appContext = androidContext;
+ return this;
+ }
+
/**
* Sets the manager's current lifecycle.
*
* @param curLifecycle The lifecycle dispatching events to this TTS
- * manager.
+ * manager.
* @return this
*/
public Builder setLifecycle(Lifecycle curLifecycle) {
diff --git a/src/test/java/io/spokestack/spokestack/tts/SpokestackTTSOutputTest.java b/src/test/java/io/spokestack/spokestack/tts/SpokestackTTSOutputTest.java
index 02c3195..3a17376 100644
--- a/src/test/java/io/spokestack/spokestack/tts/SpokestackTTSOutputTest.java
+++ b/src/test/java/io/spokestack/spokestack/tts/SpokestackTTSOutputTest.java
@@ -74,7 +74,7 @@ public void testConstruction() {
public void testResourceManagement() {
SpokestackTTSOutput ttsOutput =
new SpokestackTTSOutput(null, factory);
- ttsOutput.setAppContext(mockContext);
+ ttsOutput.setAndroidContext(mockContext);
lifecycleRegistry.addObserver(ttsOutput);
ttsOutput.prepare();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
@@ -197,7 +197,6 @@ private SpokestackTTSOutput spiedOutput() {
.when(ttsOutput).createMediaSource(any());
doReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
.when(ttsOutput).requestFocus();
- ttsOutput.setAppContext(mockContext);
return ttsOutput;
}
diff --git a/src/test/java/io/spokestack/spokestack/tts/TTSManagerTest.java b/src/test/java/io/spokestack/spokestack/tts/TTSManagerTest.java
index c002a4b..859edc2 100644
--- a/src/test/java/io/spokestack/spokestack/tts/TTSManagerTest.java
+++ b/src/test/java/io/spokestack/spokestack/tts/TTSManagerTest.java
@@ -42,13 +42,13 @@ public void before() {
public void testBuilder() throws Exception {
// invalid TTS service
assertThrows(ClassNotFoundException.class,
- () -> new TTSManager.Builder(context)
+ () -> new TTSManager.Builder()
.setTTSServiceClass("invalid")
.build());
// invalid output component
assertThrows(ClassNotFoundException.class,
- () -> new TTSManager.Builder(context)
+ () -> new TTSManager.Builder()
.setTTSServiceClass("io.spokestack.spokestack.tts.SpokestackTTSService")
.setProperty("spokestack-key", "test")
.setProperty("spokestack-secret", "test")
@@ -60,11 +60,12 @@ public void testBuilder() throws Exception {
LifecycleRegistry lifecycleRegistry =
new LifecycleRegistry(mock(LifecycleOwner.class));
- TTSManager manager = new TTSManager.Builder(context)
+ TTSManager manager = new TTSManager.Builder()
.setTTSServiceClass("io.spokestack.spokestack.tts.TTSManagerTest$Input")
.setOutputClass("io.spokestack.spokestack.tts.TTSManagerTest$Output")
.setProperty("spokestack-key", "test")
.setConfig(new SpeechConfig())
+ .setAndroidContext(context)
.setLifecycle(lifecycleRegistry)
.addTTSListener(this)
.build();
@@ -138,8 +139,8 @@ public void close() {
public static class Output extends SpeechOutput
implements DefaultLifecycleObserver {
- public Output(SpeechConfig config) {
- }
+ @SuppressWarnings("unused")
+ public Output(SpeechConfig config) { }
@Override
public void onResume(@NotNull LifecycleOwner owner) {
@@ -155,8 +156,7 @@ public void audioReceived(AudioResponse response) {
}
@Override
- public void setAppContext(Context appContext) {
- }
+ public void setAndroidContext(Context appContext) { }
@Override
public void close() {