package com.notificationFramework.sedentary.frontEnd; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Environment; import android.support.annotation.NonNull; import android.util.Log; import com.google.android.gms.awareness.Awareness; import com.google.android.gms.awareness.AwarenessStatusCodes; import com.google.android.gms.awareness.snapshot.HeadphoneStateResponse; import com.google.android.gms.awareness.snapshot.LocationResponse; import com.google.android.gms.awareness.snapshot.PlacesResponse; import com.google.android.gms.awareness.snapshot.WeatherResponse; import com.google.android.gms.awareness.state.Weather; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.places.Place; import com.google.android.gms.location.places.PlaceLikelihood; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.RuntimeExecutionException; import com.google.android.gms.tasks.Task; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * Created by Peter De Jonckheere on 12/02/2018. * <p> * Contains the static methods which record various information about the notifications in the log * files stored in the external storage of the device. */ public class SaveFile { /** * Saves a general timestamp which logs that a notification was either sent, clicked or * acknowledged. The timestamp is saved in standard Android calendar format with 2 bits after. * "00" - acknowledged * "01" - clicked * "10" - sent * "11" - goal notification * * @param sent 1 if a notification has been sent, 1 if a goal notification has been sent, * 0 otherwise * @param clicked 1 if a notification has been clicked on, 1 if a goal notification has been * sent, 0 otherwise * @param context the application context which the method has been called from and which will * be used to access and write to the external storage of the device */ private static void recordTimeStamp(int sent, int clicked, int movement, int acknowledged, Context context) { Calendar cal = Calendar.getInstance(); ArrayList<String> contents = new ArrayList<String>(); String line; String prevLine = null; String[] items; SharedPreferences shared = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); SharedPreferences.Editor editor = shared.edit(); editor.putInt(context.getString(R.string.store_last_ack), acknowledged); editor.commit(); //Checks for external storage if (isExternalStorageMounted()) { File dir = getDirectory(context); File file = new File(dir, "TIMESTAMPLOG.txt"); //Creates a new file if one doesn't already exist if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { Log.e("FILE_ERROR", "COULDN'T CREATE NEW FILE"); } } //Reads the current contents of the log file try { if (file.isFile() && file.canRead()) { BufferedReader br = new BufferedReader(new FileReader(file)); while ((line = br.readLine()) != null) { contents.add(line + '\n'); prevLine = line; } try { items = prevLine.split(" "); if (items[6].equals("10") && (movement == 1)){ if(DataAnalysis.analyse(prevLine, cal, context)){ recordNotification(0,0,0,1, context); } } }catch(NullPointerException e){ Log.e("SPLIT", "NULL POINTER"); } } //Adds the new log to the file up to 1000 items. At 1000 items the oldest is removed if (file.isFile() & file.canWrite()) { BufferedWriter bw = new BufferedWriter(new FileWriter(file)); if (contents.size() > 999) { contents.remove(0); } for (String ts : contents) { bw.write(ts); } bw.write(cal.getTime().toString() + " " + sent + "" + clicked + " " + acknowledged); bw.close(); } } catch (IOException e) { Log.e("FILEERROR", "COULD NOT WRITE TO FILE"); } } } /** * Keeps track of the number of each type of record stored in a file. Three integers only are * written to the file used in this method. The first is the number of notifications sent, the * second is the number clicked on and the third is the number of movements registered. * * @param sent 1 if a notification has been sent, 1 if a goal notification has been * sent 0 otherwise * @param clicked 1 if a notification has been clicked on, 1 if a goal notification has * been sent, 0 otherwise * @param movement 1 if a movement has been registered, 0 otherwise * @param acknowledged 1 if a notification has been acknowledged by a movement, 0 otherwise * @param context the application context which the method has been called from and which * will be used to access and write to the external storage of the device */ public static void recordNotification(int sent, int clicked, int movement, int acknowledged, Context context){ int ns = 0; int nc = 0; int nm = 0; int na = 0; //Checks if external storage is present if (isExternalStorageMounted()) { File dir = getDirectory(context); if (dir.isDirectory()) { File file = new File(dir, "NOTLOG.txt"); //Creates a new file if one does not already exist try { if (!file.exists()) { file.createNewFile(); } //Reads the current contents of the file if (file.isFile() & file.canRead()) { BufferedReader br = new BufferedReader(new FileReader(file)); try { ns = Integer.parseInt(br.readLine()); nc = Integer.parseInt(br.readLine()); nm = Integer.parseInt(br.readLine()); na = Integer.parseInt(br.readLine()); } catch (NumberFormatException e) { Log.e("FILEERROR", "COULD NOT READ FILE"); } //Writes the modified values to the file based on the parameters if (file.isFile() & file.canWrite()) { BufferedWriter bw = new BufferedWriter(new FileWriter(file)); ns += sent; nc += clicked; nm += movement; na += acknowledged; bw.write(String.valueOf(ns)); bw.newLine(); bw.write(String.valueOf(nc)); bw.newLine(); bw.write(String.valueOf(nm)); bw.newLine(); bw.write(String.valueOf(na)); bw.close(); } } } catch (IOException e) { Log.e("FILEERROR", "WRITE FILE ERROR"); } } } recordTimeStamp(sent, clicked, movement, acknowledged, context); getExtraData(context); } /** * Compresses the files present in the log directory into a single zipped folder. * * @param context the application context from which this method was called * @return the zip folder created */ static File zipFiles(Context context) { File file = null; //Checks for presence of external storage if (isExternalStorageMounted()) { File dir = getDirectory(context); if (dir.isDirectory()) { file = new File(dir, "ZIPFILE.zip"); //Deletes any old zip folder and creates a new one try { if (file.exists()) { file.delete(); } if (file.createNewFile()) { FileOutputStream fos = new FileOutputStream(file); ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos)); File[] files = dir.listFiles(); try { //For each file in the directory buffer the file the write into the zip //folder by byte for (int i = 0; i < files.length; i++) { byte[] buffer = new byte[1024]; FileInputStream fis = new FileInputStream(files[i]); zos.putNextEntry(new ZipEntry(files[i].getName())); int length; while ((length = fis.read(buffer)) > 0) { zos.write(buffer, 0, length); } zos.closeEntry(); fis.close(); } } finally { zos.close(); } } } catch (IOException e) { Log.e("ZIPERROR", "COULDN'T ZIP FOLDER"); } } } return file; } /** * Records the type of preferences which were present when a notification was sent. * * @param context the application context which this method was called from */ public static void recordNotificationType(Context context) { SharedPreferences shared = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); int sound = 0; int led = 0; int vibration = 0; //Checks if external storage is available if (isExternalStorageMounted()) { File dir = getDirectory(context); if (dir.isDirectory()) { File file = new File(dir, "TYPELOG.txt"); //If the file does not exist it is created try { if (!file.exists()) { file.createNewFile(); } //Reads the current contents of the file if (file.isFile() & file.canRead()) { BufferedReader br = new BufferedReader(new FileReader(file)); try { sound = Integer.parseInt(br.readLine()); led = Integer.parseInt(br.readLine()); vibration = Integer.parseInt(br.readLine()); } catch (NumberFormatException e) { Log.e("FILEERROR", "CANNOT READ FILE"); } //Writes new information to file based on parameters if (file.isFile() & file.canWrite()) { BufferedWriter bw = new BufferedWriter(new FileWriter(file)); if (shared.getBoolean(context.getString(R.string.audio), false)) { sound++; } if (shared.getBoolean(context.getString(R.string.led), false)) { led++; } if (shared.getBoolean(context.getString(R.string.vibration), false)) { vibration++; } bw.write(String.valueOf(sound)); bw.newLine(); bw.write(String.valueOf(led)); bw.newLine(); bw.write(String.valueOf(vibration)); bw.close(); } } } catch (IOException e) { Log.e("FILEERROR", "COULD NOT WRITE TO FILE"); } } } } public static void recordData(Context context) { SharedPreferences preferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); double latitude = Double.longBitsToDouble(preferences.getLong(context.getString(R.string.store_lat), 0)); double longitude = Double.longBitsToDouble(preferences.getLong(context.getString(R.string.store_long), 0)); String places = ""; for(int i = 0 ; i < 3; i++) { try { String place = preferences.getString(context.getString(R.string.store_place) + String.valueOf(i), ""); place = place.replace( " ", ""); String placeType = String.valueOf(preferences.getInt(context.getString(R.string.store_place_type) + String.valueOf(i), 0)); String placeLikelihood = String.valueOf(preferences.getFloat(context.getString(R.string.store_place_like) + String.valueOf(i), 0)); places = places + place + " " + placeType + " " + placeLikelihood + " "; }catch(NullPointerException | IndexOutOfBoundsException e){ } } float temperature = preferences.getFloat(context.getString(R.string.store_temp), 0); String line; ArrayList<String> contents = new ArrayList<String>(); String writeLine = String.valueOf(latitude) + " " + String.valueOf(longitude) + " " + places + String.valueOf(temperature); if (isExternalStorageMounted()) { File dir = getDirectory(context); File file = new File(dir, "DATALOG.txt"); //Creates a new file if one doesn't already exist if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { Log.e("FILE_ERROR", "COULDN'T CREATE NEW FILE"); } } //Reads the current contents of the log file try { if (file.isFile() && file.canRead()) { BufferedReader br = new BufferedReader(new FileReader(file)); while ((line = br.readLine()) != null) { contents.add(line + '\n'); } } //Adds the new log to the file up to 1000 items. At 1000 items the oldest is removed if (file.isFile() & file.canWrite()) { BufferedWriter bw = new BufferedWriter(new FileWriter(file)); if (contents.size() > 999) { contents.remove(0); } for (String ts : contents) { bw.write(ts); } bw.write(writeLine); bw.close(); } } catch (IOException e) { Log.e("FILEERROR", "COULD NOT WRITE TO FILE"); } } DataAnalysis.placeAnalysis(context); } private static void getExtraData(final Context context){ GoogleApiClient client; client = new GoogleApiClient.Builder(context) .addApi(Awareness.getSnapshotClient(context).getApi()).build(); client.connect(); try { Awareness.getSnapshotClient(context).getHeadphoneState().addOnCompleteListener(new OnCompleteListener<HeadphoneStateResponse>() { @Override public void onComplete(@NonNull Task<HeadphoneStateResponse> task) { task.getResult().getHeadphoneState().getState(); } }); Awareness.getSnapshotClient(context).getLocation().addOnCompleteListener(new OnCompleteListener<LocationResponse>() { @Override public void onComplete(@NonNull Task<LocationResponse> task) { SharedPreferences preferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putLong(context.getString(R.string.store_lat), Double.doubleToLongBits(task.getResult().getLocation().getLatitude())); editor.putLong(context.getString(R.string.store_long), Double.doubleToLongBits(task.getResult().getLocation().getLongitude())); editor.commit(); } }); Awareness.getSnapshotClient(context).getPlaces().addOnCompleteListener(new OnCompleteListener<PlacesResponse>() { @Override public void onComplete(@NonNull Task<PlacesResponse> task) { SharedPreferences preferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); List<PlaceLikelihood> placeLikelihoods = task.getResult().getPlaceLikelihoods(); for(int i = 0; i < 3; i++) { try { PlaceLikelihood placeLikelihood = placeLikelihoods.get(i); Place place = placeLikelihood.getPlace(); editor.putFloat(context.getString(R.string.store_place_like) + String.valueOf(i), placeLikelihood.getLikelihood()); editor.putString(context.getString(R.string.store_place) + String.valueOf(i), String.valueOf(place.getName())); editor.putInt(context.getString(R.string.store_place_type) + String.valueOf(i), place.getPlaceTypes().get(i)); } catch (NullPointerException | IndexOutOfBoundsException e) { } } editor.commit(); } }); Awareness.getSnapshotClient(context).getWeather().addOnCompleteListener(new OnCompleteListener<WeatherResponse>() { @Override public void onComplete(@NonNull Task<WeatherResponse> task) { SharedPreferences preferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putFloat(context.getString(R.string.store_temp),task.getResult().getWeather().getTemperature(Weather.CELSIUS)); editor.commit(); } }); recordData(context); }catch(SecurityException e){ Intent i = new Intent(context, RequestPermission.class); context.startActivity(i); } } static List<com.notificationFramework.sedentary.frontEnd.Place> getPlaces(Context context){ String line = ""; String[] items; List<com.notificationFramework.sedentary.frontEnd.Place> places = new ArrayList<>(); if (isExternalStorageMounted()) { File dir = getDirectory(context); File file = new File(dir, "DATALOG.txt"); //Creates a new file if one doesn't already exist if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { Log.e("FILE_ERROR", "COULDN'T CREATE NEW FILE"); } } //Reads the current contents of the log file try { if (file.isFile() && file.canRead()) { BufferedReader br = new BufferedReader(new FileReader(file)); while ((line = br.readLine()) != null) { items = line.split(" "); for(int i = 0; i < 3; i++) { try { com.notificationFramework.sedentary.frontEnd.Place place = new com.notificationFramework.sedentary.frontEnd.Place( items[i + 2], Float.parseFloat(items[i + 4]), Integer.parseInt(items[i + 3])); places.add(place); }catch(NumberFormatException | IndexOutOfBoundsException e){ } } } } }catch(IOException e){ } } return places; } static void writePopularType(Context context, int popularType, int acknowledged) { String line = ""; ArrayList<String> contents = new ArrayList<String>(); boolean matched = false; if (isExternalStorageMounted()) { File dir = getDirectory(context); File file = new File(dir, "POPLOG.txt"); //Creates a new file if one doesn't already exist if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { Log.e("FILE_ERROR", "COULDN'T CREATE NEW FILE"); } } //Reads the current contents of the log file try { if (file.isFile() && file.canRead()) { BufferedReader br = new BufferedReader(new FileReader(file)); while ((line = br.readLine()) != null) { contents.add(line + '\n'); } } if (file.isFile() & file.canWrite()) { BufferedWriter bw = new BufferedWriter(new FileWriter(file)); for (String s : contents) { String[] items = s.split(" "); if (items[0].equals(String.valueOf(popularType))) { int no = Integer.parseInt(items[1].trim()); no++; int ack = Integer.parseInt(items[2].trim()); ack += acknowledged; SharedPreferences shared = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE); SharedPreferences.Editor editor = shared.edit(); editor.putInt(context.getString(R.string.place_ack_total), ack ); editor.apply(); bw.write(items[0] + " " + String.valueOf(no) + " " + ack); matched = true; }else{ bw.write(s); } } if(!matched){ bw.write(String.valueOf(popularType) + " " + String.valueOf(1) + " " + acknowledged); } bw.close(); } }catch(IOException e){ } } } static int findMostPopularType(Context context) { String line = ""; ArrayList<String> contents = new ArrayList<String>(); if (isExternalStorageMounted()) { File dir = getDirectory(context); File file = new File(dir, "POPLOG.txt"); //Creates a new file if one doesn't already exist if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { Log.e("FILE_ERROR", "COULDN'T CREATE NEW FILE"); } } //Reads the current contents of the log file try { if (file.isFile() && file.canRead()) { BufferedReader br = new BufferedReader(new FileReader(file)); while ((line = br.readLine()) != null) { contents.add(line + '\n'); } if(contents.size() >= 100) { return mostPopular(contents); } } }catch(IOException e){ } } return -1; } private static int mostPopular(List<String> contents){ int count = 1; int tempCount; String[] items = contents.get(0).split(" "); int popular = Integer.parseInt(items[0]); int temp; for (int i = 0; i < (contents.size() - 1); i++) { items = contents.get(i).split(" "); temp = Integer.parseInt(items[0]); tempCount = Integer.parseInt(items[1]); if (tempCount > count){ popular = temp; count = tempCount; } } return popular; } /** * Checks if external storage is available on the device. * * @return true if external storage is available, false otherwise */ private static boolean isExternalStorageMounted() { return (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())); } /** * Gets the directory for the application where data files should be stored. This ensures each * file uses the same directory and makes compresssion of data easier also. * * @param context the application context from which this method was called * @return the directory for files to be placed in */ private static File getDirectory(Context context) { File file = new File(context.getExternalFilesDir( Environment.DIRECTORY_DOCUMENTS), "NOTLOGS"); if (!file.exists()) { if (!file.mkdirs()) { Log.e("DIRERROR", "Directory not created"); } } return file; } }