Mercurial > hg > ccmiandroid
changeset 1:66b3a838feca logging tip
Added logging of user interaction
author | Fiore Martin <fiore@eecs.qmul.ac.uk> |
---|---|
date | Tue, 12 Feb 2013 15:31:48 +0000 |
parents | e0ee6ac3a45f |
children | |
files | res/raw/cancel.mp3 src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleCheckbox.java src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleDialogBuilder.java src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleSpinner.java src/uk/ac/qmul/eecs/ccmi/accessibility/LayoutSonifier.java src/uk/ac/qmul/eecs/ccmi/activities/AccessibleActivity.java src/uk/ac/qmul/eecs/ccmi/activities/CcmiEditorAppActivity.java src/uk/ac/qmul/eecs/ccmi/activities/TreeNavigation.java src/uk/ac/qmul/eecs/ccmi/utilities/ILogger.java |
diffstat | 9 files changed, 208 insertions(+), 9 deletions(-) [+] |
line wrap: on
line diff
--- a/src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleCheckbox.java Thu Dec 13 20:00:21 2012 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleCheckbox.java Tue Feb 12 15:31:48 2013 +0000 @@ -19,6 +19,7 @@ package uk.ac.qmul.eecs.ccmi.accessibility; import uk.ac.qmul.eecs.ccmi.activities.R; +import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; @@ -100,6 +101,7 @@ @Override public void onClick(View view) { spinner.setSelection((spinner.getSelectedItemPosition()+1) % spinner.getCount()); + ILogger.logTap("checkbox (new value="+spinner.getSelectedItem().toString()+')'); if(service != null){ service.speak(spinner.getSelectedItem().toString()+(checks[spinner.getSelectedItemPosition()] ? " " : " un")+"checked"); } @@ -118,7 +120,8 @@ } if(service != null) service.speak(spinner.getSelectedItem().toString()+(checks[spinner.getSelectedItemPosition()] ? "" : "un")+"checked"); - + ILogger.log("user long tap: spinner"); + ILogger.log(spinner.getSelectedItem().toString()+' '+(checks[spinner.getSelectedItemPosition()] ? "" : "un")+"checked"); spinner.setAdapter(new ArrayAdapter<String>(getContext(),R.layout.list_item_1,markedValues)); spinner.setSelection(itemPosition); return true;
--- a/src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleDialogBuilder.java Thu Dec 13 20:00:21 2012 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleDialogBuilder.java Tue Feb 12 15:31:48 2013 +0000 @@ -20,6 +20,7 @@ import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService.SoundEvent; import uk.ac.qmul.eecs.ccmi.activities.R; +import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; @@ -83,6 +84,8 @@ args.putInt("dialogType", type); args.putString("tag", tag); accessibleDialogFragment.setArguments(args); + + ILogger.logDialog(tag); /* show the dialog */ accessibleDialogFragment.show(fragmentManager,tag); } @@ -110,6 +113,8 @@ if(text != null) args.putString("text", text); accessibleDialogFragment.setArguments(args); + + ILogger.logDialog(tag); /* show the dialog */ accessibleDialogFragment.show(fragmentManager,tag); } @@ -136,6 +141,8 @@ args.putInt("dialogType", R.layout.selection_dialog); args.putString("tag", tag); accessibleDialogFragment.setArguments(args); + + ILogger.logDialog(tag); /* show the dialog */ accessibleDialogFragment.show(fragmentManager,tag); } @@ -165,6 +172,8 @@ args.putInt("dialogType", R.layout.checkbox_dialog); args.putString("tag", tag); accessibleDialogFragment.setArguments(args); + + ILogger.logDialog(tag); /* show the dialog */ accessibleDialogFragment.show(fragmentManager,tag); } @@ -261,6 +270,12 @@ LayoutSonifier.getInstance().onTouch(layout, evt, accessibilityService); return super.dispatchTouchEvent(evt); } + + @Override + public void onBackPressed(){ + super.onBackPressed(); + ILogger.logTap("back (cancel)"); + } } }
--- a/src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleSpinner.java Thu Dec 13 20:00:21 2012 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/accessibility/AccessibleSpinner.java Tue Feb 12 15:31:48 2013 +0000 @@ -18,6 +18,7 @@ */ package uk.ac.qmul.eecs.ccmi.accessibility; +import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; @@ -66,6 +67,7 @@ if(service != null){ service.speak(getSelectedItem().toString()); } + ILogger.logTap("spinner (new value=("+getSelectedItem().toString()+')'); } return true;
--- a/src/uk/ac/qmul/eecs/ccmi/accessibility/LayoutSonifier.java Thu Dec 13 20:00:21 2012 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/accessibility/LayoutSonifier.java Tue Feb 12 15:31:48 2013 +0000 @@ -18,6 +18,7 @@ */ package uk.ac.qmul.eecs.ccmi.accessibility; +import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -104,19 +105,24 @@ accessibilityService.vibrate(); CharSequence contentDescription = v.getContentDescription(); /* if the view has accessibility content description set, use it */ - if(contentDescription != null ) + if(contentDescription != null ){ accessibilityService.speak(contentDescription.toString() + ((v instanceof TextView) ? ((TextView)v).getText() : "") ); - else + ILogger.logHover(contentDescription.toString()); + }else{ accessibilityService.speak(((TextView)v).getText().toString()); + ILogger.logHover(((TextView)v).getText().toString()); + } }else if(v instanceof AccessibleCheckbox){ AccessibleCheckbox ab = (AccessibleCheckbox)v; boolean isChecked = ab.getChecks()[ab.getSelectedValuePosition()]; accessibilityService.vibrate(); accessibilityService.speak(ab.getSelectedValue()+ (isChecked ? ", checked" : ", unchecked")); + ILogger.logHover("check box"); }else if (v instanceof Spinner){ accessibilityService.vibrate(); accessibilityService.speak(v.getContentDescription()+" "+((Spinner)v).getSelectedItem()); + ILogger.logHover("spinner"); } }
--- a/src/uk/ac/qmul/eecs/ccmi/activities/AccessibleActivity.java Thu Dec 13 20:00:21 2012 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/activities/AccessibleActivity.java Tue Feb 12 15:31:48 2013 +0000 @@ -22,6 +22,7 @@ import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService.SoundEvent; import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleDialogBuilder; import uk.ac.qmul.eecs.ccmi.accessibility.LayoutSonifier; +import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import android.content.pm.PackageManager; import android.media.AudioManager; import android.os.Bundle; @@ -31,6 +32,7 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; +import android.widget.AbsListView; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ListAdapter; @@ -102,6 +104,26 @@ } }; list.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + list.setOnScrollListener(new AbsListView.OnScrollListener(){ + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {} + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + switch(scrollState){ + case AbsListView.OnScrollListener.SCROLL_STATE_FLING : + ILogger.log("user scroll: fling"); + break; + case AbsListView.OnScrollListener.SCROLL_STATE_IDLE : + ILogger.log("user scroll: "+ view.getItemAtPosition(list.getFirstVisiblePosition())); + break; + case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL : + ILogger.log("user scroll: started scrolling"); + break; + } + } + }); listLayout.addView(list); /* init header with a button to scroll down an up the list */
--- a/src/uk/ac/qmul/eecs/ccmi/activities/CcmiEditorAppActivity.java Thu Dec 13 20:00:21 2012 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/activities/CcmiEditorAppActivity.java Tue Feb 12 15:31:48 2013 +0000 @@ -30,6 +30,7 @@ import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService; import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService.SoundEvent; import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleDialogBuilder; +import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import uk.ac.qmul.eecs.ccmi.xmlparser.Diagram; import android.content.Intent; import android.content.res.AssetManager; @@ -40,6 +41,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; +import android.widget.TextView; /** * @@ -71,6 +73,10 @@ list.setOnItemClickListener(this); list.setOnItemLongClickListener(this); + + ILogger.log("--- APPLICATION STARTED ---"); + + /* init assets */ AssetManager assetManager = getAssets(); try { @@ -96,12 +102,15 @@ /*----- listeners to implement the navigation ----*/ @Override public void onItemClick(AdapterView<?> av, View v, int pos, long id) { + ILogger.logTap(((TextView)v).getText()); if(!navigation.goNext(pos)){ accessibilityService.playSound(AccessibilityService.SoundEvent.V_ERROR); + ILogger.logError("end of tree"); return; } accessibilityService.playSound(AccessibilityService.SoundEvent.V_EXPAND); - accessibilityService.speak("Displaying " + getHeaderText()); + accessibilityService.speak("displaying " + getHeaderText()); + ILogger.logActivity(getHeaderText()); } /** @@ -112,9 +121,12 @@ */ @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + ILogger.logLongTap(((TextView)view).getText()); boolean doneSomething = navigation.getController().performEditAction(parent, view, position, id, this); - if(!doneSomething) + if(!doneSomething){ accessibilityService.playSound(AccessibilityService.SoundEvent.V_ERROR); + ILogger.logError("no action available on this item"); + } return doneSomething; } @@ -125,9 +137,11 @@ */ @Override public void onBackPressed() { + ILogger.logTap("back"); if(navigation.goPrevious()){ accessibilityService.playSound(SoundEvent.V_COLLAPSE); accessibilityService.speak("Displaying "+getHeaderText()); + ILogger.logActivity(getHeaderText()); update(); }else{ accessibilityService.playSound(SoundEvent.V_EDITING_MODE, true); @@ -135,19 +149,26 @@ @Override public void onClick(View v, DialogFragment dialogFragment, String tag) { if("EXIT".equals(v.getTag())){ + ILogger.logButton("exit"); accessibilityService.speak(getResources().getString(R.string.exitMessage)); + ILogger.log("--- EXIT ---"); + ILogger.dispose(); finish(); }else if("CANCEL".equals(v.getTag())){ + ILogger.logButton("cancel"); + accessibilityService.playSound(SoundEvent.V_CANCEL); accessibilityService.speak("Cancel"); accessibilityService.stopSound(); dialogFragment.dismiss(); }else if("OPEN".equals(v.getTag())){ + ILogger.logButton("open"); /* start the activity to open a file */ Intent intent = new Intent(CcmiEditorAppActivity.this, FileSelectorActivity.Open.class); intent.putExtra("extension", CCMI_EXTENSION); startActivityForResult(intent,OPEN_REQUEST); dialogFragment.dismiss(); }else if("SAVE".equals(v.getTag())){ + ILogger.logButton("save"); Intent intent = new Intent(CcmiEditorAppActivity.this, FileSelectorActivity.Save.class); intent.putExtra("extension", CCMI_EXTENSION); startActivityForResult(intent,SAVE_REQUEST); @@ -165,6 +186,7 @@ try { InputStream in = new FileInputStream(new File(data.getData().getPath())); readXML(in); + ILogger.logActivity(navigation.getCurrentPath()); } catch (Exception e) { accessibilityService.playSound(SoundEvent.V_ERROR); accessibilityService.speak("File could not be open");
--- a/src/uk/ac/qmul/eecs/ccmi/activities/TreeNavigation.java Thu Dec 13 20:00:21 2012 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/activities/TreeNavigation.java Tue Feb 12 15:31:48 2013 +0000 @@ -30,6 +30,7 @@ import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService.SoundEvent; import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleCheckbox; import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleDialogBuilder; +import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import uk.ac.qmul.eecs.ccmi.utilities.Stack; import uk.ac.qmul.eecs.ccmi.xmlparser.Diagram; import uk.ac.qmul.eecs.ccmi.xmlparser.DiagramUpdater; @@ -501,7 +502,7 @@ }; /* - * Responds to the long cick of the user. It acts according to the list item clicked + * Responds to the long click of the user. It acts according to the list item clicked * by the user (and therefore according to the hierarchy level currently displayed). * When a dialog needs to be shown, it used dialogBuilder.displayDialog() and register itself * as buttoClickListener to handle the click of the user in the same class. @@ -522,19 +523,25 @@ /* construct the node and add it to the diagram */ Node node = new Node(((NodeType)clickedItem).getType(),properties); diagramUpdater.addNode(node); + dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_OK); dialogBuilder.getAccessibilityService().speak("Node "+ node +" created"); + ILogger.log("Node "+ node +" created"); }else{ // user clicked on edge List<EdgeType> edgeTypes = diagram.getPrototypes().getEdgeTypes(); clickedItem = edgeTypes.get(position - nodeTypes.size()); /* respect min and max attached nodes */ if(selectedNodes.size() < ((EdgeType)clickedItem).getMinAttachedNodes()){ + dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_ERROR); dialogBuilder.getAccessibilityService().speak("you must select at least "+ ((EdgeType)clickedItem).getMinAttachedNodes()+" nodes"); + ILogger.logError("selected nodes < "+((EdgeType)clickedItem).getMinAttachedNodes()); return true; } if(selectedNodes.size() > ((EdgeType)clickedItem).getMaxAttachedNodes()){ + dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_ERROR); dialogBuilder.getAccessibilityService().speak("you must select at most "+ ((EdgeType)clickedItem).getMaxAttachedNodes()+" nodes"); + ILogger.logError("selected nodes > "+((EdgeType)clickedItem).getMaxAttachedNodes()); return true; } @@ -547,6 +554,7 @@ edge.getAttachedNodes().add(new EdgeNode(n.getId())); } diagramUpdater.addEdge(edge); + dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_OK); StringBuilder builder = new StringBuilder(); builder.append(edge).append(" created between "); for(int i=0; i<selectedNodes.size(); i++){ @@ -560,6 +568,7 @@ } selectedNodes.clear(); // when an edge is added the selected node are cleared dialogBuilder.getAccessibilityService().speak(builder.toString()); + ILogger.log(builder.toString()); } /* update the view */ cachedList = buildCurrentChildList(); @@ -611,16 +620,18 @@ return false; } - /* this is the callback triggered when the user clicks on any bottom of the dialog shown. v is the button + /* this is the callback triggered when the user clicks on any botton of the dialog shown. v is the button * The method first checks for the dialog tag to understand which dialog it's handling, then it checks * for the button tag to understand which button the user pressed. The first check though is on "CANCEL" * button as its tag it's the same for all the dialog */ @Override public void onClick(View v, DialogFragment dialogFragment, String dialogTag) { Object buttonTag = v.getTag(); + ILogger.logButton(buttonTag.toString()); AccessibilityService accessibility = dialogBuilder.getAccessibilityService(); if("CANCEL".equals(buttonTag)){ + accessibility.playSound(SoundEvent.V_CANCEL); accessibility.speak("Cancel"); dialogFragment.dismiss(); return; @@ -631,10 +642,12 @@ selectedNodes.add((Node)clickedItem); accessibility.playSound(SoundEvent.V_OK); accessibility.speak(clickedItem + " selected"); + ILogger.log(clickedItem + " selected"); }else if("UNSELECT".equals(buttonTag)){ selectedNodes.remove(clickedItem); accessibility.playSound(SoundEvent.V_OK); accessibility.speak(clickedItem + " unselected"); + ILogger.log(clickedItem + " unselected"); }else if("RENAME".equals(buttonTag)){ dialogFragment.dismiss(); dialogBuilder.displayDialog(R.layout.alert_dialog_rename, RENAME_DIALOG_TAG+clickedItem, this); @@ -654,19 +667,23 @@ if(text.length() == 0){ accessibility.playSound(SoundEvent.V_ERROR); accessibility.speak("Text cannot be empty"); + ILogger.logError("text empty"); return; } String oldName = clickedItem.toString(); diagramUpdater.rename(clickedItem,text); accessibility.playSound(SoundEvent.V_OK); accessibility.speak(oldName+" renamed to "+clickedItem); + ILogger.log(oldName+" renamed to "+clickedItem); }else if(dialogTag.startsWith(CONFIRMATION_DIALOG_TAG)){ /* if it reaches this point it's a "YES" as a "NO" button has "CANCEL" as its tag */ /* and the match against "CANCEL" match is performed first of all */ diagramUpdater.delete(clickedItem); /* if it's a selected node, remove it from selected */ selectedNodes.remove(clickedItem); + accessibility.playSound(SoundEvent.V_OK); accessibility.speak(clickedItem+" Deleted"); + ILogger.log(clickedItem+" Deleted"); /* update the headers which show the number of children the current item contains */ if(path.current() instanceof NodeType || path.current() instanceof EdgeType){ cachedHeaderTexts.pop(); @@ -683,11 +700,13 @@ if(text.length() == 0){ accessibility.playSound(SoundEvent.V_ERROR); accessibility.speak("Text cannot be empty"); + ILogger.logError("text empty"); return; } diagramUpdater.addProperty((Node)path.get(ITEM_LEVEL),((NodeProperty)clickedItem),text); accessibility.playSound(SoundEvent.V_OK); accessibility.speak("Property "+text+" added"); + ILogger.log("Property "+text+" added"); }else if(EDIT_NODE_REF_DIALOG_TAG.equals(dialogTag)){ if("EDIT_LABEL".equals(buttonTag)){ dialogFragment.dismiss(); @@ -708,6 +727,7 @@ if(heads == null || heads.length == 0){ accessibility.playSound(SoundEvent.V_ERROR); accessibility.speak("There are no arrow heads defined for "+edge); + ILogger.logError("There are no arrow heads defined for "+edge); return; } dialogBuilder.displaySelectionDialog(EDIT_ARROWHEAD_DIALOG_TAG, heads, this); @@ -718,16 +738,19 @@ if(text.length() == 0){ accessibility.playSound(SoundEvent.V_ERROR); accessibility.speak("Text cannot be empty"); + ILogger.logError("Text empty"); return; } diagramUpdater.setLabel((EdgeNode)clickedItem,text); accessibility.playSound(SoundEvent.V_OK); accessibility.speak("Label set to "+clickedItem);// EdgeNode.toString = EdgeNode.getLabel + ILogger.log("Label set to "+clickedItem); }else if(EDIT_ARROWHEAD_DIALOG_TAG.equals(dialogTag)){ Spinner spinner = (Spinner)dialogFragment.getDialog().findViewById(R.id.selectionSpinner); - ((EdgeNode)clickedItem).setHead(spinner.getSelectedItem().toString()); + diagramUpdater.setArrowHead((EdgeNode)clickedItem, spinner.getSelectedItem().toString()); accessibility.playSound(SoundEvent.V_OK); accessibility.speak("Arrow head set to "+spinner.getSelectedItem().toString()); + ILogger.log("Arrow head set to "+spinner.getSelectedItem().toString()); }else if(EDIT_PROPERTY_DIALOG_TAG.equals(dialogTag)){ if("RENAME".equals(buttonTag)){ dialogFragment.dismiss(); @@ -766,11 +789,19 @@ AccessibleCheckbox checkbox = (AccessibleCheckbox)dialogFragment.getDialog().findViewById(R.id.checkBox); boolean[] checks = checkbox.getChecks(); List<Integer> modifiers = new ArrayList<Integer>(checks.length); + StringBuilder modifiersString = new StringBuilder(); for(int i=0; i<checks.length; i++){ - if(checks[i]) + if(checks[i]){ modifiers.add(i); + modifiersString.append(i).append(' '); + } } diagramUpdater.setModifiers((PropertyValue)clickedItem, modifiers); + accessibility.playSound(SoundEvent.V_OK); + accessibility.speak("modifiers set for "+path.current()); + if(modifiersString.length() == 0) + modifiersString.append("no modifiers"); + ILogger.log("modifiers set for "+path.current()+": "+ modifiersString.toString()); } /* update the view */ cachedList = buildCurrentChildList();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/ccmi/utilities/ILogger.java Tue Feb 12 15:31:48 2013 +0000 @@ -0,0 +1,98 @@ +package uk.ac.qmul.eecs.ccmi.utilities; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import android.os.Environment; + + +public class ILogger { + private static final Logger LOGGER = Logger.getLogger(ILogger.class.getName()); + private static final String LOG_PATH = "/Android/data/uk.ac.qmul.eecs.ccmi/files" ; + private static final String LOG_FILE = "/interaction%u.txt" ; + private static FileHandler fileHandler; + public final static int CLICK_BACK = -1; + + + private static void initHandler() throws IOException{ + String state = Environment.getExternalStorageState(); + if(!Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)){ + throw new IOException("Could not find or write on SD Card"); + } + + File logDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+LOG_PATH); + if(!logDir.exists()){ + if(!logDir.mkdirs()) + throw new IOException("Could not create log file directory"); + } + + fileHandler = new FileHandler(logDir.getAbsolutePath()+LOG_FILE,true); + fileHandler.setFormatter(new InteractionLogFormatter()); + LOGGER.addHandler(fileHandler); + } + + + public static void log(String message) { + if(fileHandler == null){ + try { + initHandler(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + LOGGER.info(message+"\n"); + } + + public static void logTap(CharSequence item){ + log("user tap on: "+ item); + } + + public static void logLongTap(CharSequence item){ + log("user long tap on: "+item); + } + + public static void logDialog(CharSequence dialog){ + log("display dialog: "+dialog); + } + + public static void logButton(CharSequence button){ + log("user button press: "+button); + } + + public static void logActivity(CharSequence activity){ + log("display: "+activity); + } + + public static void logError(String cause ){ + log("error: "+cause); + } + + public static void logHover(String text){ + log("user hover: "+text); + } + + public static void dispose(){ + if(fileHandler != null){ + fileHandler.close(); + fileHandler = null; + } + } + +} + +class InteractionLogFormatter extends Formatter { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); + + @Override + public String format(LogRecord record) { + return DATE_FORMAT.format(new Date(record.getMillis()))+','+record.getMessage(); + } + +} \ No newline at end of file