Harnessing Android Intents

This post is about Android Intents, and how to use them to start/stop/show/hide Android apps and services and perform some more advanced actions. Intents are the glue that binds numerous Android components together, and are very important for understanding and mastering various aspects of Android. Main goal of this guide is to understand enough about intents in order to be able to use them in ADB scripts and other tools that allow sending Intents.

The guide assumes you can set up ADB connectivity, know how to use ADB shell and have working Java (JRE or JDK).

It all started when I was trying to build an automated script to deploy our RxLogger tool and start logging. RxLogger is awesome little triage tool that captures numerous system logs and events and allows for easy analysis in, say, Excel, as well as more detailed debugging via dumpsys and tcpdump data if needed. It can be configured via XML-based config file and installed silently via APK, but…. if you want to start logging – you have to start the program and tap “Start” button. This is very ‘un-enterprisey’ (requires user interaction, and the device may be locked down to prevent user from even seeing the app). So my goal was to see if there is an automated way of doing it. Of course, asking our own engineering was reserved until all other options would be exhausted 🙂

So, the goal is ultimately to

  • Build a script that  installs RxLogger (pre-configured to our liking) and starts logging w/o any user interaction.
  • Learn how intents work, how they are invoked, how they are declared and how to find out which intents a particular application can process.

Much of what you will read is being included in the MBS2014 Android Workshop.

RxLogger APK is attached here: RxLogger.0.0.1.33.zip. Change the extension to .ZIP from .ZIP.DOC (free WordPress blog limitation). Of course, instead of using RxLogger, any other app can be used.

A brief recap on Intents

Intents are basically messages that can be exchanged between applications or their components.They typically contain

1. The destination: which app/OS component to address the intent to

  • If Intent has a specified destination it would be called explicit. Example would be sending “Start” message to a specific background service (I am a news reader and I do implement background sync via a service).
  • If no destination was specified – implicit. The example would be the very typical “Open Link in Browser” menu item found in many mail/news/etc apps. We cannot possibly know which browser the user prefers (stock browser, Chrome, FF, Opera, etc), so we can not a set a specific app as a destination. Thus, we don’t specify the destination, and the intent goes to the OS, which should figure the application to handle it.
    • If for whatever reason OS cannot find out the application (user is asking OS to open DWG file in its native viewer, and there’s no app on the device to view DWG files) – an error will be generated.
    • If there’s more than one possible destination, the very familiar “Complete Action With” dialog will be displayed, prompting the user to select one of the handlers (unless the default is set, of course).
    • Which is another reason why you may want your intents to be explicit – to prevent any dialogs or to circumvent the default app handler.
  • There are also broadcast Intents, which are sent to ‘everyone’. Albeit, only the apps that explicitly subscribed to them will be able to get them. An example would be subscribing to messages such as “OS Boot Completed” or “Wi-Fi is On”/”Wi-Fi is Off”, etc.

2. The action (what to do) and supplementary data:

  • Typically, the message such as “Start”,”Stop”,”Open URL”, “Open Document”, “Dial Phone Number”, “Share link”, “OS Boot completed”, etc
  • These messages are defined by the OS components and app components themselves in their respective Manifests. For example, a media player app can define actions such as “Play”, “Pause/Resume”, “Stop”, “Bring the song selection dialog up”, etc. I have a hunch that RxLogger also has defined intents for Starting/Stopping logging, but I have yet to prove that.
  • Sometimes data is required (Which URL to open? Which phone number to dial?) and sometimes not.

The big list of OS-defined actions and additional Intent attributes is available here.

The key OS component that processes intents is Activity Manager, and it has a handy CLI front-end called, unsurprisingly, am, which is built into every Android device. This will be our key tool.

So, the intended course of action will be:

  • Dissect the RxLogger app to understand what Intents it supports
    • Find if there are any intents that trigger logging
    • Find the components responsible for handling those intents
    • Find what information those intents require
  • Understand how to send those intents manually using ADB or am tool
    • Detect any possible restrictions
    • Construct a parameter line for am to trigger the action successfully
    • Test
  • Learn lots of useful stuff about intents along the way.

Let’s go!

Dissecting the app for analysis

In order to send intents to RxLogger we need to know, which intents it can handle. All handled Intents must be declared in app’s Manifest (by OS design), so we need to find it and read it. Manifest can be extracted from the APK file using APKtool (requires working Java JRE/JDK). Here’s how you typically use it (if you’re getting errors – specify full path to java.exe in the batch file):

