ViewLog.java 18 KB

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