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

Last change on this file since 1882 was 1882, checked in by funger, 9 years ago
  • Monitor the position of the element in the original GUI.
  • Property svn:mime-type set to text/plain
File size: 10.7 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     * <p>
57     * Represents the position of the current view element in the tree structure of an activity.
58     * </p>
59     */
60    private int elementPosition = 0;
61
62    /**
63     * Constructor method to get a monitor object.
64     *
65     * @return monitor
66     */
67    public static AndroidMonitor getInstanceOfAndroidMonitor() {
68        AndroidMonitor monitor = new AndroidMonitor();
69        return monitor;
70    }
71
72    /**
73     * starts tracking an activity
74     *
75     * @param activity
76     */
77    public void startMonitor(Activity activity) {
78        // create logFile only once.
79        if (AndroidMonitor.logFile == null) {
80            AndroidMonitor.logFile = new AndroidMonitorLogFile(activity);
81            Log.i("logfile", "file created");
82        }
83        activityName = activity.getClass().getSimpleName();
84        addLogListenerToView(getRootView(activity));
85
86        /*
87         * TODO listen to changes and update own listener. - Listen to known elements is implemented
88         * and trigger startMonitot(View).
89         */
90        /*
91         * TODO write backPresss as event to xml file activity.onBackPressed();
92         */
93        /*
94         * TODO handle onStop() method of the activity add a function that end up tracking if
95         * onStop() is given otherwise. Maybe: create onStop()
96         * http://developer.android.com/training/basics/activity-lifecycle/stopping.html
97         */
98    }
99
100    public void startMonitor(View view) {
101        addLogListenerToView(findFirstView(view));
102    }
103
104    /**
105     * Get the root view of an activity.
106     *
107     * @param activity
108     * @return
109     */
110    private View getRootView(Activity activity) {
111        // get root view of the activity as start point
112        View view = activity.getWindow().getDecorView().getRootView();
113        // try out if the given node is the upper one in the tree and return the
114        // first node of the tree
115        // The root of the decorView could be embedded into another layout
116        // element.
117        return findFirstView(view);
118    }
119
120    /**
121     * Returns first view element of the tree.
122     *
123     * @param view
124     * @return
125     */
126    private View findFirstView(View view) {
127        elementPosition = 0;
128        if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
129            return findFirstView((View) view.getParent());
130        }
131        else {
132            return view;
133        }
134    }
135
136    /**
137     * Replace the listener of each view with a composite listener which collects several listeners
138     * for one view. Add a TextWatcher to all TexView views.
139     *
140     * @param view
141     *            to be monitored
142     */
143    private void addLogListenerToView(View view) {
144        addLogListenerToView(view, 0);
145    }
146
147    /**
148     * Replace the listener of each view with a composite listener which collects several listeners
149     * for one view.
150     *
151     * @param view
152     *            to be monitored
153     * @param parentHash
154     *            hash of the parent view element
155     */
156    private void addLogListenerToView(View view, int parentHash) {
157
158        if (!AndroidMonitorLogFile.isComponentLogged(view.hashCode())) {
159            AndroidMonitor.logFile.addComponent(view, parentHash, activityName, getElementPosition());
160
161            // Log.i("object type", "");
162
163            // save original listener to add it later on to the groupLisatener
164            View.OnClickListener onClickListener = getOnClickListener(view);
165
166            if (onClickListener != null) {
167                // create new compositeOnClickListener to handle multiple listeners
168                // for one view
169                AndroidMonitorCompositeOnClickListener groupListener =
170                    new AndroidMonitorCompositeOnClickListener();
171                // replace the original onClickListener with the
172                // compositeOnClickListener
173                view.setOnClickListener(groupListener);
174                // add the tracking part as a several listener
175                groupListener.addOnClickListener(new View.OnClickListener() {
176                    public void onClick(View v) {
177                        AndroidMonitor.logFile.addEvent(v, "onClick");
178                        // check if something changed in the activity
179                        startMonitor(v);
180                    }
181                });
182
183                groupListener.addOnClickListener(onClickListener);
184            }
185            // if view is a TextView add a addTextChangedListener to this view
186            if (view instanceof TextView) {
187                final int hashOfView = view.hashCode();
188                TextView textView = (TextView) view;
189                textView.addTextChangedListener(new TextWatcher() {
190                    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
191                        // do nothing
192                    }
193
194                    public void onTextChanged(CharSequence s, int start, int before, int count) {
195                        // do nothing
196                    }
197
198                    public void afterTextChanged(Editable s) {
199                        AndroidMonitor.logFile.addEvent(hashOfView, "text", s.toString());
200                    }
201
202                });
203            }
204        }
205
206        // hash code of the actual view element. Problem in using
207        // view.getParent().hashCode() is described in
208        // de.ugoe.cs.autoquest.androidmonitor.AndroidMonitorLogFile#addComponent()
209        parentHash = view.hashCode();
210        // traverse all views of the activity
211        if (view instanceof ViewGroup) {           
212            ViewGroup group = (ViewGroup) view;
213            for (int i = 0; i < group.getChildCount(); i++) {               
214                View child = group.getChildAt(i);
215                addLogListenerToView(child, parentHash);
216            }
217        }
218
219    }
220
221    /**
222     * finds out if a listener exists
223     *
224     * @param view
225     * @return the listener of the view or null if no listener exists
226     */
227    // source: http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
228    private View.OnClickListener getOnClickListener(View view) {
229        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
230            return getOnClickListenerV14(view);
231        }
232        else {
233            return getOnClickListenerV(view);
234        }
235    }
236
237    // Used for APIs lower than ICS (API 14)
238    // source: http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
239    private View.OnClickListener getOnClickListenerV(View view) {
240        View.OnClickListener retrievedListener = null;
241        String viewStr = "android.view.View";
242        Field field;
243
244        try {
245            field = Class.forName(viewStr).getDeclaredField("mOnClickListener");
246            retrievedListener = (View.OnClickListener) field.get(view);
247        }
248        catch (NoSuchFieldException ex) {
249            Log.e("Reflection", "No Such Field.");
250        }
251        catch (IllegalAccessException ex) {
252            Log.e("Reflection", "Illegal Access.");
253        }
254        catch (ClassNotFoundException ex) {
255            Log.e("Reflection", "Class Not Found.");
256        }
257
258        return retrievedListener;
259    }
260
261    // Used for new ListenerInfo class structure used beginning with API 14
262    // (ICS)
263    // source: http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
264    private View.OnClickListener getOnClickListenerV14(View view) {
265        View.OnClickListener retrievedListener = null;
266        String viewStr = "android.view.View";
267        String lInfoStr = "android.view.View$ListenerInfo";
268
269        try {
270            Field listenerField = Class.forName(viewStr).getDeclaredField("mListenerInfo");
271            Object listenerInfo = null;
272
273            if (listenerField != null) {
274                listenerField.setAccessible(true);
275                listenerInfo = listenerField.get(view);
276            }
277
278            Field clickListenerField = Class.forName(lInfoStr).getDeclaredField("mOnClickListener");
279
280            if (clickListenerField != null && listenerInfo != null) {
281                retrievedListener = (View.OnClickListener) clickListenerField.get(listenerInfo);
282            }
283        }
284        catch (NoSuchFieldException ex) {
285            Log.e("Reflection", "No Such Field.");
286        }
287        catch (IllegalAccessException ex) {
288            Log.e("Reflection", "Illegal Access.");
289        }
290        catch (ClassNotFoundException ex) {
291            Log.e("Reflection", "Class Not Found.");
292        }
293
294        return retrievedListener;
295    }
296
297    /**
298     *
299     * <p>
300     * Return the AndroidMonitorLogFile that is used in the AndroidMonitor.
301     * </p>
302     *
303     * @return AndroidMonitorLogFile that is used in the AndroidMonitor.
304     */
305    public AndroidMonitorLogFile getLogFile() {
306        return logFile;
307    }
308   
309    private int getElementPosition(){
310        int element = elementPosition;
311        elementPosition ++;
312        return element;
313    }
314}
Note: See TracBrowser for help on using the repository browser.