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 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(false); 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