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

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