// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.androidmonitor; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import org.xmlpull.v1.XmlSerializer; import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.util.Log; import android.util.Xml; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; /** *

* Writes informations about the device, the application, the components of the activity and the * events in a log file. File is stored in the space of the application. Path is normally internal * storage: data/data//files/ e.g. * data/data/de.ugoe.cs.autoquest.androidmonitor.testApp/files/ *

* * @author Florian Unger * @version 1.0 */ public class AndroidMonitorLogFile { /** *

* Name of the log file which is stored in the internal space of the device. *

*/ private static String name; /** *

* Name of the Application which is monitored. *

*/ private static String appName; /** *

* File representation to store monitored information. *

*/ private static File file; /** *

* List representing all components which was written to log file before. *

*/ private static List currentLoggedComponents; /** * *

* Constructor. Creates a new AndroidmonitorLogFile. *

* * @param appName * Name of the calling application. * @param dir * Folder to store the log file. */ public AndroidMonitorLogFile(Activity activity) { createFile(activity); } /** * *

* Get file name which is in use. *

* * @return filename */ public static String getFileName() { return AndroidMonitorLogFile.name; } /** * Get application name as defined in Package Name and write it to appName. * * @param pContext * package context; could also be an activity * @return app name */ private String getAppLable(Context pContext) { // source: // http://stackoverflow.com/questions/11229219/android-get-application-name-not-package-name // (last call 2014-09-04) String appLabel; PackageManager lPackageManager = pContext.getPackageManager(); ApplicationInfo lApplicationInfo = null; try { lApplicationInfo = lPackageManager.getApplicationInfo(pContext.getApplicationInfo().packageName, 0); } catch (final NameNotFoundException e) { } appLabel = (String) (lApplicationInfo != null ? lPackageManager .getApplicationLabel(lApplicationInfo) : "Unknown"); return appLabel; } /** * *

* Get package Name. *

* * @param pContext * package context; could also be an activity * @return package name */ private String getAppPackageName(Context pContext) { return pContext.getPackageName(); } /** * *

* Writes information about the application to the log file. *

* * @param activity * */ private void setAppInformation(Activity activity) { XmlSerializer serializer = Xml.newSerializer(); StringWriter writer = new StringWriter(); try { serializer.setOutput(writer); serializer.startTag("", "application"); serializer.startTag("", "param"); serializer.attribute("", "value", getAppPackageName(activity)); serializer.attribute("", "name", "package"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", getAppLable(activity)); serializer.attribute("", "name", "name"); serializer.endTag("", "param"); serializer.endTag("", "application"); serializer.endDocument(); writeToFile(writer.toString()); } catch (IllegalArgumentException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IllegalStateException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } } /** *

* Query device information and store it to log file. *

* */ private void setDeviceInformation() { XmlSerializer serializer = Xml.newSerializer(); StringWriter writer = new StringWriter(); try { serializer.setOutput(writer); serializer.startTag("", "device"); serializer.startTag("", "param"); serializer.attribute("", "value", "" + android.os.Build.VERSION.SDK_INT); serializer.attribute("", "name", "sdk_version"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", android.os.Build.DEVICE); serializer.attribute("", "name", "device"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", android.os.Build.MANUFACTURER); serializer.attribute("", "name", "manufacturer"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", android.os.Build.MODEL); serializer.attribute("", "name", "model"); serializer.endTag("", "param"); // TODO get resolution ... serializer.endTag("", "device"); serializer.endDocument(); writeToFile(writer.toString()); } catch (IllegalArgumentException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IllegalStateException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } } /** *

* Adds some information of an component of an activity (view) to the file. *

* * @param view * view to be logged * @param parentHash * hash of the parent view * @param activityName * name of the activity that is analyzed */ public void addComponent(View view, int parentHash, String activityName) { XmlSerializer serializer = Xml.newSerializer(); StringWriter writer = new StringWriter(); try { serializer.setOutput(writer); serializer.startTag("", "component"); /* * (non-Javadoc) * TODO find a way in that the hash code is unique over time and target * * view.getId() seems to be unique over time and targets but there is a problem. In some * cases there is no ID (value: -1). * * view.getId() returns different values in case of some identical components. * E.g. in the application timerdroid the id of the list elements * changes when calling home button. */ serializer.attribute("", "hash", "" + view.hashCode()); currentLoggedComponents.add(view.hashCode()); /* * (non-Javadoc) * http://developer.android.com/reference/android/view/View.html#getId() * Returns this view's identifier. */ serializer.startTag("", "param"); serializer.attribute("", "name", "id"); serializer.attribute("", "value", "" + view.getId()); serializer.endTag("", "param"); if (view instanceof TextView) { serializer.startTag("", "param"); serializer.attribute("", "name", "title"); TextView textView = (TextView) view; serializer.attribute("", "value", "" + textView.getText()); serializer.endTag("", "param"); } //TODO in case of an image add file name if (view instanceof ImageView) { serializer.startTag("", "param"); serializer.attribute("", "name", "title"); serializer.attribute("", "value", "image:" ); serializer.endTag("", "param"); } serializer.startTag("", "param"); serializer.attribute("", "name", "path"); serializer.attribute("", "value", activityName + "/" + getViewPath(view) + view.getClass().getSimpleName()); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "name", "class"); serializer.attribute("", "value", view.getClass().getName()); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "name", "parent"); /* * (non-Javadoc) * Problem in using view.getParent().hashCode(): * http://developer.android.com/reference/android/view/View.html#getParent() tells: * "... parent is a ViewParent and not necessarily a View." ViewParent does not have a * method hashCode(). Solution is done add parentHash as parameter to method * addComponent() and Androidmonitor-> addLogListenerToView. */ serializer.attribute("", "value", "" + parentHash); serializer.endTag("", "param"); serializer.startTag("", "ancestors"); Class classobject = view.getClass(); while ((classobject != null)) { serializer.startTag("", "ancestor"); serializer.attribute("", "name", classobject.getName()); serializer.endTag("", "ancestor"); classobject = classobject.getSuperclass(); } serializer.endTag("", "ancestors"); serializer.endTag("", "component"); serializer.endDocument(); writeToFile(writer.toString()); } catch (IllegalArgumentException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IllegalStateException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } } /** *

* Add an event to the log file *

* * @param hash * hash value of the calling view of the listener * @param type * the type of listener e.g. textView ... * @param message * message typed in */ public void addEvent(int hash, String type, String message) { XmlSerializer serializer = Xml.newSerializer(); StringWriter writer = new StringWriter(); try { serializer.setOutput(writer); serializer.startTag("", "event"); serializer.attribute("", "id", type); serializer.startTag("", "param"); serializer.attribute("", "value", "" + hash); serializer.attribute("", "name", "source"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", message); serializer.attribute("", "name", "message"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", "" + System.currentTimeMillis()); serializer.attribute("", "name", "timestamp"); serializer.endTag("", "param"); serializer.endTag("", "event"); serializer.endDocument(); writeToFile(writer.toString()); } catch (IllegalArgumentException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IllegalStateException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } } /** *

* Add an event to the log file *

* * @param view * the calling view of the listener * @param type * the type of listener e.g. onClick ... */ public void addEvent(View view, String type) { String x = "" + view.getX(); String y = "" + view.getY(); XmlSerializer serializer = Xml.newSerializer(); StringWriter writer = new StringWriter(); try { serializer.setOutput(writer); serializer.startTag("", "event"); serializer.attribute("", "id", type); serializer.startTag("", "param"); serializer.attribute("", "value", x); serializer.attribute("", "name", "X"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", y); serializer.attribute("", "name", "Y"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", "" + view.hashCode()); serializer.attribute("", "name", "source"); serializer.endTag("", "param"); serializer.startTag("", "param"); serializer.attribute("", "value", "" + System.currentTimeMillis()); serializer.attribute("", "name", "timestamp"); serializer.endTag("", "param"); serializer.endTag("", "event"); serializer.endDocument(); writeToFile(writer.toString()); } catch (IllegalArgumentException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IllegalStateException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } } /** * *

* Creates a new file to store information. *

* * @param activity * Calling application. */ private void createFile(Activity activity) { AndroidMonitorLogFile.appName = getAppLable(activity); AndroidMonitorLogFile.name = "androidLogFile_" + AndroidMonitorLogFile.appName + System.currentTimeMillis() + ".log"; try { AndroidMonitorLogFile.file = new File(activity.getFilesDir(), AndroidMonitorLogFile.name); if (AndroidMonitorLogFile.file.exists() && !AndroidMonitorLogFile.file.isDirectory()) { createFile(activity, 0); } else { AndroidMonitorLogFile.currentLoggedComponents = new ArrayList(); writeHeaderToFile(activity); } } catch (Exception e) { e.printStackTrace(); Log.e("AndroidMonitorLogFile.file", "file: " + e.getMessage()); } } /** * *

* Creates a new file to store information. Counts up if file exists. *

* * @param activity * Calling application. * @param count * File number. */ private void createFile(Activity activity, int count) { AndroidMonitorLogFile.name = "androidLogFile_" + count + "_" + AndroidMonitorLogFile.appName + System.currentTimeMillis() + ".log"; try { AndroidMonitorLogFile.file = new File(activity.getFilesDir(), AndroidMonitorLogFile.name); if (AndroidMonitorLogFile.file.exists() && !AndroidMonitorLogFile.file.isDirectory()) { count++; createFile(activity, count); } else { writeHeaderToFile(activity); } } catch (Exception e) { e.printStackTrace(); Log.e("AndroidMonitorLogFile.file", "file: " + e.getMessage()); } } /** * *

* Writes XML head, device and application information to file. *

* * @param activity * Calling application. */ private void writeHeaderToFile(Activity activity) { writeToFile(""); setDeviceInformation(); setAppInformation(activity); } /** *

* Writes given information to the file. e.g. previous produced XML statements. *

* * @param data * content to add to the file */ private void writeToFile(String data) { FileOutputStream outputStream; try { outputStream = new FileOutputStream(AndroidMonitorLogFile.file, true); outputStream.write(data.getBytes()); outputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e("file", "outputstream: " + e.getMessage()); } } /** *

* Generates the path of an view element. *

* * @param view * @return path path to the element */ private String getViewPath(View view) { return getViewPath(view, null); } /** *

* Generates the path of an view element. *

* * @param view * @param path * @return path path to the element */ private String getViewPath(View view, String path) { if (path == null) { path = ""; } else { path = view.getClass().getSimpleName() + "/" + path; } if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) { return getViewPath((View) view.getParent(), path); } else { return path; } } /** * *

* Check whether a component is still written to log file. *

* * @param hashCode * hash code of the view * @return */ public static Boolean isComponentLogged(Integer hashCode) { return AndroidMonitorLogFile.currentLoggedComponents.contains(hashCode); } }