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

Last change on this file since 1833 was 1833, checked in by funger, 10 years ago
  • Property svn:mime-type set to text/plain
File size: 10.2 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.content.Context;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.os.Build;
26import android.text.Editable;
27import android.text.TextWatcher;
28import android.util.Log;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.TextView;
32
33/**
34 * <p>
35 * Monitor an android activity. Call this class via
36 * AndroidMonitor.getInstanceOfAndroidMonitor().startMonitor(this); at the end of the
37 * onCreate(Bundle savedInstanceState) method.
38 * </p>
39 *
40 * @author Florian Unger
41 * @version 1.0
42 */
43public class AndroidMonitor {
44
45    /**
46     * <p>
47     * Name of the Activity that starts the monitor.
48     * </p>
49     */
50    String activityName;
51
52    /**
53     * <p>
54     * Represents the operations to the log file.
55     * </p>
56     */
57    private AndroidMonitorLogFile logFile;
58
59    /**
60     * Constructor method to get a monitor object.
61     *
62     * @return monitor
63     */
64    public static AndroidMonitor getInstanceOfAndroidMonitor() {
65        AndroidMonitor monitor = new AndroidMonitor();
66        return monitor;
67    }
68
69    /**
70     * starts tracking an activity
71     *
72     * @param activity
73     */
74    public void startMonitor(Activity activity) {
75        activityName = activity.getClass().getSimpleName();
76
77        logFile = new AndroidMonitorLogFile(getAppLable(activity), activity.getFilesDir());
78
79        addLogListenerToView(getRootView(activity));
80
81        // TODO
82        // listen to changes and update own listener
83        // find out if it is possible to directly call addLogListenerToView
84        // again
85        // activity.onContentChanged();
86
87        // write backPresss as event to xml file
88        // activity.onBackPressed();
89
90        // handle onStop() method of the activity
91        // add a function that end up tracking if onStop() is given otherwise
92        // create onStop()
93        // http://developer.android.com/training/basics/activity-lifecycle/stopping.html
94    }
95
96    /**
97     * get the root view of an activity
98     *
99     * @param activity
100     * @return
101     */
102    public 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.
130     *
131     * @param view
132     *            to be monitored
133     */
134    public 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 (!logFile.isComponentLogged(view.hashCode())) {
150            logFile.addComponent(view, parentHash, activityName);
151        }
152
153        // hash code of the actual view element. Problem in using
154        // view.getParent().hashCode() is described in
155        // de.ugoe.cs.autoquest.androidmonitor#addComponent()
156        parentHash = view.hashCode();
157        // traverse all views of the activity
158        if (view instanceof ViewGroup) {
159            ViewGroup group = (ViewGroup) view;
160            for (int i = 0; i < group.getChildCount(); i++) {
161                View child = group.getChildAt(i);
162                addLogListenerToView(child, parentHash);
163            }
164        }
165
166        // save original listener to add it later on to the groupLisatener
167        View.OnClickListener onClickListener = getOnClickListener(view);
168
169        if (onClickListener != null) {
170            // create new compositeOnClickListener to handle multiple listeners
171            // for one view
172            AndroidMonitorCompositeOnClickListener groupListener =
173                new AndroidMonitorCompositeOnClickListener();
174            // replace the original onClickListener with the
175            // compositeOnClickListener
176            view.setOnClickListener(groupListener);
177            // add the tracking part as a several listener
178            groupListener.addOnClickListener(new View.OnClickListener() {
179                public void onClick(View v) {
180
181                    logFile.addEvent(v, "onClick");
182                }
183            });
184
185            groupListener.addOnClickListener(onClickListener);
186        }
187        // if view is a TextView add a addTextChangedListener to this view
188        if (view instanceof TextView) {
189            final int hashOfView = view.hashCode();
190            TextView textView = (TextView) view;
191            textView.addTextChangedListener(new TextWatcher() {
192                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
193                    // do nothing
194                }
195
196                public void onTextChanged(CharSequence s, int start, int before, int count) {
197                    // do nothing
198                }
199
200                public void afterTextChanged(Editable s) {
201                    logFile.addEvent(hashOfView, "text", s.toString());
202                }
203
204            });
205        }
206    }
207
208    /**
209     * finds out if a listener exists
210     *
211     * @param view
212     * @return the listener of the view or null if no listener exists
213     */
214    public View.OnClickListener getOnClickListener(View view) {
215        // http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
216        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
217            return getOnClickListenerV14(view);
218        }
219        else {
220            return getOnClickListenerV(view);
221        }
222    }
223
224    // Used for APIs lower than ICS (API 14)
225    private View.OnClickListener getOnClickListenerV(View view) {
226        View.OnClickListener retrievedListener = null;
227        String viewStr = "android.view.View";
228        Field field;
229
230        try {
231            field = Class.forName(viewStr).getDeclaredField("mOnClickListener");
232            retrievedListener = (View.OnClickListener) field.get(view);
233        }
234        catch (NoSuchFieldException ex) {
235            Log.e("Reflection", "No Such Field.");
236        }
237        catch (IllegalAccessException ex) {
238            Log.e("Reflection", "Illegal Access.");
239        }
240        catch (ClassNotFoundException ex) {
241            Log.e("Reflection", "Class Not Found.");
242        }
243
244        return retrievedListener;
245    }
246
247    // Used for new ListenerInfo class structure used beginning with API 14
248    // (ICS)
249    private View.OnClickListener getOnClickListenerV14(View view) {
250        View.OnClickListener retrievedListener = null;
251        String viewStr = "android.view.View";
252        String lInfoStr = "android.view.View$ListenerInfo";
253
254        try {
255            Field listenerField = Class.forName(viewStr).getDeclaredField("mListenerInfo");
256            Object listenerInfo = null;
257
258            if (listenerField != null) {
259                listenerField.setAccessible(true);
260                listenerInfo = listenerField.get(view);
261            }
262
263            Field clickListenerField = Class.forName(lInfoStr).getDeclaredField("mOnClickListener");
264
265            if (clickListenerField != null && listenerInfo != null) {
266                retrievedListener = (View.OnClickListener) clickListenerField.get(listenerInfo);
267            }
268        }
269        catch (NoSuchFieldException ex) {
270            Log.e("Reflection", "No Such Field.");
271        }
272        catch (IllegalAccessException ex) {
273            Log.e("Reflection", "Illegal Access.");
274        }
275        catch (ClassNotFoundException ex) {
276            Log.e("Reflection", "Class Not Found.");
277        }
278
279        return retrievedListener;
280    }
281
282    /**
283     * get application name as defined in Package Name
284     *
285     * @param pContext
286     *            package context; could also be an activity
287     * @return app name
288     */
289    public String getAppLable(Context pContext) {
290        // source (2014-09-04):
291        // http://stackoverflow.com/questions/11229219/android-get-application-name-not-package-name
292        PackageManager lPackageManager = pContext.getPackageManager();
293        ApplicationInfo lApplicationInfo = null;
294        try {
295            lApplicationInfo =
296                lPackageManager.getApplicationInfo(pContext.getApplicationInfo().packageName, 0);
297        }
298        catch (final NameNotFoundException e) {}
299        return (String) (lApplicationInfo != null ? lPackageManager
300            .getApplicationLabel(lApplicationInfo) : "Unknown");
301    }
302}
Note: See TracBrowser for help on using the repository browser.