Commit caa9c6a5 authored by Haejoong Lee's avatar Haejoong Lee

merged from upstream

parents 964828a0 a8a30b35
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.lp20.aikuma"
android:versionCode="010012"
android:versionName="1.0.0-beta.14" >
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<application
android:name="org.lp20.aikuma.Aikuma"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyTheme" >
<activity
android:name="org.lp20.aikuma.MainActivity"
android:label="@string/app_name" >
package="org.lp20.aikuma"
android:versionCode="010015"
android:versionName="1.0.0-beta.17">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<application android:label="@string/app_name"
android:name="org.lp20.aikuma.Aikuma"
android:theme="@style/MyTheme"
android:icon="@drawable/ic_launcher">
<activity android:name="MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
......
#!/bin/bash
ant checkstyle clean debug install
......@@ -11,11 +11,13 @@
<property name="scope" value="public"/>
<property name="authorFormat" value="\S"/>
</module>
<!--
<module name="JavadocMethod">
<property name="scope" value="private"/>
<property name="scope" value="protected"/>
<property name="allowMissingPropertyJavadoc" value="true"/>
</module>
<module name="JavadocVariable">
<property name="scope" value="protected"/>
</module>
-->
<module name="AvoidStarImport"/>
<module name="ConstantName"/>
<!--
......
<?xml version="1.0" encoding="UTF-8"?>
<project name="custom_rules">
<taskdef resource="checkstyletask.properties"
classpath="checkstyle/checkstyle-5.6-all.jar"/>
<checkstyle config="checkstyle/checks.xml">
<fileset dir="src/" includes="**/*.java"/>
</checkstyle>
<target name="checkstyle">
<taskdef resource="checkstyletask.properties"
classpath="checkstyle/checkstyle-5.6-all.jar"/>
<checkstyle config="checkstyle/checks.xml">
<fileset dir="src/" includes="**/*.java"/>
</checkstyle>
</target>
</project>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</LinearLayout>
......@@ -68,16 +68,6 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
>
<!--
<ToggleButton
android:id="@+id/phoneRespeaking"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:textOn="Phone respeak on"
android:textOff="Phone respeak off"
android:onClick="onPhoneRespeakingToggle"
/> -->
<Button
android:id="@+id/thumbRespeaking"
android:text="@string/thumb_respeaking"
......
......@@ -9,7 +9,6 @@
android:layout_width="wrap_content"
android:layout_height="32dp"
android:src="@drawable/play"
android:onClick="play"
android:background="@null"
/>
<view class="org.lp20.aikuma.ui.InterleavedSeekBar"
......
......@@ -10,4 +10,12 @@
android:layout_height="0dp"
android:layout_weight="1"
/>
<ImageButton
android:id="@+id/audioImport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="audioImport"
android:scaleType="centerInside"
android:src="@drawable/arrow_circle_o_down"
/>
</LinearLayout>
......@@ -3,22 +3,30 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" android:baselineAligned="false" android:id="@+id/addNewUser">
<ImageView
android:src="@drawable/microphone"
android:id="@+id/recordingType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text=""
android:layout_width="0dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:id="@+id/recordingName"
android:padding="5dp"/>
android:padding="1dp"/>
<TextView android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="12sp"
android:layout_height="wrap_content"
android:text=""
android:layout_width="0dp"
android:layout_weight="1"
android:layout_weight="0.5"
android:text=""
android:layout_gravity="center_vertical"
android:id="@+id/recordingDateDuration"
android:padding="5dp"/>
android:padding="1dp"/>
<!-- Possible Recording image distinct from the programatically added
Speaker images
<ImageView android:src="@drawable/unknown_speaker"
......
......@@ -5,7 +5,7 @@
android:title="@string/record"
android:showAsAction="always"/>
<item android:id="@+id/help"
android:icon="@drawable/help_32"
android:icon="@drawable/question"
android:title="@string/help"
android:showAsAction="always"/>
<item android:id="@+id/speakers"
......
......@@ -27,16 +27,27 @@ public class Aikuma extends android.app.Application {
private static Aikuma instance;
private static List<Language> languages;
/**
* The constructor.
*/
public Aikuma() {
instance = this;
}
/**
* Static method that provides a context when needed by code not bound to
* any meaningful context.
*
* @return A Context
*/
public static Context getContext() {
return instance;
}
/**
* Gets the android ID of the phone.
*
* @return The android ID as a String.
*/
public static String getAndroidID() {
return Secure.getString(
......@@ -79,6 +90,10 @@ public class Aikuma extends android.app.Application {
}
}
/**
* The thread used to load the language codes without interrupting the
* main thread.
*/
public static Thread loadLangCodesThread;
}
......
......@@ -9,6 +9,7 @@ import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
......@@ -26,9 +27,29 @@ import org.lp20.aikuma.ui.ListenActivity;
import org.lp20.aikuma.ui.MenuBehaviour;
import org.lp20.aikuma.ui.RecordActivity;
import org.lp20.aikuma.ui.RecordingArrayAdapter;
import org.lp20.aikuma.ui.RecordingMetadataActivity;
import org.lp20.aikuma.ui.SettingsActivity;
import org.lp20.aikuma.util.SyncUtil;
// For audio imports
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentTransaction;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.widget.Toast;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.lp20.aikuma.R;
import org.lp20.aikuma.model.Recording;
import org.lp20.aikuma.model.WaveFile;
/**
* The primary activity that lists existing recordings and allows you to select
* them for listening and subsequent respeaking.
......@@ -38,7 +59,10 @@ import org.lp20.aikuma.util.SyncUtil;
*/
public class MainActivity extends ListActivity {
/** Called when the activity is first created. */
// Helps us store how far down the list we are when MainActivity gets
// stopped.
private Parcelable listViewState;
@Override
public void onCreate(Bundle savedInstanceState)
{
......@@ -66,18 +90,23 @@ public class MainActivity extends ListActivity {
return menuBehaviour.onOptionsItemSelected(item);
}
@Override
public void onPause() {
super.onPause();
listViewState = getListView().onSaveInstanceState();
}
@Override
public void onResume() {
super.onResume();
List<Recording> recordings = Recording.readAll();
ArrayAdapter adapter = new RecordingArrayAdapter(this, recordings);
setListAdapter(adapter);
if (listViewState != null) {
getListView().onRestoreInstanceState(listViewState);
}
}
/**
* When a recording item in the list is clicked, start the ListenActivity
* and send the relevant UUID through.
*/
@Override
public void onListItemClick(ListView l, View v, int position, long id){
Recording recording = (Recording) getListAdapter().getItem(position);
......@@ -87,4 +116,126 @@ public class MainActivity extends ListActivity {
}
MenuBehaviour menuBehaviour;
////////////////////////////////////////////
//// /////
//// Things pertaining to AudioImport. /////
//// /////
////////////////////////////////////////////
/**
* Called when the import button is pressed; starts the import process.
*
* @param _view the audio import button.
*/
public void audioImport(View _view) {
mPath = Environment.getExternalStorageDirectory();
loadFileList(mPath, FILE_TYPE);
showAudioFilebrowserDialog();
}
/**
* Loads the list of files in the specified directory into mFileList
*
* @param dir The directory to scan.
* @param fileType The type of file (other than directories) to look
* for.
*/
private void loadFileList(File dir, final String fileType) {
if(dir.exists()) {
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String filename) {
File sel = new File(dir, filename);
return filename.contains(fileType) || sel.isDirectory();
}
};
mFileList = mPath.list(filter);
}
else {
mFileList= new String[0];
}
}
/**
* Presents the dialog for choosing audio files to the user.
*/
private void showAudioFilebrowserDialog() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
FilebrowserDialogFragment fbdf = new FilebrowserDialogFragment();
fbdf.show(ft, "dialog");
}
/**
* Used to display audio files that the user can choose to load from.
*/
public class FilebrowserDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Choose your file");
if(mFileList == null) {
Log.e("importfile", "Showing file picker before loading the file list");
dialog = builder.create();
return dialog;
}
builder.setItems(mFileList, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mChosenFile = mFileList[which];
Log.i("importfile", "mChosenFile: " + mChosenFile);
mPath = new File(mPath, mChosenFile);
if (mPath.isDirectory()) {
loadFileList(mPath, ".wav");
showAudioFilebrowserDialog();
} else {
//Then it must be a .wav file.
UUID uuid = UUID.randomUUID();
long sampleRate = -1;
int durationMsec = -1;
// Determine sample rate and duration from the actual
// file.
try {
WaveFile waveFile = new WaveFile(mPath);
sampleRate = (long) waveFile.getSampleRate();
durationMsec = (int) (waveFile.getDuration() * 1000);
} catch (IOException e) {
Toast.makeText(getActivity(),
"Failed to read the WAVE file.",
Toast.LENGTH_LONG).show();
}
//Copy the file to the no-sync directory.
try {
FileUtils.copyFile(mPath,
new File(Recording.getNoSyncRecordingsPath(),
uuid.toString() + ".wav"));
} catch (IOException e) {
Toast.makeText(getActivity(),
"Failed to import the recording.",
Toast.LENGTH_LONG).show();
}
// Pass the info along to RecordingMetadataActivity.
Intent intent = new Intent(getActivity(),
RecordingMetadataActivity.class);
intent.putExtra("uuidString", uuid.toString());
intent.putExtra("sampleRate", sampleRate);
intent.putExtra("durationMsec", durationMsec);
startActivity(intent);
}
}
});
dialog = builder.show();
return dialog;
}
}
private String[] mFileList;
private File mPath;
private String mChosenFile;
private static final String FILE_TYPE = ".wav";
}
......@@ -17,29 +17,48 @@ import android.media.AudioManager;
* @author Florian Hanke <florian.hanke@gmail.com>
*/
public class Audio {
/**
* Resets the activity playing through the main speakers.
*
* @param activity The activity in question.
*/
public static void reset(Activity activity) {
playThroughSpeaker(activity);
}
/**
* Plays the audio through the earpiece, like a phone call.
*
* @param activity The activity that this method governs.
* @param toSetMode True if the Audiomanager needs to be set to call mode.
*/
public static void playThroughEarpiece(Activity activity, boolean toSetMode) {
AudioManager audioManager = getAudioManager(activity);
if (toSetMode) {
audioManager.setMode(AudioManager.MODE_IN_CALL);
}
Log.i("speaker", "setting it off");
audioManager.setSpeakerphoneOn(false);
Log.i("speaker", "set it off. isSpeakerphoneOn: " + audioManager.isSpeakerphoneOn());
}
/**
* Plays the audio through the main speaker (not the earpiece as a phone
* call would)
*
* @param activity The activity that this method governs.
*/
public static void playThroughSpeaker(Activity activity) {
AudioManager audioManager = getAudioManager(activity);
audioManager.setMode(AudioManager.MODE_NORMAL);
Log.i("speaker", "setting it on");
audioManager.setSpeakerphoneOn(true);
Log.i("speaker", "set it on. isSpeakerphoneOn: " + audioManager.isSpeakerphoneOn());
}
/**
* Gets the audio manager for the given activity.
*
* @param activity The activity whose AudioManager is needed.
* @return The audiomanager.
*/
protected static AudioManager getAudioManager(Activity activity) {
return (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
}
......
......@@ -26,50 +26,89 @@ public class Beeper {
private MediaPlayer beep;
private MediaPlayer beepBeep;
/**
* Constructor
*
* @param context The context that the Beeper should beep in.
*/
public Beeper(Context context) {
this.context = context;
}
/**
* Plays one beep.
*/
public void beep() { beep(null); }
/**
* Plays one beep.
*
* @param listener The callback to play when the beep is complete.
*/
public void beep(OnCompletionListener listener) {
getBeep(listener).start();
}
/**
* Plays two beeps in succession.
*/
public void beepBeep() { beepBeep(null); }
/**
* Plays two beeps in succession.
*
* @param listener The callback to play when the beep is complete.
*/
public void beepBeep(OnCompletionListener listener) {
getBeepBeep(listener).start();
}
/** Static convenience methods */
/**
* Plays one beep.
*
* @param context The context to play the beep in
* @param listener The callback to play when the beep is complete.
*/
public static void beep(Context context, OnCompletionListener listener) {
start(context, listener, R.raw.beep);
}
/**
* Plays two beeps in succession.
*
* @param context The context to play the beep in
* @param listener The callback to play when the beep is complete.
*/
public static void beepBeep(Context context, OnCompletionListener listener) {
start(context, listener, R.raw.beeps2);
}
/**
* Plays a long beep.
*
* @param context The context to play the beep in
* @param listener The callback to play when the beep is complete.
*/
public static void longBeep(Context context, OnCompletionListener listener) {
start(context, listener, R.raw.longbeep);
}
// Starts playing the given audio resource.
private static void start(Context context, final OnCompletionListener listener, int resource) {
new Beeper(context).create(listener, resource).start();
}
/** Private methods */
// Gets a mediaplayer that plays a beep.
private MediaPlayer getBeep(final OnCompletionListener listener) {
return create(listener, R.raw.beep);
}
// Gets a mediaplayer that plays two beeps in succession
private MediaPlayer getBeepBeep(final OnCompletionListener listener) {
Log.i("beep", "beepin");
return create(listener, R.raw.beeps2);
}
// Creates a mediaplayer to play the beeps with.
private MediaPlayer create(final OnCompletionListener listener, int resource) {
final MediaPlayer beeper = MediaPlayer.create(context, resource);
if (listener != null) {
......
......@@ -28,6 +28,7 @@ public class InterleavedPlayer extends Player {
* original
*
* @param recording The metadata of the recording to play.
* @throws IOException If there is an issue reading the recordings.
*/
public InterleavedPlayer(Recording recording) throws IOException {
setRecording(recording);
......@@ -46,6 +47,9 @@ public class InterleavedPlayer extends Player {
completedOnce = false;
}
// Initializes the completion listeners for the original and respeaking,
// such that when one of them completes the InterleavedPlayer's
// onCompletionListener is run, only once.
private void initializeCompletionListeners() {
Player.OnCompletionListener bothCompletedListener = new
Player.OnCompletionListener() {
......@@ -67,10 +71,17 @@ public class InterleavedPlayer extends Player {
respeaking.setOnCompletionListener(bothCompletedListener);
}
/**
* Resumes playing the interleaved recording, from the original segment
* where it was last paused.
*/
public void play() {
playOriginal();
}
/**
* Resets the player to the beginning.
*/
public void reset() {
originalSegmentIterator = null;
currentOriginalSegment = null;
......@@ -78,7 +89,11 @@ public class InterleavedPlayer extends Player {
respeaking.seekToSample(0l);
}
/** Indicates whether the recording is currently being played. */
/**
* Indicates whether the recording is currently being played.
*
* @return true if the recording is currently playing; false otherwise.
*/
public boolean isPlaying() {
return original.isPlaying() || respeaking.isPlaying();
}
......@@ -93,12 +108,20 @@ public class InterleavedPlayer extends Player {
}
}
/** Get current point in the recording in milliseconds. */
/**
* Get current point in the recording in milliseconds.
*
* @return The current point in the recording in milliseconds as an int.
*/
public int getCurrentMsec() {
return original.getCurrentMsec();
}
/** Get the duration of the recording in milliseconds. */
/**
* Get the duration of the recording in milliseconds.
*
* @return The duration of the recording in milliseconds as an int.
*/
public int getDurationMsec() {
return original.getDurationMsec();
}
......@@ -109,6 +132,7 @@ public class InterleavedPlayer extends Player {
respeaking.release();
}
// Plays the current original segment.