18d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath/*
28d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Copyright (C) 2011 The Android Open Source Project
38d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath *
48d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); you may not
58d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * use this file except in compliance with the License. You may obtain a copy of
68d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * the License at
78d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath *
88d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * http://www.apache.org/licenses/LICENSE-2.0
98d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath *
108d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Unless required by applicable law or agreed to in writing, software
118d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
128d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
138d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * License for the specific language governing permissions and limitations under
148d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * the License.
158d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath */
168d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamathpackage android.speech.tts;
178d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
188d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamathimport android.util.Log;
198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
204924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamathimport java.util.Iterator;
2167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamathimport java.util.concurrent.LinkedBlockingQueue;
224924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
234924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamathclass AudioPlaybackHandler {
248d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    private static final String TAG = "TTS.AudioPlaybackHandler";
258d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    private static final boolean DBG = false;
268d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
2767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private final LinkedBlockingQueue<PlaybackQueueItem> mQueue =
2867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            new LinkedBlockingQueue<PlaybackQueueItem>();
294924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    private final Thread mHandlerThread;
304924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
3167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private volatile PlaybackQueueItem mCurrentWorkItem = null;
328d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
334924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    AudioPlaybackHandler() {
344924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        mHandlerThread = new Thread(new MessageLoop(), "TTS.AudioPlaybackThread");
354924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    }
368d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
374924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    public void start() {
384924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        mHandlerThread.start();
398d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    }
408d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
4167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private void stop(PlaybackQueueItem item) {
4267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (item == null) {
434924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath            return;
444924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        }
454924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
46fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak        item.stop(TextToSpeech.STOPPED);
4767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
488d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
4967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public void enqueue(PlaybackQueueItem item) {
5067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        try {
5167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mQueue.put(item);
5267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        } catch (InterruptedException ie) {
5367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // This exception will never be thrown, since we allow our queue
5467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // to be have an unbounded size. put() will therefore never block.
558d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        }
568d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    }
578d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
5867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public void stopForApp(Object callerIdentity) {
5967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (DBG) Log.d(TAG, "Removing all callback items for : " + callerIdentity);
6067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        removeWorkItemsFor(callerIdentity);
61be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath
6267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final PlaybackQueueItem current = mCurrentWorkItem;
63492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        if (current != null && (current.getCallerIdentity() == callerIdentity)) {
64be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath            stop(current);
65be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath        }
664924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    }
674924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
6867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public void stop() {
6967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (DBG) Log.d(TAG, "Stopping all items");
704924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        removeAllMessages();
7140f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath
7267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        stop(mCurrentWorkItem);
734924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    }
744924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
758d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    /**
76c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath     * @return false iff the queue is empty and no queue item is currently
77c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath     *        being handled, true otherwise.
78c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath     */
79c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath    public boolean isSpeaking() {
8067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        return (mQueue.peek() != null) || (mCurrentWorkItem != null);
81c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath    }
82c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath
83c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath    /**
848d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath     * Shut down the audio playback thread.
858d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath     */
8667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public void quit() {
87be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath        removeAllMessages();
8867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        stop(mCurrentWorkItem);
8967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mHandlerThread.interrupt();
904924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    }
914924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
924924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    /*
934924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath     * Atomically clear the queue of all messages.
944924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath     */
9567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private void removeAllMessages() {
964924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        mQueue.clear();
974924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    }
984924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
994924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    /*
1004924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath     * Remove all messages that originate from a given calling app.
1014924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath     */
10267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private void removeWorkItemsFor(Object callerIdentity) {
10367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        Iterator<PlaybackQueueItem> it = mQueue.iterator();
1044924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
1054924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        while (it.hasNext()) {
10667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            final PlaybackQueueItem item = it.next();
10767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            if (item.getCallerIdentity() == callerIdentity) {
1084924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath                it.remove();
1094924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath            }
1104924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        }
1114924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    }
1124924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath
1134924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath    /*
11467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * The MessageLoop is a handler like implementation that
11567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * processes messages from a priority queue.
1164924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath     */
11767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private final class MessageLoop implements Runnable {
1184924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath        @Override
11967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        public void run() {
12067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            while (true) {
12167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                PlaybackQueueItem item = null;
12267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                try {
12367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    item = mQueue.take();
12467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                } catch (InterruptedException ie) {
12567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    if (DBG) Log.d(TAG, "MessageLoop : Shutting down (interrupted)");
12667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    return;
12769bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath                }
128673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath
12967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                // If stop() or stopForApp() are called between mQueue.take()
13067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                // returning and mCurrentWorkItem being set, the current work item
13167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                // will be run anyway.
1328d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
13367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                mCurrentWorkItem = item;
13467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                item.run();
13567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                mCurrentWorkItem = null;
13667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            }
1378d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath        }
1388d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath    }
1398d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath
1408d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath}
141