diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 06d819c..1cac71f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -87,6 +87,14 @@ + + + + + + + + - \ No newline at end of file + diff --git a/res/drawable/speech_bubbles.png b/res/drawable/speech_bubbles.png new file mode 100644 index 0000000..143f45d Binary files /dev/null and b/res/drawable/speech_bubbles.png differ diff --git a/res/layout-sw600dp/activity_welcome.xml b/res/layout-sw600dp/activity_welcome.xml new file mode 100644 index 0000000..4cac703 --- /dev/null +++ b/res/layout-sw600dp/activity_welcome.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/activity_production_experiment_datum_list.xml b/res/layout/activity_production_experiment_datum_list.xml new file mode 100644 index 0000000..99731a1 --- /dev/null +++ b/res/layout/activity_production_experiment_datum_list.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/res/layout/activity_speech_recognition.xml b/res/layout/activity_speech_recognition.xml new file mode 100644 index 0000000..c709cbe --- /dev/null +++ b/res/layout/activity_speech_recognition.xml @@ -0,0 +1,7 @@ + diff --git a/res/layout/activity_welcome.xml b/res/layout/activity_welcome.xml new file mode 100644 index 0000000..f210dff --- /dev/null +++ b/res/layout/activity_welcome.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/fragment_datum_detail.xml b/res/layout/fragment_datum_detail.xml index 19ba77a..6052cef 100644 --- a/res/layout/fragment_datum_detail.xml +++ b/res/layout/fragment_datum_detail.xml @@ -6,25 +6,25 @@ + + diff --git a/res/layout/fragment_datum_detail_simple.xml b/res/layout/fragment_datum_detail_simple.xml new file mode 100644 index 0000000..ecc6409 --- /dev/null +++ b/res/layout/fragment_datum_detail_simple.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/fragment_datum_speech_recognition_hypotheses.xml b/res/layout/fragment_datum_speech_recognition_hypotheses.xml new file mode 100644 index 0000000..b11ee9b --- /dev/null +++ b/res/layout/fragment_datum_speech_recognition_hypotheses.xml @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/actions_datum_speech_recognition.xml b/res/menu/actions_datum_speech_recognition.xml new file mode 100644 index 0000000..cd5cb7f --- /dev/null +++ b/res/menu/actions_datum_speech_recognition.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index dda359f..883d9a0 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -2,6 +2,7 @@ ქართული ერთად + ქართული speech ფრაზა - + \ No newline at end of file diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 3827dbf..f8df0da 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -1,7 +1,8 @@ - Learn Georgian Together - Phrase + ქართული ერთად + ქართული speech + ფრაზა diff --git a/res/values/strings.xml b/res/values/strings.xml index fc98d5a..f8df0da 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2,6 +2,7 @@ ქართული ერთად + ქართული speech ფრაზა diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/FieldDBApplication.java b/src/com/github/opensourcefieldlinguistics/fielddb/FieldDBApplication.java index 2a1bf6f..8a1ccb7 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/FieldDBApplication.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/FieldDBApplication.java @@ -8,6 +8,7 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.Locale; import org.acra.ACRA; import org.acra.ACRAConfiguration; @@ -16,16 +17,21 @@ import ca.ilanguage.oprime.database.User; import ca.ilanguage.oprime.database.UserContentProvider.UserTable; +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider; import com.github.opensourcefieldlinguistics.fielddb.database.FieldDBUserContentProvider; +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider.DatumTable; import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; -import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.R; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.BuildConfig; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; import com.github.opensourcefieldlinguistics.fielddb.service.DownloadDatumsService; +import com.github.opensourcefieldlinguistics.fielddb.service.KartuliSMSCorpusService; import com.github.opensourcefieldlinguistics.fielddb.service.RegisterUserService; import android.app.Application; import android.content.Context; import android.content.CursorLoader; import android.content.Intent; +import android.content.res.Configuration; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -39,8 +45,10 @@ public class FieldDBApplication extends Application { @Override public final void onCreate() { super.onCreate(); + String language = forceLocale(Config.DATA_IS_ABOUT_LANGUAGE_ISO); + Log.d(Config.TAG, "Forced the locale to " + language); - (new File(Config.DEFAULT_OUTPUT_DIRECTORY)).mkdirs(); + // (new File(Config.DEFAULT_OUTPUT_DIRECTORY)).mkdirs(); ACRAConfiguration config = ACRA.getNewDefaultConfig(this); config.setFormUri(Config.ACRA_SERVER_URL); @@ -90,27 +98,29 @@ public final void onCreate() { ACRA.setConfig(config); - ACRA.init(this); + if (!BuildConfig.DEBUG) + ACRA.init(this); // Get the user from the db - String[] userProjection = { UserTable.COLUMN_ID, UserTable.COLUMN_REV, + String[] userProjection = {UserTable.COLUMN_ID, UserTable.COLUMN_REV, UserTable.COLUMN_USERNAME, UserTable.COLUMN_FIRSTNAME, UserTable.COLUMN_LASTNAME, UserTable.COLUMN_EMAIL, UserTable.COLUMN_GRAVATAR, UserTable.COLUMN_AFFILIATION, UserTable.COLUMN_RESEARCH_INTEREST, - UserTable.COLUMN_DESCRIPTION, UserTable.COLUMN_SUBTITLE }; + UserTable.COLUMN_DESCRIPTION, UserTable.COLUMN_SUBTITLE}; CursorLoader cursorLoader = new CursorLoader(getApplicationContext(), FieldDBUserContentProvider.CONTENT_URI, userProjection, null, null, null); Cursor cursor = cursorLoader.loadInBackground(); cursor.moveToFirst(); String _id = ""; + String username = "default"; if (cursor.getCount() > 0) { _id = cursor.getString(cursor .getColumnIndexOrThrow(UserTable.COLUMN_ID)); String _rev = cursor.getString(cursor .getColumnIndexOrThrow(UserTable.COLUMN_REV)); - String username = cursor.getString(cursor + username = cursor.getString(cursor .getColumnIndexOrThrow(UserTable.COLUMN_USERNAME)); String firstname = cursor.getString(cursor .getColumnIndexOrThrow(UserTable.COLUMN_FIRSTNAME)); @@ -132,13 +142,29 @@ public final void onCreate() { mUser = new User(_id, _rev, username, firstname, lastname, email, gravatar, affiliation, researchInterest, description, subtitle, null, actualJSON); - ACRA.getErrorReporter().putCustomData("username", username); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("username", username); + Config.CURRENT_USERNAME = username; } else { Log.e(Config.TAG, "There is no user... this is a problme the app wont work."); - ACRA.getErrorReporter().putCustomData("username", "unknown"); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("username", "unknown"); + } + /* Make the default corpus point to the user's own corpus */ + Config.DEFAULT_CORPUS = Config.DEFAULT_CORPUS.replace("username", + username); + Config.CURRENT_USERNAME = username; + Config.DEFAULT_OUTPUT_DIRECTORY = "/sdcard/" + + Config.DATA_IS_ABOUT_LANGUAGE_NAME_ASCII + "-" + + Config.APP_TYPE + "/" + Config.DEFAULT_CORPUS; + (new File(Config.DEFAULT_OUTPUT_DIRECTORY)).mkdirs(); + + if (!BuildConfig.DEBUG) { + ACRA.getErrorReporter().putCustomData("dbname", + Config.DEFAULT_CORPUS); } - ACRA.getErrorReporter().putCustomData("dbname", Config.DEFAULT_CORPUS); + Log.d(Config.TAG, cursor.getString(cursor .getColumnIndexOrThrow(UserTable.COLUMN_USERNAME))); cursor.close(); @@ -152,11 +178,29 @@ public final void onCreate() { .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo wifi = connManager .getNetworkInfo(ConnectivityManager.TYPE_WIFI); - if (wifi.isConnected()) { - Intent updateSamples = new Intent(getApplicationContext(), - DownloadDatumsService.class); - getApplicationContext().startService(updateSamples); - + if (Config.APP_TYPE.equals("speechrecognition")) { + Log.d(Config.TAG, + "Not downloading samples, they are included in the training app"); + + String[] datumProjection = {UserTable.COLUMN_ID}; + CursorLoader loader = new CursorLoader(getApplicationContext(), + DatumContentProvider.CONTENT_URI, datumProjection, null, + null, null); + Cursor datumCursor = loader.loadInBackground(); + if (datumCursor.getCount() == 0) { + getContentResolver().insert(DatumContentProvider.CONTENT_URI, + DatumTable.sampleData()); + Intent updateSMSSamples = new Intent(getApplicationContext(), + KartuliSMSCorpusService.class); + getApplicationContext().startService(updateSMSSamples); + } + datumCursor.close(); + } else { + if (wifi.isConnected() || Config.D) { + Intent updateSamples = new Intent(getApplicationContext(), + DownloadDatumsService.class); + getApplicationContext().startService(updateSamples); + } } if (mUser.get_rev() == null || "".equals(mUser.get_rev())) { Intent registerUser = new Intent(getApplicationContext(), @@ -167,4 +211,38 @@ public final void onCreate() { } } + + /** + * Forces the locale for the duration of the app to the language needed for + * that version of the Experiment. It accepts a variable in the form en or + * en-US containing just the language code, or the language code followed by + * a - and the co + * + * @param lang + * @return + */ + public String forceLocale(String lang) { + if (lang.equals(Locale.getDefault().getLanguage())) { + return Locale.getDefault().getDisplayLanguage(); + } + Configuration config = this.getBaseContext().getResources() + .getConfiguration(); + Locale locale; + if (lang.contains("-")) { + String[] langCountrycode = lang.split("-"); + locale = new Locale(langCountrycode[0], langCountrycode[1]); + } else { + locale = new Locale(lang); + } + Locale.setDefault(locale); + config.locale = locale; + this.getBaseContext() + .getResources() + .updateConfiguration( + config, + this.getBaseContext().getResources() + .getDisplayMetrics()); + + return Locale.getDefault().getDisplayLanguage(); + } } diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/database/AudioVideoContentProvider.java b/src/com/github/opensourcefieldlinguistics/fielddb/database/AudioVideoContentProvider.java index e978dc4..c2d9074 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/database/AudioVideoContentProvider.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/database/AudioVideoContentProvider.java @@ -27,6 +27,8 @@ public class AudioVideoContentProvider extends ContentProvider { private static final int ITEM_ID = 20; private static final String AUTHORITY = "com.github.opensourcefieldlinguistics.fielddb." + + Config.APP_TYPE.toLowerCase() + + "." + Config.DATA_IS_ABOUT_LANGUAGE_NAME_ASCII.toLowerCase() + "." + AudioVideoTable.TABLE_NAME; @@ -89,15 +91,15 @@ public Cursor query(Uri uri, String[] projection, String selection, int uriType = sURIMatcher.match(uri); switch (uriType) { - case ITEMS: - break; - case ITEM_ID: - // Adding the ID to the original query - queryBuilder.appendWhere(AudioVideoTable.COLUMN_FILENAME + "='" - + uri.getLastPathSegment() + "'"); - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri); + case ITEMS : + break; + case ITEM_ID : + // Adding the ID to the original query + queryBuilder.appendWhere(AudioVideoTable.COLUMN_FILENAME + "='" + + uri.getLastPathSegment() + "'"); + break; + default : + throw new IllegalArgumentException("Unknown URI: " + uri); } SQLiteDatabase db = database.getWritableDatabase(); @@ -117,23 +119,24 @@ public int update(Uri uri, ContentValues values, String selection, SQLiteDatabase sqlDB = database.getWritableDatabase(); int rowsUpdated = 0; switch (uriType) { - case ITEMS: - rowsUpdated = sqlDB.update(AudioVideoTable.TABLE_NAME, values, - selection, selectionArgs); - break; - case ITEM_ID: - String id = uri.getLastPathSegment(); - if (TextUtils.isEmpty(selection)) { - rowsUpdated = sqlDB.update(AudioVideoTable.TABLE_NAME, values, - AudioVideoTable.COLUMN_ID + "='" + id + "'", null); - } else { + case ITEMS : rowsUpdated = sqlDB.update(AudioVideoTable.TABLE_NAME, values, - AudioVideoTable.COLUMN_ID + "='" + id + "' and " - + selection, selectionArgs); - } - break; - default: - throw new IllegalArgumentException("Unknown Update URI: " + uri); + selection, selectionArgs); + break; + case ITEM_ID : + String id = uri.getLastPathSegment(); + if (TextUtils.isEmpty(selection)) { + rowsUpdated = sqlDB.update(AudioVideoTable.TABLE_NAME, + values, + AudioVideoTable.COLUMN_ID + "='" + id + "'", null); + } else { + rowsUpdated = sqlDB.update(AudioVideoTable.TABLE_NAME, + values, AudioVideoTable.COLUMN_ID + "='" + id + + "' and " + selection, selectionArgs); + } + break; + default : + throw new IllegalArgumentException("Unknown Update URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return rowsUpdated; @@ -222,8 +225,8 @@ public static class AudioVideoTable extends OPrimeTable { public static final String COLUMN_URL = "url"; public static final String COLUMN_DESCRIPTION = "description"; - public static String[] version1Columns = { COLUMN_FILENAME, COLUMN_URL, - COLUMN_DESCRIPTION }; + public static String[] version1Columns = {COLUMN_FILENAME, COLUMN_URL, + COLUMN_DESCRIPTION}; public static String[] currentColumns = version1Columns; diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/database/DatumContentProvider.java b/src/com/github/opensourcefieldlinguistics/fielddb/database/DatumContentProvider.java index 1f603f5..bb26c74 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/database/DatumContentProvider.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/database/DatumContentProvider.java @@ -32,6 +32,8 @@ public class DatumContentProvider extends ContentProvider { private static final int ITEM_ID = 20; private static final String AUTHORITY = "com.github.opensourcefieldlinguistics.fielddb." + + Config.APP_TYPE.toLowerCase() + + "." + Config.DATA_IS_ABOUT_LANGUAGE_NAME_ASCII.toLowerCase() + "." + DatumTable.TABLE_NAME; @@ -112,17 +114,19 @@ public Cursor query(Uri uri, String[] projection, String selection, int uriType = sURIMatcher.match(uri); switch (uriType) { - case ITEMS: -// queryBuilder.appendWhere(DatumTable.COLUMN_TRASHED + " LIKE 'deleted'"); - queryBuilder.appendWhere(DatumTable.COLUMN_TRASHED + " IS NULL"); - break; - case ITEM_ID: - // Adding the ID to the original query - queryBuilder.appendWhere(DatumTable.COLUMN_ID + "='" - + uri.getLastPathSegment() + "'"); - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri); + case ITEMS : + // queryBuilder.appendWhere(DatumTable.COLUMN_TRASHED + + // " LIKE 'deleted'"); + queryBuilder + .appendWhere(DatumTable.COLUMN_TRASHED + " IS NULL"); + break; + case ITEM_ID : + // Adding the ID to the original query + queryBuilder.appendWhere(DatumTable.COLUMN_ID + "='" + + uri.getLastPathSegment() + "'"); + break; + default : + throw new IllegalArgumentException("Unknown URI: " + uri); } SQLiteDatabase db = database.getWritableDatabase(); @@ -142,24 +146,23 @@ public int update(Uri uri, ContentValues values, String selection, SQLiteDatabase sqlDB = database.getWritableDatabase(); int rowsUpdated = 0; switch (uriType) { - case ITEMS: - rowsUpdated = sqlDB.update(DatumTable.TABLE_NAME, values, - selection, selectionArgs); - break; - case ITEM_ID: - String id = uri.getLastPathSegment(); - if (TextUtils.isEmpty(selection)) { + case ITEMS : rowsUpdated = sqlDB.update(DatumTable.TABLE_NAME, values, - DatumTable.COLUMN_ID + "='" + id + "'", null); - } else { - rowsUpdated = sqlDB - .update(DatumTable.TABLE_NAME, values, - DatumTable.COLUMN_ID + "='" + id + "' and " - + selection, selectionArgs); - } - break; - default: - throw new IllegalArgumentException("Unknown Update URI: " + uri); + selection, selectionArgs); + break; + case ITEM_ID : + String id = uri.getLastPathSegment(); + if (TextUtils.isEmpty(selection)) { + rowsUpdated = sqlDB.update(DatumTable.TABLE_NAME, values, + DatumTable.COLUMN_ID + "='" + id + "'", null); + } else { + rowsUpdated = sqlDB.update(DatumTable.TABLE_NAME, values, + DatumTable.COLUMN_ID + "='" + id + "' and " + + selection, selectionArgs); + } + break; + default : + throw new IllegalArgumentException("Unknown Update URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return rowsUpdated; @@ -186,7 +189,7 @@ public void onCreate(SQLiteDatabase db) { NetworkInfo mWifi = connManager .getNetworkInfo(ConnectivityManager.TYPE_WIFI); - if (mWifi.isConnected()) { + if (!Config.APP_TYPE.equals("speechrecognition") && mWifi.isConnected()) { // if the user has a wifi connection we can download some // real sample data Intent downloadSamples = new Intent(getContext(), @@ -194,8 +197,7 @@ public void onCreate(SQLiteDatabase db) { getContext().startService(downloadSamples); } else { // Otherwise, insert offline data - db.insert(DatumTable.TABLE_NAME, null, - DatumTable.sampleData()); + insert(null, DatumTable.sampleData()); } } catch (SQLException e) { @@ -277,24 +279,37 @@ public static class DatumTable extends OPrimeTable { public static final String COLUMN_ENTERED_BY_USER = "enteredByUser"; public static final String COLUMN_MODIFIED_BY_USER = "modifiedByUser"; - public static String[] version1Columns = { COLUMN_UTTERANCE, + public static String[] version1Columns = {COLUMN_UTTERANCE, COLUMN_MORPHEMES, COLUMN_GLOSS, COLUMN_TRANSLATION, COLUMN_ORTHOGRAPHY, COLUMN_CONTEXT, COLUMN_IMAGE_FILES, COLUMN_AUDIO_VIDEO_FILES, COLUMN_LOCATIONS, COLUMN_REMINDERS, COLUMN_TAGS, COLUMN_COMMENTS, COLUMN_VALIDATION_STATUS, - COLUMN_ENTERED_BY_USER, COLUMN_MODIFIED_BY_USER }; + COLUMN_ENTERED_BY_USER, COLUMN_MODIFIED_BY_USER}; public static String[] currentColumns = version1Columns; // Offline Sample data - private static ContentValues sampleData() { + public static ContentValues sampleData() { + // ContentValues values = new ContentValues(); + // values.put(COLUMN_ID, "sample12345"); + // values.put(COLUMN_MORPHEMES, "e'sig"); + // values.put(COLUMN_GLOSS, "clam"); + // values.put(COLUMN_TRANSLATION, "Clam"); + // values.put(COLUMN_ORTHOGRAPHY, "e'sig"); + // values.put(COLUMN_CONTEXT, " "); + // return values; + ContentValues values = new ContentValues(); - values.put(COLUMN_ID, "sample12345"); - values.put(COLUMN_MORPHEMES, "e'sig"); - values.put(COLUMN_GLOSS, "clam"); - values.put(COLUMN_TRANSLATION, "Clam"); - values.put(COLUMN_ORTHOGRAPHY, "e'sig"); - values.put(COLUMN_CONTEXT, " "); + values.put(COLUMN_ID, "instructions"); + values.put( + COLUMN_UTTERANCE, + "You need to read a few sentences to train the recognizer to your voice and your words."); + values.put( + COLUMN_ORTHOGRAPHY, + "შენ უნდა წაიკითხო რამოდენიმე წინადადება, რათა გადაამზადო აპლიკაცია შენს ხმაზე და შენს სიტყვებზე"); + values.put( + COLUMN_CONTEXT, + "The Georgian language is very complex and very different from other languages which were used to build Speech Recognition systems. This means each person should have their own recognizer."); return values; } diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/database/FieldDBUserContentProvider.java b/src/com/github/opensourcefieldlinguistics/fielddb/database/FieldDBUserContentProvider.java index 0998423..120c68b 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/database/FieldDBUserContentProvider.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/database/FieldDBUserContentProvider.java @@ -10,10 +10,12 @@ public class FieldDBUserContentProvider extends UserContentProvider { @Override public boolean onCreate() { + UserTable.ANONYMOUS_PREFIX = Config.ANONYMOUS_USER_PREFIX; if (Config.D) { UserTable.ANONYMOUS_PREFIX = "testing" + UserTable.ANONYMOUS_PREFIX; } AUTHORITY = "com.github.opensourcefieldlinguistics.fielddb." + + Config.APP_TYPE.toLowerCase() + "." + Config.DATA_IS_ABOUT_LANGUAGE_NAME_ASCII.toLowerCase() + "." + UserTable.TABLE_NAME; CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH); diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/Config.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/Config.java index 0f1896c..42268fd 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/Config.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/Config.java @@ -7,20 +7,30 @@ public class Config extends ca.ilanguage.oprime.Config { public static final String ACRA_USER = PrivateConstants.ACRA_USER; public static final String DATA_IS_ABOUT_LANGUAGE_ISO = "ka"; public static final String DATA_IS_ABOUT_LANGUAGE_NAME_ASCII = "kartuli"; -// public static final String DATA_IS_ABOUT_LANGUAGE_NAME = "ქართული"; + public static final String APP_TYPE = "speechrecognition"; + // public static final String DATA_IS_ABOUT_LANGUAGE_NAME = "ქართული"; - public static final String DEFAULT_CORPUS = "community-georgian"; + public static String DEFAULT_CORPUS = "username-" + + DATA_IS_ABOUT_LANGUAGE_NAME_ASCII; + public static String CURRENT_USERNAME = "default"; public static final String DEFAULT_DATA_LOGIN = PrivateConstants.DEFAULT_DATA_SERVER_URL + "/_session"; public static final String DEFAULT_AUTH_LOGIN_URL = PrivateConstants.DEFAULT_AUTH_LOGIN_URL; public static final String DEFAULT_DATA_SERVER_URL = PrivateConstants.DEFAULT_DATA_SERVER_URL; - public static final String DEFAULT_OUTPUT_DIRECTORY = "/sdcard/"+DEFAULT_CORPUS; + public static String DEFAULT_OUTPUT_DIRECTORY = "/sdcard/" + + DATA_IS_ABOUT_LANGUAGE_NAME_ASCII + "-" + APP_TYPE + "/" + + DEFAULT_CORPUS; public static final String DEFAULT_PUBLIC_USER_PASS = PrivateConstants.DEFAULT_PUBLIC_USER_PASS; public static final String DEFAULT_PUBLIC_USERNAME = PrivateConstants.DEFAULT_PUBLIC_USERNAME; + public static final String DEFAULT_UPLOAD_TOKEN = PrivateConstants.DEFAULT_UPLOAD_TOKEN; public static final String DEFAULT_REGISTER_USER_URL = PrivateConstants.DEFAULT_REGISTER_USER_URL; + public static final String DEFAULT_UPLOAD_AUDIO_VIDEO_URL = PrivateConstants.DEFAULT_UPLOAD_AUDIO_VIDEO_URL; public static final String DEFAULT_SAMPLE_DATA_URL = PrivateConstants.DEFAULT_DATA_SERVER_URL + "/" + DEFAULT_CORPUS + "/_design/learnx/_view/byTag"; public static final String KEYSTORE_PASS = PrivateConstants.KEYSTORE_PASS; public static final String USER_FRIENDLY_DATA_NAME = "ფრაზა"; + public static final String ANONYMOUS_USER_PREFIX = "anonymous" + + DATA_IS_ABOUT_LANGUAGE_NAME_ASCII + APP_TYPE; + } diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/PrivateConstantsSample.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/PrivateConstantsSample.java index aad62a5..890d751 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/PrivateConstantsSample.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/PrivateConstantsSample.java @@ -18,6 +18,7 @@ public class PrivateConstantsSample { public static final String DEFAULT_PUBLIC_USER_PASS = "ausername"; public static final String DEFAULT_PUBLIC_USERNAME = "apassword"; public static final String DEFAULT_REGISTER_USER_URL = "http://10.0.2.2:3183/register"; + public static final String DEFAULT_UPLOAD_AUDIO_VIDEO_URL = "http://10.0.2.2:3184/upload/extract/utterances"; /* * Example on how to support self signed ssl certificates by creating a diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailActivity.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailActivity.java index f965bb3..5ea5793 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailActivity.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailActivity.java @@ -1,6 +1,7 @@ package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.R; +import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; import android.content.Intent; import android.os.Bundle; @@ -51,17 +52,17 @@ protected void onCreate(Bundle savedInstanceState) { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: - // This ID represents the Home or Up button. In the case of this - // activity, the Up button is shown. Use NavUtils to allow users - // to navigate up one level in the application structure. For - // more details, see the Navigation pattern on Android Design: - // - // http://developer.android.com/design/patterns/navigation.html#up-vs-back - // - NavUtils.navigateUpTo(this, new Intent(this, - DatumListActivity.class)); - return true; + case android.R.id.home : + // This ID represents the Home or Up button. In the case of this + // activity, the Up button is shown. Use NavUtils to allow users + // to navigate up one level in the application structure. For + // more details, see the Navigation pattern on Android Design: + // + // http://developer.android.com/design/patterns/navigation.html#up-vs-back + // + NavUtils.navigateUpTo(this, new Intent(this, + DatumListActivity.class)); + return true; } return super.onOptionsItemSelected(item); } diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailFragment.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailFragment.java index 638f678..432e027 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailFragment.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumDetailFragment.java @@ -16,10 +16,13 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; +import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.NavUtils; +import android.support.v4.view.ViewPager; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; @@ -30,8 +33,10 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.MediaController; +import android.widget.TextView; import android.widget.VideoView; import ca.ilanguage.oprime.datacollection.AudioRecorder; import ca.ilanguage.oprime.datacollection.TakePicture; @@ -41,7 +46,9 @@ import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider; import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider.DatumTable; import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; -import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.R; +import com.github.opensourcefieldlinguistics.fielddb.service.UploadAudioVideoService; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.BuildConfig; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; import com.github.opensourcefieldlinguistics.fielddb.model.Datum; /** @@ -56,11 +63,13 @@ public class DatumDetailFragment extends Fragment { */ public static final String ARG_ITEM_ID = "item_id"; + public static final String ARG_TOTAL_DATUM_IN_LIST = "total_datum_count_in_list"; + /** * The content this fragment is presenting. */ - private Datum mItem; - private Uri mUri; + protected Datum mItem; + protected Uri mUri; public boolean mTwoPane = false; /** @@ -70,13 +79,22 @@ public class DatumDetailFragment extends Fragment { public DatumDetailFragment() { } - private String TAG = "FieldDB"; - private boolean mRecordingAudio = false; - private VideoView mVideoView; - private ImageView mImageView; - private MediaController mMediaController; + protected boolean mRecordingAudio = false; + protected VideoView mVideoView; + protected ImageView mImageView; + protected MediaController mMediaController; + protected MediaPlayer mAudioPlayer; protected DeviceDetails mDeviceDetails; protected HashMap mDatumEditCounts; + protected ImageButton mSpeechRecognizerFeedback; + protected TextView mSpeechRecognizerInstructions; + protected boolean isPlaying = false; + + protected ViewPager mDatumPager; + + protected int mLastDatumIndex; + + private String mAudioFileName; @Override public void onCreate(Bundle savedInstanceState) { @@ -84,21 +102,23 @@ public void onCreate(Bundle savedInstanceState) { setHasOptionsMenu(true); if (this.mDeviceDetails == null) { this.mDeviceDetails = new DeviceDetails(getActivity(), Config.D, - this.TAG); + Config.TAG); } if (getArguments().containsKey(ARG_ITEM_ID)) { String id = getArguments().getString(ARG_ITEM_ID); + this.mLastDatumIndex = getArguments().getInt( + ARG_TOTAL_DATUM_IN_LIST); Log.d(Config.TAG, "Will get id " + id); String selection = null; String[] selectionArgs = null; String sortOrder = null; - String[] datumProjection = { DatumTable.COLUMN_ORTHOGRAPHY, - DatumTable.COLUMN_MORPHEMES, DatumTable.COLUMN_GLOSS, - DatumTable.COLUMN_TRANSLATION, DatumTable.COLUMN_CONTEXT, - DatumTable.COLUMN_IMAGE_FILES, - DatumTable.COLUMN_AUDIO_VIDEO_FILES }; + String[] datumProjection = {DatumTable.COLUMN_ORTHOGRAPHY, + DatumTable.COLUMN_UTTERANCE, DatumTable.COLUMN_MORPHEMES, + DatumTable.COLUMN_GLOSS, DatumTable.COLUMN_TRANSLATION, + DatumTable.COLUMN_CONTEXT, DatumTable.COLUMN_IMAGE_FILES, + DatumTable.COLUMN_AUDIO_VIDEO_FILES, DatumTable.COLUMN_TAGS}; mUri = Uri.withAppendedPath(DatumContentProvider.CONTENT_URI, id); CursorLoader cursorLoader = new CursorLoader(getActivity(), mUri, datumProjection, selection, selectionArgs, sortOrder); @@ -117,17 +137,24 @@ public void onCreate(Bundle savedInstanceState) { .getColumnIndexOrThrow(DatumTable.COLUMN_TRANSLATION)), cursor.getString(cursor .getColumnIndexOrThrow(DatumTable.COLUMN_CONTEXT))); + datum.setId(id); + datum.setUtterance(cursor.getString(cursor + .getColumnIndexOrThrow(DatumTable.COLUMN_UTTERANCE))); datum.addMediaFiles(cursor.getString(cursor .getColumnIndexOrThrow(DatumTable.COLUMN_IMAGE_FILES))); - datum.addMediaFiles((cursor.getString(cursor .getColumnIndexOrThrow(DatumTable.COLUMN_AUDIO_VIDEO_FILES)))); + + datum.setTagsFromSting(cursor.getString(cursor + .getColumnIndexOrThrow(DatumTable.COLUMN_TAGS))); cursor.close(); mItem = datum; this.recordUserEvent("loadDatum", mUri.getLastPathSegment()); - ACRA.getErrorReporter().putCustomData("urlString", - mUri.toString()); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("urlString", + mUri.toString()); + } } @@ -138,23 +165,33 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_datum_detail, container, false); + if (Config.APP_TYPE.equals("speechrecognition")) { + rootView = inflater.inflate(R.layout.fragment_datum_detail_simple, + container, false); + } if (mItem != null) { + this.prepareEditTextListeners(rootView); + this.prepareVideoAndImages(rootView); + this.prepareSpeechRecognitionButton(rootView); + } - final EditText orthographyEditText = ((EditText) rootView - .findViewById(R.id.orthography)); + return rootView; + } + + protected void prepareEditTextListeners(View rootView) { + final EditText orthographyEditText = ((EditText) rootView + .findViewById(R.id.orthography)); + if (orthographyEditText != null) { orthographyEditText.setText(mItem.getOrthography()); orthographyEditText.addTextChangedListener(new TextWatcher() { - @Override public void afterTextChanged(Editable arg0) { } - @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } - @Override public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { @@ -168,21 +205,21 @@ public void onTextChanged(CharSequence arg0, int arg1, recordUserEvent("editDatum", "orthography"); } }); + } + + final EditText morphemesEditText = ((EditText) rootView + .findViewById(R.id.morphemes)); + if (morphemesEditText != null) { - final EditText morphemesEditText = ((EditText) rootView - .findViewById(R.id.morphemes)); morphemesEditText.setText(mItem.getMorphemes()); morphemesEditText.addTextChangedListener(new TextWatcher() { - @Override public void afterTextChanged(Editable arg0) { } - @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } - @Override public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { @@ -195,21 +232,20 @@ public void onTextChanged(CharSequence arg0, int arg1, recordUserEvent("editDatum", "morphemes"); } }); + } - final EditText glossEditText = ((EditText) rootView - .findViewById(R.id.gloss)); + final EditText glossEditText = ((EditText) rootView + .findViewById(R.id.gloss)); + if (glossEditText != null) { glossEditText.setText(mItem.getGloss()); glossEditText.addTextChangedListener(new TextWatcher() { - @Override public void afterTextChanged(Editable arg0) { } - @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } - @Override public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { @@ -222,12 +258,11 @@ public void onTextChanged(CharSequence arg0, int arg1, recordUserEvent("editDatum", "gloss"); } }); + } - ((EditText) rootView.findViewById(R.id.gloss)).setText(mItem - .getGloss()); - - final EditText translationEditText = ((EditText) rootView - .findViewById(R.id.translation)); + final EditText translationEditText = ((EditText) rootView + .findViewById(R.id.translation)); + if (translationEditText != null) { translationEditText.setText(mItem.getTranslation()); translationEditText.addTextChangedListener(new TextWatcher() { @@ -253,11 +288,11 @@ public void onTextChanged(CharSequence arg0, int arg1, recordUserEvent("editDatum", "translation"); } }); - ((EditText) rootView.findViewById(R.id.translation)).setText(mItem - .getTranslation()); + } - final EditText contextEditText = ((EditText) rootView - .findViewById(R.id.context)); + final EditText contextEditText = ((EditText) rootView + .findViewById(R.id.context)); + if (contextEditText != null) { contextEditText.setText(mItem.getContext()); contextEditText.addTextChangedListener(new TextWatcher() { @@ -279,28 +314,84 @@ public void onTextChanged(CharSequence arg0, int arg1, values.put(DatumTable.COLUMN_CONTEXT, currentText); getActivity().getContentResolver().update(mUri, values, null, null); - recordUserEvent("editDatum", "translation"); + recordUserEvent("editDatum", "context"); } }); - ((EditText) rootView.findViewById(R.id.context)).setText(mItem - .getContext()); - if (mImageView == null) { - mImageView = (ImageView) rootView.findViewById(R.id.image_view); - } - if (mMediaController == null) { - mMediaController = new MediaController(getActivity()); - mMediaController.setAnchorView((VideoView) rootView - .findViewById(R.id.video_view)); - // mMediaController.setPadding(0, 0, 0, 200); - } - if (mVideoView == null) { - mVideoView = (VideoView) rootView.findViewById(R.id.video_view); + } + + final EditText tagsEditText = ((EditText) rootView + .findViewById(R.id.tags)); + if (tagsEditText != null) { + tagsEditText.setText(mItem.getTagsString()); + tagsEditText.addTextChangedListener(new TextWatcher() { + + @Override + public void afterTextChanged(Editable arg0) { + } + + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + String currentText = tagsEditText.getText().toString(); + mItem.setTagsFromSting(currentText); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_TAGS, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "tags"); + } + }); + } + } + + protected void prepareVideoAndImages(View rootView) { + if (mImageView == null) { + mImageView = (ImageView) rootView.findViewById(R.id.image_view); + } + if (mMediaController == null) { + mMediaController = new MediaController(getActivity()); + mMediaController.setAnchorView((VideoView) rootView + .findViewById(R.id.video_view)); + // mMediaController.setPadding(0, 0, 0, 200); + } + if (mVideoView == null) { + mVideoView = (VideoView) rootView.findViewById(R.id.video_view); + if (mVideoView != null) { mVideoView.setMediaController(mMediaController); } - this.loadVisuals(false); } + this.loadVisuals(false); + } - return rootView; + protected void prepareSpeechRecognitionButton(View rootView) { + + mSpeechRecognizerFeedback = (ImageButton) rootView + .findViewById(R.id.speech_recognizer_feedback); + if (mSpeechRecognizerFeedback != null) { + mSpeechRecognizerFeedback + .setImageResource(R.drawable.speech_recognizer_listening); + mSpeechRecognizerFeedback + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + toggleAudioRecording(null); + } + }); + } + + mSpeechRecognizerInstructions = (TextView) rootView + .findViewById(R.id.speech_recognizer_instructions); + if (mSpeechRecognizerInstructions != null) { + mSpeechRecognizerInstructions.setText(""); + } + + this.mDatumPager = (ViewPager) getActivity().findViewById( + R.id.viewpager); } @Override @@ -326,7 +417,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { * or changes, you must update the share intent by again calling * mShareActionProvider.setShareIntent() */ - private Intent getDefaultIntent() { + protected Intent getDefaultIntent() { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("image/*"); return intent; @@ -336,72 +427,149 @@ private Intent getDefaultIntent() { public boolean onOptionsItemSelected(MenuItem item) { // handle item selection switch (item.getItemId()) { - case R.id.action_speak: - if (!this.mRecordingAudio) { - String audioFileName = Config.DEFAULT_OUTPUT_DIRECTORY + "/" - + mItem.getBaseFilename() - + Config.DEFAULT_AUDIO_EXTENSION; - Intent intent; - intent = new Intent(getActivity(), AudioRecorder.class); - intent.putExtra(Config.EXTRA_RESULT_FILENAME, audioFileName); - mItem.addAudioFile(audioFileName.replace( - Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); - getActivity().startService(intent); - ContentValues values = new ContentValues(); - values.put(DatumTable.COLUMN_AUDIO_VIDEO_FILES, - mItem.getMediaFilesAsCSV(mItem.getAudioVideoFiles())); - getActivity().getContentResolver().update(mUri, values, null, - null); - Log.d(TAG, "Recording audio " + audioFileName); - this.mRecordingAudio = true; + case R.id.action_speak : + return this.toggleAudioRecording(item); + case R.id.action_play : + return this.loadMainVideo(true); + case R.id.action_videos : + return this.captureVideo(); + case R.id.action_images : + return this.captureImage(); + case R.id.action_delete : + return this.delete(); + default : + return super.onOptionsItemSelected(item); + } + } + + public void onToggleAudioRecording(View view) { + this.toggleAudioRecording(null); + } + + public boolean toggleAudioRecording(MenuItem item) { + if (!this.mRecordingAudio) { + String audioFileName = Config.DEFAULT_OUTPUT_DIRECTORY + "/" + + mItem.getBaseFilename() + Config.DEFAULT_AUDIO_EXTENSION; + this.mAudioFileName = audioFileName; + Intent intent; + intent = new Intent(getActivity(), AudioRecorder.class); + intent.putExtra(Config.EXTRA_RESULT_FILENAME, audioFileName); + mItem.addAudioFile(audioFileName.replace( + Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); + getActivity().startService(intent); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_AUDIO_VIDEO_FILES, + mItem.getMediaFilesAsCSV(mItem.getAudioVideoFiles())); + getActivity().getContentResolver().update(mUri, values, null, null); + Log.d(Config.TAG, "Recording audio " + audioFileName); + this.mRecordingAudio = true; + if (item != null) { item.setIcon(R.drawable.ic_action_stop); - this.recordUserEvent("captureAudio", audioFileName); + } + this.recordUserEvent("captureAudio", audioFileName); + + if (mSpeechRecognizerFeedback != null) { + mSpeechRecognizerFeedback + .setImageResource(R.drawable.speech_recognizer_recognizing); + } + + if (mSpeechRecognizerInstructions != null) { + mSpeechRecognizerInstructions.setText("Tap to end"); + } + + } else { + this.turnOffRecorder(item); + } + return true; + } + protected boolean turnOffRecorder(MenuItem item) { + if (mAudioFileName == null) { + return false; + } + Intent audio = new Intent(getActivity(), AudioRecorder.class); + getActivity().stopService(audio); + Handler mainHandler = new Handler(getActivity().getMainLooper()); + Runnable launchUploadAudioService = new Runnable() { + + @Override + public void run() { + if (getActivity() == null) { + return; + } + Intent uploadAudioFile = new Intent(getActivity(), + UploadAudioVideoService.class); + uploadAudioFile.setData(Uri.parse(mAudioFileName)); + uploadAudioFile.putExtra(Config.EXTRA_PARTICIPANT_ID, + Config.CURRENT_USERNAME); + uploadAudioFile.putExtra( + Config.EXTRA_EXPERIMENT_TRIAL_INFORMATION, + mDeviceDetails.getCurrentDeviceDetails()); + getActivity().startService(uploadAudioFile); + } + }; + mainHandler.postDelayed(launchUploadAudioService, 1000); + + this.mRecordingAudio = false; + if (item != null) { + item.setIcon(R.drawable.ic_action_mic); + } + this.recordUserEvent("stopAudio", this.mAudioFileName); + + if (mSpeechRecognizerFeedback != null) { + mSpeechRecognizerFeedback + .setImageResource(R.drawable.speech_recognizer_waiting); + } + if (mSpeechRecognizerInstructions != null) { + mSpeechRecognizerInstructions.setText("Tap to speak again"); + } + if (Config.APP_TYPE.equals("speechrecognition")) { + autoAdvanceAfterRecordingAudio(); + } + return true; + } + + protected boolean autoAdvanceAfterRecordingAudio() { + if (this.mDatumPager != null) { + int currentStimulusIndex = this.mDatumPager.getCurrentItem(); + if (currentStimulusIndex == this.mLastDatumIndex) { + Intent openDataList = new Intent(getActivity(), + DatumListActivity.class); + startActivity(openDataList); } else { - Intent audio = new Intent(getActivity(), AudioRecorder.class); - getActivity().stopService(audio); - this.mRecordingAudio = false; - item.setIcon(R.drawable.ic_action_mic); - this.recordUserEvent("stopAudio", ""); + this.mDatumPager.setCurrentItem(this.mDatumPager + .getCurrentItem() + 1); } - return true; - case R.id.action_play: - return this.loadMainVideo(true); - case R.id.action_videos: - return this.captureVideo(); - case R.id.action_images: - return this.captureImage(); - case R.id.action_delete: - return this.delete(); - default: - return super.onOptionsItemSelected(item); } + return true; } - private boolean delete() { + protected boolean delete() { AlertDialog deleteConfirmationDialog = new AlertDialog.Builder( getActivity()) - .setTitle("Are you sure?") + .setTitle(R.string.are_you_sure) .setMessage( - "Are you sure you want to put this " - + Config.USER_FRIENDLY_DATA_NAME - + " in the trash?") + getString(R.string.are_you_sure_put_in_trash).replace( + "datum", Config.USER_FRIENDLY_DATA_NAME)) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - getActivity().getContentResolver() - .delete(mUri, null, null); + getActivity().getContentResolver().delete(mUri, + null, null); dialog.dismiss(); - - if(mTwoPane){ - getActivity().getSupportFragmentManager().popBackStack(); - }else{ - NavUtils.navigateUpTo(getActivity(), new Intent(getActivity(), - DatumListActivity.class)); + + if (mTwoPane) { + getActivity().getSupportFragmentManager() + .popBackStack(); + } else { + NavUtils.navigateUpTo(getActivity(), + new Intent(getActivity(), + DatumListActivity.class)); } - + } }) .setNegativeButton(android.R.string.cancel, @@ -415,8 +583,7 @@ public void onClick(DialogInterface dialog, deleteConfirmationDialog.show(); return true; } - - private void loadVisuals(boolean playImmediately) { + protected void loadVisuals(boolean playImmediately) { loadMainVideo(playImmediately); } @@ -429,61 +596,86 @@ public boolean loadMainVideo(boolean playNow) { this.loadMainImage(); return false; } - mVideoView.setVideoPath(fileName); - if (fileName.endsWith(Config.DEFAULT_AUDIO_EXTENSION)) { - loadMainImage(); - } else { - int sdk = android.os.Build.VERSION.SDK_INT; - if (sdk >= 16) { - mVideoView.setBackground(null); + if (mVideoView != null) { + mVideoView.setVideoPath(fileName); + if (fileName.endsWith(Config.DEFAULT_AUDIO_EXTENSION)) { + loadMainImage(); } else { - Log.e(Config.TAG, - "Couldnt set the video background. (this might be a kindle)"); - mImageView.setImageBitmap(null); - mImageView.setVisibility(View.VISIBLE); - mVideoView.setVisibility(View.GONE); + int sdk = android.os.Build.VERSION.SDK_INT; + if (sdk >= 16) { + mVideoView.setBackground(null); + } else { + Log.e(Config.TAG, + "Couldnt set the video background. (this might be a kindle)"); + mImageView.setImageBitmap(null); + mImageView.setVisibility(View.VISIBLE); + mVideoView.setVisibility(View.GONE); + } } - } - if (playNow) { - this.recordUserEvent("loadMainVideo", fileName); + if (playNow) { + this.recordUserEvent("loadMainVideo", fileName); - mVideoView.start(); - mMediaController.setPrevNextListeners(new View.OnClickListener() { + mVideoView.start(); + mMediaController.setPrevNextListeners( + new View.OnClickListener() { - @Override - public void onClick(View v) { - String filename = mItem.getPrevNextMediaFile("audio", - mItem.getAudioVideoFiles(), "next"); - if (filename != null) { - mVideoView.stopPlayback(); - mVideoView.setVideoPath(Config.DEFAULT_OUTPUT_DIRECTORY - + "/" + filename); - mVideoView.start(); - } - } - }, new View.OnClickListener() { + @Override + public void onClick(View v) { + String filename = mItem.getPrevNextMediaFile( + "audio", mItem.getAudioVideoFiles(), + "next"); + if (filename != null) { + mVideoView.stopPlayback(); + mVideoView + .setVideoPath(Config.DEFAULT_OUTPUT_DIRECTORY + + "/" + filename); + mVideoView.start(); + } + } + }, new View.OnClickListener() { - @Override - public void onClick(View v) { - String filename = mItem.getPrevNextMediaFile("audio", - mItem.getAudioVideoFiles(), "prev"); - if (filename != null) { - mVideoView.stopPlayback(); - mVideoView.setVideoPath(Config.DEFAULT_OUTPUT_DIRECTORY - + "/" + filename); - mVideoView.start(); - } - } - }); + @Override + public void onClick(View v) { + String filename = mItem.getPrevNextMediaFile( + "audio", mItem.getAudioVideoFiles(), + "prev"); + if (filename != null) { + mVideoView.stopPlayback(); + mVideoView + .setVideoPath(Config.DEFAULT_OUTPUT_DIRECTORY + + "/" + filename); + mVideoView.start(); + } + } + }); + } + } else { + Log.d(Config.TAG, "Playing audio only (no video)"); + mAudioPlayer = MediaPlayer.create( + getActivity(), + Uri.parse("file://" + Config.DEFAULT_OUTPUT_DIRECTORY + "/" + + fileName)); + mAudioPlayer + .setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mp.start(); + } + }); } return true; } - @SuppressLint("NewApi") - private void loadMainImage() { + protected void loadMainImage() { File image = new File(Config.DEFAULT_OUTPUT_DIRECTORY + "/" + mItem.getMainImageFile()); if (!image.exists()) { + if (mVideoView != null) { + mVideoView.setVisibility(View.GONE); + } + if (mImageView != null) { + mImageView.setVisibility(View.VISIBLE); + } return; } Bitmap d = new BitmapDrawable(this.getResources(), @@ -493,17 +685,17 @@ private void loadMainImage() { } int nh = (int) (d.getHeight() * (512.0 / d.getWidth())); Bitmap scaled = Bitmap.createScaledBitmap(d, 512, nh, true); - int sdk = android.os.Build.VERSION.SDK_INT; - if (sdk >= 16) { - mVideoView - .setBackground(new BitmapDrawable(getResources(), scaled)); - } else { - Log.e(Config.TAG, - "Couldnt set the video background. (this might be a kindle)"); - mImageView.setImageBitmap(scaled); - mImageView.setVisibility(View.VISIBLE); - mVideoView.setVisibility(View.GONE); - } + // int sdk = android.os.Build.VERSION.SDK_INT; + // if (falssdk >= 16) { + // mVideoView + // .setBackground(new BitmapDrawable(getResources(), scaled)); + // } else { + // Log.e(Config.TAG, + // "Couldnt set the video background. (this might be a kindle)"); + mImageView.setImageBitmap(scaled); + mImageView.setVisibility(View.VISIBLE); + mVideoView.setVisibility(View.GONE); + // } } @@ -514,52 +706,54 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { // } String resultFile; switch (requestCode) { - case Config.CODE_EXPERIMENT_COMPLETED: - if (data != null && data.hasExtra(Config.EXTRA_RESULT_FILENAME)) { - resultFile = data.getExtras().getString( - Config.EXTRA_RESULT_FILENAME); - if (resultFile != null) { - // if (resultFile != null && new File(resultFile).exists()) - // { - if (resultFile.endsWith(Config.DEFAULT_AUDIO_EXTENSION)) { - mItem.addAudioFile(resultFile.replace( - Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); - } else { - mItem.addVideoFile(resultFile.replace( - Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); + case Config.CODE_EXPERIMENT_COMPLETED : + if (data != null && data.hasExtra(Config.EXTRA_RESULT_FILENAME)) { + resultFile = data.getExtras().getString( + Config.EXTRA_RESULT_FILENAME); + if (resultFile != null) { + // if (resultFile != null && new + // File(resultFile).exists()) + // { + if (resultFile.endsWith(Config.DEFAULT_AUDIO_EXTENSION)) { + mItem.addAudioFile(resultFile.replace( + Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); + } else { + mItem.addVideoFile(resultFile.replace( + Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); + } + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_AUDIO_VIDEO_FILES, mItem + .getMediaFilesAsCSV(mItem.getAudioVideoFiles())); + getActivity().getContentResolver().update(mUri, values, + null, null); + this.loadMainVideo(false); } - ContentValues values = new ContentValues(); - values.put(DatumTable.COLUMN_AUDIO_VIDEO_FILES, mItem - .getMediaFilesAsCSV(mItem.getAudioVideoFiles())); - getActivity().getContentResolver().update(mUri, values, - null, null); - this.loadMainVideo(false); } - } - break; - case Config.CODE_PICTURE_TAKEN: - if (data != null && data.hasExtra(Config.EXTRA_RESULT_FILENAME)) { - resultFile = data.getExtras().getString( - Config.EXTRA_RESULT_FILENAME); - if (resultFile != null) { - // if (resultFile != null && new File(resultFile).exists()) - // { - mItem.addImageFile(resultFile.replace( - Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); - ContentValues values = new ContentValues(); - values.put(DatumTable.COLUMN_IMAGE_FILES, - mItem.getMediaFilesAsCSV(mItem.getImageFiles())); - getActivity().getContentResolver().update(mUri, values, - null, null); - this.loadMainImage(); + break; + case Config.CODE_PICTURE_TAKEN : + if (data != null && data.hasExtra(Config.EXTRA_RESULT_FILENAME)) { + resultFile = data.getExtras().getString( + Config.EXTRA_RESULT_FILENAME); + if (resultFile != null) { + // if (resultFile != null && new + // File(resultFile).exists()) + // { + mItem.addImageFile(resultFile.replace( + Config.DEFAULT_OUTPUT_DIRECTORY + "/", "")); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_IMAGE_FILES, + mItem.getMediaFilesAsCSV(mItem.getImageFiles())); + getActivity().getContentResolver().update(mUri, values, + null, null); + this.loadMainImage(); + } } - } - break; + break; } super.onActivityResult(requestCode, requestCode, data); } - private boolean captureVideo() { + protected boolean captureVideo() { String videoFileName = Config.DEFAULT_OUTPUT_DIRECTORY + "/" + mItem.getBaseFilename() + Config.DEFAULT_VIDEO_EXTENSION; Intent intent = new Intent(getActivity(), VideoRecorder.class); @@ -576,7 +770,7 @@ private boolean captureVideo() { return true; } - private boolean captureImage() { + protected boolean captureImage() { String imageFileName = Config.DEFAULT_OUTPUT_DIRECTORY + "/" + mItem.getBaseFilename() + Config.DEFAULT_IMAGE_EXTENSION; Intent intent = new Intent(getActivity(), TakePicture.class); @@ -601,12 +795,22 @@ public void onPause() { it.remove(); // avoids a ConcurrentModificationException } edits = "[" + edits + "]"; + if (Config.D) { + Log.d(Config.TAG, "edits: " + edits); + } recordUserEvent("totalDatumEditsOnPause", edits); } + + if (mAudioPlayer != null) { + // if (mPrompt.isPlaying()) { + // mPrompt.stop(); + // } + mAudioPlayer.release(); + } + super.onPause(); } - - private void recordUserEvent(String eventType, String eventValue) { + protected void recordUserEvent(String eventType, String eventValue) { if ("editDatum".equals(eventType)) { if (this.mDatumEditCounts == null) { this.mDatumEditCounts = new HashMap(); @@ -618,13 +822,15 @@ private void recordUserEvent(String eventType, String eventValue) { this.mDatumEditCounts.put(eventValue, count); return; } - ACRA.getErrorReporter().putCustomData("action", - "{" + eventType + " : " + eventValue + "}"); - ACRA.getErrorReporter().putCustomData("androidTimestamp", - System.currentTimeMillis() + ""); - ACRA.getErrorReporter().putCustomData("deviceDetails", - this.mDeviceDetails.getCurrentDeviceDetails()); - ACRA.getErrorReporter().handleException( - new Exception("*** User event " + eventType + " ***")); + if (!BuildConfig.DEBUG) { + ACRA.getErrorReporter().putCustomData("action", + "{\"" + eventType + "\" : \"" + eventValue + "\"}"); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter().putCustomData("deviceDetails", + this.mDeviceDetails.getCurrentDeviceDetails()); + ACRA.getErrorReporter().handleException( + new Exception("*** User event " + eventType + " ***")); + } } } diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumFragmentPagerAdapter.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumFragmentPagerAdapter.java new file mode 100644 index 0000000..9436fda --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumFragmentPagerAdapter.java @@ -0,0 +1,91 @@ +package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; + +import java.util.ArrayList; + +import ca.ilanguage.oprime.database.UserContentProvider.UserTable; + +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider; +import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; + +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.util.Log; + +public class DatumFragmentPagerAdapter extends FragmentPagerAdapter { + private ArrayList mDatumsIds; + private ArrayList mFragments; + + Uri mVisibleDatumUri; + + public DatumFragmentPagerAdapter(FragmentManager fm) { + super(fm); + mFragments = new ArrayList(); + } + + public void swapCursor(Cursor cursor) { + this.mDatumsIds = new ArrayList(); + this.mDatumsIds.add("instructions"); + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + while (cursor.moveToNext()) { + String id = cursor.getString(cursor + .getColumnIndexOrThrow(UserTable.COLUMN_ID)); + if (!"instructions".equals(id)) { + this.mDatumsIds.add(id); + } + } + cursor.close(); + } + } + @Override + public Fragment getItem(int position) { + Log.d(Config.TAG, "Displaying datum in position " + position); + if (mFragments.size() > position) { + if (mFragments.get(position) != null) { + return mFragments.get(position); + } + } + + String id = "instructions"; + if (mDatumsIds.size() > position) { + id = mDatumsIds.get(position); + } + Bundle arguments = new Bundle(); + DatumDetailFragment fragment = new DatumProductionExperimentFragment(); + if (Config.APP_TYPE.equals("speechrecognition")) { +// fragment = new DatumProductionExperimentFragment(); + } else { + fragment = new DatumDetailFragment(); + } + mVisibleDatumUri = Uri.parse(DatumContentProvider.CONTENT_URI + "/" + + id); + Log.d(Config.TAG, mVisibleDatumUri + ""); + arguments.putParcelable(DatumContentProvider.CONTENT_ITEM_TYPE, + mVisibleDatumUri); + arguments.putString(DatumDetailFragment.ARG_ITEM_ID, id); + arguments.putInt(DatumDetailFragment.ARG_TOTAL_DATUM_IN_LIST, + mDatumsIds.size() - 1); + + fragment.mTwoPane = false; + fragment.setArguments(arguments); + if (mFragments.size() == position ||mFragments.size() < position) { + mFragments.add(fragment); + } else { + mFragments.set(position, fragment); + } + return fragment; + } + + @Override + public int getCount() { + if (mDatumsIds != null) { + return mDatumsIds.size(); + } + return 0; + } + +} diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListActivity.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListActivity.java index b9f3858..dcb638d 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListActivity.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListActivity.java @@ -1,9 +1,8 @@ package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; -import ca.ilanguage.oprime.Config; - import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider; import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.R; +import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; import android.content.Intent; import android.net.Uri; diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListFragment.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListFragment.java index d0da3a4..2c3e5c3 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListFragment.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumListFragment.java @@ -27,6 +27,7 @@ import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider; import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.R; +import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.BuildConfig; /** * A list fragment representing a list of Datums. This fragment also supports @@ -250,7 +251,7 @@ private void fillData() { android.R.layout.simple_list_item_activated_1, null, from, to, 0); setListAdapter(adapter); - ACRA.getErrorReporter().handleException( + if (!BuildConfig.DEBUG) ACRA.getErrorReporter().handleException( new Exception("*** User load datum list ***")); } @@ -292,7 +293,7 @@ public boolean onOptionsItemSelected(MenuItem item) { if (newDatum != null) { mCallbacks.onItemSelected(newDatum.getLastPathSegment()); } else { - ACRA.getErrorReporter().handleException( + if (!BuildConfig.DEBUG) ACRA.getErrorReporter().handleException( new Exception( "*** Error inserting a datum in DB ***")); } diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumProductionExperimentFragment.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumProductionExperimentFragment.java new file mode 100644 index 0000000..49cb51f --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumProductionExperimentFragment.java @@ -0,0 +1,191 @@ +package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.media.MediaPlayer; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.DialogFragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.ImageView; +import ca.ilanguage.oprime.Config; + +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; + +public class DatumProductionExperimentFragment extends DatumDetailFragment { + + protected int mAudioPromptResource; + protected boolean mIsInstructions = false; + protected long WAIT_TO_RECORD_AFTER_PROMPT_START = 400; + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // no menu + } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_production_stimulus, + container, false); + + if (mItem != null) { + this.prepareSpeechRecognitionButton(rootView); + this.prepareVideoAndImages(rootView); + + final TextView orthographyTextView = ((TextView) rootView + .findViewById(R.id.orthography)); + orthographyTextView.setText(mItem.getOrthography()); + + final TextView contextTextView = ((TextView) rootView + .findViewById(R.id.context)); + contextTextView.setText(mItem.getContext()); + + if (mImageView == null) { + mImageView = (ImageView) rootView.findViewById(R.id.image_view); + } + String tags = mItem.getTagsString(); + if (tags.contains("WebSearch")) { + mImageView.setImageResource(R.drawable.search_selected); + } else if (tags.contains("LegalSearch")) { + mImageView.setImageResource(R.drawable.legal_search_selected); + } else if (tags.contains("SMS")) { + mImageView.setImageResource(R.drawable.sms_selected); + } + + String id = mItem.getId(); + Log.d(Config.TAG, "Prompt for this datum will be " + id); + if ("instructions".equals(id)) { + this.mIsInstructions = true; + mAudioPromptResource = R.raw.instructions; + mImageView.setImageResource(R.drawable.instructions); + mSpeechRecognizerFeedback.setVisibility(View.GONE); + mSpeechRecognizerInstructions.setText("Swipe to begin..."); + playPromptContext(); + } else { + mAudioPromptResource = R.raw.prompt; + } + + } + + return rootView; + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (this.isVisible() && !this.isPlaying) { + playPromptContext(); + } + if (!this.isVisible()) { + turnOffRecorder(null); + } + } + + protected void playPromptContext() { + isPlaying = true; + + Log.d(Config.TAG, "Playing prompting context"); + mAudioPlayer = MediaPlayer.create(getActivity(), mAudioPromptResource); + if (mAudioPlayer != null) { + mAudioPlayer + .setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + + @Override + public void onCompletion(MediaPlayer mp) { + mp.release(); + if (mIsInstructions) { + autoAdvanceAfterRecordingAudio(); + } + } + }); +// mAudioPlayer +// .setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { +// +// @Override +// public void onBufferingUpdate(MediaPlayer arg0, int arg1) { +// Log.d(Config.TAG, "Buffering " + arg1); +// } +// }); + mAudioPlayer + .setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mp.start(); + } + }); + + if (mSpeechRecognizerInstructions != null && !mIsInstructions) { + mSpeechRecognizerInstructions.setText("Speak after beep"); + } + } + /* + * begin recording almost immediately so that the user wont speak too + * early + */ + Handler mainHandler = new Handler(getActivity().getMainLooper()); + Runnable myRunnable = new Runnable() { + + @Override + public void run() { + if (!mIsInstructions) { + toggleAudioRecording(null); + } + } + }; + mainHandler.postDelayed(myRunnable, WAIT_TO_RECORD_AFTER_PROMPT_START); + } + + protected boolean autoAdvanceAfterRecordingAudio() { + if (this.mDatumPager != null) { + int currentStimulusIndex = this.mDatumPager.getCurrentItem(); + if (currentStimulusIndex == this.mLastDatumIndex) { + + // Confirm dialog if they want to add their own sentences. + ContinueToAdvancedTraining continueDialog = new ContinueToAdvancedTraining(); + continueDialog.show(getChildFragmentManager(), Config.TAG); + + } else { + this.mDatumPager.setCurrentItem(this.mDatumPager + .getCurrentItem() + 1); + } + } + return true; + } + + public static class ContinueToAdvancedTraining extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.dialog_continue_to_advanced_training) + .setPositiveButton(R.string.continue_word, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int id) { + Intent openTrainer = new Intent( + getActivity(), + DatumListActivity.class); + startActivity(openTrainer); + } + }) + .setNegativeButton(R.string.finished, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int id) { + Intent openRecognizer = new Intent( + getActivity(), + SpeechRecognitionActivity.class); + startActivity(openRecognizer); + } + }); + return builder.create(); + } + } +} diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumSpeechRecognitionHypothesesFragment.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumSpeechRecognitionHypothesesFragment.java new file mode 100644 index 0000000..37e01f5 --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/DatumSpeechRecognitionHypothesesFragment.java @@ -0,0 +1,675 @@ +package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.Handler; +import android.speech.RecognizerIntent; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.TableLayout; +import android.widget.Toast; + +import com.github.opensourcefieldlinguistics.fielddb.FieldDBApplication; +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider.DatumTable; +import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; + +public class DatumSpeechRecognitionHypothesesFragment + extends + DatumProductionExperimentFragment { + + private boolean mHasRecognized; + private boolean mIsRecognizing; + private boolean mPerfectMatch; + private static final int RETURN_FROM_VOICE_RECOGNITION_REQUEST_CODE = 341; + EditText hypothesis1EditText; + EditText hypothesis2EditText; + EditText hypothesis3EditText; + EditText hypothesis4EditText; + EditText hypothesis5EditText; + TableLayout hypothesesArea; + protected long WAIT_TO_RECORD_AFTER_PROMPT_START = 100; + + private static final String[] TAGS = new String[]{"WebSearch", "SMS", + "EMail"}; + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.actions_datum_speech_recognition, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // handle item selection + switch (item.getItemId()) { + case R.id.action_speak : + playSpeechRecognitionPrompt(); + return true; + case R.id.action_delete : + return this.delete(); + default : + return super.onOptionsItemSelected(item); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate( + R.layout.fragment_datum_speech_recognition_hypotheses, + container, false); + + if (mItem != null) { + this.prepareEditTextListeners(rootView); + playSpeechRecognitionPrompt(); + } + + return rootView; + } + + protected void showOrthographyOnly(View rootView) { + if (mHasRecognized == false) { + return; + } + TableLayout datumArea = (TableLayout) rootView + .findViewById(R.id.datumArea); + if (datumArea != null) { + datumArea.setVisibility(View.VISIBLE); + } + + if (hypothesesArea != null) { + hypothesesArea.setVisibility(View.GONE); + } + + final EditText contextEditText = ((EditText) rootView + .findViewById(R.id.context)); + if (contextEditText != null) { + contextEditText.setText(mItem.getContext()); + contextEditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + String currentText = contextEditText.getText().toString(); + mItem.setContext(currentText); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_CONTEXT, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "context"); + } + }); + } + + final AutoCompleteTextView tagsEditText = ((AutoCompleteTextView) rootView + .findViewById(R.id.tags)); + if (tagsEditText != null) { + ArrayAdapter tagsAdapter = new ArrayAdapter( + getActivity(), android.R.layout.simple_dropdown_item_1line, + TAGS); + tagsEditText.setAdapter(tagsAdapter); + tagsEditText.setText(mItem.getTagsString()); + tagsEditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + String currentText = tagsEditText.getText().toString(); + mItem.setTagsFromSting(currentText); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_TAGS, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "tags"); + } + }); + } + + final EditText orthographyEditText = ((EditText) rootView + .findViewById(R.id.orthography)); + if (orthographyEditText != null) { + orthographyEditText.setText(mItem.getOrthography()); + int textLength = mItem.getOrthography().length(); + if (this.mPerfectMatch) { + orthographyEditText.setSelection(0, textLength); + } else { + orthographyEditText.setSelection(textLength, textLength); + } + orthographyEditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + String currentText = orthographyEditText.getText() + .toString(); + mItem.setOrthography(currentText); + contextEditText.setVisibility(View.VISIBLE); + tagsEditText.setVisibility(View.VISIBLE); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_ORTHOGRAPHY, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "orthography"); + } + }); + } + } + + protected void prepareEditTextListeners(final View rootView) { + hypothesesArea = (TableLayout) rootView + .findViewById(R.id.hypothesesArea); + + hypothesis1EditText = ((EditText) rootView + .findViewById(R.id.hypothesis1)); + if (hypothesis1EditText != null) { + // hypothesis1EditText.setText(mItem.getOrthography()); + hypothesis1EditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + if (mHasRecognized == false) { + return; + } + String currentText = hypothesis1EditText.getText() + .toString(); + mItem.setOrthography(currentText); + showOrthographyOnly(rootView); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_ORTHOGRAPHY, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "hypothesis1"); + } + }); + // hypothesis1EditText + // .setOnFocusChangeListener(new OnFocusChangeListener() { + // @Override + // public void onFocusChange(View v, boolean hasFocus) { + // if (!hasFocus) { + // return; + // } + // showOrthographyOnly(rootView); + // String currentText = hypothesis1EditText.getText() + // .toString(); + // mItem.setOrthography(currentText); + // } + // }); + } + + final ImageButton removeHypothesis1Button = (ImageButton) rootView + .findViewById(R.id.removeHypothesis1); + if (removeHypothesis1Button != null) { + removeHypothesis1Button + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeHypothesis1Button.setVisibility(View.GONE); + hypothesis1EditText.setVisibility(View.GONE); + recordUserEvent("removeHypothesis", + "hypothesis1:::" + + hypothesis1EditText.getText() + .toString()); + + } + }); + } + + hypothesis2EditText = ((EditText) rootView + .findViewById(R.id.hypothesis2)); + if (hypothesis2EditText != null) { + // hypothesis2EditText.setText(mItem.getOrthography()); + hypothesis2EditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + if (mHasRecognized == false) { + return; + } + String currentText = hypothesis2EditText.getText() + .toString(); + mItem.setOrthography(currentText); + showOrthographyOnly(rootView); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_ORTHOGRAPHY, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "hypothesis2"); + } + }); + // hypothesis2EditText + // .setOnFocusChangeListener(new OnFocusChangeListener() { + // @Override + // public void onFocusChange(View v, boolean hasFocus) { + // if (!hasFocus) { + // return; + // } + // showOrthographyOnly(rootView); + // String currentText = hypothesis2EditText.getText() + // .toString(); + // mItem.setOrthography(currentText); + // } + // }); + } + + final ImageButton removeHypothesis2Button = (ImageButton) rootView + .findViewById(R.id.removeHypothesis2); + if (removeHypothesis2Button != null) { + removeHypothesis2Button + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeHypothesis2Button.setVisibility(View.GONE); + hypothesis2EditText.setVisibility(View.GONE); + recordUserEvent("removeHypothesis", + "hypothesis2:::" + + hypothesis2EditText.getText() + .toString()); + + } + }); + } + + hypothesis3EditText = ((EditText) rootView + .findViewById(R.id.hypothesis3)); + if (hypothesis3EditText != null) { + // hypothesis3EditText.setText(mItem.getOrthography()); + hypothesis3EditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + if (mHasRecognized == false) { + return; + } + String currentText = hypothesis3EditText.getText() + .toString(); + mItem.setOrthography(currentText); + showOrthographyOnly(rootView); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_ORTHOGRAPHY, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "hypothesis3"); + } + }); + // hypothesis3EditText + // .setOnFocusChangeListener(new OnFocusChangeListener() { + // @Override + // public void onFocusChange(View v, boolean hasFocus) { + // if (!hasFocus) { + // return; + // } + // showOrthographyOnly(rootView); + // String currentText = hypothesis3EditText.getText() + // .toString(); + // mItem.setOrthography(currentText); + // } + // }); + } + + final ImageButton removeHypothesis3Button = (ImageButton) rootView + .findViewById(R.id.removeHypothesis3); + if (removeHypothesis3Button != null) { + removeHypothesis3Button + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeHypothesis3Button.setVisibility(View.GONE); + hypothesis3EditText.setVisibility(View.GONE); + recordUserEvent("removeHypothesis", + "hypothesis3:::" + + hypothesis3EditText.getText() + .toString()); + + } + }); + } + + hypothesis4EditText = ((EditText) rootView + .findViewById(R.id.hypothesis4)); + if (hypothesis4EditText != null) { + // hypothesis4EditText.setText(mItem.getOrthography()); + hypothesis4EditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + if (mHasRecognized == false) { + return; + } + String currentText = hypothesis4EditText.getText() + .toString(); + mItem.setOrthography(currentText); + showOrthographyOnly(rootView); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_ORTHOGRAPHY, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "hypothesis4"); + } + }); + // hypothesis4EditText + // .setOnFocusChangeListener(new OnFocusChangeListener() { + // @Override + // public void onFocusChange(View v, boolean hasFocus) { + // if (!hasFocus) { + // return; + // } + // showOrthographyOnly(rootView); + // String currentText = hypothesis4EditText.getText() + // .toString(); + // mItem.setOrthography(currentText); + // } + // }); + } + + final ImageButton removeHypothesis4Button = (ImageButton) rootView + .findViewById(R.id.removeHypothesis4); + if (removeHypothesis4Button != null) { + removeHypothesis4Button + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeHypothesis4Button.setVisibility(View.GONE); + hypothesis4EditText.setVisibility(View.GONE); + recordUserEvent("removeHypothesis", + "hypothesis4:::" + + hypothesis4EditText.getText() + .toString()); + + } + }); + } + + hypothesis5EditText = ((EditText) rootView + .findViewById(R.id.hypothesis5)); + if (hypothesis5EditText != null) { + // hypothesis5EditText.setText(mItem.getOrthography()); + hypothesis5EditText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable arg0) { + } + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + } + @Override + public void onTextChanged(CharSequence arg0, int arg1, + int arg2, int arg3) { + if (mHasRecognized == false) { + return; + } + String currentText = hypothesis5EditText.getText() + .toString(); + mItem.setOrthography(currentText); + showOrthographyOnly(rootView); + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_ORTHOGRAPHY, currentText); + getActivity().getContentResolver().update(mUri, values, + null, null); + recordUserEvent("editDatum", "hypothesis5"); + } + }); + // hypothesis5EditText + // .setOnFocusChangeListener(new OnFocusChangeListener() { + // @Override + // public void onFocusChange(View v, boolean hasFocus) { + // if (!hasFocus) { + // return; + // } + // showOrthographyOnly(rootView); + // String currentText = hypothesis5EditText.getText() + // .toString(); + // mItem.setOrthography(currentText); + // } + // }); + } + + final ImageButton removeHypothesis5Button = (ImageButton) rootView + .findViewById(R.id.removeHypothesis5); + if (removeHypothesis5Button != null) { + removeHypothesis5Button + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeHypothesis5Button.setVisibility(View.GONE); + hypothesis5EditText.setVisibility(View.GONE); + recordUserEvent("removeHypothesis", + "hypothesis5:::" + + hypothesis5EditText.getText() + .toString()); + + } + }); + } + + } + public void playSpeechRecognitionPrompt() { + this.mIsRecognizing = true; + this.mHasRecognized = false; + mAudioPromptResource = R.raw.im_listening; + playPromptContext(); + Handler mainHandler = new Handler(getActivity().getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + startVoiceRecognitionActivity(); + } + }; + mainHandler.postDelayed(myRunnable, 1000);/* + * make delay long to get some + * idea of what they will say + */ + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (this.isVisible() && !this.mIsRecognizing) { + // playSpeechRecognitionPrompt(); + } + } + + /** + * Fire an intent to start the voice recognition activity. + */ + private void startVoiceRecognitionActivity() { + recordUserEvent("recognizeSpeech", ""); + if (isIntentAvailable(getActivity(), + RecognizerIntent.ACTION_RECOGNIZE_SPEECH)) { + + recordUserEvent("recognizeSpeech", "defaultEngine:::" + + Locale.getDefault().getDisplayLanguage()); + + FieldDBApplication app = (FieldDBApplication) getActivity() + .getApplication(); + app.forceLocale(Config.DATA_IS_ABOUT_LANGUAGE_ISO); + + Toast.makeText( + getActivity(), + "Your voice model is not ready, using the default recognition for your system.", + Toast.LENGTH_LONG).show(); + + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + intent.putExtra(RecognizerIntent.EXTRA_PROMPT, + getString(R.string.im_listening)); + startActivityForResult(intent, + RETURN_FROM_VOICE_RECOGNITION_REQUEST_CODE); + } else { + Toast.makeText( + getActivity(), + "You have no speech recognition engine installed so we cannot provide the default recognizer for you. You must wait for your model to be downloaded.", + Toast.LENGTH_LONG).show(); + + recordUserEvent("recognizeSpeech", "noRecognizerIntent:::" + + Locale.getDefault().getDisplayLanguage()); + Handler mainHandler = new Handler(getActivity().getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }; + mainHandler.postDelayed(myRunnable, 200); + } + + } + + /** + * Handle the results from the voice recognition activity. + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + getActivity(); + if (requestCode == RETURN_FROM_VOICE_RECOGNITION_REQUEST_CODE + && resultCode == Activity.RESULT_OK) { + turnOffRecorder(null); + hypothesesArea.setVisibility(View.VISIBLE); + /* + * Populate the wordsList with the String values the recognition + * engine thought it heard, and then Toast them to the user and say + * them out loud. + */ + ArrayList matches = data + .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); + + if (hypothesis1EditText != null) { + if (matches.size() > 0 && matches.get(0) != null) { + hypothesis1EditText.setText(matches.get(0)); + } else { + // hypothesis1EditText.setVisibility(View.GONE); + } + // hypothesis1EditText.clearFocus(); + } + if (hypothesis2EditText != null) { + if (matches.size() > 1 && matches.get(1) != null) { + hypothesis2EditText.setText(matches.get(1)); + } else { + // hypothesis2EditText.setVisibility(View.GONE); + } + // hypothesis2EditText.clearFocus(); + } + if (hypothesis3EditText != null) { + if (matches.size() > 2 && matches.get(2) != null) { + hypothesis3EditText.setText(matches.get(2)); + } else { + // hypothesis3EditText.setVisibility(View.GONE); + } + // hypothesis3EditText.clearFocus(); + } + if (hypothesis4EditText != null) { + if (matches.size() > 3 && matches.get(3) != null) { + hypothesis4EditText.setText(matches.get(3)); + } else { + // hypothesis4EditText.setVisibility(View.GONE); + } + // hypothesis4EditText.clearFocus(); + } + if (hypothesis5EditText != null) { + if (matches.size() > 4 && matches.get(4) != null) { + hypothesis5EditText.setText(matches.get(4)); + } else { + // hypothesis5EditText.setVisibility(View.GONE); + } + // hypothesis5EditText.clearFocus(); + } + + if (matches.size() > 0) { + this.mHasRecognized = true; + } else { + /* make it possible for the user to create a datum anyway. */ + this.mHasRecognized = true; + } + + if (matches.size() == 1) { + // Trigger hypothesis 1 to be the orthography + hypothesis1EditText.setText(matches.get(0)); + this.mPerfectMatch = true; + + } + recordUserEvent("recognizedHypotheses", matches.toString()); + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onPause() { + turnOffRecorder(null); + super.onPause(); + } + + public static boolean isIntentAvailable(Context context, String action) { + final PackageManager packageManager = context.getPackageManager(); + final Intent intent = new Intent(action); + List list = packageManager.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } + +} diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/ListenAndRepeat.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/ListenAndRepeat.java new file mode 100644 index 0000000..6684104 --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/ListenAndRepeat.java @@ -0,0 +1,136 @@ +package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; + +import java.util.ArrayList; +import java.util.Locale; + +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.speech.RecognizerIntent; +import android.speech.tts.TextToSpeech; +import android.speech.tts.TextToSpeech.OnInitListener; +import android.util.Log; +import android.widget.Toast; + +/** + * + * Building on what we saw in MakeItTalk, now lets make it Listen. Here is some + * super simple code that uses the VoiceRecognition Intent to recognize what the + * user says, and then uses Text To Speech to tell the user what it might have + * heard. + * + * + */ +public class ListenAndRepeat extends Activity implements OnInitListener { + private static final String TAG = "MakeItListen"; + private static final int RETURN_FROM_VOICE_RECOGNITION_REQUEST_CODE = 341; + /** Talk to the user */ + private TextToSpeech mTts; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mTts = new TextToSpeech(this, this); + + } + + protected void promptTheUserToTalk() { + mTts.speak(getString(R.string.im_listening), TextToSpeech.QUEUE_ADD, + null); + } + + /** + * Fire an intent to start the voice recognition activity. + */ + private void startVoiceRecognitionActivity() { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + intent.putExtra(RecognizerIntent.EXTRA_PROMPT, + getString(R.string.im_listening)); + startActivityForResult(intent, + RETURN_FROM_VOICE_RECOGNITION_REQUEST_CODE); + } + + /** + * Handle the results from the voice recognition activity. + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == RETURN_FROM_VOICE_RECOGNITION_REQUEST_CODE + && resultCode == RESULT_OK) { + /* + * Populate the wordsList with the String values the recognition + * engine thought it heard, and then Toast them to the user and say + * them out loud. + */ + ArrayList matches = data + .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); + for (int iMightHaveHeardThis = 0; iMightHaveHeardThis < matches + .size(); iMightHaveHeardThis++) { + + /* Build a carrierPhrase if you want it to make some sense */ + String carrierPhrase = getString(R.string.i_might_have_heard); + if (iMightHaveHeardThis > 0) { + carrierPhrase = getString(R.string.or_maybe); + } + carrierPhrase += " " + matches.get(iMightHaveHeardThis) + "."; + + Toast.makeText(this, carrierPhrase, Toast.LENGTH_LONG).show(); + mTts.speak(carrierPhrase, TextToSpeech.QUEUE_ADD, null); + + /* + * Don't go on forever, it there are too many potential matches + * don't say them all + */ + if (iMightHaveHeardThis == 2 && matches.size() > 2) { + mTts.speak(getString(R.string.there_were_others), + TextToSpeech.QUEUE_ADD, null); + break; + } + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onDestroy() { + if (mTts != null) { + mTts.stop(); + mTts.shutdown(); + } + super.onDestroy(); + } + + @Override + public void onInit(int status) { + if (status == TextToSpeech.SUCCESS) { + int result = mTts.setLanguage(Locale.getDefault()); + if (result == TextToSpeech.LANG_MISSING_DATA + || result == TextToSpeech.LANG_NOT_SUPPORTED) { + Log.e(TAG, "Language is not available."); + Toast.makeText( + this, + "The " + + Locale.getDefault().getDisplayLanguage() + + " TextToSpeech isn't installed, you can go into the " + + "\nAndroid's settings in the " + + "\nVoice Input and Output menu to turn it on. ", + Toast.LENGTH_LONG).show(); + } else { + // everything is working. + promptTheUserToTalk(); + startVoiceRecognitionActivity(); + } + } else { + Toast.makeText( + this, + "Sorry, I can't talk to you because " + + "I could not initialize TextToSpeech.", + Toast.LENGTH_LONG).show(); + } + } +} \ No newline at end of file diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/ProductionExperimentActivity.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/ProductionExperimentActivity.java new file mode 100644 index 0000000..6abc195 --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/ProductionExperimentActivity.java @@ -0,0 +1,83 @@ +package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; + +import ca.ilanguage.oprime.Config; +import ca.ilanguage.oprime.datacollection.AudioRecorder; + +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider; +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider.DatumTable; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; + +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.view.ViewPager; +import android.util.Log; + +public class ProductionExperimentActivity extends FragmentActivity + implements + LoaderManager.LoaderCallbacks { + + private DatumFragmentPagerAdapter mPagerAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.setContentView(R.layout.activity_production_experiment_datum_list); + + this.initialisePaging(); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + String[] projection = {DatumTable.COLUMN_ID}; + String filterStr = "AutomaticallyRecognized"; + String selection = DatumTable.COLUMN_VALIDATION_STATUS + " IS NULL OR " + + DatumTable.COLUMN_VALIDATION_STATUS + " NOT LIKE ? "; + String[] selectionArgs = new String[]{"%" + filterStr + "%"}; + + CursorLoader cursorLoader = new CursorLoader(this, + DatumContentProvider.CONTENT_URI, projection, selection, + selectionArgs, null); + Cursor cursor = cursorLoader.loadInBackground(); + this.mPagerAdapter.swapCursor(cursor); + + return cursorLoader; + } + @Override + public void onLoadFinished(Loader loader, Cursor data) { + + Log.d(Config.TAG, "Finished loading the ids for swipe paging"); + this.mPagerAdapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + this.mPagerAdapter.swapCursor(null); + } + + /** + * Initialise the fragments to be paged + */ + private void initialisePaging() { + + this.mPagerAdapter = new DatumFragmentPagerAdapter( + super.getSupportFragmentManager()); + this.onCreateLoader(0, null); + + ViewPager pager = (ViewPager) super.findViewById(R.id.viewpager); + pager.setAdapter(this.mPagerAdapter); + } + + @Override + public void onBackPressed() { + Intent audio = new Intent(this, AudioRecorder.class); + this.stopService(audio); + + super.onBackPressed(); + } + +} diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/SpeechRecognitionActivity.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/SpeechRecognitionActivity.java new file mode 100644 index 0000000..b1fcc34 --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/SpeechRecognitionActivity.java @@ -0,0 +1,55 @@ +package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; + +import org.acra.ACRA; + +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider; +import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider.DatumTable; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.BuildConfig; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; + +import android.content.ContentValues; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +public class SpeechRecognitionActivity extends FragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.setContentView(R.layout.activity_speech_recognition); + + // savedInstanceState is non-null when there is fragment state + // saved from previous configurations of this activity + // (e.g. when rotating the screen from portrait to landscape). + // In this case, the fragment will automatically be re-added + // to its container so we don't need to manually add it. + // For more information, see the Fragments API guide at: + // + // http://developer.android.com/guide/components/fragments.html + // + if (savedInstanceState == null) { + // Create the detail fragment and add it to the activity + // using a fragment transaction. + ContentValues values = new ContentValues(); + values.put(DatumTable.COLUMN_VALIDATION_STATUS, + "ToBeChecked,AutomaticallyRecognized"); + Uri newDatum = this.getContentResolver().insert( + DatumContentProvider.CONTENT_URI, values); + if (newDatum == null) { + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter() + .handleException( + new Exception( + "*** Error inserting a speech recognition datum in DB ***")); + } + Bundle arguments = new Bundle(); + arguments.putString(DatumDetailFragment.ARG_ITEM_ID, + newDatum.getLastPathSegment()); + DatumSpeechRecognitionHypothesesFragment fragment = new DatumSpeechRecognitionHypothesesFragment(); + fragment.setArguments(arguments); + getSupportFragmentManager().beginTransaction() + .add(R.id.datum_detail_container, fragment).commit(); + } + } +} diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/WelcomeActivity.java b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/WelcomeActivity.java new file mode 100644 index 0000000..8cebf88 --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/lessons/ui/WelcomeActivity.java @@ -0,0 +1,35 @@ +package com.github.opensourcefieldlinguistics.fielddb.lessons.ui; + +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; + +public class WelcomeActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.setContentView(R.layout.activity_welcome); + + } + + public void onTrainClick(View view) { + Intent openTrainer = new Intent(this, + ProductionExperimentActivity.class); + startActivity(openTrainer); + } + + public void onRecognizeClick(View view) { + Intent openRecognizer = new Intent(this, SpeechRecognitionActivity.class); + startActivity(openRecognizer); + } + + public void goToWebSite(View view) { + Intent go = new Intent(Intent.ACTION_VIEW).setData(Uri + .parse("http://batumi.github.io")); + startActivity(go); + } +} diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/model/Datum.java b/src/com/github/opensourcefieldlinguistics/fielddb/model/Datum.java index f7a4915..205a97c 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/model/Datum.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/model/Datum.java @@ -1,6 +1,7 @@ package com.github.opensourcefieldlinguistics.fielddb.model; import java.util.ArrayList; +import java.util.Arrays; import android.util.Log; @@ -23,6 +24,7 @@ public class Datum { protected ArrayList related; protected ArrayList reminders; protected ArrayList tags; + protected ArrayList validationStati; protected ArrayList coments; protected String actualJSON; @@ -33,7 +35,8 @@ public Datum(String id, String rev, DatumField utterance, ArrayList audioVideoFiles, ArrayList videoFiles, ArrayList locations, ArrayList related, ArrayList reminders, - ArrayList tags, ArrayList coments, String actualJSON) { + ArrayList tags, ArrayList validationStati, + ArrayList coments, String actualJSON) { super(); this._id = id; this._rev = rev; @@ -49,6 +52,7 @@ public Datum(String id, String rev, DatumField utterance, this.related = related; this.reminders = reminders; this.tags = tags; + this.validationStati = validationStati; this.coments = coments; this.actualJSON = actualJSON; } @@ -68,6 +72,7 @@ public Datum(String orthography) { this.related = new ArrayList(); this.reminders = new ArrayList(); this.tags = new ArrayList(); + this.validationStati = new ArrayList(); this.coments = new ArrayList(); this.actualJSON = ""; } @@ -75,7 +80,7 @@ public Datum(String orthography) { public Datum(String orthography, String morphemes, String gloss, String translation) { super(); - this._id = System.currentTimeMillis() + translation; + this._id = System.currentTimeMillis() + ""; this.utterance = new DatumField("utterance", orthography); this.morphemes = new DatumField("morphemes", morphemes); this.gloss = new DatumField("gloss", gloss); @@ -88,6 +93,7 @@ public Datum(String orthography, String morphemes, String gloss, this.related = new ArrayList(); this.reminders = new ArrayList(); this.tags = new ArrayList(); + this.validationStati = new ArrayList(); this.coments = new ArrayList(); this.actualJSON = ""; } @@ -95,7 +101,7 @@ public Datum(String orthography, String morphemes, String gloss, public Datum(String orthography, String morphemes, String gloss, String translation, String context) { super(); - this._id = System.currentTimeMillis() + translation; + this._id = System.currentTimeMillis() + ""; this.utterance = new DatumField("utterance", orthography); this.morphemes = new DatumField("morphemes", morphemes); this.gloss = new DatumField("gloss", gloss); @@ -108,6 +114,7 @@ public Datum(String orthography, String morphemes, String gloss, this.related = new ArrayList(); this.reminders = new ArrayList(); this.tags = new ArrayList(); + this.validationStati = new ArrayList(); this.coments = new ArrayList(); this.actualJSON = ""; } @@ -127,6 +134,7 @@ public Datum() { this.related = new ArrayList(); this.reminders = new ArrayList(); this.tags = new ArrayList(); + this.validationStati = new ArrayList(); this.coments = new ArrayList(); this.actualJSON = ""; } @@ -255,10 +263,53 @@ public ArrayList getTags() { return tags; } + public String getTagsString() { + String result = ""; + for (String tag : this.tags) { + if (!"".equals(result)) { + result += ","; + } + result += tag; + } + return result; + } + + public void setTagsFromSting(String tags) { + if (tags != null && !"".equals(tags)) { + this.tags = new ArrayList(Arrays.asList(tags.split(","))); + } + } + public void setTags(ArrayList tags) { this.tags = tags; } + public ArrayList getValidationStati() { + return validationStati; + } + + public String getValidationStatiString() { + String result = ""; + for (String validationStatus : this.validationStati) { + if (!"".equals(result)) { + result += ","; + } + result += validationStatus; + } + return result; + } + + public void setValidationStatiFromSting(String validationStati) { + if (validationStati != null && !"".equals(validationStati)) { + this.validationStati = new ArrayList( + Arrays.asList(validationStati.split(","))); + } + } + + public void setValidationStati(ArrayList validationStati) { + this.validationStati = validationStati; + } + public ArrayList getComents() { return coments; } @@ -530,7 +581,7 @@ public String getBaseFilename() { } if (filenameBasedOnMorphemesOrWhateverIsAvailable == null || "".equals(filenameBasedOnMorphemesOrWhateverIsAvailable)) { - filenameBasedOnMorphemesOrWhateverIsAvailable = "unknown"; + filenameBasedOnMorphemesOrWhateverIsAvailable = "audio"; } filenameBasedOnMorphemesOrWhateverIsAvailable = Config .getSafeUri(filenameBasedOnMorphemesOrWhateverIsAvailable); diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/service/DownloadDatumsService.java b/src/com/github/opensourcefieldlinguistics/fielddb/service/DownloadDatumsService.java index fbc78c3..a099985 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/service/DownloadDatumsService.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/service/DownloadDatumsService.java @@ -19,7 +19,8 @@ import com.github.opensourcefieldlinguistics.fielddb.database.AudioVideoContentProvider.AudioVideoTable; import com.github.opensourcefieldlinguistics.fielddb.database.DatumContentProvider.DatumTable; import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; -import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.R; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.BuildConfig; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -46,6 +47,11 @@ public DownloadDatumsService() { @Override protected void onHandleIntent(Intent intent) { + + if (Config.D) { + return; + } + this.D = Config.D; this.statusMessage = "Downloading samples " + Config.USER_FRIENDLY_DATA_NAME; @@ -62,17 +68,27 @@ protected void onHandleIntent(Intent intent) { Log.d(Config.TAG, this.urlStringSampleDataDownload); } - ACRA.getErrorReporter().putCustomData("action", - "downloadDatums:::" + datumTagToDownload); - ACRA.getErrorReporter().putCustomData("urlString", - this.urlStringSampleDataDownload); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("action", + "downloadDatums:::" + datumTagToDownload); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("urlString", + this.urlStringSampleDataDownload); super.onHandleIntent(intent); if (!"".equals(this.userFriendlyErrorMessage)) { this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, this.notificationId, true); - ACRA.getErrorReporter().handleException( - new Exception(this.userFriendlyErrorMessage)); + if (!BuildConfig.DEBUG){ + ACRA.getErrorReporter().putCustomData("action", + "downloadDatums:::" + datumTagToDownload); + ACRA.getErrorReporter().putCustomData("urlString", + this.urlStringSampleDataDownload); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); + } return; } @@ -81,8 +97,16 @@ protected void onHandleIntent(Intent intent) { if (!"".equals(this.userFriendlyErrorMessage)) { this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, this.notificationId, true); - ACRA.getErrorReporter().handleException( - new Exception(this.userFriendlyErrorMessage)); + if (!BuildConfig.DEBUG){ + ACRA.getErrorReporter().putCustomData("action", + "downloadDatums:::" + datumTagToDownload); + ACRA.getErrorReporter().putCustomData("urlString", + this.urlStringSampleDataDownload); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); + } return; } @@ -90,8 +114,16 @@ protected void onHandleIntent(Intent intent) { if (!"".equals(this.userFriendlyErrorMessage)) { this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, this.notificationId, true); - ACRA.getErrorReporter().handleException( - new Exception(this.userFriendlyErrorMessage)); + if (!BuildConfig.DEBUG){ + ACRA.getErrorReporter().putCustomData("action", + "downloadDatums:::" + datumTagToDownload); + ACRA.getErrorReporter().putCustomData("urlString", + this.urlStringSampleDataDownload); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); + } return; } @@ -99,20 +131,32 @@ protected void onHandleIntent(Intent intent) { if (!"".equals(this.userFriendlyErrorMessage)) { this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, this.notificationId, true); - ACRA.getErrorReporter().handleException( - new Exception(this.userFriendlyErrorMessage)); + if (!BuildConfig.DEBUG){ + ACRA.getErrorReporter().putCustomData("action", + "downloadDatums:::" + datumTagToDownload); + ACRA.getErrorReporter().putCustomData("urlString", + this.urlStringSampleDataDownload); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); + } return; } /* Success: remove the notification */ ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)) .cancel(this.notificationId); - ACRA.getErrorReporter().putCustomData("action", - "downloadDatums:::" + datumTagToDownload); - ACRA.getErrorReporter().putCustomData("urlString", - this.urlStringSampleDataDownload); - ACRA.getErrorReporter().handleException( - new Exception("*** Downloaded data sucessfully ***")); + if (!BuildConfig.DEBUG) { + ACRA.getErrorReporter().putCustomData("action", + "downloadDatums:::" + datumTagToDownload); + ACRA.getErrorReporter().putCustomData("urlString", + this.urlStringSampleDataDownload); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter().handleException( + new Exception("*** Downloaded data sucessfully ***")); + } } public void getSampleData() { @@ -162,7 +206,7 @@ public void processCouchDBMapResponse() { JsonObject datumJson; String id = ""; Uri uri; - String[] datumProjection = { DatumTable.COLUMN_ID }; + String[] datumProjection = {DatumTable.COLUMN_ID}; Cursor cursor; String mediaFilesAsString = ""; ContentValues datumAsValues; @@ -244,7 +288,7 @@ public void processCouchDBMapResponse() { Log.d(Config.TAG, "TODO download the image and audio files through a filter that makes them smaller... "); } - // ACRA.getErrorReporter().handleException( + // if (!BuildConfig.DEBUG) ACRA.getErrorReporter().handleException( // new Exception("*** Download Data Completed ***")); return; } @@ -323,13 +367,18 @@ public void downloadMediaFile(String mediaFileUrl) { } output.close(); this.statusMessage = "Downloaded " + filename; - ACRA.getErrorReporter().putCustomData("action", - "downloadMedia:::" + filename); - ACRA.getErrorReporter() - .putCustomData("urlString", mediaFileUrl); - ACRA.getErrorReporter().handleException( - new Exception( - "*** Downloaded media file sucessfully ***")); + if (!BuildConfig.DEBUG) { + ACRA.getErrorReporter().putCustomData("action", + "downloadMedia:::" + filename); + ACRA.getErrorReporter().putCustomData("urlString", + mediaFileUrl); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter() + .handleException( + new Exception( + "*** Downloaded media file sucessfully ***")); + } } else { this.userFriendlyErrorMessage = "Server replied " + status; } @@ -345,7 +394,7 @@ public void downloadMediaFile(String mediaFileUrl) { public Uri insertMediaFileInDB(String url) { String filename = Uri.parse(url).getLastPathSegment(); - String[] audioVideoProjection = { AudioVideoTable.COLUMN_FILENAME }; + String[] audioVideoProjection = {AudioVideoTable.COLUMN_FILENAME}; Uri uri = Uri.withAppendedPath(AudioVideoContentProvider.CONTENT_URI, filename); diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/service/RegisterUserService.java b/src/com/github/opensourcefieldlinguistics/fielddb/service/RegisterUserService.java index e0cc281..2186183 100644 --- a/src/com/github/opensourcefieldlinguistics/fielddb/service/RegisterUserService.java +++ b/src/com/github/opensourcefieldlinguistics/fielddb/service/RegisterUserService.java @@ -15,7 +15,8 @@ import com.github.opensourcefieldlinguistics.fielddb.database.FieldDBUserContentProvider; import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; -import com.github.opensourcefieldlinguistics.fielddb.lessons.georgian.R; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.BuildConfig; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; import com.google.gson.JsonObject; import android.app.NotificationManager; @@ -45,17 +46,20 @@ protected void onHandleIntent(Intent intent) { if (Config.D) { Log.d(Config.TAG, "Inside RegisterUserService intent"); } - ACRA.getErrorReporter().putCustomData("action", "registerUser:::"); - ACRA.getErrorReporter().putCustomData("urlString", - Config.DEFAULT_REGISTER_USER_URL); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("action", "registerUser:::"); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("urlString", + Config.DEFAULT_REGISTER_USER_URL); super.onHandleIntent(intent); if (!"".equals(this.userFriendlyErrorMessage)) { this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, this.notificationId, true); - ACRA.getErrorReporter().handleException( - new Exception(this.userFriendlyErrorMessage)); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); return; } @@ -66,8 +70,9 @@ protected void onHandleIntent(Intent intent) { if (!"".equals(this.userFriendlyErrorMessage)) { this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, this.notificationId, true); - ACRA.getErrorReporter().handleException( - new Exception(this.userFriendlyErrorMessage)); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); return; } @@ -75,8 +80,9 @@ protected void onHandleIntent(Intent intent) { if (!"".equals(this.userFriendlyErrorMessage)) { this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, this.notificationId, true); - ACRA.getErrorReporter().handleException( - new Exception(this.userFriendlyErrorMessage)); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); return; } @@ -87,8 +93,9 @@ protected void onHandleIntent(Intent intent) { /* Success: remove the notification */ ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)) .cancel(this.notificationId); - ACRA.getErrorReporter().handleException( - new Exception("*** Registered user ssucessfully ***")); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception("*** Registered user ssucessfully ***")); } public String loginUser(String username, String password, String loginUrl) { @@ -138,20 +145,25 @@ public String loginUser(String username, String password, String loginUrl) { } public String registerUsers(Uri uri) { - String[] userProjection = { UserTable.COLUMN_REV, + String[] userProjection = {UserTable.COLUMN_REV, UserTable.COLUMN_USERNAME, UserTable.COLUMN_FIRSTNAME, UserTable.COLUMN_LASTNAME, UserTable.COLUMN_EMAIL, UserTable.COLUMN_GRAVATAR, UserTable.COLUMN_AFFILIATION, UserTable.COLUMN_RESEARCH_INTEREST, UserTable.COLUMN_DESCRIPTION, UserTable.COLUMN_SUBTITLE, UserTable.COLUMN_GENERATED_PASSWORD, - UserTable.COLUMN_APP_VERSIONS_WHEN_MODIFIED }; + UserTable.COLUMN_APP_VERSIONS_WHEN_MODIFIED}; if (uri == null) { uri = FieldDBUserContentProvider.CONTENT_URI; } CursorLoader cursorLoader = new CursorLoader(getApplicationContext(), uri, userProjection, null, null, null); Cursor cursor = cursorLoader.loadInBackground(); + if (cursor == null) { + Log.e(Config.TAG, + "There is no user... this is not supposed to happen."); + return null; + } cursor.moveToFirst(); if (cursor.getCount() == 0) { Log.e(Config.TAG, @@ -197,7 +209,8 @@ public String registerUsers(Uri uri) { cursor.close(); this.statusMessage = "Registering user " + username; - ACRA.getErrorReporter().putCustomData("registerUser", username); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("registerUser", username); String urlStringAuthenticationSession = Config.DEFAULT_REGISTER_USER_URL; URL url; HttpURLConnection urlConnection; @@ -264,6 +277,10 @@ public String registerUsers(Uri uri) { return null; } String JSONResponse = this.processResponse(url, urlConnection); + if (JSONResponse == null) { + this.userFriendlyErrorMessage = "Unknown error registering user"; + return null; + } if (JSONResponse.contains("name already exists")) { JSONResponse = this.loginUser(username, generatedPassword, Config.DEFAULT_AUTH_LOGIN_URL); @@ -271,10 +288,6 @@ public String registerUsers(Uri uri) { if (!"".equals(this.userFriendlyErrorMessage)) { return null; } - if (JSONResponse == null) { - this.userFriendlyErrorMessage = "Unknown error reading sample data from server"; - return null; - } return JSONResponse; } diff --git a/src/com/github/opensourcefieldlinguistics/fielddb/service/UploadAudioVideoService.java b/src/com/github/opensourcefieldlinguistics/fielddb/service/UploadAudioVideoService.java new file mode 100644 index 0000000..c19419e --- /dev/null +++ b/src/com/github/opensourcefieldlinguistics/fielddb/service/UploadAudioVideoService.java @@ -0,0 +1,317 @@ +package com.github.opensourcefieldlinguistics.fielddb.service; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.security.KeyStore; + +import org.acra.ACRA; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.mime.content.StringBody; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.SingleClientConnManager; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; + +import ca.ilanguage.oprime.datacollection.NotifyingIntentService; + +import com.github.opensourcefieldlinguistics.fielddb.lessons.Config; +import com.github.opensourcefieldlinguistics.fielddb.lessons.PrivateConstants; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.BuildConfig; +import com.github.opensourcefieldlinguistics.fielddb.speech.kartuli.R; +import com.google.gson.JsonObject; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.util.Log; + +public class UploadAudioVideoService extends NotifyingIntentService { + protected String mDeviceDetails = "{}"; + protected String mUsername = "default"; + public UploadAudioVideoService(String name) { + super(name); + } + + public UploadAudioVideoService() { + super("UploadAudioVideoService"); + } + + @Override + protected void onHandleIntent(Intent intent) { + if (Config.D) { + return; + } + + /* only upload files when connected to wifi */ + ConnectivityManager connManager = (ConnectivityManager) getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo wifi = connManager + .getNetworkInfo(ConnectivityManager.TYPE_WIFI); + if (!wifi.isConnected()) { + return; + } + + /* only upload files with content */ + if (intent.getData() == null) { + return; + } + Uri uri = intent.getData(); + if (uri.getPath() == null) { + return; + } + File f = new File(uri.getPath()); + if (!f.exists()) { + return; + } + if (f.length() < 5000) { + Log.d(Config.TAG, "Not uploading, " + uri.getLastPathSegment() + + " was too small " + f.length()); + return; + } + + this.D = Config.D; + this.statusMessage = "Uploading audio video"; + this.tryAgain = intent; + this.keystoreResourceId = R.raw.sslkeystore; + if (Config.D) { + Log.d(Config.TAG, "Inside UploadAudioVideoService intent"); + } + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("action", + "uploadAudioVideo:::"); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("urlString", + Config.DEFAULT_UPLOAD_AUDIO_VIDEO_URL); + + super.onHandleIntent(intent); + + if (!"".equals(this.userFriendlyErrorMessage)) { + this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, + this.notificationId, true); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); + return; + } + + if (intent.hasExtra(Config.EXTRA_PARTICIPANT_ID)) { + mUsername = intent.getExtras().getString( + Config.EXTRA_PARTICIPANT_ID); + } + if (intent.hasExtra(Config.EXTRA_EXPERIMENT_TRIAL_INFORMATION)) { + mDeviceDetails = intent.getExtras().getString( + Config.EXTRA_EXPERIMENT_TRIAL_INFORMATION); + } + + String JSONResponse = this.upload(intent.getData()); + if (JSONResponse == null && "".equals(this.userFriendlyErrorMessage)) { + this.userFriendlyErrorMessage = "Server response was missing. Please report this."; + } + if (!"".equals(this.userFriendlyErrorMessage)) { + this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, + this.notificationId, true); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); + return; + } + + processUploadResponse(intent.getData(), JSONResponse); + if (!"".equals(this.userFriendlyErrorMessage)) { + this.notifyUser(" " + this.userFriendlyErrorMessage, this.noti, + this.notificationId, true); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception(this.userFriendlyErrorMessage)); + return; + } + + /* all is well, get their cookie set */ + // this.getCouchCookie(username, generatedPassword, + // Config.DEFAULT_DATA_LOGIN); + + /* Success: remove the notification */ + ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)) + .cancel(this.notificationId); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().handleException( + new Exception("*** Uploaded audio sucessfully ***")); + + } + + public String upload(Uri uri) { + String filePath = uri.getPath(); + this.statusMessage = "Uploading audio " + uri.getLastPathSegment(); + if (!BuildConfig.DEBUG) + ACRA.getErrorReporter().putCustomData("uploadAudio", + uri.getLastPathSegment()); + String urlStringAuthenticationSession = Config.DEFAULT_UPLOAD_AUDIO_VIDEO_URL; + + /* Actually uploads the video */ + HttpClient httpClient = new SecureHttpClient(getApplicationContext()); + // HttpClient httpClient = new DefaultHttpClient(); + + HttpContext localContext = new BasicHttpContext(); + String url = Config.DEFAULT_UPLOAD_AUDIO_VIDEO_URL; + HttpPost httpPost = new HttpPost(url); + + MultipartEntity entity = new MultipartEntity( + HttpMultipartMode.BROWSER_COMPATIBLE, null, + Charset.forName("UTF-8")); + + try { + + entity.addPart("videoFile", new FileBody(new File(filePath))); + + entity.addPart("token", new StringBody(Config.DEFAULT_UPLOAD_TOKEN, + "text/plain", Charset.forName("UTF-8"))); + + entity.addPart("username", new StringBody(mUsername, "text/plain", + Charset.forName("UTF-8"))); + + entity.addPart("dbname", new StringBody(Config.DEFAULT_CORPUS, + "text/plain", Charset.forName("UTF-8"))); + + entity.addPart("returnTextGrid", new StringBody("true", + "text/plain", Charset.forName("UTF-8"))); + + } catch (UnsupportedEncodingException e) { + Log.d(Config.TAG, + "Failed to add entity parts due to string encodinuserFriendlyMessageg UTF-8"); + e.printStackTrace(); + } + + httpPost.setEntity(entity); + String userFriendlyErrorMessage = ""; + String JSONResponse = ""; + + try { + HttpResponse response = httpClient.execute(httpPost, localContext); + BufferedReader reader = new BufferedReader(new InputStreamReader( + response.getEntity().getContent(), "UTF-8")); + String newLine; + do { + newLine = reader.readLine(); + if (newLine != null) { + JSONResponse += newLine; + } + } while (newLine != null); + + } catch (ClientProtocolException e1) { + this.userFriendlyErrorMessage = "Problem using POST, please report this error."; + e1.printStackTrace(); + } catch (IOException e1) { + this.userFriendlyErrorMessage = "Problem opening upload connection to server, please report this error."; + e1.printStackTrace(); + } + + if ("".equals(JSONResponse)) { + this.userFriendlyErrorMessage = "Unknown error uploading data to server"; + return null; + } + + if (!"".equals(this.userFriendlyErrorMessage)) { + return null; + } + return JSONResponse; + } + + public class SecureHttpClient extends DefaultHttpClient { + + final Context context; + + public SecureHttpClient(Context context) { + this.context = context; + } + + @Override + protected ClientConnectionManager createClientConnectionManager() { + try { + // Get an instance of the Bouncy Castle KeyStore format + KeyStore trusted = KeyStore.getInstance("BKS"); + // Get the raw resource, which contains the keystore with + // your trusted certificates (root and any intermediate certs) + InputStream in = getApplicationContext().getResources() + .openRawResource(R.raw.sslkeystore); + try { + // Initialize the keystore with the provided trusted + // certificates + // Also provide the password of the keystore + trusted.load(in, + PrivateConstants.KEYSTORE_PASS.toCharArray()); + } finally { + in.close(); + } + // Pass the keystore to the SSLSocketFactory. The factory is + // responsible + // for the verification of the server certificate. + SSLSocketFactory sf = new SSLSocketFactory(trusted); + // Hostname verification from certificate + // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 + sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + // sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); + + SchemeRegistry registry = new SchemeRegistry(); + registry.register(new Scheme("http", PlainSocketFactory + .getSocketFactory(), 80)); + // Register for port 443 our SSLSocketFactory with our keystore + // to the ConnectionManager + registry.register(new Scheme("https", sf, 443)); + return new SingleClientConnManager(getParams(), registry); + } catch (Exception e) { + throw new AssertionError(e); + } + } + } + + public int processUploadResponse(Uri uri, String jsonResponse) { + if (jsonResponse != null && this.D) { + Log.d(Config.TAG, jsonResponse); + } + JsonObject json = (JsonObject) NotifyingIntentService.jsonParser + .parse(jsonResponse); + if (json.has("userFriendlyErrors")) { + this.userFriendlyErrorMessage = json.get("userFriendlyErrors") + .getAsString(); + return 0; + } + if (!json.has("files")) { + this.userFriendlyErrorMessage = "The server response is very strange, please report this."; + return 0; + } else { + String eventType = "uploadAudio"; + String eventValue = jsonResponse; + if (!BuildConfig.DEBUG) { + ACRA.getErrorReporter().putCustomData("action", + "{" + eventType + " : " + eventValue + "}"); + ACRA.getErrorReporter().putCustomData("androidTimestamp", + System.currentTimeMillis() + ""); + ACRA.getErrorReporter().putCustomData("deviceDetails", + mDeviceDetails); + ACRA.getErrorReporter().handleException( + new Exception("*** User event " + eventType + " ***")); + } + } + + return 0; + } +}