summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorNadav Bar <nadavbar@google.com>2021-02-20 06:21:18 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2021-02-20 06:21:18 +0000
commite8d124dc20be0a9579980b83ccb6dfd0a3204df3 (patch)
tree1a5a04f3b86eae698a24098a2479f04bc31178d5 /core/java
parent849587032600a159416035e4978b451969f1d695 (diff)
parent50c987fbb884b8c92ec2844e60ea76da2356d1c3 (diff)
Merge "Add a system TextToSpeech implementation that initiates the connection through the system server." into sc-dev
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/content/Context.java8
-rw-r--r--core/java/android/speech/tts/ITextToSpeechManager.aidl29
-rw-r--r--core/java/android/speech/tts/ITextToSpeechSession.aidl33
-rw-r--r--core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl32
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java170
5 files changed, 244 insertions, 28 deletions
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0093b6342da6..d15e53e54cb5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4583,6 +4583,14 @@ public abstract class Context {
public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
/**
+ * Official published name of the (internal) text to speech manager service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech";
+
+ /**
* Official published name of the content capture service.
*
* @hide
diff --git a/core/java/android/speech/tts/ITextToSpeechManager.aidl b/core/java/android/speech/tts/ITextToSpeechManager.aidl
new file mode 100644
index 000000000000..e6b63dff1553
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech.tts;
+
+import android.speech.tts.ITextToSpeechSessionCallback;
+
+/**
+ * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the
+ * specified provider proxied by the system service.
+ *
+ * @hide
+ */
+oneway interface ITextToSpeechManager {
+ void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback);
+}
diff --git a/core/java/android/speech/tts/ITextToSpeechSession.aidl b/core/java/android/speech/tts/ITextToSpeechSession.aidl
new file mode 100644
index 000000000000..b2afeb0d1ba8
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechSession.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech.tts;
+
+/**
+ * TextToSpeech session interface. Allows to control remote TTS service session once connected.
+ *
+ * @see ITextToSpeechManager
+ * @see ITextToSpeechSessionCallback
+ *
+ * {@hide}
+ */
+oneway interface ITextToSpeechSession {
+
+ /**
+ * Disconnects the client from the TTS provider.
+ */
+ void disconnect();
+} \ No newline at end of file
diff --git a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
new file mode 100644
index 000000000000..545622a007f3
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech.tts;
+import android.speech.tts.ITextToSpeechSession;
+
+/**
+ * Callback interface for a session created by {@link ITextToSpeechManager} API.
+ *
+ * @hide
+ */
+oneway interface ITextToSpeechSessionCallback {
+
+ void onConnected(in ITextToSpeechSession session, in IBinder serviceBinder);
+
+ void onDisconnected();
+
+ void onError(in String errorInfo);
+} \ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 7a185382a631..78e5eabb69a8 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -35,6 +35,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
@@ -51,6 +52,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
*
@@ -695,6 +697,8 @@ public class TextToSpeech {
public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
}
+ private static final boolean DEBUG = false;
+
private final Context mContext;
@UnsupportedAppUsage
private Connection mConnectingServiceConnection;
@@ -716,6 +720,9 @@ public class TextToSpeech {
private final Map<CharSequence, Uri> mUtterances;
private final Bundle mParams = new Bundle();
private final TtsEngines mEnginesHelper;
+ private final boolean mIsSystem;
+ @Nullable private final Executor mInitExecutor;
+
@UnsupportedAppUsage
private volatile String mCurrentEngine = null;
@@ -758,8 +765,21 @@ public class TextToSpeech {
*/
public TextToSpeech(Context context, OnInitListener listener, String engine,
String packageName, boolean useFallback) {
+ this(context, /* initExecutor= */ null, listener, engine, packageName,
+ useFallback, /* isSystem= */ true);
+ }
+
+ /**
+ * Used internally to instantiate TextToSpeech objects.
+ *
+ * @hide
+ */
+ private TextToSpeech(Context context, @Nullable Executor initExecutor,
+ OnInitListener initListener, String engine, String packageName, boolean useFallback,
+ boolean isSystem) {
mContext = context;
- mInitListener = listener;
+ mInitExecutor = initExecutor;
+ mInitListener = initListener;
mRequestedEngine = engine;
mUseFallback = useFallback;
@@ -768,6 +788,9 @@ public class TextToSpeech {
mUtteranceProgressListener = null;
mEnginesHelper = new TtsEngines(mContext);
+
+ mIsSystem = isSystem;
+
initTts();
}
@@ -842,10 +865,14 @@ public class TextToSpeech {
}
private boolean connectToEngine(String engine) {
- Connection connection = new Connection();
- Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
- intent.setPackage(engine);
- boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ Connection connection;
+ if (mIsSystem) {
+ connection = new SystemConnection();
+ } else {
+ connection = new DirectConnection();
+ }
+
+ boolean bound = connection.connect(engine);
if (!bound) {
Log.e(TAG, "Failed to bind to " + engine);
return false;
@@ -857,11 +884,19 @@ public class TextToSpeech {
}
private void dispatchOnInit(int result) {
- synchronized (mStartLock) {
- if (mInitListener != null) {
- mInitListener.onInit(result);
- mInitListener = null;
+ Runnable onInitCommand = () -> {
+ synchronized (mStartLock) {
+ if (mInitListener != null) {
+ mInitListener.onInit(result);
+ mInitListener = null;
+ }
}
+ };
+
+ if (mInitExecutor != null) {
+ mInitExecutor.execute(onInitCommand);
+ } else {
+ onInitCommand.run();
}
}
@@ -878,7 +913,7 @@ public class TextToSpeech {
// Special case, we are asked to shutdown connection that did finalize its connection.
synchronized (mStartLock) {
if (mConnectingServiceConnection != null) {
- mContext.unbindService(mConnectingServiceConnection);
+ mConnectingServiceConnection.disconnect();
mConnectingServiceConnection = null;
return;
}
@@ -2127,13 +2162,17 @@ public class TextToSpeech {
return mEnginesHelper.getEngines();
}
- private class Connection implements ServiceConnection {
+ private abstract class Connection implements ServiceConnection {
private ITextToSpeechService mService;
private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
private boolean mEstablished;
+ abstract boolean connect(String engine);
+
+ abstract void disconnect();
+
private final ITextToSpeechCallback.Stub mCallback =
new ITextToSpeechCallback.Stub() {
public void onStop(String utteranceId, boolean isStarted)
@@ -2199,11 +2238,6 @@ public class TextToSpeech {
};
private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
- private final ComponentName mName;
-
- public SetupConnectionAsyncTask(ComponentName name) {
- mName = name;
- }
@Override
protected Integer doInBackground(Void... params) {
@@ -2227,7 +2261,7 @@ public class TextToSpeech {
mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
}
- Log.i(TAG, "Set up connection to " + mName);
+ Log.i(TAG, "Setting up the connection to TTS engine...");
return SUCCESS;
} catch (RemoteException re) {
Log.e(TAG, "Error connecting to service, setCallback() failed");
@@ -2249,11 +2283,11 @@ public class TextToSpeech {
}
@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
synchronized(mStartLock) {
mConnectingServiceConnection = null;
- Log.i(TAG, "Connected to " + name);
+ Log.i(TAG, "Connected to TTS engine");
if (mOnSetupConnectionAsyncTask != null) {
mOnSetupConnectionAsyncTask.cancel(false);
@@ -2263,7 +2297,7 @@ public class TextToSpeech {
mServiceConnection = Connection.this;
mEstablished = false;
- mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
+ mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();
mOnSetupConnectionAsyncTask.execute();
}
}
@@ -2277,7 +2311,7 @@ public class TextToSpeech {
*
* @return true if we cancel mOnSetupConnectionAsyncTask in progress.
*/
- private boolean clearServiceConnection() {
+ protected boolean clearServiceConnection() {
synchronized(mStartLock) {
boolean result = false;
if (mOnSetupConnectionAsyncTask != null) {
@@ -2295,8 +2329,8 @@ public class TextToSpeech {
}
@Override
- public void onServiceDisconnected(ComponentName name) {
- Log.i(TAG, "Asked to disconnect from " + name);
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.i(TAG, "Disconnected from TTS engine");
if (clearServiceConnection()) {
/* We need to protect against a rare case where engine
* dies just after successful connection - and we process onServiceDisconnected
@@ -2308,11 +2342,6 @@ public class TextToSpeech {
}
}
- public void disconnect() {
- mContext.unbindService(this);
- clearServiceConnection();
- }
-
public boolean isEstablished() {
return mService != null && mEstablished;
}
@@ -2342,6 +2371,91 @@ public class TextToSpeech {
}
}
+ // Currently all the clients are routed through the System connection. Direct connection
+ // is left for debugging, testing and benchmarking purposes.
+ // TODO(b/179599071): Remove direct connection once system one is fully tested.
+ private class DirectConnection extends Connection {
+ @Override
+ boolean connect(String engine) {
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ intent.setPackage(engine);
+ return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ void disconnect() {
+ mContext.unbindService(this);
+ clearServiceConnection();
+ }
+ }
+
+ private class SystemConnection extends Connection {
+
+ @Nullable
+ private volatile ITextToSpeechSession mSession;
+
+ @Override
+ boolean connect(String engine) {
+ IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE);
+
+ ITextToSpeechManager manager = ITextToSpeechManager.Stub.asInterface(binder);
+
+ if (manager == null) {
+ Log.e(TAG, "System service is not available!");
+ return false;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Connecting to engine: " + engine);
+ }
+
+ try {
+ manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() {
+ @Override
+ public void onConnected(ITextToSpeechSession session, IBinder serviceBinder) {
+ mSession = session;
+ onServiceConnected(
+ /* componentName= */ null,
+ serviceBinder);
+ }
+
+ @Override
+ public void onDisconnected() {
+ onServiceDisconnected(/* componentName= */ null);
+ }
+
+ @Override
+ public void onError(String errorInfo) {
+ Log.w(TAG, "System TTS connection error: " + errorInfo);
+ // The connection was not established successfully - handle as
+ // disconnection: clear the state and notify the user.
+ onServiceDisconnected(/* componentName= */ null);
+ }
+ });
+
+ return true;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Error communicating with the System Server: ", ex);
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ void disconnect() {
+ ITextToSpeechSession session = mSession;
+
+ if (session != null) {
+ try {
+ session.disconnect();
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Error disconnecting session", ex);
+ }
+
+ clearServiceConnection();
+ }
+ }
+ }
+
private interface Action<R> {
R run(ITextToSpeechService service) throws RemoteException;
}