package de.tudarmstadt.informatik.hostage.ui; import java.io.File; import java.io.FileOutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Locale; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import de.tudarmstadt.informatik.hostage.R; import de.tudarmstadt.informatik.hostage.commons.HelperUtils; import de.tudarmstadt.informatik.hostage.logging.Logger; import de.tudarmstadt.informatik.hostage.logging.Record; import de.tudarmstadt.informatik.hostage.logging.SQLLogger; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.DatePickerDialog; import android.app.Dialog; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.DatePicker; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; import android.widget.TimePicker; import android.widget.Toast; /** * ViewLog shows Statistics about the recorded Attacks. * It also offers the user several options to perform actions with the database. * This includes:
* - show records,
* - export records,
* - upload records,
* - and delete records. * @author Lars Pandikow */ public class ViewLog extends Activity { public static final int JSON = 0x01; private final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd,yyyy HH:mm", Locale.US); private Logger logger; private SharedPreferences pref; private Editor editor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_viewlog); logger = new SQLLogger(this); pref = PreferenceManager.getDefaultSharedPreferences(this); editor = pref.edit(); initStatistic(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { case R.id.action_settings: startActivity(new Intent(this, SettingsActivity.class)); break; case R.id.action_about: startActivity(new Intent(this, AboutActivity.class)); break; default: } return super.onOptionsItemSelected(item); } /** * Creates a Dialog to choose export format. * Then calls {@link ViewLog#exportDatabase(int)} * @param view View elements which triggers the method call. */ public void exportDatabase(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.export_dialog_title); builder.setItems(R.array.format, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { exportDatabase(position); } }); builder.create(); builder.show(); } /** * Exports all records in a given format. Before exporting checks export location from preferences. * @param format Integer coded export format * @see Record#toString(int) */ private void exportDatabase(int format){ try { FileOutputStream log; String filename = "hostage_" + format + "_" + System.currentTimeMillis() + ".log"; boolean externalStorage = pref.getBoolean("pref_external_storage", false); String externalLocation = pref.getString("pref_external_location", ""); if(externalStorage){ String root = Environment.getExternalStorageDirectory().toString(); if(root != null && HelperUtils.isExternalStorageWritable()){ File dir = new File(root + externalLocation); dir.mkdirs(); File file = new File(dir, filename); log = new FileOutputStream(file); }else { Toast.makeText(this, "Could not write to SD Card", Toast.LENGTH_SHORT).show(); return; } } else{ log = this.openFileOutput("hostage_" + format + "_" + System.currentTimeMillis() + ".log", Context.MODE_PRIVATE); } ArrayList records = logger.getAllRecords(); for(Record record : records){ log.write((record.toString(format) + "\n").getBytes()); } log.flush(); log.close(); Toast.makeText(this, externalStorage ? filename + " saved on external memory! " + externalLocation : filename + " saved on internal memory!", Toast.LENGTH_LONG).show(); } catch (Exception e) { Toast.makeText(this, "Could not write to SD Card", Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } /** * Uploads a JSON Representation of each attack to a server, specified in the preferences.
* The method only uploads information about attacks that have net been uploaded yet. * For the Upload it uses a HttpPost with a HttpsClient, which does not validate any certificates.
* The Upload runs in its own Thread.
* While uploading the method also creates a notification to inform the user about the status of the upload. * @param view View elements which triggers the method call. * @see de.tudarmstadt.informatik.hostage.net.MySSLSocketFactory */ public void uploadDatabase(View view){ //Create a Notification final NotificationCompat.Builder builder; final NotificationManager mNotifyManager; final int lastUploadedAttackId = pref.getInt("LAST_UPLOADED_ATTACK_ID", -1); int currentAttackId = pref.getInt("ATTACK_ID_COUNTER", 0); // Check if there are new records to upload if(lastUploadedAttackId == currentAttackId -1){ // Inform user that no upload is necessary Toast.makeText(this, "All data have already been uploaded.", Toast.LENGTH_SHORT).show(); return; } // Build and show Upload Notification in notification bar builder = new NotificationCompat.Builder(this) .setContentTitle(this.getString(R.string.app_name)) .setContentText("Upload in progress...") .setTicker("Uploading Data...") .setSmallIcon(R.drawable.ic_launcher) .setWhen(System.currentTimeMillis()); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(MainActivity.class); stackBuilder.addNextIntent(new Intent()); PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(resultPendingIntent); builder.setAutoCancel(true); builder.setOnlyAlertOnce(false); mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotifyManager.notify(2, builder.build()); // Create a new Thread for upload new Thread(new Runnable() { public void run() { // Create a https client. Uses MySSLSocketFactory to accept all certificates HttpClient httpclient = HelperUtils.createHttpClient(); // get RecordList ArrayList recordList = logger.getRecordOfEachAttack(lastUploadedAttackId); final int progressMax = recordList.size(); Log.i("SQLLogger", "Logs to upload: " + progressMax); HttpPost httppost; int progressBarStatus = 0; for(Record record: recordList){ Log.i("SQLLogger", "Uploading log: " + progressBarStatus); try { // Create HttpPost httppost = new HttpPost(pref.getString("pref_upload", "https://ssi.cased.de")); // Create JSON String of Record StringEntity se = new StringEntity(record.toString(JSON)); httppost.setEntity(se); // Execute HttpPost HttpResponse response = httpclient.execute(httppost); Log.i("SQLLogger", "Statuscode of log " + progressBarStatus + ": " + " " + response.getStatusLine().getReasonPhrase()); Log.i("SQLLogger", "Statuscode of log " + progressBarStatus + ": " + " " + response.getStatusLine().getStatusCode()); // Update Notification progress bar progressBarStatus++; builder.setProgress(progressMax, progressBarStatus, false); // Update the progress bar mNotifyManager.notify(2, builder.build()); } catch (Exception e) { Log.i("SQLLogger", "Failed"); e.printStackTrace(); } } if(progressBarStatus == progressMax){ // When the loop is finished, updates the notification builder.setContentText("Upload complete") .setTicker("Upload complete") // Removes the progress bar .setProgress(0,0,false); mNotifyManager.notify(2, builder.build()); } }}).start(); editor.putInt("LAST_UPLOADED_ATTACK_ID",currentAttackId - 1); editor.commit(); } /** * Starts a ViewLogTable Activity. * @param view View elements which triggers the method call. * @see ViewLogTable */ public void showLog(View view) { startActivity(new Intent(this, ViewLogTable.class)); } /** * Creates a Dialog that lets the user decide which criteria he want to use to delete records. * Then calls the corresponding method. * @param view View elements which triggers the method call. * @see ViewLog#deleteByBSSID() * @see ViewLog#deleteByDate() * @see ViewLog#deleteAll() */ public void deleteLog(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.delete_dialog_title); builder.setItems(R.array.delete_criteria, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { switch (position) { case 0: deleteByBSSID(); break; case 1: deleteByDate(); break; case 2: deleteAll(); } } }); builder.create(); builder.show(); } /** * Shows a List with all recorded BSSIDs. * If a BSSID is selected the method calls {@link Logger#deleteByBSSID(String)} to delete all records with the chosen BSSID * @see Logger#deleteByBSSID */ private void deleteByBSSID() { AlertDialog.Builder builder = new AlertDialog.Builder(this); final String[] bssidArray = logger.getAllBSSIDS(); for(int i = 0; i < bssidArray.length; i++){ bssidArray[i] = bssidArray[i] + " (" + logger.getSSID(bssidArray[i]) +")"; } builder.setTitle(R.string.delete_dialog_title); builder.setItems(bssidArray, new DialogInterface.OnClickListener() { @SuppressLint("NewApi") public void onClick(DialogInterface dialog, int position) { logger.deleteByBSSID(bssidArray[position]); Toast.makeText( getApplicationContext(), "All entries with bssid '" + bssidArray[position] + "' deleted.", Toast.LENGTH_SHORT).show(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { recreate(); } else { Intent intent = getIntent(); finish(); startActivity(intent); } } }); builder.create(); builder.show(); } /** * Creates a DatePicking Dialog where the user can choose a date. Afterwards {@link ViewLog#deleteByDate(int, int, int)} is called. */ private void deleteByDate() { showDialog(0); } @Override protected Dialog onCreateDialog(int id) { switch (id) { case 0: final Calendar cal = Calendar.getInstance(); int pYear = cal.get(Calendar.YEAR); int pMonth = cal.get(Calendar.MONTH); int pDay = cal.get(Calendar.DAY_OF_MONTH); return new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {deleteByDate(year, monthOfYear, dayOfMonth);}} , pYear, pMonth, pDay); } return null; } /** * Shows a Dialog with the given date and ask him to confirm deleting records that are older than the shown date. * When the user confirms {@link Logger#deleteByDate(long)} is called with a long representation of the Date. * If the user cancels the Dialog nothing happens and the dialog disappears. * @param year * @param monthOfYear * @param dayOfMonth */ private void deleteByDate(int year, int monthOfYear, int dayOfMonth) { TimePicker timePicker = new TimePicker(this); final Calendar calendar = Calendar.getInstance(); calendar.set(year, monthOfYear, dayOfMonth, timePicker.getCurrentHour(), timePicker.getCurrentMinute(), 0); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.dialog_clear_database_date) .setMessage(sdf.format(calendar.getTime())) .setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { @SuppressLint("NewApi") public void onClick(DialogInterface dialog, int id) { long time = calendar.getTimeInMillis(); // Delete Data logger.deleteByDate(time); Toast.makeText(getApplicationContext(), "Data sets deleted!", Toast.LENGTH_SHORT).show(); // Recreate the activity if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { recreate(); } else { Intent intent = getIntent(); finish(); startActivity(intent); } } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // User cancelled the dialog } }); // Create the AlertDialog object builder.create(); builder.show(); } /** * Shows a Dialog to confirm that the database should be cleared. * If confirmed {@link SQLLogger#clearData()} is called. * @see Logger#clearData() */ private void deleteAll() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.dialog_clear_database) .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() { @SuppressLint("NewApi") public void onClick(DialogInterface dialog, int id) { // Clear all Data logger.clearData(); editor.putInt("ATTACK_ID_COUNTER", 0); editor.putInt("LAST_UPLOADED_ATTACK_ID", -1); editor.commit(); Toast.makeText(getApplicationContext(), "Database cleared!", Toast.LENGTH_SHORT) .show(); // Recreate the activity if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { recreate(); } else { Intent intent = getIntent(); finish(); startActivity(intent); } } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // User cancelled the dialog } }); // Create the AlertDialog object builder.create(); builder.show(); } /** * Initializes the Statistics. Creates a table row for every protocol and checks the dabase for the attack count. * Calls {@link ViewLog#setFirstAndLastAttack()} to set the TextViews. * @see Logger#getAttackCount() * @see Logger#getAttackPerProtokolCount(String) */ private void initStatistic() { TableLayout table = (TableLayout) findViewById(R.id.layoutContainer); ArrayList protocols = new ArrayList(); protocols.add("Total"); protocols.addAll(Arrays.asList(getResources().getStringArray( R.array.protocols))); for (String protocol : protocols) { TableRow row = new TableRow(this); TextView protocolName = new TextView(this); protocolName.setBackgroundResource(R.color.dark_grey); protocolName.setText(protocol); protocolName.setTextAppearance(this, android.R.style.TextAppearance_Medium); protocolName.setPadding(6, 0, 0, 0); row.addView(protocolName); TextView value = new TextView(this); value.setBackgroundResource(R.color.light_grey); value.setTextAppearance(this, android.R.style.TextAppearance_Medium); value.setGravity(Gravity.RIGHT); value.setPadding(3, 0, 3, 0); row.addView(value); if (protocol.equals("Total")) { value.setText("" + logger.getAttackCount()); } else { value.setText("" + logger.getAttackPerProtokolCount(protocol)); } table.addView(row); } setFirstAndLastAttack(); } /** * Sets the TextViews for first and last attack. * @see Logger#getSmallestAttackId() * @see Logger#getHighestAttackId() * @see Logger#getRecordOfAttackId(long) */ private void setFirstAndLastAttack() { Record firstAttack = logger.getRecordOfAttackId(logger.getSmallestAttackId()); Record lastAttack = logger.getRecordOfAttackId(logger.getHighestAttackId()); if (firstAttack != null) { Date resultdate = new Date(firstAttack.getTimestamp()); TextView text = (TextView) findViewById(R.id.textFirstAttackValue); text.setText(sdf.format(resultdate)); text = (TextView) findViewById(R.id.textLastAttackValue); resultdate = new Date(lastAttack.getTimestamp()); text.setText(sdf.format(resultdate)); } else { TextView text = (TextView) findViewById(R.id.textFirstAttackValue); text.setText("-"); text = (TextView) findViewById(R.id.textLastAttackValue); text.setText("-"); } } }