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;
}
}