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  */
16 package android.speech.tts;
17 
18 import android.util.Log;
19 
20 import java.util.Iterator;
21 import java.util.concurrent.LinkedBlockingQueue;
22 
23 class 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 
AudioPlaybackHandler()33     AudioPlaybackHandler() {
34         mHandlerThread = new Thread(new MessageLoop(), "TTS.AudioPlaybackThread");
35     }
36 
start()37     public void start() {
38         mHandlerThread.start();
39     }
40 
stop(PlaybackQueueItem item)41     private void stop(PlaybackQueueItem item) {
42         if (item == null) {
43             return;
44         }
45 
46         item.stop(TextToSpeech.STOPPED);
47     }
48 
enqueue(PlaybackQueueItem item)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 
stopForApp(Object callerIdentity)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 
stop()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      */
isSpeaking()79     public boolean isSpeaking() {
80         return (mQueue.peek() != null) || (mCurrentWorkItem != null);
81     }
82 
83     /**
84      * Shut down the audio playback thread.
85      */
quit()86     public void quit() {
87         removeAllMessages();
88         stop(mCurrentWorkItem);
89         mHandlerThread.interrupt();
90     }
91 
92     /*
93      * Atomically clear the queue of all messages.
94      */
removeAllMessages()95     private void removeAllMessages() {
96         mQueue.clear();
97     }
98 
99     /*
100      * Remove all messages that originate from a given calling app.
101      */
removeWorkItemsFor(Object callerIdentity)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
run()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