/* * 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 mQueue = new LinkedBlockingQueue(); 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 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; } } } }