/*
* Copyright (C) 2011 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.util.Log;
import java.util.Iterator;
import java.util.concurrent.LinkedBlockingQueue;
class AudioPlaybackHandler {
private static final String TAG = "TTS.AudioPlaybackHandler";
private static final boolean DBG = false;
private final LinkedBlockingQueue<PlaybackQueueItem> mQueue =
new LinkedBlockingQueue<PlaybackQueueItem>();
private final Thread mHandlerThread;
private volatile PlaybackQueueItem mCurrentWorkItem = null;
AudioPlaybackHandler() {
mHandlerThread = new Thread(new MessageLoop(), "TTS.AudioPlaybackThread");
}
public void start() {
mHandlerThread.start();
}
private void stop(PlaybackQueueItem item) {
if (item == null) {
return;
}
item.stop(TextToSpeech.STOPPED);
}
public void enqueue(PlaybackQueueItem item) {
try {
mQueue.put(item);
} catch (InterruptedException ie) {
// This exception will never be thrown, since we allow our queue
// to be have an unbounded size. put() will therefore never block.
}
}
public void stopForApp(Object callerIdentity) {
if (DBG) Log.d(TAG, "Removing all callback items for : " + callerIdentity);
removeWorkItemsFor(callerIdentity);
final PlaybackQueueItem current = mCurrentWorkItem;
if (current != null && (current.getCallerIdentity() == callerIdentity)) {
stop(current);
}
}
public void stop() {
if (DBG) Log.d(TAG, "Stopping all items");
removeAllMessages();
stop(mCurrentWorkItem);
}
/**
* @return false iff the queue is empty and no queue item is currently
* being handled, true otherwise.
*/
public boolean isSpeaking() {
return (mQueue.peek() != null) || (mCurrentWorkItem != null);
}
/**
* Shut down the audio playback thread.
*/
public void quit() {
removeAllMessages();
stop(mCurrentWorkItem);
mHandlerThread.interrupt();
}
/*
* Atomically clear the queue of all messages.
*/
private void removeAllMessages() {
mQueue.clear();
}
/*
* Remove all messages that originate from a given calling app.
*/
private void removeWorkItemsFor(Object callerIdentity) {
Iterator<PlaybackQueueItem> it = mQueue.iterator();
while (it.hasNext()) {
final PlaybackQueueItem item = it.next();
if (item.getCallerIdentity() == callerIdentity) {
it.remove();
}
}
}
/*
* The MessageLoop is a handler like implementation that
* processes messages from a priority queue.
*/
private final class MessageLoop implements Runnable {
@Override
public void run() {
while (true) {
PlaybackQueueItem item = null;
try {
item = mQueue.take();
} catch (InterruptedException ie) {
if (DBG) Log.d(TAG, "MessageLoop : Shutting down (interrupted)");
return;
}
// If stop() or stopForApp() are called between mQueue.take()
// returning and mCurrentWorkItem being set, the current work item
// will be run anyway.
mCurrentWorkItem = item;
item.run();
mCurrentWorkItem = null;
}
}
}
}