/*
* Copyright 2011 Greg Milette and Adam Stroud
*
* 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 root.gast.playground.speech.food;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import root.gast.playground.R;
import root.gast.playground.pref.PreferenceHelper;
import root.gast.playground.pref.SummarizingEditPreferences;
import root.gast.playground.speech.food.db.Food;
import root.gast.playground.speech.food.db.FtsIndexedFoodDatabase;
import root.gast.playground.speech.food.db.MatchedFood;
import root.gast.playground.speech.food.lucene.FoodIndexBuilder;
import root.gast.playground.speech.food.lucene.FoodSearcher;
import root.gast.playground.speech.food.multimatcher.MultiPartUnderstander;
import root.gast.playground.speech.food.multimatcher.MultiPartUnderstanderNoOrder;
import root.gast.playground.speech.food.multimatcher.MultiPartUnderstanderOrdered;
import root.gast.speech.RecognizerIntentFactory;
import root.gast.speech.SpeechRecognizingAndSpeakingActivity;
import root.gast.speech.text.match.WordMatcher;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
/**
* For testing out matchers
* @author Greg Milette <<a href="mailto:gregorym@gmail.com">gregorym@gmail.com</a>>
*/
public class FoodDialogPlay extends SpeechRecognizingAndSpeakingActivity
{
private static final String TAG = "FoodDialogPlay";
private FtsIndexedFoodDatabase foodDb;
private FoodSearcher luceneSearcher;
private FoodSpeechResultUnderstander understander;
private TextView log;
private PreferenceHelper preferences;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.fooddialog);
preferences = new PreferenceHelper(getResources().getString(R.string.food_preferences_key),
this.getApplicationContext());
initDbs();
hookButtons();
}
/**
* make sure the databases have data
*/
private void initDbs()
{
foodDb = FtsIndexedFoodDatabase.getInstance(this);
if (foodDb.isEmpty())
{
Log.d(TAG, "loading foods");
InputStream stream = getResources().openRawResource(R.raw.foods);
try
{
foodDb.loadFrom(stream);
}
catch (IOException io)
{
Log.d(TAG, "failed to load db");
}
}
}
private void hookButtons()
{
log = (TextView)findViewById(R.id.tv_resultlog);
Button edit = (Button)findViewById(R.id.btn_food_edit);
edit.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
understander = new FoodSpeechResultUnderstander()
{
@Override
public void interpret(List<String> heard, float[] confidenceScores)
{
doFoodEditAndCompare(heard, confidenceScores);
}
};
Intent recognizerIntent =
RecognizerIntentFactory.getSimpleRecognizerIntent(
getString(R.string.food_edit_compare_prompt));
recognize(recognizerIntent);
}
});
Button food = (Button)findViewById(R.id.btn_food_lookup);
food.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
understander = new FoodSpeechResultUnderstander()
{
@Override
public void interpret(List<String> heard, float[] confidenceScores)
{
doFoodLookup(heard, confidenceScores);
}
};
Intent recognizerIntent = RecognizerIntentFactory.getSimpleRecognizerIntent(
getString(R.string.food_lookup_prompt));
recognize(recognizerIntent);
}
});
}
protected void receiveWhatWasHeard(List<String> heard,
float[] confidenceScores)
{
Log.d(TAG, "received " + heard.size());
understander.interpret(heard, confidenceScores);
}
/**
* implements food edit and compare, does ordered and
* not ordered matching, uses a series of if checks
* to prioritize between the three possible commands
*/
private void doFoodEditAndCompare(List<String> heard,
float[] confidenceScores)
{
//make sure the DB is initialized
initDbs();
MultiPartUnderstander noOrder = new MultiPartUnderstanderNoOrder();
MultiPartUnderstander order = new MultiPartUnderstanderOrdered();
boolean ordered =
preferences.getBoolean(this, R.string.pref_food_ordered, R.string.pref_food_default);
MultiPartUnderstander matcher;
if (ordered)
{
matcher = order;
}
else
{
matcher = noOrder;
}
//add (free text)
//remove (lookup text)
//compare
String toSpeak = null;
for (String said : heard)
{
Log.d(TAG, "*understanding: " + said);
Food add = matcher.addFreeText(said);
if (add == null)
{
Log.d(TAG, "not add");
Food removed = matcher.removeExistingFood(said);
if (removed == null)
{
Log.d(TAG, "not remove");
String comparison = matcher.compareCalories(said);
if (comparison == null)
{
Log.d(TAG, "not comparison");
}
else
{
toSpeak = comparison;
Log.d(TAG, "MATCHED compare: " + toSpeak);
break;
}
}
else
{
Log.d(TAG, "MATCHED remove");
toSpeak = "removed " + removed.getName();
FtsIndexedFoodDatabase.getInstance(this).removeFood(removed.getName());
break;
}
}
else
{
Log.d(TAG, "MATCHED add");
toSpeak = "added " + add.getName();
FtsIndexedFoodDatabase.getInstance(this).insertFood(add.getName(), add.getCalories());
break;
}
}
if (toSpeak == null)
{
toSpeak = getResources().getString(R.string.speechRecognitionFailed);
}
getTts().speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null);
Log.d(TAG, "speaking.. " + toSpeak);
appendToLog(toSpeak);
}
private void doFoodLookup(List<String> heard,
float[] confidenceScores)
{
Log.d(TAG, "do food lookup");
//if lucene is true the do lucene
boolean lucene =
preferences.getBoolean(this, R.string.pref_food_lucene, R.string.pref_food_default);
boolean or =
preferences.getBoolean(this, R.string.pref_food_or, R.string.pref_food_or_default);
boolean prefix =
preferences.getBoolean(this, R.string.pref_food_prefix, R.string.pref_food_default);
boolean phrase =
preferences.getBoolean(this, R.string.pref_food_phrase, R.string.pref_food_default);
//setup lucene or fts
FtsIndexedFoodDatabase foodFts = null;
if (lucene)
{
Log.d(TAG, "do lucene");
//This creates the searcher so this method can use it later
receiveWhatWasHeardLuceneFood(heard, confidenceScores);
}
else
{
Log.d(TAG, "do fts");
foodFts = FtsIndexedFoodDatabase.getInstance(this);
}
for (String said : heard)
{
List<MatchedFood> foods;
//search lucene or fts
if (lucene)
{
List<Food> foodMatches = luceneSearcher.findMatching(said);
foods = new ArrayList<MatchedFood>();
for (Food food : foodMatches)
{
foods.add(new MatchedFood(0, 0, food));
}
} else
{
foods = foodFts.retrieveBestMatch(said, prefix, or, phrase);
}
//result
if (foods.size() > 0)
{
Food heardFood = foods.get(0).getFood();
Log.d(TAG, "heard a food " + heardFood);
String toSpeak = heardFood.getName() + " has " + heardFood.getFormattedCalories() + " calories.";
getTts().speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null);
appendToLog(toSpeak);
break;
}
}
}
protected void receiveWhatWasHeardAdd(List<String> heard,
float[] confidenceScores)
{
WordMatcher command = new WordMatcher("add");
for (String said : heard)
{
if (command.isIn(said.split("\\s")))
{
Log.d(TAG, "heard add");
break;
}
}
}
protected void receiveWhatWasHeardFts(List<String> heard,
float[] confidenceScores)
{
FtsIndexedFoodDatabase food = FtsIndexedFoodDatabase.getInstance(this);
for (String said : heard)
{
if (food.retrieveBestMatch(said).size() > 0)
{
Log.d(TAG, "heard a food");
break;
}
}
}
/**
* handle receive using Lucene
*/
protected void receiveWhatWasHeardLuceneFood(List<String> heard,
float[] confidenceScores)
{
// create the food index only once
if (luceneSearcher == null)
{
// don't overwrite, but do stemming
FoodIndexBuilder builder =
new FoodIndexBuilder(getExternalFilesDir("foodindex")
.getAbsolutePath(), false, false, true);
try
{
// read the foods file and add foods to the builder
loadLuceneIndex(builder);
} catch (IOException e)
{
Log.e(TAG, "unable to load index", e);
}
try
{
luceneSearcher = builder.get();
} catch (IOException e)
{
Log.e(TAG, "error", e);
}
}
for (String said : heard)
{
if (luceneSearcher.findMatching(said).size() > 0)
{
Log.d(TAG, "heard a food");
break;
}
}
}
private void loadLuceneIndex(FoodIndexBuilder builder) throws IOException
{
Log.d(TAG, "loading foods");
InputStream stream = getResources().openRawResource(R.raw.foods);
BufferedReader is =
new BufferedReader(new InputStreamReader(stream, "UTF8"));
String line;
line = is.readLine();
while (line != null)
{
String[] parts = line.split(",");
String food = parts[0];
float cals = Float.valueOf(parts[1]);
builder.addFood(food, cals);
Log.d(TAG, "loaded: " + food);
line = is.readLine();
}
}
private void appendToLog(String appendThis)
{
String currentLog = log.getText().toString();
currentLog = appendThis + "\n" + currentLog;
log.setText(currentLog);
}
//menu handling
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.food_dialog_menu, menu);
return true;
}
public boolean onOptionsItemSelected (MenuItem item)
{
if (item.getItemId() == R.id.m_match_param)
{
//launch preferences activity
Intent i = new Intent(this, SummarizingEditPreferences.class);
i.putExtra(SummarizingEditPreferences.WHICH_PREFERENCES_INTENT, R.xml.food_preferences);
String preferenceName = getResources().getString(R.string.food_preferences_key);
i.putExtra(SummarizingEditPreferences.WHICH_PREFERENCES_NAME_INTENT, preferenceName);
startActivity(i);
}
else if (item.getItemId() == R.id.m_resetdb_param)
{
FtsIndexedFoodDatabase.getInstance(this).clean(this);
//now load
initDbs();
Toast.makeText(this, getString(R.string.menu_resetdb_param), Toast.LENGTH_SHORT).show();
}
else if (item.getItemId() == R.id.m_showdb_param)
{
Intent browseIntent = new Intent(this, FoodBrowser.class);
startActivity(browseIntent);
}
return true;
}
}