Add new Android voice translator sample app
This adds a simple voice translator Android app that uses the JNI bindings to the PGF C runtime. Caveats: - Since the C runtime doesn't compile for Android right now, I've bundled an old copy, along with its Java bindings. That should be removed once the C runtime compiels for Android again. - Adding an automated build would be nice. - Replacing the grammar requires editing a Java file, that should really be more dynamic.
@@ -3,11 +3,7 @@
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES">
|
||||
<attributes>
|
||||
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="jni"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>GFTranslator</name>
|
||||
<name>GFVoiceExample</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.grammaticalframework.ui.android"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="8"
|
||||
android:targetSdkVersion="8" />
|
||||
android:targetSdkVersion="18" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name="org.grammaticalframework.ui.android.MainActivity"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
66
src/ui/android/README
Normal file
@@ -0,0 +1,66 @@
|
||||
= Overview =
|
||||
|
||||
This directory contains a sample Android app tht uses
|
||||
the Android speech recognition and TTS APIs along with
|
||||
JNI bindings to the C PGF runtime to implement a simple
|
||||
speech translation app.
|
||||
|
||||
|
||||
= Requirements =
|
||||
|
||||
1. Android SDK: http://developer.android.com/sdk/
|
||||
installed in $ANDROID_SDK_LOCATION
|
||||
|
||||
2. Android NDK: http://developer.android.com/tools/sdk/ndk/
|
||||
installed in $ANDROID_NDK_LOCATION
|
||||
|
||||
= Building =
|
||||
|
||||
Set up Android project:
|
||||
|
||||
# Creates local.properties, not to be checked in
|
||||
$ $ANDROID_SDK_LOCATION/tools/android update project -p .
|
||||
|
||||
Build libs/libjpgf.jar:
|
||||
|
||||
$ (cd ../../runtime/java && javac org/grammaticalframework/pgf/*.java && jar -cf libjpgf.jar org/grammaticalframework/pgf/*.class)
|
||||
$ cp ../../runtime/java/libjpgf.jar libs
|
||||
|
||||
Build JNI code:
|
||||
|
||||
$ cd jni
|
||||
$ $ANDROID_NDK_LOCATION/ndk-build
|
||||
|
||||
|
||||
Build APK:
|
||||
|
||||
$ ant debug
|
||||
|
||||
|
||||
Install on your device:
|
||||
|
||||
$ ant debug install
|
||||
|
||||
or:
|
||||
|
||||
$ adb install -r bin/MainActivity-debug.apk
|
||||
|
||||
|
||||
= Changing the grammar =
|
||||
|
||||
1. Replace assets/ResourceDemo.pgf
|
||||
|
||||
2. Edit Translator.java to point to the new file and include its metadata
|
||||
|
||||
|
||||
= Developing in Eclipse =
|
||||
|
||||
1. Install Android ADT
|
||||
|
||||
2. Eclipse > File > Import > Existing Projects into Workspace > Next
|
||||
|
||||
3. Select root directory...
|
||||
|
||||
4. Select GF/src/ui/android
|
||||
|
||||
5. Finish
|
||||
BIN
src/ui/android/assets/ResourceDemo.pgf
Normal file
92
src/ui/android/build.xml
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="MainActivity" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contains the path to the SDK. It should *NOT* be checked into
|
||||
Version Control Systems. -->
|
||||
<property file="local.properties" />
|
||||
|
||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||
'android' tool to add properties to it.
|
||||
This is the place to change some Ant specific build properties.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
source.dir
|
||||
The name of the source directory. Default is 'src'.
|
||||
out.dir
|
||||
The name of the output directory. Default is 'bin'.
|
||||
|
||||
For other overridable properties, look at the beginning of the rules
|
||||
files in the SDK, at tools/ant/build.xml
|
||||
|
||||
Properties related to the SDK location or the project target should
|
||||
be updated using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="ant.properties" />
|
||||
|
||||
<!-- if sdk.dir was not set from one of the property file, then
|
||||
get it from the ANDROID_HOME env var.
|
||||
This must be done before we load project.properties since
|
||||
the proguard config can use sdk.dir -->
|
||||
<property environment="env" />
|
||||
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||
<isset property="env.ANDROID_HOME" />
|
||||
</condition>
|
||||
|
||||
<!-- The project.properties file is created and updated by the 'android'
|
||||
tool, as well as ADT.
|
||||
|
||||
This contains project specific properties such as project target, and library
|
||||
dependencies. Lower level build properties are stored in ant.properties
|
||||
(or in .classpath for Eclipse projects).
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems. -->
|
||||
<loadproperties srcFile="project.properties" />
|
||||
|
||||
<!-- quick check on sdk.dir -->
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
<import file="custom_rules.xml" optional="true" />
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
To customize existing targets, there are two options:
|
||||
- Customize only one target:
|
||||
- copy/paste the target into this file, *before* the
|
||||
<import> task.
|
||||
- customize it to your needs.
|
||||
- Customize the whole content of build.xml
|
||||
- copy/paste the content of the rules files (minus the top node)
|
||||
into this file, replacing the <import> task.
|
||||
- customize to your needs.
|
||||
|
||||
***********************
|
||||
****** IMPORTANT ******
|
||||
***********************
|
||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||
in order to avoid having your file be overridden by tools such as "android update project"
|
||||
-->
|
||||
<!-- version-tag: 1 -->
|
||||
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||
|
||||
</project>
|
||||
BIN
src/ui/android/libs/android-support-v4.jar
Normal file
BIN
src/ui/android/libs/armeabi/libjpgf.so
Normal file
BIN
src/ui/android/libs/libjpgf.jar
Normal file
20
src/ui/android/proguard-project.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
BIN
src/ui/android/res/drawable-hdpi/ic_action_switch.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
src/ui/android/res/drawable-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/ui/android/res/drawable-hdpi/ic_mic.png
Normal file
|
After Width: | Height: | Size: 665 B |
BIN
src/ui/android/res/drawable-mdpi/ic_action_switch.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
src/ui/android/res/drawable-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/ui/android/res/drawable-mdpi/ic_mic.png
Normal file
|
After Width: | Height: | Size: 437 B |
BIN
src/ui/android/res/drawable-xhdpi/ic_action_switch.png
Normal file
|
After Width: | Height: | Size: 547 B |
BIN
src/ui/android/res/drawable-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/ui/android/res/drawable-xhdpi/ic_mic.png
Normal file
|
After Width: | Height: | Size: 783 B |
BIN
src/ui/android/res/drawable-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="4dp" />
|
||||
<solid android:color="#75CD75" />
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="4dp" />
|
||||
<solid android:color="#7575CD" />
|
||||
</shape>
|
||||
85
src/ui/android/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/top_bg"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:padding="8dp"
|
||||
android:background="#C0C0C0"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/start_stop"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_alignTop="@+id/source_language"
|
||||
android:layout_alignBottom="@+id/target_language"
|
||||
android:layout_alignParentRight="true"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_mic"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/microphone"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/switch_languages"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_alignTop="@+id/source_language"
|
||||
android:layout_alignBottom="@+id/target_language"
|
||||
android:layout_toLeftOf="@id/start_stop"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_action_switch"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/switch_languages"
|
||||
/>
|
||||
|
||||
<org.grammaticalframework.ui.android.LanguageSelector
|
||||
android:id="@+id/source_language"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toLeftOf="@id/switch_languages"
|
||||
android:padding="0dp"
|
||||
/>
|
||||
|
||||
<org.grammaticalframework.ui.android.LanguageSelector
|
||||
android:id="@+id/target_language"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_below="@id/source_language"
|
||||
android:layout_toLeftOf="@id/switch_languages"
|
||||
android:padding="0dp"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<org.grammaticalframework.ui.android.ConversationView
|
||||
android:id="@+id/conversation"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@id/top_bg"
|
||||
>
|
||||
<LinearLayout
|
||||
android:id="@+id/conversation_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
>
|
||||
</LinearLayout>
|
||||
</org.grammaticalframework.ui.android.ConversationView>
|
||||
|
||||
</RelativeLayout>
|
||||
11
src/ui/android/res/layout/first_person_utterance.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginRight="32dp"
|
||||
android:layout_gravity="left"
|
||||
android:padding="8dp"
|
||||
android:textSize="20sp"
|
||||
android:background="@drawable/first_person_utterance_bg"
|
||||
/>
|
||||
8
src/ui/android/res/layout/languages_item.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="8dp"
|
||||
android:textSize="20sp"
|
||||
/>
|
||||
12
src/ui/android/res/layout/second_person_utterance.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:layout_gravity="right"
|
||||
android:padding="8dp"
|
||||
android:textSize="20sp"
|
||||
android:background="@drawable/second_person_utterance_bg"
|
||||
/>
|
||||
8
src/ui/android/res/values-sw600dp/dimens.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Customize dimensions originally defined in res/values/dimens.xml (such as
|
||||
screen margins) for sw600dp devices (e.g. 7" tablets) here.
|
||||
-->
|
||||
|
||||
</resources>
|
||||
9
src/ui/android/res/values-sw720dp-land/dimens.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Customize dimensions originally defined in res/values/dimens.xml (such as
|
||||
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
|
||||
-->
|
||||
<dimen name="activity_horizontal_margin">128dp</dimen>
|
||||
|
||||
</resources>
|
||||
11
src/ui/android/res/values-v11/styles.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 11+. This theme completely replaces
|
||||
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
|
||||
<!-- API 11 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
12
src/ui/android/res/values-v14/styles.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 14+. This theme completely replaces
|
||||
AppBaseTheme from BOTH res/values/styles.xml and
|
||||
res/values-v11/styles.xml on API 14+ devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<!-- API 14 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
7
src/ui/android/res/values/dimens.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
</resources>
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">GFTranslator</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
<string name="app_name">GF Translator</string>
|
||||
|
||||
<string name="microphone">Microphone</string>
|
||||
<string name="switch_languages">Switch languages</string>
|
||||
|
||||
</resources>
|
||||
|
||||
20
src/ui/android/res/values/styles.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme, dependent on API level. This theme is replaced
|
||||
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Light">
|
||||
<!--
|
||||
Theme customizations available in newer API levels can go in
|
||||
res/values-vXX/styles.xml, while customizations related to
|
||||
backward-compatibility can go here.
|
||||
-->
|
||||
</style>
|
||||
|
||||
<!-- Application theme. -->
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
240
src/ui/android/src/org/grammaticalframework/ui/android/ASR.java
Normal file
@@ -0,0 +1,240 @@
|
||||
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.speech.RecognitionListener;
|
||||
import android.speech.RecognizerIntent;
|
||||
import android.speech.SpeechRecognizer;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Convenience wrapper around the {@link SpeechRecognizer} API.
|
||||
*/
|
||||
public class ASR {
|
||||
|
||||
private static final boolean DBG = true;
|
||||
private static final String TAG = "ASR";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private SpeechRecognizer mSpeechRecognizer;
|
||||
|
||||
private String mLanguage = null;
|
||||
|
||||
private State mState = State.IDLE;
|
||||
|
||||
private Listener mListener;
|
||||
|
||||
public static enum State {
|
||||
IDLE, INITIALIZING, WAITING_FOR_SPEECH, RECORDING, WAITING_FOR_RESULTS;
|
||||
}
|
||||
|
||||
public ASR(Context context) {
|
||||
mContext = context;
|
||||
if (SpeechRecognizer.isRecognitionAvailable(context)) {
|
||||
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
|
||||
mSpeechRecognizer.setRecognitionListener(new MyRecognitionListener());
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
mLanguage = language;
|
||||
}
|
||||
|
||||
public void startRecognition() {
|
||||
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
|
||||
if (!TextUtils.isEmpty(mLanguage)) {
|
||||
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, mLanguage);
|
||||
}
|
||||
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
|
||||
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
|
||||
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 2);
|
||||
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
|
||||
// Weird, this shouldn't be required, but on ICS it seems to be
|
||||
intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
|
||||
mContext.getPackageName());
|
||||
|
||||
mSpeechRecognizer.startListening(intent);
|
||||
setState(State.INITIALIZING);
|
||||
}
|
||||
|
||||
public void stopRecognition() {
|
||||
mSpeechRecognizer.stopListening();
|
||||
setState(State.IDLE);
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return mState != State.IDLE;
|
||||
}
|
||||
|
||||
private void setState(State newState) {
|
||||
if (DBG) Log.d(TAG, "Entering state: " + newState);
|
||||
mState = newState;
|
||||
if (mListener != null) {
|
||||
mListener.onStateChanged(mState);
|
||||
}
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (mSpeechRecognizer != null) {
|
||||
mSpeechRecognizer.destroy();
|
||||
mSpeechRecognizer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePartialInput(String text) {
|
||||
if (mListener != null) {
|
||||
mListener.onPartialInput(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSpeechInput(String text) {
|
||||
if (mListener != null) {
|
||||
mListener.onSpeechInput(text);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyRecognitionListener implements RecognitionListener {
|
||||
@Override
|
||||
public void onReadyForSpeech(Bundle params) {
|
||||
if (DBG) Log.d(TAG, "onReadyForSpeech");
|
||||
setState(State.WAITING_FOR_SPEECH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeginningOfSpeech() {
|
||||
if (DBG) Log.d(TAG, "onBeginningOfSpeech");
|
||||
setState(State.RECORDING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferReceived(byte[] buffer) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRmsChanged(float rmsdB) {
|
||||
if (DBG) Log.d(TAG, "onRmsChanged(" + rmsdB + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEndOfSpeech() {
|
||||
if (DBG) Log.d(TAG, "onEndOfSpeech");
|
||||
setState(State.WAITING_FOR_RESULTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int error) {
|
||||
if (DBG) Log.d(TAG, "Error: " + errorMessage(error) + " (" + error + ")");
|
||||
setState(State.IDLE);
|
||||
}
|
||||
|
||||
private String errorMessage(int speechRecognizerError) {
|
||||
switch(speechRecognizerError) {
|
||||
case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
|
||||
return "network timeout";
|
||||
case SpeechRecognizer.ERROR_NETWORK:
|
||||
return "network";
|
||||
case SpeechRecognizer.ERROR_AUDIO:
|
||||
return "audio";
|
||||
case SpeechRecognizer.ERROR_SERVER:
|
||||
return "server";
|
||||
case SpeechRecognizer.ERROR_CLIENT:
|
||||
return "client";
|
||||
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
|
||||
return "timeout waiting for speech";
|
||||
case SpeechRecognizer.ERROR_NO_MATCH:
|
||||
return "no match found";
|
||||
case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
|
||||
return "recognizer busy";
|
||||
case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
|
||||
return "insufficient permissions (missing RECORD_AUDIO?)";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int eventType, Bundle params) {
|
||||
if (DBG) Log.d(TAG, "onEvent(" + eventType + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartialResults(Bundle bundle) {
|
||||
if (DBG) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("onPartialResults:");
|
||||
appendResults(sb, bundle);
|
||||
Log.d(TAG, sb.toString());
|
||||
}
|
||||
|
||||
String result = getResult(bundle);
|
||||
if (!TextUtils.isEmpty(result)) {
|
||||
handlePartialInput(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResults(Bundle bundle) {
|
||||
if (DBG) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("onResults:");
|
||||
appendResults(sb, bundle);
|
||||
Log.d(TAG, sb.toString());
|
||||
}
|
||||
|
||||
setState(State.IDLE);
|
||||
|
||||
String result = getResult(bundle);
|
||||
if (!TextUtils.isEmpty(result)) {
|
||||
handleSpeechInput(result);
|
||||
}
|
||||
}
|
||||
|
||||
private String getResult(Bundle bundle) {
|
||||
ArrayList<String> results =
|
||||
bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
|
||||
if (results != null && !results.isEmpty()) {
|
||||
return results.get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void appendResults(StringBuilder sb, Bundle bundle) {
|
||||
ArrayList<String> results =
|
||||
bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
|
||||
float[] scores = bundle.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES);
|
||||
|
||||
if (results != null) {
|
||||
int size = results.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
sb.append("\n> ").append(results.get(i));
|
||||
if (scores != null && i < scores.length) {
|
||||
sb.append(" [").append(scores[i]).append("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onPartialInput(String input);
|
||||
void onSpeechInput(String input);
|
||||
void onStateChanged(State newState);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ConversationView extends ScrollView {
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private ViewGroup mContent;
|
||||
|
||||
public ConversationView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public ConversationView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ConversationView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mContent = (ViewGroup) findViewById(R.id.conversation_content);
|
||||
mInflater = LayoutInflater.from(getContext());
|
||||
}
|
||||
|
||||
public void addFirstPersonUtterance(CharSequence text) {
|
||||
addUtterance(R.layout.first_person_utterance, text);
|
||||
}
|
||||
|
||||
public void addSecondPersonUtterance(CharSequence text) {
|
||||
addUtterance(R.layout.second_person_utterance, text);
|
||||
}
|
||||
|
||||
private void addUtterance(int res, CharSequence text) {
|
||||
TextView view = (TextView) mInflater.inflate(res, mContent, false);
|
||||
view.setText(text);
|
||||
mContent.addView(view);
|
||||
post(new Runnable() {
|
||||
public void run() {
|
||||
fullScroll(FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updateLastUtterance(CharSequence text) {
|
||||
int count = mContent.getChildCount();
|
||||
if (count > 0) {
|
||||
TextView view = (TextView) mContent.getChildAt(count - 1);
|
||||
view.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
public class Language {
|
||||
private final String mLangCode;
|
||||
private final String mLangName;
|
||||
private final String mConcrete;
|
||||
|
||||
public Language(String langCode, String langName, String concrete) {
|
||||
mLangCode = langCode;
|
||||
mLangName = langName;
|
||||
mConcrete = concrete;
|
||||
}
|
||||
|
||||
public String getLangCode() {
|
||||
return mLangCode;
|
||||
}
|
||||
|
||||
public String getLangName() {
|
||||
return mLangName;
|
||||
}
|
||||
|
||||
String getConcrete() {
|
||||
return mConcrete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getLangName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LanguageSelector extends Spinner {
|
||||
|
||||
public LanguageSelector(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public LanguageSelector(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LanguageSelector(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setLanguages(List<Language> languages) {
|
||||
setAdapter(new LanguagesAdapter(getContext(), languages));
|
||||
}
|
||||
|
||||
public void setSelectedLanguage(Language selected) {
|
||||
setSelection(((LanguagesAdapter) getAdapter()).getPosition(selected));
|
||||
}
|
||||
|
||||
public void setOnLanguageSelectedListener(final OnLanguageSelectedListener listener) {
|
||||
setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (listener != null) {
|
||||
listener.onLanguageSelected((Language) parent.getItemAtPosition(position));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface OnLanguageSelectedListener {
|
||||
void onLanguageSelected(Language language);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.SpinnerAdapter;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LanguagesAdapter extends ArrayAdapter<Language> implements SpinnerAdapter {
|
||||
|
||||
public LanguagesAdapter(Context context, List<Language> objects) {
|
||||
super(context, R.layout.languages_item, objects);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Collections of utils to handle locales.
|
||||
*/
|
||||
public class LocaleUtils {
|
||||
|
||||
/**
|
||||
* Parses a locale string formatted by {@link Locale#toString()}.
|
||||
*
|
||||
* @return the parsed {@code Locale} or {@code defaultLocale} if the input was null or empty.
|
||||
*/
|
||||
public static Locale parseJavaLocale(String localeString, Locale defaultLocale) {
|
||||
if (TextUtils.isEmpty(localeString)) {
|
||||
return defaultLocale;
|
||||
}
|
||||
final char separator = '_';
|
||||
int pos1 = localeString.indexOf(separator);
|
||||
if (pos1 == -1) {
|
||||
return new Locale(localeString);
|
||||
}
|
||||
String language = localeString.substring(0, pos1);
|
||||
|
||||
int start2 = pos1 + 1;
|
||||
int pos2 = localeString.indexOf(separator, start2);
|
||||
if (pos2 == -1) {
|
||||
return new Locale(language, localeString.substring(start2));
|
||||
}
|
||||
String country = localeString.substring(start2, pos2);
|
||||
|
||||
int start3 = pos2 + 1;
|
||||
int pos3 = localeString.indexOf(separator, start3);
|
||||
String variant = (pos3 == -1)
|
||||
? localeString.substring(start3)
|
||||
: localeString.substring(start3, pos3);
|
||||
return new Locale(language, country, variant);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.speech.SpeechRecognizer;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.grammaticalframework.ui.android.ASR.State;
|
||||
import org.grammaticalframework.ui.android.LanguageSelector.OnLanguageSelectedListener;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
private static final boolean DBG = true;
|
||||
private static final String TAG = "DemoActivity";
|
||||
|
||||
private static final boolean FAKE_SPEECH = false;
|
||||
|
||||
private ImageView mStartStopButton;
|
||||
|
||||
private ConversationView mConversationView;
|
||||
|
||||
private LanguageSelector mSourceLanguageView;
|
||||
|
||||
private LanguageSelector mTargetLanguageView;
|
||||
|
||||
private ImageView mSwitchLanguagesButton;
|
||||
|
||||
private ASR mAsr;
|
||||
|
||||
private TTS mTts;
|
||||
|
||||
private Translator mTranslator;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
mStartStopButton = (ImageView) findViewById(R.id.start_stop);
|
||||
mConversationView = (ConversationView) findViewById(R.id.conversation);
|
||||
mSourceLanguageView = (LanguageSelector) findViewById(R.id.source_language);
|
||||
mTargetLanguageView = (LanguageSelector) findViewById(R.id.target_language);
|
||||
mSwitchLanguagesButton = (ImageView) findViewById(R.id.switch_languages);
|
||||
|
||||
mStartStopButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mAsr.isRunning()) {
|
||||
stopRecognition();
|
||||
} else {
|
||||
startRecognition();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mStartStopButton.setEnabled(SpeechRecognizer.isRecognitionAvailable(this));
|
||||
|
||||
mAsr = new ASR(this);
|
||||
mAsr.setListener(new SpeechInputListener());
|
||||
|
||||
mTts = new TTS(this);
|
||||
|
||||
mTranslator = new Translator(this);
|
||||
new AsyncTask<Void,Void,Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
mTranslator.init();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
mSourceLanguageView.setLanguages(mTranslator.getAvailableSourceLanguages());
|
||||
mSourceLanguageView.setSelectedLanguage(mTranslator.getSourceLanguage());
|
||||
mSourceLanguageView.setOnLanguageSelectedListener(new OnLanguageSelectedListener() {
|
||||
@Override
|
||||
public void onLanguageSelected(Language language) {
|
||||
onSourceLanguageSelected(language);
|
||||
}
|
||||
});
|
||||
mTargetLanguageView.setLanguages(mTranslator.getAvailableTargetLanguages());
|
||||
mTargetLanguageView.setSelectedLanguage(mTranslator.getTargetLanguage());
|
||||
mTargetLanguageView.setOnLanguageSelectedListener(new OnLanguageSelectedListener() {
|
||||
@Override
|
||||
public void onLanguageSelected(Language language) {
|
||||
onTargetLanguageSelected(language);
|
||||
}
|
||||
});
|
||||
|
||||
mSwitchLanguagesButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onSwitchLanguages();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (mAsr != null) {
|
||||
mAsr.destroy();
|
||||
mAsr = null;
|
||||
}
|
||||
if (mTts != null) {
|
||||
mTts.destroy();
|
||||
mTts = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
void onSourceLanguageSelected(Language language) {
|
||||
mTranslator.setSourceLanguage(language);
|
||||
}
|
||||
|
||||
void onTargetLanguageSelected(Language language) {
|
||||
mTranslator.setTargetLanguage(language);
|
||||
}
|
||||
|
||||
public String getSourceLanguageCode() {
|
||||
return mTranslator.getSourceLanguage().getLangCode();
|
||||
}
|
||||
|
||||
public String getTargetLanguageCode() {
|
||||
return mTranslator.getTargetLanguage().getLangCode();
|
||||
}
|
||||
|
||||
void onSwitchLanguages() {
|
||||
Language newSource = mTranslator.getTargetLanguage();
|
||||
Language newTarget = mTranslator.getSourceLanguage();
|
||||
mSourceLanguageView.setSelectedLanguage(newSource);
|
||||
mTargetLanguageView.setSelectedLanguage(newTarget);
|
||||
}
|
||||
|
||||
private void startRecognition() {
|
||||
mConversationView.addFirstPersonUtterance("...");
|
||||
|
||||
if (FAKE_SPEECH) {
|
||||
handleSpeechInput("where is the hotel");
|
||||
} else {
|
||||
mAsr.setLanguage(getSourceLanguageCode());
|
||||
mAsr.startRecognition();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopRecognition() {
|
||||
mAsr.stopRecognition();
|
||||
}
|
||||
|
||||
private void handlePartialSpeechInput(String input) {
|
||||
mConversationView.updateLastUtterance(input);
|
||||
}
|
||||
|
||||
private void handleSpeechInput(final String input) {
|
||||
mConversationView.updateLastUtterance(input);
|
||||
new AsyncTask<Void,Void,String>() {
|
||||
@Override
|
||||
protected String doInBackground(Void... params) {
|
||||
return mTranslator.translate(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String result) {
|
||||
outputText(result);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void outputText(String text) {
|
||||
if (DBG) Log.d(TAG, "Speaking: " + text);
|
||||
mConversationView.addSecondPersonUtterance(text);
|
||||
if (!FAKE_SPEECH) {
|
||||
mTts.setLanguage(getTargetLanguageCode());
|
||||
mTts.speak(text);
|
||||
}
|
||||
}
|
||||
|
||||
private class SpeechInputListener implements ASR.Listener {
|
||||
|
||||
@Override
|
||||
public void onPartialInput(String input) {
|
||||
handlePartialSpeechInput(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpeechInput(String input) {
|
||||
handleSpeechInput(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(State newState) {
|
||||
// if (newState == ASR.State.IDLE) {
|
||||
// mStartStopButton.setImageResource(R.drawable.mic_idle);
|
||||
// } else {
|
||||
// mStartStopButton.setImageResource(R.drawable.mic_open);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
public class TTS {
|
||||
|
||||
private static final String TAG = "TTS";
|
||||
|
||||
private TextToSpeech mTts;
|
||||
|
||||
public TTS(Context context) {
|
||||
mTts = new TextToSpeech(context, new InitListener());
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
Locale locale = LocaleUtils.parseJavaLocale(language.replace('-', '_'),
|
||||
Locale.getDefault());
|
||||
|
||||
int result = mTts.setLanguage(locale);
|
||||
if (result == TextToSpeech.LANG_MISSING_DATA ||
|
||||
result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
Log.e(TAG, "Language is not available");
|
||||
} else {
|
||||
// TODO: the language may be available for the locale,
|
||||
// but not for the specified country and variant.
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle speak() calls before service connects
|
||||
public void speak(String text) {
|
||||
HashMap<String,String> params = new HashMap<String,String>();
|
||||
// TODO: how can I get network / embedded fallback?
|
||||
// Using both crashes the TTS engine if the offline data is not installed
|
||||
// Using only one doesn't allow the other
|
||||
// params.put(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS, "true");
|
||||
// params.put(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true");
|
||||
mTts.speak(text, TextToSpeech.QUEUE_FLUSH, params);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (mTts != null) {
|
||||
mTts.stop();
|
||||
mTts.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private class InitListener implements TextToSpeech.OnInitListener {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
if (status == TextToSpeech.SUCCESS) {
|
||||
Log.d(TAG, "Initialized TTS");
|
||||
} else {
|
||||
Log.e(TAG, "Failed to initialize TTS");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.grammaticalframework.pgf.Concr;
|
||||
import org.grammaticalframework.pgf.Expr;
|
||||
import org.grammaticalframework.pgf.PGF;
|
||||
import org.grammaticalframework.pgf.ParseError;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Translator {
|
||||
|
||||
private static final String TAG = "Translator";
|
||||
|
||||
// TODO: allow changing
|
||||
private String mGrammar = "ResourceDemo.pgf";
|
||||
|
||||
// TODO: build dynamically?
|
||||
private Language[] mLanguages = {
|
||||
new Language("en-US", "English", "ResourceDemoEng"),
|
||||
new Language("de-DE", "German", "ResourceDemoGer"),
|
||||
new Language("es-ES", "Spanish", "ResourceDemoSpa"),
|
||||
new Language("fr-FR", "French", "ResourceDemoFre"),
|
||||
};
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private Language mSourceLanguage;
|
||||
|
||||
private Language mTargetLanguage;
|
||||
|
||||
private PGF mPgf;
|
||||
|
||||
public Translator(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public List<Language> getAvailableSourceLanguages() {
|
||||
return Arrays.asList(mLanguages);
|
||||
}
|
||||
|
||||
public List<Language> getAvailableTargetLanguages() {
|
||||
return Arrays.asList(mLanguages);
|
||||
}
|
||||
|
||||
public void setSourceLanguage(Language language) {
|
||||
mSourceLanguage = language;
|
||||
}
|
||||
|
||||
public void setTargetLanguage(Language language) {
|
||||
mTargetLanguage = language;
|
||||
}
|
||||
|
||||
public Language getSourceLanguage() {
|
||||
return mSourceLanguage != null ? mSourceLanguage : mLanguages[0];
|
||||
}
|
||||
|
||||
public Language getTargetLanguage() {
|
||||
return mTargetLanguage != null ? mTargetLanguage : mLanguages[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a lot of time. Must not be called on the main thread.
|
||||
*/
|
||||
public void init() {
|
||||
ensureLoaded(mGrammar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a lot of time. Must not be called on the main thread.
|
||||
*/
|
||||
public String translate(String input) {
|
||||
ensureLoaded(mGrammar);
|
||||
return translateInternal(input);
|
||||
}
|
||||
|
||||
private synchronized void ensureLoaded(String grammarName) {
|
||||
if (mPgf != null) return;
|
||||
|
||||
try {
|
||||
// TODO: use PGF API to read this directly from assets
|
||||
Log.d(TAG, "Copying grammar...");
|
||||
File file = copyAsset(grammarName);
|
||||
Log.d(TAG, "Trying to open " + file);
|
||||
mPgf = PGF.readPGF(file.getPath());
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "File not found", e);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error loading grammar", e);
|
||||
}
|
||||
}
|
||||
|
||||
private File copyAsset(String asset) throws IOException {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = getContext().getAssets().open(asset);
|
||||
out = getContext().openFileOutput(asset, Context.MODE_PRIVATE);
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
return getContext().getFileStreamPath(asset);
|
||||
} finally {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String translateInternal(String input) {
|
||||
try {
|
||||
Concr sourceGrammar = getConcr(getSourceLanguage().getConcrete());
|
||||
Expr expr = sourceGrammar.parseBest("S", input);
|
||||
Concr targetGrammar = getConcr(getTargetLanguage().getConcrete());
|
||||
String output = targetGrammar.linearize(expr);
|
||||
return output;
|
||||
} catch (ParseError e) {
|
||||
Log.e(TAG, "Parse error: " + e);
|
||||
return "parse error"; // TODO: no no no
|
||||
}
|
||||
}
|
||||
|
||||
private Concr getConcr(String name) {
|
||||
return mPgf.getLanguages().get(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package se.fnord.android.layout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* ViewGroup that arranges child views in a similar way to text, with them laid
|
||||
* out one line at a time and "wrapping" to the next line as needed.
|
||||
*
|
||||
* Code licensed under CC-by-SA
|
||||
*
|
||||
* @author Henrik Gustafsson
|
||||
* @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
|
||||
* @license http://creativecommons.org/licenses/by-sa/2.5/
|
||||
*
|
||||
*/
|
||||
public class PredicateLayout extends ViewGroup {
|
||||
|
||||
private int line_height;
|
||||
|
||||
public static class LayoutParams extends ViewGroup.LayoutParams {
|
||||
public final int horizontal_spacing;
|
||||
public final int vertical_spacing;
|
||||
|
||||
/**
|
||||
* @param horizontal_spacing Pixels between items, horizontally
|
||||
* @param vertical_spacing Pixels between items, vertically
|
||||
*/
|
||||
public LayoutParams(int horizontal_spacing, int vertical_spacing) {
|
||||
this(0, 0, horizontal_spacing, vertical_spacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param width
|
||||
* @param height
|
||||
* @param horizontal_spacing Pixels between items, horizontally
|
||||
* @param vertical_spacing Pixels between items, vertically
|
||||
*/
|
||||
public LayoutParams(int width, int height, int horizontal_spacing, int vertical_spacing) {
|
||||
super(width, height);
|
||||
this.horizontal_spacing = horizontal_spacing;
|
||||
this.vertical_spacing = vertical_spacing;
|
||||
}
|
||||
}
|
||||
|
||||
public PredicateLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PredicateLayout(Context context, AttributeSet attrs){
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
assert(MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED);
|
||||
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
|
||||
final int count = getChildCount();
|
||||
int line_height = 0;
|
||||
|
||||
int xpos = getPaddingLeft();
|
||||
int ypos = getPaddingTop();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() != GONE) {
|
||||
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
|
||||
|
||||
final int childw = child.getMeasuredWidth();
|
||||
line_height = Math.max(line_height, child.getMeasuredHeight() + lp.vertical_spacing);
|
||||
|
||||
if (xpos + childw > width) {
|
||||
xpos = getPaddingLeft();
|
||||
ypos += line_height;
|
||||
}
|
||||
|
||||
xpos += childw + lp.horizontal_spacing;
|
||||
}
|
||||
}
|
||||
this.line_height = line_height;
|
||||
|
||||
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED){
|
||||
height = ypos + line_height;
|
||||
|
||||
} else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){
|
||||
if (ypos + line_height < height){
|
||||
height = ypos + line_height;
|
||||
}
|
||||
}
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(1, 1); // default of 1px spacing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
||||
if (p instanceof LayoutParams)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int count = getChildCount();
|
||||
final int width = r - l;
|
||||
int xpos = getPaddingLeft();
|
||||
int ypos = getPaddingTop();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() != GONE) {
|
||||
final int childw = child.getMeasuredWidth();
|
||||
final int childh = child.getMeasuredHeight();
|
||||
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
||||
if (xpos + childw > width) {
|
||||
xpos = getPaddingLeft();
|
||||
ypos += line_height;
|
||||
}
|
||||
child.layout(xpos, ypos, xpos + childw, ypos + childh);
|
||||
xpos += childw + lp.horizontal_spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||