ViewLog.java 19 KB

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