Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 60dbdc5f authored by Nadav Bar's avatar Nadav Bar Committed by Automerger Merge Worker
Browse files

Merge "Add a system TextToSpeech implementation that initiates the connection...

Merge "Add a system TextToSpeech implementation that initiates the connection through the system server." into sc-dev am: e8d124dc

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13612148

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I7e300a3c83c24959eca5c0530ceb43077373beb5
parents 13e44548 e8d124dc
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -4582,6 +4582,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.
     *
+29 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+33 −0
Original line number Diff line number Diff line
/*
 * 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
+32 −0
Original line number Diff line number Diff line
/*
 * 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
+142 −28
Original line number Diff line number Diff line
@@ -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,12 +884,20 @@ public class TextToSpeech {
    }

    private void dispatchOnInit(int result) {
        Runnable onInitCommand = () -> {
            synchronized (mStartLock) {
                if (mInitListener != null) {
                    mInitListener.onInit(result);
                    mInitListener = null;
                }
            }
        };

        if (mInitExecutor != null) {
            mInitExecutor.execute(onInitCommand);
        } else {
            onInitCommand.run();
        }
    }

    private IBinder getCallerIdentity() {
@@ -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;
    }
Loading