>apktool.bat d -f RxLogger.0.0.1.33.apk
I: Baksmaling...
I: Loading resource table... 
I: Decoding resources... 
I: Loading resource table from file: D:\Users\apc\apktool\framework\1.apk 
I: Copying assets and libs..

As a result we get a new folder and the Manifest inside (along with other files we don’t really care about):

AndrIntents01–> AndrIntents02

The AndroidManifest.xml file can be opened in any text editor. I recommend ones with syntax highlighting and XML code wrapping such as Notepad++ or SublimeText. Inside you will see lots of stuff, including component definitions. I will focus on the most interesting ones.

Examining and Activating Activities

First, let’s look at the Activities (which are basically the UI screens):

<activity android:name="com.motorolasolutions.rxlogger.main.MainActivity">
  <intent-filter>
   <action android:name="android.intent.action.MAIN" />
   <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>

This XML tells us the following:

  • There is an activity called MainActivity (for whatever reason the package name for RxLogger is com.motorolasolutions.rxlogger.main)
  • This activity should be started when the user taps the application icon in the Launcher (handles specific intent Category called LAUNCHER).
    • Technically, when the app is installed and the Manifest is processed, this piece of XML will instruct the Package Manager to create a Launcher icon for this activity. This is how a single app may install more than one icon.
  • This activity is supposed to be the app’s main screen (because it says that it handles specific Intent Action called MAIN). MAIN is also the default action when no action is specified.

We will return to Intent Actions, Categories and extras a little later. For now – let’s see if our hypothesis is correct:

>adb shell am start com.motorolasolutions.rxlogger.main/.MainActivity
 Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.motorolasolutions.rxlogger.main/.MainActivity }

Note the ‘/.‘ (slash-dot) – this is required to separate the component name from the package name.

You may also see a warning that says that app wasn’t really started, but merely brought to front (from background) – that’s OK.

If everything is correct – RxLogger’s main screen should appear. Congratulations – you’ve just sent your first intent! So far, it’s not too impressive, and definitely, much more complex than simply launching an .exe file in WM/CE – wait until you see what else can am do for you…

There are more activities in the manifest.

 <activity android:name="com.motorolasolutions.rxlogger.main.SettingsPlugin" />
 <activity android:name="com.motorolasolutions.rxlogger.main.SettingsMain" />
 <activity android:name="com.motorolasolutions.rxlogger.main.AboutActivity" />

Starting them brings a well expected result:

>adb shell am start com.motorolasolutions.rxlogger.main/.AboutActivity
 java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.motorolasolutions.rxlogger.main/.AboutActivity } from null (pid=4858, uid=2000) not exported from uid 10072

These activities are not “exported” (no “exported=true” in the manifest for them), and thus private to the app (10072 is the UID of RxLogger). Other apps cannot call them. Main activity also doesn’t have “exported” in it, but since it’s Main – it’s implied.

The command will succeed if you are root, though, and you will be able to see the RxLogger’s “About” screen.

Here are some other examples, using the built-in Gallery app, which actually does not just display pictures, but also serves as camera app (and does indeed place multiple shortcuts in the launcher)!

am start com.android.gallery3d/.app.Gallery
am start com.android.gallery3d/com.android.camera.Camera

Deactivating stuff and implicit intents

OK, so this is how I start the app and bring up specific screens (if allowed to). How do I stop the app?

If you just want to hide the screen, but leave the activity running in the background, the easiest way is to simulate the Home button press (or run another app):

am start -a android.intent.action.MAIN -c android.intent.category.HOME

Note that this time we don not actually specify any packages or components – this intent is implicit. What we do instead, is we’re send the event “into the wild” letting the Activity Manager to resolve it. That’s because Activity Manager is (usually) capable of figuring out which Activity (and, thus, app) needs to be run in order to satisfy the request. Essentially, it keeps a database of all registered app components and their supported intents (including their parameters), based on app manifests, and tries to find component(s) that match.

When more than one is found – this is when you see the traditional “Complete Action Using” dialog. If no matching activity is found, it will just throw an error.

Note that because the intent is implicit, we need to specify action explicitly.

Implicit intents are quite powerful, and used throughout the OS for many tasks. Try these examples:

