diff options
| author | Nadav Bar <nadavbar@google.com> | 2021-02-20 06:21:18 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2021-02-20 06:21:18 +0000 |
| commit | e8d124dc20be0a9579980b83ccb6dfd0a3204df3 (patch) | |
| tree | 1a5a04f3b86eae698a24098a2479f04bc31178d5 /core/java | |
| parent | 849587032600a159416035e4978b451969f1d695 (diff) | |
| parent | 50c987fbb884b8c92ec2844e60ea76da2356d1c3 (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.java | 8 | ||||
| -rw-r--r-- | core/java/android/speech/tts/ITextToSpeechManager.aidl | 29 | ||||
| -rw-r--r-- | core/java/android/speech/tts/ITextToSpeechSession.aidl | 33 | ||||
| -rw-r--r-- | core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl | 32 | ||||
| -rw-r--r-- | core/java/android/speech/tts/TextToSpeech.java | 170 |
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; } |
