1 | package de.ugoe.cs.autoquest.androidmonitor;
|
---|
2 |
|
---|
3 | import java.io.File;
|
---|
4 | import java.io.FileOutputStream;
|
---|
5 | import java.io.StringWriter;
|
---|
6 | import java.lang.reflect.Field;
|
---|
7 |
|
---|
8 | import org.xmlpull.v1.XmlSerializer;
|
---|
9 |
|
---|
10 | import de.ugoe.cs.autoquest.androidmonitor.AndroidmonitorCompositeOnClickListener;
|
---|
11 | import android.app.Activity;
|
---|
12 | import android.content.Context;
|
---|
13 | import android.content.pm.ApplicationInfo;
|
---|
14 | import android.content.pm.PackageManager;
|
---|
15 | import android.content.pm.PackageManager.NameNotFoundException;
|
---|
16 | import android.os.Build;
|
---|
17 | import android.util.Log;
|
---|
18 | import android.util.Xml;
|
---|
19 | import android.view.View;
|
---|
20 | import android.view.ViewGroup;
|
---|
21 |
|
---|
22 | public class Androidmonitor {
|
---|
23 |
|
---|
24 | String activityName; // name of the activity Class that starts a Tracker
|
---|
25 |
|
---|
26 | // Log file
|
---|
27 | private AndroidmonitorLogFile logFile;
|
---|
28 |
|
---|
29 | /**
|
---|
30 | * constructor method to get a monitor object
|
---|
31 | *
|
---|
32 | * @return monitor
|
---|
33 | */
|
---|
34 | public static Androidmonitor getInstanceOfAndroidmonitor() {
|
---|
35 | Androidmonitor monitor = new Androidmonitor();
|
---|
36 | return monitor;
|
---|
37 | }
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * starts tracking an activity
|
---|
41 | *
|
---|
42 | * @param activity
|
---|
43 | */
|
---|
44 | public void startMonitor(Activity activity) {
|
---|
45 | activityName = activity.getClass().getSimpleName();
|
---|
46 |
|
---|
47 | logFile = new AndroidmonitorLogFile(getAppLable(activity),
|
---|
48 | activity.getFilesDir());
|
---|
49 |
|
---|
50 | addLogListenerToView(getRootView(activity));
|
---|
51 |
|
---|
52 | // tbd
|
---|
53 | // listen to changes and update own listener
|
---|
54 | // find out if it is possible to directly call addLogListenerToView
|
---|
55 | // again
|
---|
56 | // activity.onContentChanged();
|
---|
57 |
|
---|
58 | // write backPresss as event to xml file
|
---|
59 | // activity.onBackPressed();
|
---|
60 |
|
---|
61 | // handle onStop() method of the activity
|
---|
62 | // add a function that end up tracking if onStop() is given otherwise
|
---|
63 | // create onStop()
|
---|
64 | // http://developer.android.com/training/basics/activity-lifecycle/stopping.html
|
---|
65 | }
|
---|
66 |
|
---|
67 | /**
|
---|
68 | * get the root view of an activity
|
---|
69 | *
|
---|
70 | * @param activity
|
---|
71 | * @return
|
---|
72 | */
|
---|
73 | public View getRootView(Activity activity) {
|
---|
74 | // get root view of the activity as start point
|
---|
75 | View view = activity.getWindow().getDecorView().getRootView();
|
---|
76 | // try out if the given node is the upper one in the tree and return the
|
---|
77 | // first node of the tree
|
---|
78 | // The root of the decorView could be embedded into another layout
|
---|
79 | // element.
|
---|
80 | return findFirstView(view);
|
---|
81 | }
|
---|
82 |
|
---|
83 | /**
|
---|
84 | * returns first view element of the tree
|
---|
85 | *
|
---|
86 | * @param view
|
---|
87 | * @return
|
---|
88 | */
|
---|
89 | private View findFirstView(View view) {
|
---|
90 | if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
|
---|
91 | return findFirstView((View) view.getParent());
|
---|
92 | } else {
|
---|
93 | return view;
|
---|
94 | }
|
---|
95 | }
|
---|
96 |
|
---|
97 | /**
|
---|
98 | * replace the listener of each view with a composite listener which
|
---|
99 | * collects several listeners for one view.
|
---|
100 | *
|
---|
101 | * @param view
|
---|
102 | */
|
---|
103 | public void addLogListenerToView(View view) {
|
---|
104 | // traverse all views of the activity
|
---|
105 | logFile.addComponent(view, activityName);
|
---|
106 | if (view instanceof ViewGroup) {
|
---|
107 | ViewGroup group = (ViewGroup) view;
|
---|
108 | for (int i = 0; i < group.getChildCount(); i++) {
|
---|
109 | View child = group.getChildAt(i);
|
---|
110 | addLogListenerToView(child);
|
---|
111 | }
|
---|
112 | }
|
---|
113 |
|
---|
114 | // save original listener to add it later on to the groupLisatener
|
---|
115 | View.OnClickListener listener = getOnClickListener(view);
|
---|
116 |
|
---|
117 | if (listener != null) {
|
---|
118 | // create new compositeOnClickListener to handle multiple listeners
|
---|
119 | // for one view
|
---|
120 | AndroidmonitorCompositeOnClickListener groupListener = new AndroidmonitorCompositeOnClickListener();
|
---|
121 | // replace the original onClickListener with the
|
---|
122 | // compositeOnClickListener
|
---|
123 | view.setOnClickListener(groupListener);
|
---|
124 | // add the tracking part as a several listener
|
---|
125 | groupListener.addOnClickListener(new View.OnClickListener() {
|
---|
126 | public void onClick(View v) {
|
---|
127 |
|
---|
128 | logFile.addEvent(v);
|
---|
129 |
|
---|
130 | // track information ...
|
---|
131 | // Log.d("MyLog",
|
---|
132 | // "activity:" + activityName + " id:" + v.getId()
|
---|
133 | // + " element:"
|
---|
134 | // + v.getClass().getSimpleName() + " x:" + v.getX() + " y:"
|
---|
135 | // + v.getY() + " time:"
|
---|
136 | // + System.currentTimeMillis());
|
---|
137 |
|
---|
138 | }
|
---|
139 | });
|
---|
140 | // add original onClick listener to groupListener of the view
|
---|
141 | groupListener.addOnClickListener(listener);
|
---|
142 | }
|
---|
143 | }
|
---|
144 |
|
---|
145 | /**
|
---|
146 | * finds out if a listener exists
|
---|
147 | *
|
---|
148 | * @param view
|
---|
149 | * @return the listener of the view or null if no listener exists
|
---|
150 | */
|
---|
151 | public View.OnClickListener getOnClickListener(View view) {
|
---|
152 | // http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
|
---|
153 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
---|
154 | return getOnClickListenerV14(view);
|
---|
155 | } else {
|
---|
156 | return getOnClickListenerV(view);
|
---|
157 | }
|
---|
158 | }
|
---|
159 |
|
---|
160 | // Used for APIs lower than ICS (API 14)
|
---|
161 | private View.OnClickListener getOnClickListenerV(View view) {
|
---|
162 | View.OnClickListener retrievedListener = null;
|
---|
163 | String viewStr = "android.view.View";
|
---|
164 | Field field;
|
---|
165 |
|
---|
166 | try {
|
---|
167 | field = Class.forName(viewStr).getDeclaredField("mOnClickListener");
|
---|
168 | retrievedListener = (View.OnClickListener) field.get(view);
|
---|
169 | } catch (NoSuchFieldException ex) {
|
---|
170 | Log.e("Reflection", "No Such Field.");
|
---|
171 | } catch (IllegalAccessException ex) {
|
---|
172 | Log.e("Reflection", "Illegal Access.");
|
---|
173 | } catch (ClassNotFoundException ex) {
|
---|
174 | Log.e("Reflection", "Class Not Found.");
|
---|
175 | }
|
---|
176 |
|
---|
177 | return retrievedListener;
|
---|
178 | }
|
---|
179 |
|
---|
180 | // Used for new ListenerInfo class structure used beginning with API 14
|
---|
181 | // (ICS)
|
---|
182 | private View.OnClickListener getOnClickListenerV14(View view) {
|
---|
183 | View.OnClickListener retrievedListener = null;
|
---|
184 | String viewStr = "android.view.View";
|
---|
185 | String lInfoStr = "android.view.View$ListenerInfo";
|
---|
186 |
|
---|
187 | try {
|
---|
188 | Field listenerField = Class.forName(viewStr).getDeclaredField(
|
---|
189 | "mListenerInfo");
|
---|
190 | Object listenerInfo = null;
|
---|
191 |
|
---|
192 | if (listenerField != null) {
|
---|
193 | listenerField.setAccessible(true);
|
---|
194 | listenerInfo = listenerField.get(view);
|
---|
195 | }
|
---|
196 |
|
---|
197 | Field clickListenerField = Class.forName(lInfoStr)
|
---|
198 | .getDeclaredField("mOnClickListener");
|
---|
199 |
|
---|
200 | if (clickListenerField != null && listenerInfo != null) {
|
---|
201 | retrievedListener = (View.OnClickListener) clickListenerField
|
---|
202 | .get(listenerInfo);
|
---|
203 | }
|
---|
204 | } catch (NoSuchFieldException ex) {
|
---|
205 | Log.e("Reflection", "No Such Field.");
|
---|
206 | } catch (IllegalAccessException ex) {
|
---|
207 | Log.e("Reflection", "Illegal Access.");
|
---|
208 | } catch (ClassNotFoundException ex) {
|
---|
209 | Log.e("Reflection", "Class Not Found.");
|
---|
210 | }
|
---|
211 |
|
---|
212 | return retrievedListener;
|
---|
213 | }
|
---|
214 |
|
---|
215 | /**
|
---|
216 | * get application name as defined in Package Name
|
---|
217 | *
|
---|
218 | * @param pContext
|
---|
219 | * package context; could also be an activity
|
---|
220 | * @return app name
|
---|
221 | */
|
---|
222 | public String getAppLable(Context pContext) {
|
---|
223 | // source (2014-09-04):
|
---|
224 | // http://stackoverflow.com/questions/11229219/android-get-application-name-not-package-name
|
---|
225 | PackageManager lPackageManager = pContext.getPackageManager();
|
---|
226 | ApplicationInfo lApplicationInfo = null;
|
---|
227 | try {
|
---|
228 | lApplicationInfo = lPackageManager.getApplicationInfo(
|
---|
229 | pContext.getApplicationInfo().packageName, 0);
|
---|
230 | } catch (final NameNotFoundException e) {
|
---|
231 | }
|
---|
232 | return (String) (lApplicationInfo != null ? lPackageManager
|
---|
233 | .getApplicationLabel(lApplicationInfo) : "Unknown");
|
---|
234 | }
|
---|
235 |
|
---|
236 | }
|
---|