package de.ugoe.cs.autoquest.androidmonitor; import java.io.File; import java.io.FileOutputStream; import java.io.StringWriter; import java.lang.reflect.Field; import org.xmlpull.v1.XmlSerializer; import de.ugoe.cs.autoquest.androidmonitor.AndroidmonitorCompositeOnClickListener; import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.util.Log; import android.util.Xml; import android.view.View; import android.view.ViewGroup; public class Androidmonitor { String activityName; // name of the activity Class that starts a Tracker // Log file private AndroidmonitorLogFile logFile; /** * constructor method to get a monitor object * * @return monitor */ public static Androidmonitor getInstanceOfAndroidmonitor() { Androidmonitor monitor = new Androidmonitor(); return monitor; } /** * starts tracking an activity * * @param activity */ public void startMonitor(Activity activity) { activityName = activity.getClass().getSimpleName(); logFile = new AndroidmonitorLogFile(getAppLable(activity), activity.getFilesDir()); addLogListenerToView(getRootView(activity)); // tbd // listen to changes and update own listener // find out if it is possible to directly call addLogListenerToView // again // activity.onContentChanged(); // write backPresss as event to xml file // activity.onBackPressed(); // handle onStop() method of the activity // add a function that end up tracking if onStop() is given otherwise // create onStop() // http://developer.android.com/training/basics/activity-lifecycle/stopping.html } /** * get the root view of an activity * * @param activity * @return */ public View getRootView(Activity activity) { // get root view of the activity as start point View view = activity.getWindow().getDecorView().getRootView(); // try out if the given node is the upper one in the tree and return the // first node of the tree // The root of the decorView could be embedded into another layout // element. return findFirstView(view); } /** * returns first view element of the tree * * @param view * @return */ private View findFirstView(View view) { if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) { return findFirstView((View) view.getParent()); } else { return view; } } /** * replace the listener of each view with a composite listener which * collects several listeners for one view. * * @param view * to be monitored */ public void addLogListenerToView(View view) { addLogListenerToView(view, 0); } /** * replace the listener of each view with a composite listener which * collects several listeners for one view. * * @param view * to be monitored * @param parentHash * hash of the parent view element */ private void addLogListenerToView(View view, int parentHash) { logFile.addComponent(view, parentHash, activityName); // hash code of the actual view element. Problem in using // view.getParent().hashCode() is described in // de.ugoe.cs.autoquest.androidmonitor addComponent() parentHash = view.hashCode(); // traverse all views of the activity if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; for (int i = 0; i < group.getChildCount(); i++) { View child = group.getChildAt(i); addLogListenerToView(child, parentHash); } } // save original listener to add it later on to the groupLisatener View.OnClickListener listener = getOnClickListener(view); if (listener != null) { // create new compositeOnClickListener to handle multiple listeners // for one view AndroidmonitorCompositeOnClickListener groupListener = new AndroidmonitorCompositeOnClickListener(); // replace the original onClickListener with the // compositeOnClickListener view.setOnClickListener(groupListener); // add the tracking part as a several listener groupListener.addOnClickListener(new View.OnClickListener() { public void onClick(View v) { logFile.addEvent(v, "onClick"); // track information ... // Log.d("MyLog", // "activity:" + activityName + " id:" + v.getId() // + " element:" // + v.getClass().getSimpleName() + " x:" + v.getX() + " y:" // + v.getY() + " time:" // + System.currentTimeMillis()); } }); // add original onClick listener to groupListener of the view groupListener.addOnClickListener(listener); } } /** * finds out if a listener exists * * @param view * @return the listener of the view or null if no listener exists */ public View.OnClickListener getOnClickListener(View view) { // http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { return getOnClickListenerV14(view); } else { return getOnClickListenerV(view); } } // Used for APIs lower than ICS (API 14) private View.OnClickListener getOnClickListenerV(View view) { View.OnClickListener retrievedListener = null; String viewStr = "android.view.View"; Field field; try { field = Class.forName(viewStr).getDeclaredField("mOnClickListener"); retrievedListener = (View.OnClickListener) field.get(view); } catch (NoSuchFieldException ex) { Log.e("Reflection", "No Such Field."); } catch (IllegalAccessException ex) { Log.e("Reflection", "Illegal Access."); } catch (ClassNotFoundException ex) { Log.e("Reflection", "Class Not Found."); } return retrievedListener; } // Used for new ListenerInfo class structure used beginning with API 14 // (ICS) private View.OnClickListener getOnClickListenerV14(View view) { View.OnClickListener retrievedListener = null; String viewStr = "android.view.View"; String lInfoStr = "android.view.View$ListenerInfo"; try { Field listenerField = Class.forName(viewStr).getDeclaredField( "mListenerInfo"); Object listenerInfo = null; if (listenerField != null) { listenerField.setAccessible(true); listenerInfo = listenerField.get(view); } Field clickListenerField = Class.forName(lInfoStr) .getDeclaredField("mOnClickListener"); if (clickListenerField != null && listenerInfo != null) { retrievedListener = (View.OnClickListener) clickListenerField .get(listenerInfo); } } catch (NoSuchFieldException ex) { Log.e("Reflection", "No Such Field."); } catch (IllegalAccessException ex) { Log.e("Reflection", "Illegal Access."); } catch (ClassNotFoundException ex) { Log.e("Reflection", "Class Not Found."); } return retrievedListener; } /** * get application name as defined in Package Name * * @param pContext * package context; could also be an activity * @return app name */ public String getAppLable(Context pContext) { // source (2014-09-04): // http://stackoverflow.com/questions/11229219/android-get-application-name-not-package-name PackageManager lPackageManager = pContext.getPackageManager(); ApplicationInfo lApplicationInfo = null; try { lApplicationInfo = lPackageManager.getApplicationInfo( pContext.getApplicationInfo().packageName, 0); } catch (final NameNotFoundException e) { } return (String) (lApplicationInfo != null ? lPackageManager .getApplicationLabel(lApplicationInfo) : "Unknown"); } }