package uk.ac.cam.tfmw2.stegdroid; import imp.javax.sound.sampled.AudioFileFormat.Type; import imp.javax.sound.sampled.AudioFormat; import imp.javax.sound.sampled.AudioInputStream; import imp.javax.sound.sampled.UnsupportedAudioFileException; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.CountDownTimer; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.preference.PreferenceManager; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.sun.media.sound.WaveFileReader; import com.sun.media.sound.WaveFileWriter; public class StegDroid extends Activity { public static final String SETTINGS_ENCRYPTION = "use_crypto"; public static final String SETTINGS_PARANOIA = "paranoia"; public static final String SETTINGS_ENCRYPTION_KEY = "key"; private static final int DISPLAY_MESSAGE = 0; private static final int EMBED_SUCCEEDED = 1; private static final int EMBED_FAILED = 2; private static final int DECRYPTION_FAIL = 3; private static final int ERROR = 4; private static final int MENU_TEST = 0; private static final int MENU_SETTINGS = 1; private static final int MENU_FEEDBACK = 2; private static final int MENU_DECODE = 3; private static final int MENU_HELP = 4; private static final int MENU_TEST_ITEM = 5; //TODO remove private static final int SOURCE_FILE = 0; private static final int SOURCE_INTENT_WAV = 1; private static final int SOURCE_INTENT_OGG = 2; private static final String TAG = "StegDroid"; public static final String DECRYPTION_FAIL_MESSAGE = "Decryption Failed"; private String rootDir; public static final String unencodedFilePath = "unencoded.wav"; public static final String encodedFilePath = "encoded.wav"; public static final String shareableFilePath = "share.ogg"; private boolean isPlaying = false; private boolean isRecordingLongEnough = false; private boolean sendIntent = false; private int source; private int page = 1; private String messageString = ""; private Button startStop; private Button playUnencoded; private Button playEncoded; private Button extractData; private Button next; private Button back; private Button share; private Button multicast; private TextView charCount; private TextView timeCount; private TextView counterText; private EditText messageBox; private Uri sourceUri; private ProgressDialog loadingDialog; private MediaPlayer mediaPlayer = new MediaPlayer(); private IRecordService recordService = null; private SharedPreferences settings; private StegTimer timer; private PowerManager.WakeLock wakeLock; private class StegTimer extends CountDownTimer{ // CountDownTimer to update views when recording public StegTimer(int seconds) { // Simplify constructor to just deal with a number of seconds super(seconds * 1000, 1000); } @Override public void onTick(long millisLeft){ // update timeCount every second timeCount.setText(Long.toString(millisLeft/1000)); } @Override public void onFinish(){ // if the counter reaches the end, then the recordig is long enough isRecordingLongEnough = true; timeCount.setText(R.string.counter_finished); counterText.setText(R.string.counter_text_finished); } }; private int secondsNeeded(int chars){ //if crypto is set round up to 16 byte blocks if(settings.getBoolean(SETTINGS_ENCRYPTION, false)) chars = (((chars / 16) + 1) * 16); //data rate is ~18.8 bits/s so this is a safe number return (chars / 2) + 1; } private MediaPlayer.OnCompletionListener FinishedPlaying = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { isPlaying = false; mp.stop(); mp.reset(); Button b = (Button) findViewById(R.id.play); // some action is different depending on the page we're on. if(page == 2){ b.setText(R.string.play_unencoded); startStop.setEnabled(true); next.setEnabled(isRecordingLongEnough); }else if(page == 3) { b.setText(R.string.play_encoded); share.setEnabled(true); extractData.setEnabled(true); } b.setBackgroundResource(android.R.drawable.btn_default); b.setCompoundDrawablesWithIntrinsicBounds(R.drawable.play, 0, 0, 0); back.setEnabled(true); } }; private Handler messageHandler = new Handler(){ @Override public void handleMessage(final Message m){ switch(m.what){ case DISPLAY_MESSAGE: String message = (String) m.obj; AlertDialog.Builder builder = new AlertDialog.Builder(StegDroid.this); builder.setMessage(message) .setTitle("Extracted Message:") .setCancelable(false) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); builder.create().show(); //re-enable button extractData.setEnabled(true); break; case EMBED_SUCCEEDED: updatePage(3); break; case EMBED_FAILED: AlertDialog.Builder failBuilder = new AlertDialog.Builder(StegDroid.this); failBuilder.setMessage(R.string.embed_fail_message) .setCancelable(false) .setTitle("Decrypted Message") .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); failBuilder.create().show(); break; case DECRYPTION_FAIL: //decryption failed, incorrect password. Get a new key from the user or cancel //unfortunately, this View can't be expanded from XML, so it must be created manually EditTextDialogView dialogLayout = new EditTextDialogView(StegDroid.this,R.string.decryption_fail_message,R.string.decryption_fail_hint); final EditText dialogResult = dialogLayout.getEditText(); AlertDialog.Builder keyBuilder = new AlertDialog.Builder(StegDroid.this); keyBuilder .setCancelable(false) .setView(dialogLayout) .setTitle("Decryption Failed") .setPositiveButton("Try Again", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { //set new key in settings settings.edit().putString(SETTINGS_ENCRYPTION_KEY,dialogResult.getText().toString()).commit(); //retry if(source == SOURCE_INTENT_WAV || source == SOURCE_INTENT_OGG){ try { InputStream input = getContentResolver().openInputStream(sourceUri); extractThread et = new extractThread(input,(source == SOURCE_INTENT_OGG)); loadingDialog = ProgressDialog.show(StegDroid.this,"",getString(R.string.loader_extracting_data),true); et.start(); } catch (FileNotFoundException e) { showError("File not found, please email tfmw2@cam.ac.uk"); }; }else if(source == SOURCE_FILE){ try { InputStream input = new FileInputStream(new File(rootDir + shareableFilePath));extractThread et = new extractThread(input,true); loadingDialog = ProgressDialog.show(StegDroid.this,"",getString(R.string.loader_extracting_data),true); et.start(); } catch (FileNotFoundException e) { showError("File not found, please email tfmw2@cam.ac.uk"); } } } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { //re-enable button extractData.setEnabled(true); dialog.cancel(); } }); keyBuilder.create().show(); break; case ERROR: showError((String) m.obj); break; } } }; private void runUnitTests() { // run each unit test and show a dialog with results boolean stringResult = Tests.bitStreamTest(); String message = "StringBitStream: "; message += (stringResult) ? "PASS" : "FAIL"; boolean complexLogResult = Tests.complexLogTest(); message += "\nComplexLog: "; message += (complexLogResult) ? "PASS" : "FAIL"; boolean cryptoTestResult = Tests.cryptoTest(); message += "\nCrypto: "; message += (cryptoTestResult) ? "PASS" : "FAIL"; boolean longCharTestResult = Tests.bitStreamLongCharsTest(); message += "\nLongChars: "; message += (longCharTestResult) ? "PASS" : "FAIL"; AlertDialog.Builder builder = new AlertDialog.Builder(StegDroid.this); builder.setMessage(message) .setCancelable(false) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); builder.create().show(); } private class embedThread extends Thread{ public void run(){ try { WaveFileReader wfr = new WaveFileReader(); File waveFile = new File(rootDir + unencodedFilePath); File saveFile = new File(rootDir + encodedFilePath); AudioInputStream stream = wfr.getAudioInputStream(waveFile); AudioFormat format = stream.getFormat(); Log.v(TAG,"Format info: "+format.getEncoding().toString()+format.getSampleRate()+" "+format.getSampleSizeInBits()+" "+format.getChannels()+" "+format.getFrameSize()+" "+format.getFrameRate()+" "+(format.isBigEndian() ? "true" : "false")); int length = (int) (stream.getFrameLength() * format.getFrameSize()); Log.v(TAG,"Encode Length: "+Integer.toString(length)); // read the entire stream byte[] samples = new byte[length]; DataInputStream dis = new DataInputStream(stream); try { // This could be improved by using a ByteBuffer //TODO dis.readFully(samples); } catch (IOException ex) { //TODO ex.printStackTrace(); } BitStream inStream; Log.v(TAG,"checking use_crypto: "+(settings.getBoolean(SETTINGS_ENCRYPTION, false) ? "true" : "false")); if(settings.getBoolean(SETTINGS_ENCRYPTION, false)){ //encrypted inStream = new BitStream(messageString,settings.getString(SETTINGS_ENCRYPTION_KEY, "")); }else{ //unencrypted inStream = new BitStream(messageString); } EchoStegFile esf = new EchoStegFile(inStream); //filter the whole thing in one go byte[] newSamples = esf.embed(samples, 0, length); ByteArrayInputStream bais = new ByteArrayInputStream(newSamples); if(!inStream.endOfBitStream()){ Message m = Message.obtain(); m.what = EMBED_FAILED; messageHandler.sendMessage(m); loadingDialog.dismiss(); } WaveFileWriter wfw = new WaveFileWriter(); AudioInputStream oStream = new AudioInputStream(bais,format,newSamples.length / 2); wfw.write(oStream, Type.WAVE, saveFile); // Encode file as ogg Vorbis ve = new Vorbis(); ve.encode(new FileInputStream(saveFile),new File(rootDir + shareableFilePath)); } catch (UnsupportedAudioFileException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //update views Message m = Message.obtain(); m.what = EMBED_SUCCEEDED; messageHandler.sendMessage(m); loadingDialog.dismiss(); } } private TextWatcher messageWatcher = 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 s, int start, int before, int count) { updateCounters(s.toString()); } }; private void updateCounters(String s) { // Update the counters underneath the message box int stringLength = s.length(); int bytesLength = s.getBytes().length; charCount.setText(stringLength+"/120 characters"); timeCount.setText(secondsNeeded(bytesLength)+"s of audio required"); } private void updatePage(int n){ /* Fetch page layout from XML and assign buttons etc. * Only layout happens here, anything else is dealt with in * nextListener and previousListener */ switch(n){ case 1: setContentView(R.layout.page1); next = (Button) findViewById(R.id.p1Next); next.setOnClickListener(nextListener); messageBox = (EditText) findViewById(R.id.message); messageBox.addTextChangedListener(messageWatcher); //reload previous message if there is one if(messageString.length() > 0) messageBox.setText(messageString); charCount = (TextView) findViewById(R.id.char_counter); timeCount = (TextView) findViewById(R.id.counter); page = 1; //make the messageBox focus automatically, and launch the soft keyboard if neccesary messageBox.requestFocus(); InputMethodManager imm = (InputMethodManager)getSystemService( Context.INPUT_METHOD_SERVICE); imm.showSoftInput(messageBox, 0); break; case 2: setContentView(R.layout.page2); next = (Button) findViewById(R.id.p2Next); back = (Button) findViewById(R.id.p2Back); startStop = (Button) findViewById(R.id.record); playUnencoded = (Button) findViewById(R.id.play); timeCount = (TextView) findViewById(R.id.counter); counterText = (TextView) findViewById(R.id.counterText); timeCount.setText(Integer.toString(secondsNeeded(messageString.length()))); next.setOnClickListener(nextListener); back.setOnClickListener(backListener); startStop.setOnClickListener(RecordListener); playUnencoded.setOnClickListener(PlayUnencodedListener); page = 2; break; case 3: setContentView(R.layout.page3); back = (Button) findViewById(R.id.p3Back); share = (Button) findViewById(R.id.share); multicast = (Button) findViewById(R.id.multicast); playEncoded = (Button) findViewById(R.id.play); extractData = (Button) findViewById(R.id.extract); back.setOnClickListener(backListener); playEncoded.setOnClickListener(PlayEncodedListener); extractData.setOnClickListener(ExtractDataListener); share.setOnClickListener(shareListener); multicast.setOnClickListener(multicastListener); page = 3; break; } //Finally update the status bar. updateStatusBar(); } private void updateStatusBar() { //Update the status bar with details of encryption & paranoia status //Separate from other layout functionality as it must be called from other places TextView paranoiaStatus = (TextView) findViewById(R.id.status_paranoia); paranoiaStatus.setText(settings.getBoolean(SETTINGS_PARANOIA,false) ? "Paranoid mode: ON" : "Paranoid mode: OFF"); TextView encryptionStatus = (TextView) findViewById(R.id.status_encryption); encryptionStatus.setText(settings.getBoolean(SETTINGS_ENCRYPTION,false) ? "Encryption: ON" : "Encryption: OFF"); //TextView progress = (TextView) findViewById(R.id.progress); //progress.setText("Step "+page+"/"+3); } private class extractThread extends Thread{ //Thread to perform extraction from a stream. private InputStream from; private boolean ogg; public extractThread(InputStream input, boolean isOgg){ from = input; ogg = isOgg; } private void error(String s){ Message m = Message.obtain(); m.what = ERROR; m.obj = s; messageHandler.sendMessage(m); } public void run(){ //get the return message here in case there's an error Message m = Message.obtain(); if(ogg){ //transcode ogg file to wav Vorbis v = new Vorbis(); try { File outputFile = new File(rootDir + encodedFilePath); v.decode(from,outputFile); from = new FileInputStream(outputFile); Log.v(TAG,"Decoded Ogg file, trying to extract from wav."); } catch (IOException e) { error("There was an IO Exception (Code 3), your message could not be read. Please email tfmw2@cam.ac.uk!"); e.printStackTrace(); return; } } WaveFileReader wfr = new WaveFileReader(); AudioInputStream stream; try { stream = wfr.getAudioInputStream(from); } catch (UnsupportedAudioFileException e) { //this should never happen, only a .wav file can get this far return; } catch (IOException e) { error("There was an IO Exception (Code 1), your message could not be read. Please email tfmw2@cam.ac.uk!"); return; } //get the format information and initialize buffers AudioFormat format = stream.getFormat(); int length = (int) (stream.getFrameLength() * format.getFrameSize()); // read the entire stream byte[] samples = new byte[length]; DataInputStream dis = new DataInputStream(stream); try { dis.readFully(samples); } catch (IOException ex) { //This actually happens, not sure what's causing it... //try ignoring for now? TODO //error("There was an IO Exception (Code 2), your message could not be read. Please email tfmw2@cam.ac.uk!"); //return; } //Set up BitStream, EchoStegFile, Message and String for output BitStream sbs = new BitStream(); EchoStegFile esf = new EchoStegFile(); String result; //Perform extraction esf.extract(samples, sbs); //check if it's encrypted if(sbs.isEncrypted()){ //decrypt using stored key. If it fails, send message to prompt user, otherwise display result result = sbs.decryptString(settings.getString(SETTINGS_ENCRYPTION_KEY,"")); if(result == DECRYPTION_FAIL_MESSAGE){ m.what = DECRYPTION_FAIL; m.obj = from; messageHandler.sendMessage(m); }else{ m.what = DISPLAY_MESSAGE; m.obj = result; messageHandler.sendMessage(m); } }else{ //display result result = sbs.getString(); m.what = DISPLAY_MESSAGE; m.obj = result; messageHandler.sendMessage(m); } //dismiss the loading spinner loadingDialog.dismiss(); } } private void showError(String message) { try{ loadingDialog.dismiss(); }catch(NullPointerException e){ //ignore, this is just incase loading Dialog isn't showing } AlertDialog.Builder builder = new AlertDialog.Builder(StegDroid.this); builder.setMessage(message) .setCancelable(false) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); builder.create().show(); } private OnClickListener ExtractDataListener = new OnClickListener(){ @Override public void onClick(View v) { //disable button to prevent accidental double clicks extractData.setEnabled(false); //extract data from encoded file and display in dialog File inputFile = new File(rootDir + shareableFilePath); extractThread et; try { et = new extractThread(new FileInputStream(inputFile),true); } catch (FileNotFoundException e) { showError("Could not find the file to extract from."); return; } et.start(); loadingDialog = ProgressDialog.show(StegDroid.this,"",getString(R.string.loader_extracting_data),true); } }; private OnClickListener PlayEncodedListener = new OnClickListener() { @Override public void onClick(View v){ //play unencoded file if(isPlaying){ isPlaying = false; mediaPlayer.stop(); mediaPlayer.reset(); playEncoded.setText(R.string.play_encoded); playEncoded.setBackgroundResource(android.R.drawable.btn_default); playEncoded.setCompoundDrawablesWithIntrinsicBounds(R.drawable.play, 0, 0, 0); back.setEnabled(true); share.setEnabled(true); extractData.setEnabled(true); }else{ isPlaying = true; try { mediaPlayer.setOnCompletionListener(FinishedPlaying); mediaPlayer.setDataSource(rootDir + encodedFilePath); mediaPlayer.prepare(); mediaPlayer.start(); playEncoded.setText(R.string.stop); playEncoded.setBackgroundResource(R.drawable.btn_default_green); playEncoded.setCompoundDrawablesWithIntrinsicBounds(R.drawable.stop, 0, 0, 0); back.setEnabled(false); share.setEnabled(false); extractData.setEnabled(false); } catch (IllegalArgumentException e) { //Should never happen } catch (IllegalStateException e) { //Should never happen } catch (IOException e) { showError("Could not find the file to play"); } } } }; private OnClickListener PlayUnencodedListener = new OnClickListener() { @Override public void onClick(View v){ //play unencoded file if(isPlaying){ isPlaying = false; mediaPlayer.stop(); mediaPlayer.reset(); playUnencoded.setText(R.string.play_unencoded); playUnencoded.setCompoundDrawablesWithIntrinsicBounds(R.drawable.play, 0, 0, 0); playUnencoded.setBackgroundResource(android.R.drawable.btn_default); startStop.setEnabled(true); next.setEnabled(isRecordingLongEnough); back.setEnabled(true); }else{ isPlaying = true; try { mediaPlayer.setOnCompletionListener(FinishedPlaying); mediaPlayer.setDataSource(rootDir + unencodedFilePath); mediaPlayer.prepare(); mediaPlayer.start(); playUnencoded.setText(R.string.stop); playUnencoded.setBackgroundResource(R.drawable.btn_default_green); playUnencoded.setCompoundDrawablesWithIntrinsicBounds(R.drawable.stop, 0, 0, 0); startStop.setEnabled(false); next.setEnabled(false); back.setEnabled(false); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }; private OnClickListener RecordListener = new OnClickListener(){ @Override public void onClick(View v) { try { switch(urbanstew.RehearsalAssistant.RecordService.State.values()[recordService.getState()]) { case STARTED: timer = new StegTimer(secondsNeeded(messageString.length())); counterText.setText(R.string.counter_text); recordService.startRecording(); timer.start(); //stop the screen turning off wakeLock.acquire(); //Make the button red while recording startStop.setBackgroundResource(R.drawable.btn_default_red); startStop.setText(R.string.stop); startStop.setCompoundDrawablesWithIntrinsicBounds (R.drawable.stop,0,0,0); playUnencoded.setEnabled(false); return; default: timer.cancel(); recordService.stopRecording(); //allow the screen to turn off wakeLock.release(); //Make the button the default colour again //startStop.setBackgroundResource(R.drawable.btn_default_normal); startStop.invalidate(); startStop.setBackgroundResource(android.R.drawable.btn_default); startStop.setText(R.string.rerecord); startStop.setCompoundDrawablesWithIntrinsicBounds (R.drawable.record,0,0,0); //check if recording is long enough if(!isRecordingLongEnough){ //set message Toast.makeText(StegDroid.this, R.string.short_recording_toast, Toast.LENGTH_SHORT).show(); next.setEnabled(false); }else{ //enable next next.setEnabled(true); } playUnencoded.setEnabled(true); } } catch (RemoteException e) { } } }; private OnClickListener nextListener = new OnClickListener(){ @Override public void onClick(View v) { switch(v.getId()){ case R.id.p1Next: messageString = messageBox.getText().toString(); //Hide the keyboard InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(messageBox.getWindowToken(), 0); updatePage(2); break; case R.id.p2Next: //rapid presses can cause problems, so disable the button immediately next.setEnabled(false); source = SOURCE_FILE; //embed embedThread et = new embedThread(); et.start(); loadingDialog = ProgressDialog.show(StegDroid.this,"",getString(R.string.loader_embedding_data),true); //if successful, update view, otherwise show message } } }; private OnClickListener backListener = new OnClickListener(){ @Override public void onClick(View v) { switch(v.getId()){ case R.id.p2Back: // cancel the timer, to prevent it updating counter if(timer != null)timer.cancel(); updatePage(1); updateCounters(messageString); break; case R.id.p3Back: updatePage(2); } } }; private OnClickListener shareListener = new OnClickListener(){ @Override public void onClick(View v) { sendIntent = true; File toShare = new File(rootDir + shareableFilePath); Uri uri = Uri.fromFile(toShare); Intent it = new Intent(Intent.ACTION_SEND); it.putExtra(Intent.EXTRA_STREAM, uri); it.setType("audio/ogg"); startActivity(it); } }; private OnClickListener multicastListener = new OnClickListener(){ @Override public void onClick(View v) { sendIntent = true; Intent i = new Intent(getApplicationContext(), MultiSend.class); i.putExtra("file_path", rootDir + shareableFilePath); startActivity(i); } }; /** * Class for interacting with the secondary interface of the service. */ private ServiceConnection recordServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { recordService = IRecordService.Stub.asInterface(service); try { recordService.setSession(0); } catch (RemoteException e) { Log.v("Service","RemoteException"); } } @Override public void onServiceDisconnected(ComponentName className) { recordService = null; startStop.setEnabled(false); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); rootDir = Environment.getExternalStorageDirectory().getAbsolutePath()+"/StegDroid/"; settings = PreferenceManager.getDefaultSharedPreferences(this); wakeLock = ((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "Wake Lock"); //Check if app was launch with data, i.e. from an email attachment Uri uri = getIntent() != null ? getIntent().getData() : null; if(uri != null){ InputStream is; BufferedInputStream in; try { is = getContentResolver().openInputStream(uri); in = new BufferedInputStream(is); } catch (FileNotFoundException e) { e.printStackTrace(); return; } Log.v(TAG,"Extra Info: "+getIntent().getType()+" "+getIntent().resolveType(this)); /* Some apps (notably Gmail) do not report the correct mimetype. * Gmail reports a type of audio/wav for an ogg file * To get around this, read the first 4 bytes of the file * An ogg file will have the header OggS, a wave file RIFF */ byte[] top4 = new byte[4]; String header = ""; try { in.mark(5); in.read(top4); header = new String(top4); Log.v(TAG,"header: "+header+((header.equals("OggS")) ? "true" : "false")); // Reset to the start after reading in.reset(); } catch (IOException e) { showError("File not found, please contact tfmw2@cam.ac.uk"); } source = (getIntent().getType().equals("audio/ogg") || header.equals("OggS")) ? SOURCE_INTENT_OGG : SOURCE_INTENT_WAV; sourceUri = uri; extractThread et = new extractThread(in,(source == SOURCE_INTENT_OGG)); updatePage(3); loadingDialog = ProgressDialog.show(StegDroid.this,"",getString(R.string.loader_extracting_data),true); et.start(); }else{ source = SOURCE_FILE; } //setContentView(new StegView(this)); bindService(new Intent(IRecordService.class.getName()), recordServiceConnection, Context.BIND_AUTO_CREATE); updatePage(1); } public boolean onCreateOptionsMenu(Menu m){ m.add(0, MENU_TEST, 0, "Run Tests").setIcon(R.drawable.ic_menu_test); m.add(0, MENU_SETTINGS, 0, "Settings").setIcon(R.drawable.ic_menu_settings); m.add(0, MENU_FEEDBACK, 0, "Send Feedback Email").setIcon(R.drawable.ic_menu_feedback); m.add(0, MENU_DECODE, 0, "Decode a message").setIcon(R.drawable.ic_menu_extract); m.add(0, MENU_HELP, 0, "Help").setIcon(R.drawable.ic_menu_help); //m.add(0, MENU_TEST_ITEM, 0, "Multisend"); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_TEST: runUnitTests(); break; case MENU_SETTINGS: Intent settingsIntent = new Intent(this, Settings.class); startActivity(settingsIntent); break; case MENU_FEEDBACK: Intent emailIntent = new Intent(Intent.ACTION_SEND); emailIntent.setType("text/plain"); emailIntent.putExtra(Intent.EXTRA_EMAIL , new String[]{"tfmw2@cam.ac.uk"}); emailIntent.putExtra(Intent.EXTRA_SUBJECT, "StegDroid Feedback"); startActivity(Intent.createChooser(emailIntent, "Send feedback with:")); break; case MENU_DECODE: Toast.makeText(StegDroid.this, R.string.decode_instructions, Toast.LENGTH_LONG).show(); break; case MENU_HELP: Intent helpIntent = new Intent(this, FAQ.class); startActivity(helpIntent); break; case MENU_TEST_ITEM: Intent msIntent = new Intent(this, MultiSend.class); startActivity(msIntent); // float result1 = Tests.stegReliabilityTest(rootDir, unencodedFilePath); // Log.v(TAG,"Test Result: "+result1); // float result2 = Tests.vorbisTest(rootDir, "test.wav"); // Log.v(TAG,"Test Result: "+result2); // break; } return false; } @Override public void onPause(){ //unbind service unbindService(recordServiceConnection); if(settings.getBoolean(SETTINGS_PARANOIA, false) && !sendIntent){ Log.v(TAG,"paranoia ON"); //get rid of all data messageString = ""; File dir = new File(rootDir); File unencoded = new File(rootDir+unencodedFilePath); File encoded = new File(rootDir+encodedFilePath); File shared = new File(rootDir+shareableFilePath); //delete files unencoded.delete(); encoded.delete(); shared.delete(); if(dir.exists()){ if( dir.listFiles().length > 0){ //can't delete dir, has other files in Log.v(TAG,dir.listFiles()[0].getAbsolutePath()); }else{ dir.delete(); } } //remove key settings.edit().putString(SETTINGS_ENCRYPTION_KEY, "").commit(); updatePage(1); }else if(settings.getBoolean(SETTINGS_PARANOIA, false) && sendIntent){ sendIntent = false; } super.onPause(); } @Override public void onResume(){ if(page == 1) updateCounters(messageString); bindService(new Intent(IRecordService.class.getName()), recordServiceConnection, Context.BIND_AUTO_CREATE); super.onResume(); updateStatusBar(); } @Override public void onBackPressed(){ switch(page){ case 3: updatePage(2); break; case 2: updatePage(1); updateCounters(messageString); break; default: super.onBackPressed(); } } }