Operating System Support for Inter-Application Monitoring in Android
←
→
Page content transcription
If your browser does not render page correctly, please read the page content below
Operating System Support for Inter-Application Monitoring in Android Daniel M. Jackowitz Spring 2013 Submitted in partial fulfillment of the requirements of the Master of Science in Software Engineering
Abstract In the stock Android mobile operating system there exists no mechanism by which an application is able to observe state changes within other applications. While for most typical applications this functionality is not necessary, there are particular instances in which it proves incredibly useful. One such scenario presented in this paper is an app to monitor the behavioral patterns of users through their device interactions. The addition of this feature also opens up many new possibilities for more adaptive applications that respond to how the device is being used. This thesis presents a mechanism by which apps can register to be notified of lifecycle transitions within other applications, extends the Android Open Source Project to include this mechanism, and ensures that the security of the device is not compromised and that performance is impacted as little as possible. i
Table of Contents Abstract .......................................................................................................................................................... i 1 Introduction .......................................................................................................................................... 1 1.1 Motivation..................................................................................................................................... 1 1.2 System Goals ................................................................................................................................. 1 1.3 Extending the Potential Uses ........................................................................................................ 2 1.4 Related Work ................................................................................................................................ 3 2 Android Application Structure .............................................................................................................. 3 3 What to Intercept ................................................................................................................................. 6 4 The Communication Protocol ............................................................................................................... 7 4.1 General Mechanism ...................................................................................................................... 7 4.2 Broadcast Actions ......................................................................................................................... 8 4.3 Intent Extras .................................................................................................................................. 9 5 Permissions ......................................................................................................................................... 10 6 Fragments ........................................................................................................................................... 11 7 Implementation .................................................................................................................................. 15 8 Using the System: Developer Samples................................................................................................ 21 9 Limitations........................................................................................................................................... 25 9.1 Falsified Broadcasts..................................................................................................................... 25 9.2 Android Support Library.............................................................................................................. 25 10 Conclusion ....................................................................................................................................... 27 Appendix A: Building Android from Source ................................................................................................ 28 Glossary ....................................................................................................................................................... 30 References .................................................................................................................................................. 31 ii
1 Introduction 1.1 Motivation The original idea for this project was born out of a very specialized goal – to be able to record data about application usage on smart devices and send that data to a remote server where a pattern analysis could be performed and abnormalities in user behavior detected. The intent was to implement this mechanism in the form of an iOS application and distribute it for the Apple family of mobile devices. It quickly became apparent, however, that this monitoring of other applications would not be possible under the strict iOS security model. As a result, Google’s Android mobile operating system emerged as the new target platform. Like iOS, however, Android provides a security model to protect users from malicious applications intruding on other apps. This protection therefore also eliminates the possibility of a simple monitoring app on the Android platform as well. Fortunately, the open nature of the Android operating system lends itself to an alternative approach. Whereas much of the internals of iOS are proprietary to Apple and hidden from developers, the entirety1 of the Android OS has been open-sourced in the form of the Android Open Source Project (AOSP) [1]. All code in the AOSP repository, which completely encompasses core Android functionality, is freely accessible and modifiable by anyone who is interested. Therefore, the goal of the project migrated away from creating a monitoring app running on a mobile operating system to instead creating a custom, monitoring-enabled version of the Android OS itself. 1.2 System Goals With the need for modification to the operating system itself identified, a high-level list of the necessary features of the system was outlined. These initial requirements involved augmenting the Android operating system to: Intercept application events 1 With the exception of some low-level hardware drivers for specific devices. 1
Log each event to a repository on the device Periodically upload the contents of the repository to a web service and then reset the repository on success While this solution will work to accomplish the task at hand, it is a suboptimal design. The major issue is that some of the responsibilities being moved into the operating system would be more appropriately handled by an application. Android provides apps with easy ways to both write to local storage [2] and make HTTP requests [3], so these existing solutions should be leveraged whenever possible. The only feature of the system truly beyond the capabilities of a standard app is the actual interception of events. Therefore, the requirements are revised to include: An operating system modification to intercept application events An application to log, process and upload the events A communication protocol between the operating system and the application This updated solution is much more elegant as it maintains a clear separation between the tasks of the operating system and the tasks of the application. 1.3 Extending the Potential Uses With the processing logic moved out of the operating system and communication being performed over a defined protocol, it becomes possible to extend the system to cover a much larger set of potential uses. In order to accomplish the original goal of behavioral monitoring through device interaction, the monitoring application is particularly interested in other applications’ launch and close events. The app must also receive timing data regarding both when and for how long the application was used. Any additional details, such as what URL is opened by the browser app, should also be made available to the monitor if possible. Considering this core set of data, the “What?”, “When?” and “How?” of user interaction, it is not difficult to imagine other scenarios beyond behavioral monitoring where it may also prove useful. One 2
simple such case would be providing the ability to define macros of applications. For example, a mobile device user notices that he or she consistently checks for new e-mail immediately upon waking in the morning. Utilizing the proposed system, it would be possible for a software developer to create an application that allows a user to define a macro describing the relationships between the executions of other applications – in this example, the e-mail app immediately following the alarm clock app. This macro app then listens for the alarm clock application close event and reacts by automatically launching the e-mail app. The next logical extension of this idea takes the automation even further – based on patterns in past events, the app learns what the proper macros should be. 1.4 Related Work LogCat is the Android logging system mechanism for collecting and viewing system debug output [4]. It is most often used as a debugging tool during application development but frequently logging statements are left in deployed applications either by accident or for the purpose of error reporting. In addition to providing command line tools, LogCat can also be executed from within an Android application allowing for programmatic parsing of the logs. Although Android 4.1 added the restriction that applications are now able to read only the log entries which they themselves have written, in prior versions of the operating system it is possible for developers to glean some information about other applications by analyzing the LogCat logs. Parsing the logs relies heavily on heuristics and guesswork, however, as not all applications log the same information or even any information at all. As a result, monitoring applications developed in this fashion typically behave quite inconsistently. 3
2 Android Application Structure In order to determine how to best capture Android application events, the structure of the applications must first be understood. The major component of an Android app is the activity. An activity is a single, focused thing that the user can do and, typically, corresponds to a single screen in the user interface [4]. Only a single activity can be active at any given time and there is always an active activity when the device is in use. This activity is defined as being in the running or foreground state. Activities communicate with each other via intents. An intent is an abstract description of an operation to be performed as well as a container for data related to that operation [5]. An intent falls into one of two categories – explicit or implicit. An explicit intent specifies the exact target of the intent by means of its class and package names. Explicit intents are typically used to transition between activities within a single application. An implicit intent does not specify a target, but rather provides a set of criteria, in the form of actions and categories, used by the system to find an appropriate target. Implicit intents are often used to start an activity which is part of a separate application. For example, when the user clicks on a phone number within the Browser app, it will launch an implicit intent requesting the system to start an activity which has registered itself as being capable of handling a phone number. In this case, the action will be android.intent.action.CALL and the phone number will be passed as a key-value pair in an intent extra. There are many other nuances related to the use of intents, but the above can be considered as the representative cases. At any point in time, an Android activity is in one of four states. As mentioned earlier, the current activity the user is interacting with is in the running state. An activity becomes paused when it has lost focus, but is still visible. The standard example of a paused activity is one with a dialog box partially covering it. A stopped activity is completely obscured by another activity. When an activity issues an intent to start another activity, it becomes briefly paused, and then stopped, after the newly started activity enters the running state. In both the paused and stopped states the activity is kept in 4
memory unless the space is truly needed elsewhere. If the memory used by an activity must be reclaimed, the activity will then enter the destroyed state. An activity can also request for itself to be destroyed when it is no longer needed. Although the states of activities are managed by the operating system, activities are informed of transitions through a series of lifecycle callbacks. There are 6 main lifecycle callbacks, each representing a transition between a pair of the above states as described in Figure 1. Figure 1: The activity lifecycle callbacks. All Android activities must extend the Activity class, which provides the basic implementations necessary for interacting with the Android framework classes. Developers then add functionality to their activities by overriding Activity methods to include additional implementations. It is very important to note that for many of the activity methods, and particularly for all of the lifecycle callbacks, it is required that all overriding implementations first call through to the superclass implementation to ensure that all framework interaction is handled properly. This requirement is essential to the system as it provides a well-defined set of guaranteed checkpoints at which activities can be examined. Since all activity implementations must call through to the superclass, by altering the callback methods in the Activity class itself it becomes possible to intercept the callbacks for the activities of all applications without requiring any cooperation, or even awareness, on the part of the developers of said applications. 5
3 What to Intercept After identifying where this interception of activity transitions should occur, the focus is shifted to defining exactly what data should be extracted from the activities. The goal throughout this process is to provide enough data to completely represent the most basic components of the state of the activity at the time of the callback. Firstly, the set of interest for lifecycle callbacks is identified as the six callbacks in Figure 1 - onCreate(), onStart(), onResume(), onPause(), onStop() and onDestroy(). Next, extracting appropriate data fields begins with the obvious – the method of identifying the activity itself. In Android, activities are identified by a component name. Encapsulated in a ComponentName object, it is the combination of the package name of the application and the fully qualified name of the Java class of the activity [6]. Android guarantees that application package names are unique and Java guarantees that class names are unique within a package, so the component name serves this purpose perfectly. The component name of an activity is returned by getComponentName(). The system must also provide a means of identifying which of the lifecycle callbacks was invoked, and when. How the former will be conveyed depends largely on the communication protocol chosen, while the latter is accomplished with a simple timestamp via getSystemTimeMillis(). The final field is the current intent for the activity. More specifically, this is the result of calling getIntent() on the activity at the time the callback is invoked. When an activity is started, this field is initialized to the intent used to start it, but it can be altered by the activity during its lifetime by calling setIntent(). In general, this intent will contain any extra information passed to the activity for processing. For example, an intent used to start a browser activity may contain the URL of a page to load. 6
4 The Communication Protocol 4.1 General Mechanism Next, a communication protocol is defined to marshal the intercepted activity data back to the monitor applications. The initial approach designated a particular file within the application space as a repository. The interception component would then write to this file and the applications would read from it. The approach has some serious drawbacks, however, as it requires guarding against two processes accessing, and potentially modifying, the file concurrently. It is also overly restrictive in the fact that the path of the file is statically restricted to within a single application’s space. Supporting multiple monitor apps would then require either writing to multiple files or, despite security recommendations, making the repository file accessible to all apps. Additionally, the responsibility of managing these files is not clearly defined. This can result in either excessive accumulation or premature deletion of the event data. A much better technique makes use of a broadcast-receive protocol, where an app can register itself as desiring to be notified when a lifecycle callback occurs. Then, when an event is intercepted, instead of writing to a file or some other repository the system sends a broadcast containing the data it has gathered to each of the registered receivers. The important advantage here is that each of the receivers can process this data independently and in whatever way it deems appropriate. In the example applications described earlier the behavioral monitor can log all events while the macro app will only listen for particular events and then respond to them immediately. Fortunately, the Android framework provides such a broadcast mechanism in the form of the BroadcastReceiver [7]. When a broadcast receiver is registered, either statically within an application manifest or dynamically by a running application, it is associated with one or more actions, encapsulated in an implicit intent as described earlier. When an activity (or other component) wants to send a broadcast, it creates an intent with the desired action and then calls 7
sendBroadcast(Intent intent) with that intent as the argument. The operating system then handles delivering the intent to all broadcast receivers registered for that particular action. 4.2 Broadcast Actions When defining the protocol, the categorization of actions is an important consideration. At one extreme is a single action for all lifecycle callback broadcasts, providing an all-or-nothing approach. The simplicity of this design in both implementation and use is balanced by the inability of apps to register for only a subset of lifecycle callback broadcasts. This can potentially waste resources both in sending excessive broadcasts and in performing filtering in the receiving app. The other extreme assigns each lifecycle callback a unique action, allowing apps to be very selective about what broadcasts they want to receive at the expense of some complexity in development. After some analysis, a compromise between the above approaches was achieved by identifying a subset of the lifecycle callbacks as foreground lifecycle callbacks. The callbacks in this group are those which are invoked when the activity transitions either into or out of the running state – specifically, onResume() and onPause(). As it is anticipated that members of this set will be the only callbacks many applications using the system are interested in, it is reasonable to separate them logically. This distinction of foreground callbacks allows for partitioning of the lifecycle callbacks into two classes. Each class is then assigned its own broadcast action, shown in Table 1. Note that the sets of lifecycle callbacks in the two classes are mutually exclusive. One important consequence of this arrangement is that in order to receive the complete set of lifecycle callbacks an application must register to receive broadcasts for both actions. Broadcast Action Lifecycle Callback (Activity Class Constant) onCreate() onStart() “android.activity.action.LIFECYCLE_CALLBACK” onStop() (ACTION_LIFECYCLE_CALLBACK) onDestroy() 8
onResume() “android.activity.action.FOREGROUND_LIFECYCLE_CALLBACK” onPause() (ACTION_FOREGROUND_LIFECYCLE_CALLBACK) Table 1: The activity lifecycle callbacks and their corresponding broadcast actions. 4.3 Intent Extras In the Android broadcast-receive mechanism, data payload is passed from broadcaster to receiver by means of intent extras attached to the broadcast intent. As described earlier, an intent extra is a key-value pair where the key is a Java String and the value is a Java object that must be either Parcelable or Serializable. With the payload data already identified, the task here becomes mapping the data to appropriate keys. This mapping is shown in Table 2. Key Value Type (Activity Class Constant) “android.activity.extra.COMPONENT_NAME” Parcelable Activity Name (EXTRA_COMPONENT_NAME) (ComponentName) “android.activity.extra.TIMESTAMP” Timestamp long (EXTRA_TIMESTAMP) “android.activity.extra.CALLBACK” Callback int (EXTRA_CALLBACK) “android.activity.extra.INTENT” Current Intent Parcelable (Intent) (EXTRA_INTENT) Table 2: Intent extras for the ACTION_LIFECYCLE_CALLBACK and ACTION_FOREGROUND_LIFECYCLE_CALLBACK activity lifecycle callback broadcast actions. Of particular interest is the data type of the Callback Constant Value ON_CREATE 0 value. For consistency and ease of use, integer constants were ON_START 1 ON_RESUME 2 defined within the Activity class to correspond to each of the ON_PAUSE 3 ON_STOP 4 lifecycle callbacks. These values are shown in Table 3. Developers ON_DESTROY 5 Table 3: Activity lifecycle callback are recommended to always use the constant rather than the constants. integer value itself. 9
5 Permissions The Android security model is a complex structure stretching across all layers of the system. For the purposes of this project, however, it is sufficient to consider enforcement only at the permission level. Permissions in Android are a way to ensure that protected features of the device are not accessed without the user’s consent [8]. Examples of protected features include both hardware components, such as turning on the Bluetooth radio, and software components, such as reading the user’s list of contacts. Most simply, a protected feature is any feature that can be easily exploited in a malicious way. From this definition, it is clear that intercepting events from another application qualifies as a protected feature and therefore must be accessible only with permission. The remaining question is the appropriate coarseness of the access control. With broadcasts being sent for two separate actions, the options regarding permissions are effectively narrowed down to either a separate permission for each action, or a single permission encompassing both actions. Considering the fact that the permission model is intended largely as a benefit to the end user, a distinct permission for each broadcast action is chosen in order to make it more explicit to the user to what extent the requesting app is able to monitor other applications. The two permissions, along with the broadcast actions they protect, are shown in Table 4. Permission Broadcast Action “android.permission.MONITOR_LIFECYCLE” LIFECYCLE_CALLBACK “android.permission.MONITOR_FOREGROUND_LIFECYCLE” FOREGROUND_LIFECYCLE_CALLBACK Table 4: Permissions and the activity lifecycle callback broadcast actions they protect. 10
6 Fragments Until now, all design considerations have been working under the simplifying assumption that all foreground components with which users interact are activities. In early versions of Android this assumption held true but with Android 3.0 came the concept of fragments. In the context of this project it is easiest to think of a fragment as a sub-activity. Like an activity, each fragment represents something that the user can do. A fragment also has its own lifecycle events. The key difference is that a fragment is a lightweight component - it must always be embedded within an activity [9]. Fragments can be combined in ways that allow for complex multi-pane layouts across devices of vastly different screen dimensions and can be reused across activities. Consider the typical use of fragments illustrated in Figure 2. Figure 2: Fragments displayed serially on a phone and side-by-side on a tablet. Fragments are also dynamic. They can be added to and removed from an activity while the activity is running. For this reason, in order to get a true picture of the state of the application the system must monitor not only activity lifecycle callbacks but also fragment lifecycle callbacks. From the perspective of developers, implementing a fragment very closely parallels implementing an activity. All fragment implementations extend the Fragment class. Functionality is added by overriding methods of this class, including the lifecycle callbacks. As with activities, when overriding these methods 11
fragments must call through to the superclass implementations to ensure proper interaction with the framework. Although fragments have some additional lifecycle callbacks not present for activities, these callbacks either overlap with those of the parent activity, such as onActivityCreated(), or are directly related to another fragment callback, such as onAttach() immediately preceding onCreate(). Therefore the domain is restricted to only those callbacks present for both activity and fragment components. Following the model used for activities, broadcast actions are assigned to the fragment lifecycle callbacks as shown in Table 5. Broadcast Action Lifecycle Callback (Fragment Class Constant) onCreate() onStart() “android.fragment.action.LIFECYCLE_CALLBACK” onStop() (ACTION_LIFECYCLE_CALLBACK) onDestroy() onResume() “android.fragment.action.FOREGROUND_LIFECYCLE_CALLBACK” onPause() (ACTION_FOREGROUND_LIFECYCLE_CALLBACK) Table 5: The fragment lifecycle callbacks and their corresponding broadcast actions. Overall, the intent extras containing the data payload for fragments are also very similar to those used for the activity lifecycle callback broadcast actions, with two key differences. Since fragments can be reused across activities, the intent includes an additional field to identify the current parent activity of the fragment at the time of the callback. It is also possible to remove the extra relating to the current intent as this is a concept specific to activities. The intent extras for the fragment lifecycle callback actions are outlined in Table 6. Key Value Type (Fragment Class Constant) Fragment “android.fragment.extra.COMPONENT_NAME” Parcelable Name (EXTRA_COMPONENT_NAME) (ComponentName) Parent “android.fragment.extra.PARENT_COMPONENT_NAME” Parcelable Activity (EXTRA_PARENT_COMPONENT_NAME) (ComponentName) Name 12
“android.fragment.extra.TIMESTAMP” Timestamp long (EXTRA_TIMESTAMP) “android.fragment.extra.CALLBACK” Callback int (EXTRA_CALLBACK) Table 6: Intent extras for the ACTION_LIFECYCLE_CALLBACK and ACTION_LIFECYCLE_CALLBACK fragment lifecycle callback broadcast actions. As was the case with for the activity broadcast actions, Constant Value ON_CREATE 0 the possible values for the Callback intent extra were enumerated ON_START 1 ON_RESUME 2 in the form of integer constants in the Fragment class. These ON_PAUSE 3 ON_STOP 4 constants are listed in Table 7. ON_DESTROY 5 Table 7: Fragment lifecycle callback Rather than defining a new set of permissions, the constants. permissions defined earlier to protect the activity broadcasts are reused for protecting the fragment lifecycle callback broadcasts. This is possible because to the end user there is no practical difference between activities and fragments. In fact, introducing a separate set of permissions would likely cause additional confusion for a user. For this reason, the wording shown to the end user regarding permissions sacrifices accuracy for understandability by describing everything in terms of “applications” rather than “activities” or “fragments”. Although it would have been possible to group the fragment lifecycle callback broadcasts with those of activity, this was not done for two reasons. Firstly, using distinct broadcast actions for activities and fragments allows applications to selectively register to receive only activity callbacks, only fragment callbacks, or both. This proves quite valuable in reducing the number of unnecessary broadcasts sent as well as the amount of filtering that must be done on the receiving end if the application is only interested in a subset of these events. Secondly, by keeping the two separate the system is more easily adaptable to future changes. Consider the case where a new, interesting lifecycle callback is added to activities but not to fragments. With this implementation it is simple to make the necessary changes to one component without 13
impacting the other. Combined, the above advantages were determined to be worth the trade-off of a small amount of code duplication. 14
7 Implementation With the design outlined clearly above, the implementation is fairly straightforward. First, the framework manifest file is edited to include the definition of the new permissions. frameworks/base/core/res/AndroidManifest.xml ... ... Here two new permissions are added to the SYSTEM_TOOLS permission group. Although an additional permission group could have been defined specifically for this purpose, a review of the existing members of SYSTEM_TOOLS revealed it to be an appropriate category for the monitoring permissions. For example, SYSTEM_TOOLS also contains the GET_TASKS permission, which allows an app to retrieve the list of currently running applications. The protection level for both new permissions is declared as “dangerous”, which means that the user will always be asked to explicitly consent whenever an app requesting the permission is installed. The label and description attributes define exactly what will be displayed to the user at this time and are encapsulated in string resources. Adding these resources is the next step. frameworks/base/core/res/res/strings.xml ... monitor foreground applications Allows the app to receive broadcasts when Activities in other applications enter or leave the foreground. monitor application lifecycle events 15
Allows the app to receive broadcasts when Activities in other applications make Activity lifecycle transitions. ... The remaining modifications are made within the Activity and Fragment Java classes. These additions can be divided into two categories – constant definitions and broadcasts. Constant definitions occur at the beginning of the class definition and encompass the broadcast actions and intent extras described by the design. The broadcast components are a little more complex. Within each relevant lifecycle callback method code is added to create an intent with the proper broadcast action, collect the necessary data from the activity and package it within the intent extras, and request that the system send a broadcast with the intent using the appropriate permission. frameworks/base/core/java/android/app/Activity.java public class Activity { ... private static final String MONITOR_FOREGROUND_LIFECYCLE_PERM = android.Manifest.permission.MONITOR_FOREGROUND_LIFECYCLE; private static final String MONITOR_LIFECYCLE_PERM = android.Manifest.permission.MONITOR_LIFECYCLE; public static final String ACTION_FOREGROUND_LIFECYCLE_CALLBACK = "android.activity.action.FOREGROUND_LIFECYCLE_CALLBACK"; public static final String ACTION_LIFECYCLE_CALLBACK = "android.activity.action.LIFECYCLE_CALLBACK"; public static final String EXTRA_COMPONENT_NAME = "android.activity.extra.COMPONENT_NAME"; public static final String EXTRA_TIMESTAMP = "android.activity.extra.TIMESTAMP"; public static final String EXTRA_CALLBACK = "android.activity.extra.CALLBACK"; public static final int ON_CREATE = 0; public static final int ON_START = 1; public static final int ON_RESUME = 2; public static final int ON_PAUSE = 3; public static final int ON_STOP = 4; public static final int ON_DESTROY = 5; public static final String EXTRA_INTENT = "android.activity.extra.INTENT"; ... protected void onCreate(Bundle savedInstanceState) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); if (mLastNonConfigurationInstances != null) { mAllLoaderManagers = mLastNonConfigurationInstances.loaders; } if (mActivityInfo.parentActivityName != null) { if (mActionBar == null) { 16
mEnableDefaultActionBarUp = true; } else { mActionBar.setDefaultDisplayHomeAsUpEnabled(true); } } if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.fragments : null); } Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); broadcast.putExtra(EXTRA_COMPONENT_NAME, getComponentName()); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_CREATE); broadcast.putExtra(EXTRA_INTENT, getIntent()); sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); mCalled = true; } ... protected void onStart() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this); mCalled = true; if (!mLoadersStarted) { mLoadersStarted = true; if (mLoaderManager != null) { mLoaderManager.doStart(); } else if (!mCheckedForLoaderManager) { mLoaderManager = getLoaderManager(-1, mLoadersStarted, false); } mCheckedForLoaderManager = true; } Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); broadcast.putExtra(EXTRA_COMPONENT_NAME, getComponentName()); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_START); broadcast.putExtra(EXTRA_INTENT, getIntent()); sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); getApplication().dispatchActivityStarted(this); } ... protected void onResume() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); Intent broadcast = new Intent(ACTION_FOREGROUND_LIFECYCLE_CALLBACK); broadcast.putExtra(EXTRA_COMPONENT_NAME, getComponentName()); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_RESUME); broadcast.putExtra(EXTRA_INTENT, getIntent()); sendBroadcast(broadcast, MONITOR_FOREGROUND_LIFECYCLE_PERM); getApplication().dispatchActivityResumed(this); mCalled = true; } ... protected void onPause() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this); Intent broadcast = new Intent(ACTION_FOREGROUND_LIFECYCLE_CALLBACK); broadcast.putExtra(EXTRA_COMPONENT_NAME, getComponentName()); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_PAUSE); broadcast.putExtra(EXTRA_INTENT, getIntent()); sendBroadcast(broadcast, MONITOR_FOREGROUND_LIFECYCLE_PERM); getApplication().dispatchActivityPaused(this); 17
mCalled = true; } ... protected void onStop() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); broadcast.putExtra(EXTRA_COMPONENT_NAME, getComponentName()); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_STOP); broadcast.putExtra(EXTRA_INTENT, getIntent()); sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); getApplication().dispatchActivityStopped(this); mCalled = true; } ... protected void onDestroy() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this); mCalled = true; // dismiss any dialogs we are managing. if (mManagedDialogs != null) { final int numDialogs = mManagedDialogs.size(); for (int i = 0; i < numDialogs; i++) { final ManagedDialog md = mManagedDialogs.valueAt(i); if (md.mDialog.isShowing()) { md.mDialog.dismiss(); } } mManagedDialogs = null; } // close any cursors we are managing. synchronized (mManagedCursors) { int numCursors = mManagedCursors.size(); for (int i = 0; i < numCursors; i++) { ManagedCursor c = mManagedCursors.get(i); if (c != null) { c.mCursor.close(); } } mManagedCursors.clear(); } // Close any open search dialog if (mSearchManager != null) { mSearchManager.stopSearch(); } Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); broadcast.putExtra(EXTRA_COMPONENT_NAME, getComponentName()); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_DESTROY); broadcast.putExtra(EXTRA_INTENT, getIntent()); sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); getApplication().dispatchActivityDestroyed(this); } ... } frameworks/base/core/java/android/app/Fragment.java public class Fragment { ... private static final String MONITOR_FOREGROUND_LIFECYCLE_PERM = android.Manifest.permission.MONITOR_FOREGROUND_LIFECYCLE; 18
private static final String MONITOR_LIFECYCLE_PERM = android.Manifest.permission.MONITOR_LIFECYCLE; public static final String ACTION_FOREGROUND_LIFECYCLE_CALLBACK = "android.fragment.action.FOREGROUND_LIFECYCLE_CALLBACK"; public static final String ACTION_LIFECYCLE_CALLBACK = "android.fragment.action.LIFECYCLE_CALLBACK"; public static final String EXTRA_COMPONENT_NAME = "android.fragment.extra.COMPONENT_NAME"; public static final String EXTRA_PARENT_COMPONENT_NAME = "android.fragment.extra.PARENT_COMPONENT_NAME"; public static final String EXTRA_TIMESTAMP = "android.fragment.extra.TIMESTAMP"; public static final String EXTRA_CALLBACK = "android.fragment.extra.CALLBACK"; public static final int ON_CREATE = 0; public static final int ON_START = 1; public static final int ON_RESUME = 2; public static final int ON_PAUSE = 3; public static final int ON_STOP = 4; public static final int ON_DESTROY = 5; ... public void onCreate(Bundle savedInstanceState) { Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); ComponentName parentComponentName = getActivity().getComponentName(); ComponentName componentName = new ComponentName( parentComponentName.getPackageName(), getClass().getName()); broadcast.putExtra(EXTRA_COMPONENT_NAME, componentName); broadcast.putExtra(EXTRA_PARENT_COMPONENT_NAME, parentComponentName); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_CREATE); getActivity().sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); mCalled = true; } ... public void onStart() { mCalled = true; if (!mLoadersStarted) { mLoadersStarted = true; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doStart(); } } Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); ComponentName parentComponentName = getActivity().getComponentName(); ComponentName componentName = new ComponentName( parentComponentName.getPackageName(), getClass().getName()); broadcast.putExtra(EXTRA_COMPONENT_NAME, componentName); broadcast.putExtra(EXTRA_PARENT_COMPONENT_NAME, parentComponentName); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_START); getActivity().sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); } ... public void onResume() { 19
Intent broadcast = new Intent(ACTION_FOREGROUND_LIFECYCLE_CALLBACK); ComponentName parentComponentName = getActivity().getComponentName(); ComponentName componentName = new ComponentName( parentComponentName.getPackageName(), getClass().getName()); broadcast.putExtra(EXTRA_COMPONENT_NAME, componentName); broadcast.putExtra(EXTRA_PARENT_COMPONENT_NAME, parentComponentName); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_RESUME); getActivity().sendBroadcast(broadcast, MONITOR_FOREGROUND_LIFECYCLE_PERM); mCalled = true; } ... public void onPause() { Intent broadcast = new Intent(ACTION_FOREGROUND_LIFECYCLE_CALLBACK); ComponentName parentComponentName = getActivity().getComponentName(); ComponentName componentName = new ComponentName( parentComponentName.getPackageName(), getClass().getName()); broadcast.putExtra(EXTRA_COMPONENT_NAME, componentName); broadcast.putExtra(EXTRA_PARENT_COMPONENT_NAME, parentComponentName); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_PAUSE); getActivity().sendBroadcast(broadcast, MONITOR_FOREGROUND_LIFECYCLE_PERM); mCalled = true; } ... public void onStop() { Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); ComponentName parentComponentName = getActivity().getComponentName(); ComponentName componentName = new ComponentName( parentComponentName.getPackageName(), getClass().getName()); broadcast.putExtra(EXTRA_COMPONENT_NAME, componentName); broadcast.putExtra(EXTRA_PARENT_COMPONENT_NAME, parentComponentName); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_STOP); getActivity().sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); mCalled = true; } ... public void onDestroy() { mCalled = true; //Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager // + " mLoaderManager=" + mLoaderManager); if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doDestroy(); } Intent broadcast = new Intent(ACTION_LIFECYCLE_CALLBACK); ComponentName parentComponentName = getActivity().getComponentName(); ComponentName componentName = new ComponentName( parentComponentName.getPackageName(), getClass().getName()); broadcast.putExtra(EXTRA_COMPONENT_NAME, componentName); broadcast.putExtra(EXTRA_PARENT_COMPONENT_NAME, parentComponentName); broadcast.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()); broadcast.putExtra(EXTRA_CALLBACK, ON_DESTROY); getActivity().sendBroadcast(broadcast, MONITOR_LIFECYCLE_PERM); } ... } 20
8 Using the System: Developer Samples The following examples provide annotated sample code for interacting with the monitoring system. The first example shows how to simply log all activity foreground transition events to a file. After installing the custom platform version of the Android SDK2, create a new project and choose the AOSP build as the minimum SDK version. Open AndroidManifest.xml and ensure that the android:minSdkVersion attribute is set to “AOSP”. Next declare the application as using the android.permission.MONITOR_FOREGROUND_LIFECYCLE permission. Finally, add a receiver for the android.activity.action.FORGROUND_LIFECYCLE_CALLBACK action. AndroidManifest.xml Next the implementation of the broadcast receiver defined in AndroidManifest.xml is filled in. This is accomplished by overriding the onReceive() method to handle the intent broadcast 2 Simply copy the platform/android-x.x.x directory to android-sdk/platforms in the SDK installation. 21
by the system. For this example onReceive() simply extracts the intent extras for the timestamp, the component name, and the callback type and formats them in a comma-delimited string. It then appends this string to a file in the application space. LifecycleReceiver.java import java.io.IOException; import java.io.OutputStreamWriter; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; public class LifecycleReceiver extends BroadcastReceiver { private static final String TAG = "LifecycleReceiver"; private static final String UNKNOWN = "Unknown"; private static final String OPENED = "Opened"; private static final String CLOSED = "Closed"; @Override public void onReceive(Context context, Intent intent) { long timestamp = intent.getLongExtra(Activity.EXTRA_TIMESTAMP, -1); String className = ((ComponentName) intent .getParcelableExtra(Activity.EXTRA_COMPONENT_NAME)).getClassName(); int callback = intent.getIntExtra(Activity.EXTRA_CALLBACK, -1); String state = UNKNOWN; switch (callback) { case Activity.ON_PAUSE: state = CLOSED; break; case Activity.ON_RESUME: state = OPENED; break; } String csv = timestamp + "," + className + "," + state + "\n"; OutputStreamWriter out = null; try { out = new OutputStreamWriter(context.openFileOutput( "lifecycle.log", Context.MODE_APPEND)); out.write(csv); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } Running the above application on a device for a short period of time will produce a log file similar to the following: 22
1363879283106,com.android.launcher2.Launcher,Opened 1363879295661,com.android.launcher2.Launcher,Closed 1363879295672,com.android.email.activity.EmailActivity,Opened 1363879305036,com.android.email.activity.EmailActivity,Closed 1363879305055,com.android.launcher2.Launcher,Opened 1363879305062,com.android.launcher2.Launcher,Closed 1363879305189,com.android.browser.BrowserActivity,Opened 1363879319226,com.android.browser.BrowserActivity,Closed 1363879319256,com.android.launcher2.Launcher,Opened 1363879320468,com.android.launcher2.Launcher,Closed 1363879353116,com.android.launcher2.Launcher,Opened 1363879366429,com.android.launcher2.Launcher,Closed 1363879366889,com.android.contacts.activities.PeopleActivity,Opened 1363879369170,com.android.contacts.activities.PeopleActivity,Closed 1363879369180,com.android.launcher2.Launcher,Opened 1363879371055,com.android.launcher2.Launcher,Closed 1363879371128,com.android.deskclock.DeskClock,Opened 1363879372918,com.android.deskclock.DeskClock,Closed 1363879372934,com.android.launcher2.Launcher,Opened 1363879374060,com.android.launcher2.Launcher,Closed 1363879374266,com.android.calendar.AllInOneActivity,Opened 1363879376829,com.android.calendar.AllInOneActivity,Closed 1363879376845,com.android.launcher2.Launcher,Opened 1363879386740,com.android.launcher2.Launcher,Closed While much more sophisticated parsing and pattern analysis can be easily performed on data in this, or similar, formats, for the purposes of this example it is sufficient to note the obvious. The log begins with the launcher, or the user’s “home screen”. After that, many of the apps should be fairly recognizable from the activity names. This record shows the e-mail, browser, contacts, clock and calendar apps being used, in that order. The presence of the launcher activity between each app is expected, as the user returns there when exiting an application. The launcher, or any other app for that matter, being opened twice in a row is also not uncommon and indicates that the device screen has been turned off and then back on, temporarily pausing the foreground activity. The second developer sample takes a more active approach to using the system. It is the simplest version of the macro app described earlier in the document. This example requires no changes to the AndroidManifest.xml used in the first example. All modifications are made in the LifecycleReceiver.java file, as shown below. LifecycleReceiver.java 23
import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.util.Log; public class LifecycleReceiver extends BroadcastReceiver { private static final String TAG = "LifecycleReceiver"; private static final String TARGET_CLASS_NAME = "com.android.deskclock.AlarmAlert"; @Override public void onReceive(Context context, Intent intent) { String className = ((ComponentName) intent .getParcelableExtra(Activity.EXTRA_COMPONENT_NAME)) .getClassName(); int callback = intent.getIntExtra(Activity.EXTRA_CALLBACK, -1); if (className.equals(TARGET_CLASS_NAME) && callback == Activity.ON_PAUSE) { Intent emailIntent = context.getPackageManager() .getLaunchIntentForPackage(“com.android.email”); emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(emailIntent); } } } There are two main responsibilities of this broadcast receiver. The first is to recognize the user dismissing the alarm alert. This is done by retrieving the component name and lifecycle callback extras from the broadcast and comparing them with the name of the alarm alert activity in the desk clock application and the ON_PAUSE event, respectively. In the case where both items match, the second responsibility of launching the e-mail client application is executed3. The end result is that every time the user dismisses an alarm, the e-mail client is automatically opened. 3 From an application design perspective, using an explicit intent to launch the e-mail app is not best practice. It is used in this example for the sake of simplicity. 24
9 Limitations While the system very well suits the purpose for which it was developed, there do exist limitations which may hinder its general applicability to other problems. These limitations are described in the following sections. 9.1 Falsified Broadcasts Because the lifecycle callback broadcasts are sent (unwittingly) by the activities and fragments that are being monitored, it is currently not possible to prevent an application from “spamming” broadcasts and bombarding receivers with false data. The impact of this limitation is that a malicious application can effectively corrupt the lifecycle callback data and potentially break the functionality of any application which relies on it. Possible solutions to this problem all rely on completely revamping the design to move the interception out of the Activity and Fragment classes and into a lower-level framework, or possibly even kernel, component. If the changes introduced by this system were to be merged back into the Android Open Source Project for use on a much larger scale this redesign would likely require further consideration. 9.2 Android Support Library Although fragments were introduced in Android 3.0, they have been back-ported to Android 1.6 through the Android Support Library [10]. Developers using fragments in applications targeting lower API levels can do so by packaging this library, which includes its own implementations of Fragment and other related classes, with their application. In cases where the support library is present, the implementations from the library will always be used rather than those from the operating system and, since these implementations are entirely distinct from those which this project has modified, the interception code for fragment lifecycle callbacks will not be available. 25
At first this may seem like a problem, but the percentage of applications which actually fall into this category is very small and only going to decrease as the older operating system versions become obsolete. Additionally, only the fragment callbacks are missed – all activity callbacks are still intercepted properly. For these reasons, there is no need to address this limitation any further than simply documenting it. 26
10 Conclusion This project concludes with a fully functional, modified version of the Android mobile operating system extended to include support for inter-application monitoring. This version of the operating system has been loaded onto multiple Google Nexus 7 tablets and is indistinguishable from the stock operating system in terms of performance. In addition, several proof-of-concept apps utilizing the monitoring capabilities have been developed by the author and all perform as expected and offer functionality that would otherwise not be possible. Among these apps is the behavioral monitoring app that originally motivated the project. The author will continue the development of this application in particular as both a flagship for the operating system modifications and for use in a behavioral study. The author also has plans to contact members of the Android Open Source Project team at Google to inquire whether the changes introduced to the system by this project would be appropriate for merging back into the main branch of Android. There are existing formal requests for features similar to the monitoring capabilities presented here, so the changes may very well be welcomed by the community. If this is the case, the author has every intention of contributing. 27
Appendix A: Building Android from Source The following sections describe the steps taken to build the Android Open Source Project 4.1.1 operating system for the Google Nexus 7 on a fresh install of Ubuntu 11.10 (x64). At the time of writing, these directions are accurate but they are both subject and likely to change in future versions of Android. Install Java [11] $ sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner" $ sudo apt-get update $ sudo apt-get install sun-java6-jdk Install Dependencies $ sudo apt-get install git-core gnupg flex bison gperf build-essential \ zip curl zlib1g-dev libc6-dev lib32ncurses5-dev ia32-libs \ x11proto-core-dev libx11-dev lib32readline5-dev lib32z-dev \ libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown \ libxml2-utils xsltproc libx11-dev:i386 Configure USB Access Create file /etc/udev/rules.d/51-android.rules and save the following to it (replacing with your username): # adb protocol on grouper/tilapia (Nexus 7) SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e42", MODE="0600", OWNER="" # fastboot protocol on grouper/tilapia (Nexus 7) SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e40", MODE="0600", OWNER="" Download Source [12] The following commands download the repo script, initialize a directory for the Android source and, finally, download the source. $ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo $ chmod a+x ~/bin/repo $ mkdir WORKING_DIRECTORY $ cd WORKING_DIRECTORY $ repo init -u https://android.googlesource.com/platform/manifest $ repo sync Extract Proprietary Blobs Open https://developers.google.com/android/nexus/drivers in a browser. Locate and download the archives for the appropriate hardware and software versions of the Nexus 7. Extract these archives to WORKING_DIRECTORY and run the resulting shell scripts to extract the proprietary blobs for the various device drivers. Build [13] 28
You can also read