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 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; import de.tudarmstadt.informatik.hostage.R; import de.tudarmstadt.informatik.hostage.commons.HelperUtils; import de.tudarmstadt.informatik.hostage.logging.Record; import de.tudarmstadt.informatik.hostage.logging.formatter.TraCINgFormatter; /** * 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 { private final SimpleDateFormat sdf = new SimpleDateFormat( "MMM dd,yyyy HH:mm", Locale.US); private SharedPreferences pref; private Editor editor; /** * Creates a Dialog that lets the user decide which criteria he want to use * to delete records. Then calls the corresponding method.
* The possible criteria are coded in /res/values/arrays.xml To add a * criteria add a String to the array and extend the switch statement. * * @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.gui_delete_dialog_title); builder.setItems(R.array.delete_criteria, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int position) { switch (position) { case 0: deleteByBSSID(); break; case 1: deleteByDate(); break; case 2: deleteAll(); } } }); builder.create(); builder.show(); } /** * 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.gui_export_dialog_title); builder.setItems(R.array.format, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int position) { exportDatabase(position); } }); builder.create(); builder.show(); } @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); } /** * 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)); // TODO Delete } /** * 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.
* The local and remote IP of each record will be replaced with the external * IP of then device at the time of the upload. 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.
* Only uploads Records with a external IP that is not null! * * @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()); final Context context = this; // Create a new Thread for upload new Thread(new Runnable() { @Override public void run() { // get RecordList ArrayList recordList = null;// logger.getRecordOfEachAttack(lastUploadedAttackId); final int progressMax = recordList.size(); Log.i("SQLLogger", "Logs to upload: " + progressMax); int progressBarStatus = 0; int retry_counter = 0; while (progressBarStatus < progressMax) { Record record = recordList.get(progressBarStatus); // Only upload records with a saved external ip if (record.getExternalIP() != null) { retry_counter = 0; if (HelperUtils.uploadSingleRecord(context, record)) { // Update Notification progress bar progressBarStatus++; builder.setProgress(progressMax, progressBarStatus, false); // Update the progress bar mNotifyManager.notify(2, builder.build()); } else { retry_counter++; if (retry_counter == 3) { retry_counter = 0; progressBarStatus++; } } } } 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(); } /** * Shows a Dialog to confirm that the database should be cleared. If * confirmed {@link SQLLogger#clearData()} is called. * * @see ILogger#clearData() */ private void deleteAll() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.gui_dialog_clear_database) .setPositiveButton(R.string.gui_clear, new DialogInterface.OnClickListener() { @Override @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.gui_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // User cancelled the dialog } }); // Create the AlertDialog object builder.create(); builder.show(); } /** * Shows a List with all recorded BSSIDs. If a BSSID is selected the method * calls {@link ILogger#deleteByBSSID(String)} to delete all records with * the chosen BSSID * * @see ILogger#deleteByBSSID */ private void deleteByBSSID() { AlertDialog.Builder builder = new AlertDialog.Builder(this); final String[] bssidArray = null;// logger.getAllBSSIDS(); final String[] strings = new String[bssidArray.length]; for (int i = 0; i < bssidArray.length; i++) { strings[i] = bssidArray[i] + " (" // + logger.getSSID(bssidArray[i]) + ")"; } builder.setTitle(R.string.gui_delete_dialog_title); builder.setItems(strings, new DialogInterface.OnClickListener() { @Override @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); } /** * 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 ILogger#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.gui_dialog_clear_database_date) .setMessage(sdf.format(calendar.getTime())) .setPositiveButton(R.string.gui_delete, new DialogInterface.OnClickListener() { @Override @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.gui_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // User cancelled the dialog } }); // Create the AlertDialog object 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 = null;// logger.getAllRecords(); for (Record record : records) { log.write((record.toString((format == 1) ? TraCINgFormatter .getInstance() : null)).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(); } } /** * 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 ILogger#getAttackCount() * @see ILogger#getAttackPerProtocolCount(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.getAttackPerProtocolCount(protocol)); } table.addView(row); } setFirstAndLastAttack(); } /** * Sets the TextViews for first and last attack. * * @see ILogger#getSmallestAttackId() * @see ILogger#getHighestAttackId() * @see ILogger#getRecordOfAttackId(long) */ private void setFirstAndLastAttack() { Record firstAttack = null;// logger.getRecordOfAttackId(logger.getSmallestAttackId()); Record lastAttack = null;// 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("-"); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_viewlog); pref = PreferenceManager.getDefaultSharedPreferences(this); editor = pref.edit(); initStatistic(); } @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() { @Override public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { deleteByDate(year, monthOfYear, dayOfMonth); } }, pYear, pMonth, pDay); } return null; } }