ViewLog.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. package de.tudarmstadt.informatik.hostage.ui;
  2. import java.io.File;
  3. import java.io.FileOutputStream;
  4. import java.text.SimpleDateFormat;
  5. import java.util.ArrayList;
  6. import java.util.Arrays;
  7. import java.util.Calendar;
  8. import java.util.Date;
  9. import java.util.Locale;
  10. import android.annotation.SuppressLint;
  11. import android.app.Activity;
  12. import android.app.AlertDialog;
  13. import android.app.DatePickerDialog;
  14. import android.app.Dialog;
  15. import android.app.NotificationManager;
  16. import android.app.PendingIntent;
  17. import android.content.Context;
  18. import android.content.DialogInterface;
  19. import android.content.Intent;
  20. import android.content.SharedPreferences;
  21. import android.content.SharedPreferences.Editor;
  22. import android.os.Build;
  23. import android.os.Bundle;
  24. import android.os.Environment;
  25. import android.preference.PreferenceManager;
  26. import android.support.v4.app.NotificationCompat;
  27. import android.support.v4.app.TaskStackBuilder;
  28. import android.util.Log;
  29. import android.view.Gravity;
  30. import android.view.Menu;
  31. import android.view.MenuItem;
  32. import android.view.View;
  33. import android.widget.DatePicker;
  34. import android.widget.TableLayout;
  35. import android.widget.TableRow;
  36. import android.widget.TextView;
  37. import android.widget.TimePicker;
  38. import android.widget.Toast;
  39. import de.tudarmstadt.informatik.hostage.R;
  40. import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
  41. import de.tudarmstadt.informatik.hostage.logging.Logger;
  42. import de.tudarmstadt.informatik.hostage.logging.Record;
  43. import de.tudarmstadt.informatik.hostage.logging.SQLLogger;
  44. /**
  45. * ViewLog shows Statistics about the recorded Attacks.
  46. * It also offers the user several options to perform actions with the database.
  47. * This includes:<br>
  48. * - show records,<br>
  49. * - export records,<br>
  50. * - upload records,<br>
  51. * - and delete records.
  52. * @author Lars Pandikow
  53. */
  54. public class ViewLog extends Activity {
  55. private final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd,yyyy HH:mm", Locale.US);
  56. private Logger logger;
  57. private SharedPreferences pref;
  58. private Editor editor;
  59. @Override
  60. protected void onCreate(Bundle savedInstanceState) {
  61. super.onCreate(savedInstanceState);
  62. setContentView(R.layout.activity_viewlog);
  63. logger = new SQLLogger(this);
  64. pref = PreferenceManager.getDefaultSharedPreferences(this);
  65. editor = pref.edit();
  66. initStatistic();
  67. }
  68. @Override
  69. public boolean onCreateOptionsMenu(Menu menu) {
  70. getMenuInflater().inflate(R.menu.main, menu);
  71. return true;
  72. }
  73. @Override
  74. public boolean onOptionsItemSelected(MenuItem item) {
  75. // Handle item selection
  76. switch (item.getItemId()) {
  77. case R.id.action_settings:
  78. startActivity(new Intent(this, SettingsActivity.class));
  79. break;
  80. case R.id.action_about:
  81. startActivity(new Intent(this, AboutActivity.class));
  82. break;
  83. default:
  84. }
  85. return super.onOptionsItemSelected(item);
  86. }
  87. /**
  88. * Creates a Dialog to choose export format.
  89. * Then calls {@link ViewLog#exportDatabase(int)}
  90. * @param view View elements which triggers the method call.
  91. */
  92. public void exportDatabase(View view) {
  93. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  94. builder.setTitle(R.string.export_dialog_title);
  95. builder.setItems(R.array.format, new DialogInterface.OnClickListener() {
  96. public void onClick(DialogInterface dialog, int position) {
  97. exportDatabase(position);
  98. }
  99. });
  100. builder.create();
  101. builder.show();
  102. }
  103. /**
  104. * Exports all records in a given format. Before exporting checks export location from preferences.
  105. * @param format Integer coded export format
  106. * @see Record#toString(int)
  107. */
  108. private void exportDatabase(int format){
  109. try {
  110. FileOutputStream log;
  111. String filename = "hostage_" + format + "_" + System.currentTimeMillis() + ".log";
  112. boolean externalStorage = pref.getBoolean("pref_external_storage", false);
  113. String externalLocation = pref.getString("pref_external_location", "");
  114. if(externalStorage){
  115. String root = Environment.getExternalStorageDirectory().toString();
  116. if(root != null && HelperUtils.isExternalStorageWritable()){
  117. File dir = new File(root + externalLocation);
  118. dir.mkdirs();
  119. File file = new File(dir, filename);
  120. log = new FileOutputStream(file);
  121. }else {
  122. Toast.makeText(this, "Could not write to SD Card", Toast.LENGTH_SHORT).show();
  123. return;
  124. }
  125. } else{
  126. log = this.openFileOutput("hostage_" + format + "_" + System.currentTimeMillis() + ".log", Context.MODE_PRIVATE);
  127. }
  128. ArrayList<Record> records = logger.getAllRecords();
  129. for(Record record : records){
  130. log.write((record.toString(format)).getBytes());
  131. }
  132. log.flush();
  133. log.close();
  134. Toast.makeText(this, externalStorage ? filename + " saved on external memory! " + externalLocation : filename + " saved on internal memory!", Toast.LENGTH_LONG).show();
  135. } catch (Exception e) {
  136. Toast.makeText(this, "Could not write to SD Card", Toast.LENGTH_SHORT).show();
  137. e.printStackTrace();
  138. }
  139. }
  140. /**
  141. * Uploads a JSON Representation of each attack to a server, specified in the preferences.<br>
  142. * The method only uploads information about attacks that have net been uploaded yet.<br>
  143. * The local and remote IP of each record will be replaced with the external IP of then device at the time of the upload.
  144. * For the Upload it uses a HttpPost with a HttpsClient, which does not validate any certificates.<br>
  145. * The Upload runs in its own Thread.<br>
  146. * While uploading the method also creates a notification to inform the user about the status of the upload.<br>
  147. * <b>Only uploads Records with a external IP that is not null!
  148. * @param view View elements which triggers the method call.
  149. * @see de.tudarmstadt.informatik.hostage.net.MySSLSocketFactory
  150. */
  151. public void uploadDatabase(View view){
  152. //Create a Notification
  153. final NotificationCompat.Builder builder;
  154. final NotificationManager mNotifyManager;
  155. final int lastUploadedAttackId = pref.getInt("LAST_UPLOADED_ATTACK_ID", -1);
  156. int currentAttackId = pref.getInt("ATTACK_ID_COUNTER", 0);
  157. // Check if there are new records to upload
  158. if(lastUploadedAttackId == currentAttackId -1){
  159. // Inform user that no upload is necessary
  160. Toast.makeText(this, "All data have already been uploaded.", Toast.LENGTH_SHORT).show();
  161. return;
  162. }
  163. // Build and show Upload Notification in notification bar
  164. builder = new NotificationCompat.Builder(this)
  165. .setContentTitle(this.getString(R.string.app_name))
  166. .setContentText("Upload in progress...")
  167. .setTicker("Uploading Data...")
  168. .setSmallIcon(R.drawable.ic_launcher)
  169. .setWhen(System.currentTimeMillis());
  170. TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
  171. stackBuilder.addParentStack(MainActivity.class);
  172. stackBuilder.addNextIntent(new Intent());
  173. PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
  174. PendingIntent.FLAG_UPDATE_CURRENT);
  175. builder.setContentIntent(resultPendingIntent);
  176. builder.setAutoCancel(true);
  177. builder.setOnlyAlertOnce(false);
  178. mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
  179. mNotifyManager.notify(2, builder.build());
  180. final Context context = this;
  181. // Create a new Thread for upload
  182. new Thread(new Runnable() {
  183. public void run() {
  184. // get RecordList
  185. ArrayList<Record> recordList = logger.getRecordOfEachAttack(lastUploadedAttackId);
  186. final int progressMax = recordList.size();
  187. Log.i("SQLLogger", "Logs to upload: " + progressMax);
  188. int progressBarStatus = 0;
  189. int retry_counter = 0;
  190. while(progressBarStatus < progressMax){
  191. Record record = recordList.get(progressBarStatus);
  192. // Only upload records with a saved external ip
  193. if(record.getExternalIP() != null){
  194. retry_counter = 0;
  195. if(HelperUtils.uploadSingleRecord(context, record)){
  196. // Update Notification progress bar
  197. progressBarStatus++;
  198. builder.setProgress(progressMax, progressBarStatus, false);
  199. // Update the progress bar
  200. mNotifyManager.notify(2, builder.build());
  201. }else{
  202. retry_counter++;
  203. if(retry_counter == 3){
  204. retry_counter = 0;
  205. progressBarStatus++;
  206. }
  207. }
  208. }
  209. }
  210. if(progressBarStatus == progressMax){
  211. // When the loop is finished, updates the notification
  212. builder.setContentText("Upload complete")
  213. .setTicker("Upload complete")
  214. // Removes the progress bar
  215. .setProgress(0,0,false);
  216. mNotifyManager.notify(2, builder.build());
  217. }
  218. }}).start();
  219. editor.putInt("LAST_UPLOADED_ATTACK_ID",currentAttackId - 1);
  220. editor.commit();
  221. }
  222. /**
  223. * Starts a ViewLogTable Activity.
  224. * @param view View elements which triggers the method call.
  225. * @see ViewLogTable
  226. */
  227. public void showLog(View view) {
  228. startActivity(new Intent(this, ViewLogTable.class));
  229. //TODO Delete
  230. }
  231. /**
  232. * Creates a Dialog that lets the user decide which criteria he want to use to delete records.
  233. * Then calls the corresponding method.<br>
  234. * The possible criteria are coded in /res/values/arrays.xml
  235. * To add a criteria add a String to the array and extend the switch statement.
  236. * @param view View elements which triggers the method call.
  237. * @see ViewLog#deleteByBSSID()
  238. * @see ViewLog#deleteByDate()
  239. * @see ViewLog#deleteAll()
  240. */
  241. public void deleteLog(View view) {
  242. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  243. builder.setTitle(R.string.delete_dialog_title);
  244. builder.setItems(R.array.delete_criteria,
  245. new DialogInterface.OnClickListener() {
  246. public void onClick(DialogInterface dialog, int position) {
  247. switch (position) {
  248. case 0:
  249. deleteByBSSID();
  250. break;
  251. case 1:
  252. deleteByDate();
  253. break;
  254. case 2:
  255. deleteAll();
  256. }
  257. }
  258. });
  259. builder.create();
  260. builder.show();
  261. }
  262. /**
  263. * Shows a List with all recorded BSSIDs.
  264. * If a BSSID is selected the method calls {@link Logger#deleteByBSSID(String)} to delete all records with the chosen BSSID
  265. * @see Logger#deleteByBSSID
  266. */
  267. private void deleteByBSSID() {
  268. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  269. final String[] bssidArray = logger.getAllBSSIDS();
  270. final String[] strings = new String[bssidArray.length];
  271. for(int i = 0; i < bssidArray.length; i++){
  272. strings[i] = bssidArray[i] + " (" + logger.getSSID(bssidArray[i]) +")";
  273. }
  274. builder.setTitle(R.string.delete_dialog_title);
  275. builder.setItems(strings, new DialogInterface.OnClickListener() {
  276. @SuppressLint("NewApi")
  277. public void onClick(DialogInterface dialog, int position) {
  278. logger.deleteByBSSID(bssidArray[position]);
  279. Toast.makeText(
  280. getApplicationContext(),
  281. "All entries with bssid '" + bssidArray[position]
  282. + "' deleted.", Toast.LENGTH_SHORT).show();
  283. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  284. recreate();
  285. } else {
  286. Intent intent = getIntent();
  287. finish();
  288. startActivity(intent);
  289. }
  290. }
  291. });
  292. builder.create();
  293. builder.show();
  294. }
  295. /**
  296. * Creates a DatePicking Dialog where the user can choose a date. Afterwards {@link ViewLog#deleteByDate(int, int, int)} is called.
  297. */
  298. private void deleteByDate() {
  299. showDialog(0);
  300. }
  301. @Override
  302. protected Dialog onCreateDialog(int id) {
  303. switch (id) {
  304. case 0:
  305. final Calendar cal = Calendar.getInstance();
  306. int pYear = cal.get(Calendar.YEAR);
  307. int pMonth = cal.get(Calendar.MONTH);
  308. int pDay = cal.get(Calendar.DAY_OF_MONTH);
  309. return new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
  310. public void onDateSet(DatePicker view, int year, int monthOfYear,
  311. int dayOfMonth) {deleteByDate(year, monthOfYear, dayOfMonth);}}
  312. , pYear, pMonth, pDay);
  313. }
  314. return null;
  315. }
  316. /**
  317. * Shows a Dialog with the given date and ask him to confirm deleting records that are older than the shown date.
  318. * When the user confirms {@link Logger#deleteByDate(long)} is called with a long representation of the Date.
  319. * If the user cancels the Dialog nothing happens and the dialog disappears.
  320. * @param year
  321. * @param monthOfYear
  322. * @param dayOfMonth
  323. */
  324. private void deleteByDate(int year, int monthOfYear, int dayOfMonth) {
  325. TimePicker timePicker = new TimePicker(this);
  326. final Calendar calendar = Calendar.getInstance();
  327. calendar.set(year, monthOfYear, dayOfMonth,
  328. timePicker.getCurrentHour(), timePicker.getCurrentMinute(), 0);
  329. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  330. builder.setTitle(R.string.dialog_clear_database_date)
  331. .setMessage(sdf.format(calendar.getTime()))
  332. .setPositiveButton(R.string.delete,
  333. new DialogInterface.OnClickListener() {
  334. @SuppressLint("NewApi")
  335. public void onClick(DialogInterface dialog, int id) {
  336. long time = calendar.getTimeInMillis();
  337. // Delete Data
  338. logger.deleteByDate(time);
  339. Toast.makeText(getApplicationContext(),
  340. "Data sets deleted!",
  341. Toast.LENGTH_SHORT).show();
  342. // Recreate the activity
  343. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  344. recreate();
  345. } else {
  346. Intent intent = getIntent();
  347. finish();
  348. startActivity(intent);
  349. }
  350. }
  351. })
  352. .setNegativeButton(R.string.cancel,
  353. new DialogInterface.OnClickListener() {
  354. public void onClick(DialogInterface dialog, int id) {
  355. // User cancelled the dialog
  356. }
  357. });
  358. // Create the AlertDialog object
  359. builder.create();
  360. builder.show();
  361. }
  362. /**
  363. * Shows a Dialog to confirm that the database should be cleared.
  364. * If confirmed {@link SQLLogger#clearData()} is called.
  365. * @see Logger#clearData()
  366. */
  367. private void deleteAll() {
  368. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  369. builder.setMessage(R.string.dialog_clear_database)
  370. .setPositiveButton(R.string.clear,
  371. new DialogInterface.OnClickListener() {
  372. @SuppressLint("NewApi")
  373. public void onClick(DialogInterface dialog, int id) {
  374. // Clear all Data
  375. logger.clearData();
  376. editor.putInt("ATTACK_ID_COUNTER", 0);
  377. editor.putInt("LAST_UPLOADED_ATTACK_ID", -1);
  378. editor.commit();
  379. Toast.makeText(getApplicationContext(),
  380. "Database cleared!", Toast.LENGTH_SHORT)
  381. .show();
  382. // Recreate the activity
  383. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  384. recreate();
  385. } else {
  386. Intent intent = getIntent();
  387. finish();
  388. startActivity(intent);
  389. }
  390. }
  391. })
  392. .setNegativeButton(R.string.cancel,
  393. new DialogInterface.OnClickListener() {
  394. public void onClick(DialogInterface dialog, int id) {
  395. // User cancelled the dialog
  396. }
  397. });
  398. // Create the AlertDialog object
  399. builder.create();
  400. builder.show();
  401. }
  402. /**
  403. * Initializes the Statistics. Creates a table row for every protocol and checks the dabase for the attack count.
  404. * Calls {@link ViewLog#setFirstAndLastAttack()} to set the TextViews.
  405. * @see Logger#getAttackCount()
  406. * @see Logger#getAttackPerProtokolCount(String)
  407. */
  408. private void initStatistic() {
  409. TableLayout table = (TableLayout) findViewById(R.id.layoutContainer);
  410. ArrayList<String> protocols = new ArrayList<String>();
  411. protocols.add("Total");
  412. protocols.addAll(Arrays.asList(getResources().getStringArray(
  413. R.array.protocols)));
  414. for (String protocol : protocols) {
  415. TableRow row = new TableRow(this);
  416. TextView protocolName = new TextView(this);
  417. protocolName.setBackgroundResource(R.color.dark_grey);
  418. protocolName.setText(protocol);
  419. protocolName.setTextAppearance(this,
  420. android.R.style.TextAppearance_Medium);
  421. protocolName.setPadding(6, 0, 0, 0);
  422. row.addView(protocolName);
  423. TextView value = new TextView(this);
  424. value.setBackgroundResource(R.color.light_grey);
  425. value.setTextAppearance(this, android.R.style.TextAppearance_Medium);
  426. value.setGravity(Gravity.RIGHT);
  427. value.setPadding(3, 0, 3, 0);
  428. row.addView(value);
  429. if (protocol.equals("Total")) {
  430. value.setText("" + logger.getAttackCount());
  431. } else {
  432. value.setText("" + logger.getAttackPerProtokolCount(protocol));
  433. }
  434. table.addView(row);
  435. }
  436. setFirstAndLastAttack();
  437. }
  438. /**
  439. * Sets the TextViews for first and last attack.
  440. * @see Logger#getSmallestAttackId()
  441. * @see Logger#getHighestAttackId()
  442. * @see Logger#getRecordOfAttackId(long)
  443. */
  444. private void setFirstAndLastAttack() {
  445. Record firstAttack = logger.getRecordOfAttackId(logger.getSmallestAttackId());
  446. Record lastAttack = logger.getRecordOfAttackId(logger.getHighestAttackId());
  447. if (firstAttack != null) {
  448. Date resultdate = new Date(firstAttack.getTimestamp());
  449. TextView text = (TextView) findViewById(R.id.textFirstAttackValue);
  450. text.setText(sdf.format(resultdate));
  451. text = (TextView) findViewById(R.id.textLastAttackValue);
  452. resultdate = new Date(lastAttack.getTimestamp());
  453. text.setText(sdf.format(resultdate));
  454. } else {
  455. TextView text = (TextView) findViewById(R.id.textFirstAttackValue);
  456. text.setText("-");
  457. text = (TextView) findViewById(R.id.textLastAttackValue);
  458. text.setText("-");
  459. }
  460. }
  461. }