source: trunk/autoquest-androidmonitor/src/main/java/de/ugoe/cs/autoquest/androidmonitor/AndroidMonitor.java @ 1871

Last change on this file since 1871 was 1871, checked in by funger, 9 years ago
  • improve documentation
  • fix a bug
  • Property svn:mime-type set to text/plain
File size: 10.3 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.androidmonitor;
16
17import java.lang.reflect.Field;
18
19import de.ugoe.cs.autoquest.androidmonitor.AndroidMonitorCompositeOnClickListener;
20import android.app.Activity;
21import android.os.Build;
22import android.text.Editable;
23import android.text.TextWatcher;
24import android.util.Log;
25import android.view.View;
26import android.view.ViewGroup;
27import android.widget.TextView;
28
29/**
30 * <p>
31 * Monitor an android activity. Call this class via
32 * AndroidMonitor.getInstanceOfAndroidMonitor().startMonitor(this); at the end of the
33 * onCreate(Bundle savedInstanceState) method.
34 * </p>
35 *
36 * @author Florian Unger
37 * @version 1.0
38 */
39public class AndroidMonitor {
40
41    /**
42     * <p>
43     * Name of the Activity that starts the monitor.
44     * </p>
45     */
46    private String activityName;
47
48    /**
49     * <p>
50     * Represents the operations to the log file.
51     * </p>
52     */
53    private static AndroidMonitorLogFile logFile = null;
54
55    /**
56     * Constructor method to get a monitor object.
57     *
58     * @return monitor
59     */
60    public static AndroidMonitor getInstanceOfAndroidMonitor() {
61        AndroidMonitor monitor = new AndroidMonitor();
62        return monitor;
63    }
64
65    /**
66     * starts tracking an activity
67     *
68     * @param activity
69     */
70    public void startMonitor(Activity activity) {
71        // create logFile only once.
72        if (AndroidMonitor.logFile == null) {
73            AndroidMonitor.logFile = new AndroidMonitorLogFile(activity);
74            Log.i("logfile", "file created");
75        }
76        activityName = activity.getClass().getSimpleName();
77        addLogListenerToView(getRootView(activity));
78
79        /*
80         * TODO listen to changes and update own listener. - Listen to known elements is implemented
81         * and trigger startMonitot(View).
82         */
83        /*
84         * TODO write backPresss as event to xml file activity.onBackPressed();
85         */
86        /*
87         * TODO handle onStop() method of the activity add a function that end up tracking if
88         * onStop() is given otherwise. Maybe: create onStop()
89         * http://developer.android.com/training/basics/activity-lifecycle/stopping.html
90         */
91    }
92
93    public void startMonitor(View view) {
94        addLogListenerToView(findFirstView(view));
95    }
96
97    /**
98     * Get the root view of an activity.
99     *
100     * @param activity
101     * @return
102     */
103    private View getRootView(Activity activity) {
104        // get root view of the activity as start point
105        View view = activity.getWindow().getDecorView().getRootView();
106        // try out if the given node is the upper one in the tree and return the
107        // first node of the tree
108        // The root of the decorView could be embedded into another layout
109        // element.
110        return findFirstView(view);
111    }
112
113    /**
114     * Returns first view element of the tree.
115     *
116     * @param view
117     * @return
118     */
119    private View findFirstView(View view) {
120        if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
121            return findFirstView((View) view.getParent());
122        }
123        else {
124            return view;
125        }
126    }
127
128    /**
129     * Replace the listener of each view with a composite listener which collects several listeners
130     * for one view. Add a TextWatcher to all TexView views.
131     *
132     * @param view
133     *            to be monitored
134     */
135    private void addLogListenerToView(View view) {
136        addLogListenerToView(view, 0);
137    }
138
139    /**
140     * Replace the listener of each view with a composite listener which collects several listeners
141     * for one view.
142     *
143     * @param view
144     *            to be monitored
145     * @param parentHash
146     *            hash of the parent view element
147     */
148    private void addLogListenerToView(View view, int parentHash) {
149
150        if (!AndroidMonitorLogFile.isComponentLogged(view.hashCode())) {
151            AndroidMonitor.logFile.addComponent(view, parentHash, activityName);
152
153            Log.i("object type", "");
154
155            // save original listener to add it later on to the groupLisatener
156            View.OnClickListener onClickListener = getOnClickListener(view);
157
158            if (onClickListener != null) {
159                // create new compositeOnClickListener to handle multiple listeners
160                // for one view
161                AndroidMonitorCompositeOnClickListener groupListener =
162                    new AndroidMonitorCompositeOnClickListener();
163                // replace the original onClickListener with the
164                // compositeOnClickListener
165                view.setOnClickListener(groupListener);
166                // add the tracking part as a several listener
167                groupListener.addOnClickListener(new View.OnClickListener() {
168                    public void onClick(View v) {
169
170                        AndroidMonitor.logFile.addEvent(v, "onClick");
171                        // check if something changed in the activity
172                        startMonitor(v);
173                    }
174                });
175
176                groupListener.addOnClickListener(onClickListener);
177            }
178            // if view is a TextView add a addTextChangedListener to this view
179            if (view instanceof TextView) {
180                final int hashOfView = view.hashCode();
181                TextView textView = (TextView) view;
182                textView.addTextChangedListener(new TextWatcher() {
183                    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
184                        // do nothing
185                    }
186
187                    public void onTextChanged(CharSequence s, int start, int before, int count) {
188                        // do nothing
189                    }
190
191                    public void afterTextChanged(Editable s) {
192                        AndroidMonitor.logFile.addEvent(hashOfView, "text", s.toString());
193                    }
194
195                });
196            }
197        }
198
199        // hash code of the actual view element. Problem in using
200        // view.getParent().hashCode() is described in
201        // de.ugoe.cs.autoquest.androidmonitor.AndroidMonitorLogFile#addComponent()
202        parentHash = view.hashCode();
203        // traverse all views of the activity
204        if (view instanceof ViewGroup) {
205            ViewGroup group = (ViewGroup) view;
206            for (int i = 0; i < group.getChildCount(); i++) {
207                View child = group.getChildAt(i);
208                addLogListenerToView(child, parentHash);
209            }
210        }
211
212    }
213
214    /**
215     * finds out if a listener exists
216     *
217     * @param view
218     * @return the listener of the view or null if no listener exists
219     */
220    // source: http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
221    private View.OnClickListener getOnClickListener(View view) {
222        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
223            return getOnClickListenerV14(view);
224        }
225        else {
226            return getOnClickListenerV(view);
227        }
228    }
229
230    // Used for APIs lower than ICS (API 14)
231    // source: http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
232    private View.OnClickListener getOnClickListenerV(View view) {
233        View.OnClickListener retrievedListener = null;
234        String viewStr = "android.view.View";
235        Field field;
236
237        try {
238            field = Class.forName(viewStr).getDeclaredField("mOnClickListener");
239            retrievedListener = (View.OnClickListener) field.get(view);
240        }
241        catch (NoSuchFieldException ex) {
242            Log.e("Reflection", "No Such Field.");
243        }
244        catch (IllegalAccessException ex) {
245            Log.e("Reflection", "Illegal Access.");
246        }
247        catch (ClassNotFoundException ex) {
248            Log.e("Reflection", "Class Not Found.");
249        }
250
251        return retrievedListener;
252    }
253
254    // Used for new ListenerInfo class structure used beginning with API 14
255    // (ICS)
256    // source: http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
257    private View.OnClickListener getOnClickListenerV14(View view) {
258        View.OnClickListener retrievedListener = null;
259        String viewStr = "android.view.View";
260        String lInfoStr = "android.view.View$ListenerInfo";
261
262        try {
263            Field listenerField = Class.forName(viewStr).getDeclaredField("mListenerInfo");
264            Object listenerInfo = null;
265
266            if (listenerField != null) {
267                listenerField.setAccessible(true);
268                listenerInfo = listenerField.get(view);
269            }
270
271            Field clickListenerField = Class.forName(lInfoStr).getDeclaredField("mOnClickListener");
272
273            if (clickListenerField != null && listenerInfo != null) {
274                retrievedListener = (View.OnClickListener) clickListenerField.get(listenerInfo);
275            }
276        }
277        catch (NoSuchFieldException ex) {
278            Log.e("Reflection", "No Such Field.");
279        }
280        catch (IllegalAccessException ex) {
281            Log.e("Reflection", "Illegal Access.");
282        }
283        catch (ClassNotFoundException ex) {
284            Log.e("Reflection", "Class Not Found.");
285        }
286
287        return retrievedListener;
288    }
289
290    /**
291     *
292     * <p>
293     * Return the AndroidMonitorLogFile that is used in the AndroidMonitor.
294     * </p>
295     *
296     * @return AndroidMonitorLogFile that is used in the AndroidMonitor.
297     */
298    public AndroidMonitorLogFile getLogFile() {
299        return logFile;
300    }
301}
Note: See TracBrowser for help on using the repository browser.