am start -a android.intent.action.VIEW -d http://www.google.com
am start -a android.intent.action.CALL -d tel:12345
am start -a android.intent.action.VIEW  -t image/jpeg  -d file:///sdcard/mypic.jpg  
  [...replace with path to your picture file]

Sometimes, instead of an Action, a Category can be used, which is what we’ve done with HOME. There are more standard actions and categories, you can read about them here: Try this on GMS-enabled devices, for example:

am start -a android.intent.action.MAIN -c android.intent.category.APP_MARKET

If you want to really kill the app, use the am force-stop with the package name:

am force-stop --user -2 com.android.gallery3d
am force-stop --user -2 com.motorolasolutions.rxlogger.main

The are also kill and kill-all options, but those have unwanted side effects. Only use them when force-stop is not working. The “–user -2” (that’s right, ‘minus two’) means ‘current user’. Not specifying any user means ‘all users’ which requires root privileges to run. To learn more, read this post on Android multiuser framework.

Intents for background components: services and broadcasts

Ok, so now we can start and stop apps, show and hide specific app windows. We can bring RxLogger up, but how to enable logging? Let’s look at other RxLogger app components.

When something needs to run in the background, Services are the prime candidates, and RxLogger sports quite a few of them. Here’s the main service. Note that it is exported, and thus, accessible from the outside.

<service android:name="com.motorolasolutions.rxlogger.main.RxLoggerService" android:exported="true"> 
<intent-filter> 
  <action android:name="com.motorolasolutions.rxlogger.intent.action.REGISTER_PLUGIN" /> 
</intent-filter> 
<intent-filter> 
  <action android:name="android.intent.action.MEDIA_MOUNTED" /> 
  <data android:scheme="file" /> 
</intent-filter> 
</service>

Unfortunately, this service seems to deal only with plugin registration and SD card mount status, but not with actual logging. Same with other services. So we need to look elsewhere:

<receiver android:name="com.motorolasolutions.rxlogger.main.RxLoggerBootReceiver"> 
 <intent-filter> 
   <action android:name="android.intent.action.BOOT_COMPLETED" /> 
 </intent-filter> 
</receiver> 
 
<receiver android:name="com.motorolasolutions.rxlogger.main.RxLoggerControlReceiver"> 
 <intent-filter> 
   <action android:name="com.motorolasolutions.rxlogger.intent.action.ENABLE" /> 
   <action android:name="com.motorolasolutions.rxlogger.intent.action.DISABLE" /> 
 </intent-filter> 
</receiver>

The Broadcast Intent Receivers are also very popular when the app needs to trap a system-wide message. The first one, for example, allows RxLogged to know when the OS has booted, so, for example, it can start logging immediately w/o user confirmation, if this is how it was configured.

The second Receiver has two intents, whose names are very promising! Let’s try them out! Since they are Broadcast Intents, instead of am start we need to use am broadcast:

am broadcast --user -2 -a com.motorolasolutions.rxlogger.intent.action.ENABLE
am broadcast --user -2 -a com.motorolasolutions.rxlogger.intent.action.DISABLE

This is what you should see in the Notification Area when you send the ENABLE intent:

AndrIntents03

Hooray! Hooray!

Can you use am start instead of broadcast, so that you can make your message targeted? Looks like you can’t – broadcast receivers only receive broadcasts, simple explicit intents just don’t reach them. Keep that in mind when constructing your intents.

Summary

Now you have basic means to

  • Dissect apps to learn which intents they support
  • Explicitly Start apps, their specific windows or specific OS windows.
  • Stop apps or hide their windows
  • Start and stop services.
  • Simulate ‘Home’ button press
  • Ask the OS to view files, open URLs, call phones using the assigned apps w/o having to figure what the app is (script portability!) using Actions and Categories.
  • Send broadcast intents to all listeners
  • Pass data along with the intents

In order to learn the specific actions and data passed to the applications and some (but not all) OS components you may use a tool like Intent Intercept – a useful category of apps that intercept the intents and display their data for debugging.

To learn more about intents in general, common actions, categories and other things you can do with them, read these guides and use these tools:

Enjoy!

P.S. The ADB script (the most important part of it):

adb install -r RxLogger.apk
adb push config.xml /sdcard/RxLogger/config/config.xml
adb shell am broadcast --user -2 -a com.motorolasolutions.rxlogger.intent.action.ENABLE

 

Advertisements

One thought on “Harnessing Android Intents

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s