1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.speech.tts;
17
18import android.util.Log;
19
20import java.util.Iterator;
21import java.util.concurrent.LinkedBlockingQueue;
22
23class AudioPlaybackHandler {
24    private static final String TAG = "TTS.AudioPlaybackHandler";
25    private static final boolean DBG = false;
26
27    private final LinkedBlockingQueue<PlaybackQueueItem> mQueue =
28            new LinkedBlockingQueue<PlaybackQueueItem>();
29    private final Thread mHandlerThread;
30
31    private volatile PlaybackQueueItem mCurrentWorkItem = null;
32
33    AudioPlaybackHandler() {
34        mHandlerThread = new Thread(new MessageLoop(), "TTS.AudioPlaybackThread");
35    }
36
37    public void start() {
38        mHandlerThread.start();
39    }
40
41    private void stop(PlaybackQueueItem item) {
42        if (item == null) {
43            return;
44        }
45
46        item.stop(TextToSpeech.STOPPED);
47    }
48
49    public void enqueue(PlaybackQueueItem item) {
50        try {
51            mQueue.put(item);
52        } catch (InterruptedException ie) {
53            // This exception will never be thrown, since we allow our queue
54            // to be have an unbounded size. put() will therefore never block.
55        }
56    }
57
58    public void stopForApp(Object callerIdentity) {
59        if (DBG) Log.d(TAG, "Removing all callback items for : " + callerIdentity);
60        removeWorkItemsFor(callerIdentity);
61
62        final PlaybackQueueItem current = mCurrentWorkItem;
63        if (current != null && (current.getCallerIdentity() == callerIdentity)) {
64            stop(current);
65        }
66    }
67
68    public void stop() {
69        if (DBG) Log.d(TAG, "Stopping all items");
70        removeAllMessages();
71
72        stop(mCurrentWorkItem);
73    }
74
75    /**
76     * @return false iff the queue is empty and no queue item is currently
77     *        being handled, true otherwise.
78     */
79    public boolean isSpeaking() {
80        return (mQueue.peek() != null) || (mCurrentWorkItem != null);
81    }
82
83    /**
84     * Shut down the audio playback thread.
85     */
86    public void quit() {
87        removeAllMessages();
88        stop(mCurrentWorkItem);
89        mHandlerThread.interrupt();
90    }
91
92    /*
93     * Atomically clear the queue of all messages.
94     */
95    private void removeAllMessages() {
96        mQueue.clear();
97    }
98
99    /*
100     * Remove all messages that originate from a given calling app.
101     */
102    private void removeWorkItemsFor(Object callerIdentity) {
103        Iterator<PlaybackQueueItem> it = mQueue.iterator();
104
105        while (it.hasNext()) {
106            final PlaybackQueueItem item = it.next();
107            if (item.getCallerIdentity() == callerIdentity) {
108                it.remove();
109            }
110        }
111    }
112
113    /*
114     * The MessageLoop is a handler like implementation that
115     * processes messages from a priority queue.
116     */
117    private final class MessageLoop implements Runnable {
118        @Override
119        public void run() {
120            while (true) {
121                PlaybackQueueItem item = null;
122                try {
123                    item = mQueue.take();
124                } catch (InterruptedException ie) {
125                    if (DBG) Log.d(TAG, "MessageLoop : Shutting down (interrupted)");
126                    return;
127                }
128
129                // If stop() or stopForApp() are called between mQueue.take()
130                // returning and mCurrentWorkItem being set, the current work item
131                // will be run anyway.
132
133                mCurrentWorkItem = item;
134                item.run();
135                mCurrentWorkItem = null;
136            }
137        }
138    }
139
140}
141