blob: bbcb9ba6c0e703d58f950e021f67d6086768141a [file] [log] [blame]
/*
* Copyright (C) 2010 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 com.android.voicedialer;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothHeadset;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.Vibrator;
import android.speech.tts.TextToSpeech;
import android.util.Config;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
/**
* TODO: get rid of the anonymous classes
*
* This class is the user interface of the BluetoothVoiceDialer application.
* It begins in the INITIALIZING state.
*
* INITIALIZING :
* This transitions out on events from TTS and the BluetoothHeadset
* once TTS initialized and SCO channel set up:
* * prompt the user "speak now"
* * transition to the SPEAKING_GREETING state
*
* SPEAKING_GREETING:
* This transitions out only on events from TTS or the fallback runnable
* once the greeting utterance completes:
* * begin listening for the command using the {@link CommandRecognizerEngine}
* * transition to the WAITING_FOR_COMMAND state
*
* WAITING_FOR_COMMAND :
* This transitions out only on events from the recognizer
* on RecognitionFailure or RecognitionError:
* * begin speaking "try again."
* * remain in state SPEAKING_TRY_AGAIN
* on RecognitionSuccess:
* single result:
* * begin speaking the sentence describing the intent
* * transition to the SPEAKING_CHOSEN_ACTION
* multiple results:
* * begin speaking each of the choices in order
* * transition to the SPEAKING_CHOICES state
*
* SPEAKING_TRY_AGAIN:
* This transitions out only on events from TTS or the fallback runnable
* once the try again utterance completes:
* * begin listening for the command using the {@link CommandRecognizerEngine}
* * transition to the LISTENING_FOR_COMMAND state
*
* SPEAKING_CHOSEN_ACTION:
* This transitions out only on events from TTS or the fallback runnable
* once the utterance completes:
* * dispatch the intent that was chosen
* * transition to the EXITING state
* * finish the activity
*
* SPEAKING_CHOICES:
* This transitions out only on events from TTS or the fallback runnable
* once the utterance completes:
* * begin listening for the user's choice using the
* {@link PhoneTypeChoiceRecognizerEngine}
* * transition to the WAITING_FOR_CHOICE state.
*
* WAITING_FOR_CHOICE:
* This transitions out only on events from the recognizer
* on RecognitionFailure or RecognitionError:
* * begin speaking the "invalid choice" message, along with the list
* of choices
* * transition to the SPEAKING_CHOICES state
* on RecognitionSuccess:
* if the result is "try again", prompt the user to say a command, begin
* listening for the command, and transition back to the WAITING_FOR_COMMAND
* state.
* if the result is "exit", then being speaking the "goodbye" message and
* transition to the SPEAKING_GOODBYE state.
* if the result is a valid choice, begin speaking the action chosen,initiate
* the command the user has choose and exit.
* if not a valid choice, speak the "invalid choice" message, begin
* speaking the choices in order again, transition to the
* SPEAKING_CHOICES
*
* SPEAKING_GOODBYE:
* This transitions out only on events from TTS or the fallback runnable
* after a time out, finish the activity.
*
*/
public class BluetoothVoiceDialerActivity extends Activity {
private static final String TAG = "VoiceDialerActivity";
private static final String MICROPHONE_EXTRA = "microphone";
private static final String CONTACTS_EXTRA = "contacts";
private static final String SPEAK_NOW_UTTERANCE = "speak_now";
private static final String TRY_AGAIN_UTTERANCE = "try_again";
private static final String CHOSEN_ACTION_UTTERANCE = "chose_action";
private static final String GOODBYE_UTTERANCE = "goodbye";
private static final String CHOICES_UTTERANCE = "choices";
private static final int FIRST_UTTERANCE_DELAY = 300;
private static final int MAX_TTS_DELAY = 6000;
private static final int SAMPLE_RATE = 8000;
private static final int INITIALIZING = 0;
private static final int SPEAKING_GREETING = 1;
private static final int WAITING_FOR_COMMAND = 2;
private static final int SPEAKING_TRY_AGAIN = 3;
private static final int SPEAKING_CHOICES = 4;
private static final int WAITING_FOR_CHOICE = 5;
private static final int SPEAKING_CHOSEN_ACTION = 6;
private static final int SPEAKING_GOODBYE = 7;
private static final int EXITING = 8;
private static final CommandRecognizerEngine mCommandEngine =
new CommandRecognizerEngine();
private static final PhoneTypeChoiceRecognizerEngine mPhoneTypeChoiceEngine =
new PhoneTypeChoiceRecognizerEngine();
private CommandRecognizerClient mCommandClient;
private ChoiceRecognizerClient mChoiceClient;
private ToneGenerator mToneGenerator;
private Handler mHandler;
private Thread mRecognizerThread = null;
private AudioManager mAudioManager;
private BluetoothHeadset mBluetoothHeadset;
private TextToSpeech mTts;
private HashMap<String, String> mTtsParams;
private VoiceDialerBroadcastReceiver mReceiver;
private int mBluetoothAudioState;
private boolean mWaitingForTts;
private boolean mWaitingForScoConnection;
private Intent[] mAvailableChoices;
private Intent mChosenAction;
private int mBluetoothVoiceVolume;
private int mState;
private AlertDialog mAlertDialog;
private Runnable mFallbackRunnable;
private WakeLock mWakeLock;
@Override
protected void onCreate(Bundle icicle) {
if (Config.LOGD) Log.d(TAG, "onCreate");
super.onCreate(icicle);
mHandler = new Handler();
mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
mToneGenerator = new ToneGenerator(AudioManager.STREAM_RING,
ToneGenerator.MAX_VOLUME);
}
protected void onStart() {
if (Config.LOGD) Log.d(TAG, "onStart " + getIntent());
super.onStart();
acquireWakeLock(this);
mState = INITIALIZING;
mChosenAction = null;
mAudioManager.requestAudioFocus(
null, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
// set this flag so this activity will stay in front of the keyguard
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
getWindow().addFlags(flags);
// open main window
setTheme(android.R.style.Theme_Dialog);
setTitle(R.string.bluetooth_title);
setContentView(R.layout.voice_dialing);
findViewById(R.id.microphone_view).setVisibility(View.INVISIBLE);
findViewById(R.id.retry_view).setVisibility(View.INVISIBLE);
findViewById(R.id.microphone_loading_view).setVisibility(View.VISIBLE);
if (RecognizerLogger.isEnabled(this)) {
((TextView) findViewById(R.id.substate)).setText(R.string.logging_enabled);
}
// Get handle to BluetoothHeadset object
IntentFilter audioStateFilter;
audioStateFilter = new IntentFilter();
audioStateFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
mReceiver = new VoiceDialerBroadcastReceiver();
registerReceiver(mReceiver, audioStateFilter);
mCommandEngine.setContactsFile(newFile(getArg(CONTACTS_EXTRA)));
mCommandEngine.setMinimizeResults(true);
mCommandEngine.setAllowOpenEntries(false);
mCommandClient = new CommandRecognizerClient();
mChoiceClient = new ChoiceRecognizerClient();
mBluetoothAudioState = BluetoothHeadset.STATE_ERROR;
if (BluetoothHeadset.isBluetoothVoiceDialingEnabled(this)) {
// we can't start recognizing until we get connected to the BluetoothHeadset
// and have an connected audio state. We will listen for these
// states to change.
mWaitingForScoConnection = true;
mBluetoothHeadset = new BluetoothHeadset(this,
mBluetoothHeadsetServiceListener);
// initialize the text to speech system
mWaitingForTts = true;
mTts = new TextToSpeech(this, new TtsInitListener());
mTtsParams = new HashMap<String, String>();
mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_BLUETOOTH_SCO));
} else {
// bluetooth voice dialing is disabled, just exit
finish();
}
}
class ErrorRunnable implements Runnable {
private int mErrorMsg;
public ErrorRunnable(int errorMsg) {
mErrorMsg = errorMsg;
}
public void run() {
// put up an error and exit
mHandler.removeCallbacks(mMicFlasher);
((TextView)findViewById(R.id.state)).setText(R.string.failure);
((TextView)findViewById(R.id.substate)).setText(mErrorMsg);
((TextView)findViewById(R.id.substate)).setText(
R.string.headset_connection_lost);
findViewById(R.id.microphone_view).setVisibility(View.INVISIBLE);
findViewById(R.id.retry_view).setVisibility(View.VISIBLE);
playSound(ToneGenerator.TONE_PROP_NACK);
}
}
class FallbackRunnable implements Runnable {
public void run() {
Log.e(TAG, "utterance completion not delivered, using fallback");
// This runnable is intended as a fallback to transition to
// the next state is for some reason we never get a
// TTS utterance completion. It will behave just the same
// as if we had received utterance completion.
onSpeechCompletion();
}
}
class GreetingRunnable implements Runnable {
public void run() {
mState = SPEAKING_GREETING;
mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
SPEAK_NOW_UTTERANCE);
mTts.speak(getString(R.string.speak_now_tts),
TextToSpeech.QUEUE_FLUSH,
mTtsParams);
// Normally, the we will begin listening for the command after the
// utterance completes. As a fallback in case the utterance
// does not complete, post a delayed runnable to fire
// the intent.
mFallbackRunnable = new FallbackRunnable();
mHandler.postDelayed(mFallbackRunnable, MAX_TTS_DELAY);
}
}
class TtsInitListener implements TextToSpeech.OnInitListener {
public void onInit(int status) {
// status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
if (Config.LOGD) Log.d(TAG, "onInit for tts");
if (status != TextToSpeech.SUCCESS) {
// Initialization failed.
Log.e(TAG, "Could not initialize TextToSpeech.");
mHandler.post(new ErrorRunnable(R.string.recognition_error));
exitActivity();
return;
}
if (mTts == null) {
Log.e(TAG, "null tts");
mHandler.post(new ErrorRunnable(R.string.recognition_error));
exitActivity();
return;
}
// The TTS engine has been successfully initialized.
mWaitingForTts = false;
mTts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener());
// TTS over bluetooth is really loud,
// store the current volume away, and then turn it down.
// we will restore it in onStop.
// Limit volume to -18dB. Stream volume range represents approximately 50dB
// (See AudioSystem.cpp linearToLog()) so the number of steps corresponding
// to 18dB is 18 / (50 / maxSteps).
mBluetoothVoiceVolume = mAudioManager.getStreamVolume(
AudioManager.STREAM_BLUETOOTH_SCO);
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_BLUETOOTH_SCO);
int volume = maxVolume - ((18 / (50/maxVolume)) + 1);
if (mBluetoothVoiceVolume > volume) {
mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, 0);
}
if (mWaitingForScoConnection) {
// the bluetooth connection is not up yet, still waiting.
} else {
// we now have SCO connection and TTS, so we can start.
mHandler.postDelayed(new GreetingRunnable(), FIRST_UTTERANCE_DELAY);
}
}
}
class OnUtteranceCompletedListener
implements TextToSpeech.OnUtteranceCompletedListener {
public void onUtteranceCompleted(String utteranceId) {
Log.d(TAG, "onUtteranceCompleted " + utteranceId);
// since the utterance has completed, we no longer need the fallback.
mHandler.removeCallbacks(mFallbackRunnable);
mFallbackRunnable = null;
mHandler.post(new Runnable() {
public void run() {
onSpeechCompletion();
}
});
}
}
private void onSpeechCompletion() {
if (mState == SPEAKING_GREETING || mState == SPEAKING_TRY_AGAIN) {
listenForCommand();
} else if (mState == SPEAKING_CHOICES) {
listenForChoice();
} else if (mState == SPEAKING_GOODBYE) {
mState = EXITING;
finish();
} else if (mState == SPEAKING_CHOSEN_ACTION) {
mState = EXITING;
startActivityHelp(mChosenAction);
finish();
}
}
private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener =
new BluetoothHeadset.ServiceListener() {
public void onServiceConnected() {
if (mBluetoothHeadset != null &&
mBluetoothHeadset.getState() == BluetoothHeadset.STATE_CONNECTED) {
mBluetoothHeadset.startVoiceRecognition();
}
if (Config.LOGD) Log.d(TAG, "onServiceConnected");
}
public void onServiceDisconnected() {}
};
private class VoiceDialerBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
mBluetoothAudioState = intent.getIntExtra(
BluetoothHeadset.EXTRA_AUDIO_STATE,
BluetoothHeadset.STATE_ERROR);
if (Config.LOGD) Log.d(TAG, "HEADSET AUDIO_STATE_CHANGED -> " +
mBluetoothAudioState);
if (mBluetoothAudioState == BluetoothHeadset.AUDIO_STATE_CONNECTED &&
mWaitingForScoConnection) {
// SCO channel has just become available.
mWaitingForScoConnection = false;
if (mWaitingForTts) {
// still waiting for the TTS to be set up.
} else {
// we now have SCO connection and TTS, so we can start.
mHandler.postDelayed(new GreetingRunnable(), FIRST_UTTERANCE_DELAY);
}
} else {
if (!mWaitingForScoConnection) {
// apparently our connection to the headset has dropped.
// we won't be able to continue voicedialing.
if (Config.LOGD) Log.d(TAG, "lost sco connection");
mHandler.post(new ErrorRunnable(
R.string.headset_connection_lost));
exitActivity();
}
}
}
}
}
private class CommandRecognizerClient implements RecognizerClient {
/**
* Called by the {@link RecognizerEngine} when the microphone is started.
*/
public void onMicrophoneStart(InputStream mic) {
if (Config.LOGD) Log.d(TAG, "onMicrophoneStart");
mHandler.post(new Runnable() {
public void run() {
findViewById(R.id.retry_view).setVisibility(View.INVISIBLE);
findViewById(R.id.microphone_loading_view).setVisibility(
View.INVISIBLE);
((TextView)findViewById(R.id.state)).setText(R.string.listening);
mHandler.post(mMicFlasher);
}
});
}
/**
* Called by the {@link RecognizerEngine} if the recognizer fails.
*/
public void onRecognitionFailure(final String msg) {
if (Config.LOGD) Log.d(TAG, "onRecognitionFailure " + msg);
// we had zero results. Just try again.
askToTryAgain();
}
/**
* Called by the {@link RecognizerEngine} on an internal error.
*/
public void onRecognitionError(final String msg) {
if (Config.LOGD) Log.d(TAG, "onRecognitionError " + msg);
mHandler.post(new ErrorRunnable(R.string.recognition_error));
exitActivity();
}
private void askToTryAgain() {
// get work off UAPI thread
mHandler.post(new Runnable() {
public void run() {
if (mAlertDialog != null) {
mAlertDialog.dismiss();
}
mState = SPEAKING_TRY_AGAIN;
mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
TRY_AGAIN_UTTERANCE);
mTts.speak(getString(R.string.no_results_tts),
TextToSpeech.QUEUE_FLUSH,
mTtsParams);
mHandler.removeCallbacks(mMicFlasher);
((TextView)findViewById(R.id.state)).setText(R.string.please_try_again);
findViewById(R.id.state).setVisibility(View.VISIBLE);
findViewById(R.id.microphone_view).setVisibility(View.INVISIBLE);
findViewById(R.id.retry_view).setVisibility(View.VISIBLE);
// don't listen for command yet, wait for the utterance to complete.
}
});
}
/**
* Called by the {@link RecognizerEngine} when is succeeds. If there is
* only one item, then the Intent is dispatched immediately.
* If there are more, then an AlertDialog is displayed and the user is
* prompted to select.
* @param intents a list of Intents corresponding to the sentences.
*/
public void onRecognitionSuccess(final Intent[] intents) {
if (Config.LOGD) Log.d(TAG, "onRecognitionSuccess " + intents.length);
// store the intents in a member variable so that we can access it
// later when the user choses which action to perform.
mAvailableChoices = intents;
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(mMicFlasher);
String[] sentences = new String[intents.length];
for (int i = 0; i < intents.length; i++) {
sentences[i] = intents[i].getStringExtra(
RecognizerEngine.SENTENCE_EXTRA);
}
if (intents.length == 0) {
onRecognitionFailure("zero intents");
return;
}
if (intents.length > 0) {
// see if we the response was "exit" or "cancel".
String value = intents[0].getStringExtra(
RecognizerEngine.SEMANTIC_EXTRA);
if (Config.LOGD) Log.d(TAG, "value " + value);
if ("X".equals(value)) {
exitActivity();
return;
}
}
if ((intents.length == 1) ||
(!Intent.ACTION_CALL_PRIVILEGED.equals(
intents[0].getAction()))) {
// Either there is only one match, or multiple
// matches for some type of intent other than "call".
// If there's only one match, we may as well just
// dispatch it. If it's not a "call" intent, then
// we don't have a good way to let the user choose
// which match without touching the screen. In this
// case, we simply take the highest confidence match.
// Speak the sentence for the action we are about
// to dispatch so that the user knows what is happening.
String sentenceSpoken = spaceOutDigits(
mAvailableChoices[0].getStringExtra(
RecognizerEngine.SENTENCE_EXTRA));
mState = SPEAKING_CHOSEN_ACTION;
mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
CHOSEN_ACTION_UTTERANCE);
mTts.speak(sentenceSpoken,
TextToSpeech.QUEUE_FLUSH,
mTtsParams);
mChosenAction = intents[0];
// Normally, the intent will be dispatched after the
// utterance completes. As a fallback in case the utterance
// does not complete, post a delayed runnable to fire
// the intent.
mFallbackRunnable = new FallbackRunnable();
mHandler.postDelayed(mFallbackRunnable, MAX_TTS_DELAY);
return;
} else {
// We have multiple call intents. There should only
// be results for a single name, but multiple phone types.
// speak the choices to the user, and then listen for
// the choice.
// We will not start listening until the utterance
// of the choice list completes.
speakChoices();
// Normally, listening will begin after the
// utterance completes. As a fallback in case the utterance
// does not complete, post a delayed runnable to begin
// listening.
mFallbackRunnable = new FallbackRunnable();
mHandler.postDelayed(mFallbackRunnable, MAX_TTS_DELAY);
DialogInterface.OnCancelListener cancelListener =
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
if (Config.LOGD) {
Log.d(TAG, "cancelListener.onCancel");
}
dialog.dismiss();
finish();
}
};
DialogInterface.OnClickListener clickListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (Config.LOGD) {
Log.d(TAG, "clickListener.onClick " + which);
}
startActivityHelp(intents[which]);
dialog.dismiss();
finish();
}
};
DialogInterface.OnClickListener negativeListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (Config.LOGD) {
Log.d(TAG, "negativeListener.onClick " +
which);
}
dialog.dismiss();
finish();
}
};
mAlertDialog =
new AlertDialog.Builder(
BluetoothVoiceDialerActivity.this)
.setTitle(R.string.title)
.setItems(sentences, clickListener)
.setOnCancelListener(cancelListener)
.setNegativeButton(android.R.string.cancel,
negativeListener)
.show();
}
}
});
}
}
private class ChoiceRecognizerClient implements RecognizerClient {
public void onRecognitionSuccess(final Intent[] intents) {
if (Config.LOGD) Log.d(TAG, "ChoiceRecognizerClient onRecognitionSuccess");
if (mAlertDialog != null) {
mAlertDialog.dismiss();
}
// disregard all but the first intent.
if (intents.length > 0) {
String value = intents[0].getStringExtra(
RecognizerEngine.SEMANTIC_EXTRA);
if (Config.LOGD) Log.d(TAG, "value " + value);
if ("R".equals(value)) {
mHandler.post(new GreetingRunnable());
} else if ("X".equals(value)) {
exitActivity();
} else {
// it's a phone type response
mChosenAction = null;
for (int i = 0; i < mAvailableChoices.length; i++) {
if (value.equalsIgnoreCase(
mAvailableChoices[i].getStringExtra(
CommandRecognizerEngine.PHONE_TYPE_EXTRA))) {
mChosenAction = mAvailableChoices[i];
}
}
if (mChosenAction != null) {
mState = SPEAKING_CHOSEN_ACTION;
mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
CHOSEN_ACTION_UTTERANCE);
mTts.speak(mChosenAction.getStringExtra(
RecognizerEngine.SENTENCE_EXTRA),
TextToSpeech.QUEUE_FLUSH,
mTtsParams);
// Normally, the intent will be dispatched after the
// utterance completes. As a fallback in case the utterance
// does not complete, post a delayed runnable to fire
// the intent.
mFallbackRunnable = new FallbackRunnable();
mHandler.postDelayed(mFallbackRunnable, MAX_TTS_DELAY);
} else {
// invalid choice
if (Config.LOGD) Log.d(TAG, "invalid choice" + value);
mTtsParams.remove(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID);
mTts.speak(getString(R.string.invalid_choice_tts),
TextToSpeech.QUEUE_FLUSH,
mTtsParams);
// repeat the list of choices. We will not start
// listening until this utterance completes.
speakChoices();
// Normally, listening will begin after the
// utterance completes. As a fallback in case the utterance
// does not complete, post a delayed runnable begin
// listening.
mFallbackRunnable = new FallbackRunnable();
mHandler.postDelayed(mFallbackRunnable, MAX_TTS_DELAY);
}
}
}
}
public void onRecognitionFailure(String msg) {
if (Config.LOGD) Log.d(TAG, "ChoiceRecognizerClient onRecognitionFailure");
exitActivity();
}
public void onRecognitionError(String err) {
if (Config.LOGD) Log.d(TAG, "ChoiceRecognizerClient onRecognitionError");
mHandler.post(new ErrorRunnable(R.string.recognition_error));
exitActivity();
}
public void onMicrophoneStart(InputStream mic) {
if (Config.LOGD) Log.d(TAG, "ChoiceRecognizerClient onMicrophoneStart");
}
}
private void speakChoices() {
if (Config.LOGD) Log.d(TAG, "speakChoices");
mState = SPEAKING_CHOICES;
String sentenceSpoken = spaceOutDigits(
mAvailableChoices[0].getStringExtra(
RecognizerEngine.SENTENCE_EXTRA));
// When we have multiple choices, they will be of the form
// "call jack jones at home", "call jack jones on mobile".
// Speak the entire first sentence, then the last word from each
// of the remaining sentences. This will come out to something
// like "call jack jones at home mobile or work".
StringBuilder builder = new StringBuilder();
builder.append(sentenceSpoken);
int count = mAvailableChoices.length;
for (int i=1; i < count; i++) {
if (i == count-1) {
builder.append(" or ");
} else {
builder.append(" ");
}
String tmpSentence = mAvailableChoices[i].getStringExtra(
RecognizerEngine.SENTENCE_EXTRA);
String[] words = tmpSentence.trim().split(" ");
builder.append(words[words.length-1]);
}
mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
CHOICES_UTTERANCE);
mTts.speak(builder.toString(),
TextToSpeech.QUEUE_ADD,
mTtsParams);
}
private static String spaceOutDigits(String sentenceDisplay) {
// if we have a sentence of the form "dial 123 456 7890",
// we need to insert a space between each digit, otherwise
// the TTS engine will say "dial one hundred twenty three...."
// When there already is a space, we also insert a comma,
// so that it pauses between sections. For the displayable
// sentence "dial 123 456 7890" it will speak
// "dial 1 2 3, 4 5 6, 7 8 9 0"
char buffer[] = sentenceDisplay.toCharArray();
StringBuilder builder = new StringBuilder();
boolean buildingNumber = false;
int l = sentenceDisplay.length();
for (int index = 0; index < l; index++) {
char c = buffer[index];
if (Character.isDigit(c)) {
if (buildingNumber) {
builder.append(" ");
}
buildingNumber = true;
builder.append(c);
} else if (c == ' ') {
if (buildingNumber) {
builder.append(",");
} else {
builder.append(" ");
}
} else {
buildingNumber = false;
builder.append(c);
}
}
return builder.toString();
}
private void startActivityHelp(Intent intent) {
startActivity(intent);
}
private void listenForCommand() {
if (Config.LOGD) Log.d(TAG, ""
+ "Command(): MICROPHONE_EXTRA: "+getArg(MICROPHONE_EXTRA)+
", CONTACTS_EXTRA: "+getArg(CONTACTS_EXTRA));
mState = WAITING_FOR_COMMAND;
mRecognizerThread = new Thread() {
public void run() {
mCommandEngine.recognize(mCommandClient,
BluetoothVoiceDialerActivity.this,
newFile(getArg(MICROPHONE_EXTRA)),
SAMPLE_RATE);
}
};
mRecognizerThread.start();
}
private void listenForChoice() {
if (Config.LOGD) Log.d(TAG, "listenForChoice(): MICROPHONE_EXTRA: " +
getArg(MICROPHONE_EXTRA));
mState = WAITING_FOR_CHOICE;
mRecognizerThread = new Thread() {
public void run() {
mPhoneTypeChoiceEngine.recognize(mChoiceClient,
BluetoothVoiceDialerActivity.this,
newFile(getArg(MICROPHONE_EXTRA)), SAMPLE_RATE);
}
};
mRecognizerThread.start();
}
private void exitActivity() {
synchronized(this) {
if (mState != EXITING) {
if (Config.LOGD) Log.d(TAG, "exitActivity");
mState = SPEAKING_GOODBYE;
mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
GOODBYE_UTTERANCE);
mTts.speak(getString(R.string.goodbye_tts),
TextToSpeech.QUEUE_FLUSH,
mTtsParams);
// Normally, the activity will finish() after the
// utterance completes. As a fallback in case the utterance
// does not complete, post a delayed runnable finish the
// activity.
mFallbackRunnable = new FallbackRunnable();
mHandler.postDelayed(mFallbackRunnable, MAX_TTS_DELAY);
}
}
}
private String getArg(String name) {
if (name == null) return null;
String arg = getIntent().getStringExtra(name);
if (arg != null) return arg;
arg = SystemProperties.get("app.voicedialer." + name);
return arg != null && arg.length() > 0 ? arg : null;
}
private static File newFile(String name) {
return name != null ? new File(name) : null;
}
private int playSound(int toneType) {
int msecDelay = 1;
// use the MediaPlayer to prompt the user
if (mToneGenerator != null) {
mToneGenerator.startTone(toneType);
msecDelay = StrictMath.max(msecDelay, 300);
}
// use the Vibrator to prompt the user
if ((mAudioManager != null) && (mAudioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER))) {
final int VIBRATOR_TIME = 150;
final int VIBRATOR_GUARD_TIME = 150;
Vibrator vibrator = new Vibrator();
vibrator.vibrate(VIBRATOR_TIME);
msecDelay = StrictMath.max(msecDelay,
VIBRATOR_TIME + VIBRATOR_GUARD_TIME);
}
return msecDelay;
}
protected void onStop() {
if (Config.LOGD) Log.d(TAG, "onStop");
synchronized(this) {
mState = EXITING;
}
if (mAlertDialog != null) {
mAlertDialog.dismiss();
}
// set the volume back to the level it was before we started.
mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO,
mBluetoothVoiceVolume, 0);
mAudioManager.abandonAudioFocus(null);
// shut down bluetooth, if it exists
if (mBluetoothHeadset != null) {
mBluetoothHeadset.stopVoiceRecognition();
mBluetoothHeadset.close();
mBluetoothHeadset = null;
}
// shut down recognizer and wait for the thread to complete
if (mRecognizerThread != null) {
mRecognizerThread.interrupt();
try {
mRecognizerThread.join();
} catch (InterruptedException e) {
if (Config.LOGD) Log.d(TAG, "onStop mRecognizerThread.join exception " + e);
}
mRecognizerThread = null;
}
// clean up UI
mHandler.removeCallbacks(mMicFlasher);
mHandler.removeMessages(0);
if (mTts != null) {
mTts.stop();
mTts.shutdown();
mTts = null;
}
unregisterReceiver(mReceiver);
super.onStop();
releaseWakeLock();
// It makes no sense to have this activity maintain state when in
// background. When it stops, it should just be destroyed.
finish();
}
private void acquireWakeLock(Context context) {
if (mWakeLock == null) {
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothVoiceDialer");
mWakeLock.acquire();
}
}
private void releaseWakeLock() {
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
}
private Runnable mMicFlasher = new Runnable() {
int visible = View.VISIBLE;
public void run() {
findViewById(R.id.microphone_view).setVisibility(visible);
findViewById(R.id.state).setVisibility(visible);
visible = visible == View.VISIBLE ? View.INVISIBLE : View.VISIBLE;
mHandler.postDelayed(this, 750);
}
};
@Override
protected void onDestroy() {
if (Config.LOGD) Log.d(TAG, "onDestroy");
super.onDestroy();
}
}