Android Activity Management with Appium — Powerful Shortcut to Mobile Automation

Lana Begunova
9 min readDec 16, 2023

--

In the previous post, we discovered that Android apps come with a type of segments that iOS apps don’t, called Activities. Using Appium enables us to interact directly with Activities and not only apps.

ApiDemos.apk — Android App Activity Examples: AccessibilityNode, SMS Messaging, and Video Player.

What are Android Activities?

On Android, apps are not just one monolithic thing. They are composed of multiple independently launch-able partitions known as Activities.

Android apps are not really just one monolithic thing. On iOS, an app is more or less an app. Certainly, it is composed of multiple views, but from the perspective of the operating system, it’s all just one app. On Android, the situation is different. Android apps are actually collections of smaller divisions known as Activities. An app might consist of only one Activity, but more commonly there will be more than one. Take a look at the various views of the Android Settings app here. Each of these different views is actually a separate Activity which has its own unique name.

Settings App Activities (SubSettings)

As we move through the app, the system is actually switching between the various activities under the hood. At any given point in the lifetime of the operating system, Android knows exactly which app is running, and it knows exactly which activity within that app is running. An app is never running without one of its activities running. Of course, there is a default sort of activity, known as the Launch Activity, which is the activity that is loaded whenever our app is launched.

Users might want to open an app at a specific place within it. E.g., going directly to the WiFi picker that would otherwise be buried deep within the Settings app.

What’s the point of Activities, then? Why did Android decide to allow apps to be split into these activities? One reason is that users often want to go directly to specific places within an app. Imagine wanting to check for available WiFi networks without wanting to open up the Settings app, and navigate through a number of different views to get there. By making the WiFi Settings view a separate activity, the system is able to open that activity directly if it is directed to do so, bypassing all the other app views. Essentially, each Activity is a portion of our app which could be triggered directly, even from outside the app.

Activities can be registered with Android as being able to handle Intents. Intents are attempted generic user actions (capture an image, share item with a contact).

The other main design consideration for Activities is that each Activity can be registered with Android as handling one or more generic action requests, called Intents. Examples of intents would be the intent of a user to capture an image, or to share something with a contact. When users take actions on the device, these Intents can be triggered, and Android will look for any activity on the device which has registered support for that intent. For example, if we want to set the wallpaper on our device, that triggers an intent action called android.intent.action.SET_WALLPAPER.

android.intent.action.SET_WALLPAPER

What happens now is that Android looks for any app which has registered an activity that supports this particular intent action. If it finds more than one, it will present a list of options like you see in the image above, asking the user to pick which app it should use to fulfill the action. When the user selects the app, then the particular activity which supports that intent is launched. When the activity is launched, it is also told what intent triggered it, so that it can take immediate action. When intents are triggered, they can also contain special kinds of metadata, for example the URL of a link to be viewed, or similar. This data is also passed to the Activity when it’s launched, so it can take appropriate action based on the context.

We can use the Activity Manager (am) command along with ADB (adb shell) to launch any particular activity from the terminal:

% adb shell am start -a android.intent.action.SET_WALLPAPER

Starting: Intent { act=android.intent.action.SET_WALLPAPER }

Each Activity has its own unique Activity name, and can be launched directly with various arguments.

Remember that every time we are looking at an Android app, we aren’t just looking at an app, we’re looking at one of those app’s Activities. It has its own special ID, and it can be launched directly, and potentially launched via a number of different intent actions. What’s great is that we can actually make all these things happen just by using Appium commands. This way we can test that the apps do the right things when triggered by different intents.

Start Activity Capabilities

The two things we care about are the package ID of the application we want to start and the activity name or ID to launch within that app.

We've already discussed how to determine the package ID of an app in an earlier post. But now we have to figure out which activities the app has available, and what their names are. Similarly to package ID, we can use a special tool included in the Android SDK to determine this information. This tool is called AAPT (Android Asset Packaging Tool).

It may already be on your path in the .zshrc or .bashrc file on macOS or set as an environment variable in the advanced system settings on Windows. Check this post to learn more about setting an environment on either platform.

$ANDROID_HOME/build-tools/<build_number> set on path as an environment variable on macOS.

On Mac, the AAPT executable is stored at ~/Library/Android/sdk/build-tools/<build_number>/aapt. We can launch it with the aapt command. In order to find out the names of activities from within the .apk we can run:

aapt list -a ./ApiDemos.apk | grep -E "android:name.+io.appium.android.apis"

On Windows, we can run the following aapt command:

aapt list -a ./ApiDemos.apk | findstr /i "android:name.*io.appium.android.apis"

Regardless of the platform we are on, this long command starts out by using aapt's list subcommand to list out a bunch of information from the .apk. But this will result in a firehose that makes it hard for us to distinguish what we care about from amidst the noise. So we use the grep tool on Mac or the findstr command on Windows, this time with something called a regular expression (RegEx), to find any lines that match a string including android:name as well as our package ID, which is io.appium.android.apis. Obviously if we're running this command trying to determine activities for another app, we'd want to replace it with that app's package ID.

This is what we can do with AAPT. We have access to this kind of tooling to find out the list of the activities within an Android app.

Once we’ve got our package and activity name, we’re pretty much ready to go. We don’t really need anything else in order to start an activity.

Activity Properties

Activity Properties

There are two other driver properties that have to do with packages and activities. The first is driver.current_activity, which is a property that, when retrieved, will give us the name of the currently running activity. We could use this to do different things in our script depending on where we've gotten to in our app, or just to use Appium itself to find out what the activity name is. And the second is driver.current_package, which gives us the package ID of the currently running app.

Practical Example

Let’s code this up in Python and make sure to install the ApiDemos app at the beginning of the test. Then we want to launch the app at a specific Activity, so we‘ll head over to the terminal to run the command mentioned earlier to figure out what activities are available:

aapt list -a ./ApiDemos.apk | grep -E "android:name.+io.appium.android.apis"

Again, this just uses the aapt command to list out a bunch of stuff and set up a filter to catch only activity names corresponding to our app's package ID. So when we run this, we get a ton of output.

ApiDemos.apk Activities

If we have a specific app view open on a device, we can use ADB to extract the name of that view activity by running an adb shell dumpsys command:

adb shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp|mInputMethodTarget|mSurface'
adb shell dumpsys window windows | grep -E ‘mCurrentFocus|mFocusedApp|mInputMethodTarget|mSurface’ → .animation.Transition3d

This app sure has a lot of activities. But let’s pick three of them to test.

1. app_act1 = ‘.accessibility.AccessibilityNodeProviderActivity’

There is one called io.appium.android.apis.accessibility.AccessibilityNodeProviderActivity. It's some kind of accessibility screen. In a file named activities_android_accessibility_node.py, along with the package ID, we'll define a capability to hold this activity name:

"appPackage": "io.appium.android.apis",
"appActivity": ".accessibility.AccessibilityNodeProviderActivity"
.accessibility.AccessibilityNodeProviderActivity

2. app_act2 = ‘.os.SmsMessagingDemo’

Back to the list of activities, what else can we use? There is one called .os.SmsMessagingDemo, so let's give that a go as well:

"appPackage": "io.appium.android.apis",
"appActivity": ".os.SmsMessagingDemo"
.os.SmsMessagingDemo

3. app_act3 = ‘view.VideoPlayerActivity’

The third activity would be .view.VideoPlayerActivity:

"appPackage": "io.appium.android.apis",
"appActivity": ".view.VideoPlayerActivity"
.view.VideoPlayerActivity

That’s all we need to do, just include the app package ID and the activity name. Now that we have our activities defined, let’s do something with them. Immediately after launching the activity, let’s get our current package and activity printed out to the console):

print(f"The current app activity is: {driver.current_activity}.")
print(f"The current app package is: {driver.current_package}.")

Now we want to be sure we can see if this works while we’re watching the test, so I’m going to include a short static wait. Remember, the only purpose for this is so we can watch the activity load before something else happens. To perform the static wait, we’ll first import the time library at the top of the file:

import time

Now, we can use time.sleep to wait for 3 seconds, after we start an activity:

time.sleep(3)

There are several use cases for these sorts of features, but one of the most important is the ability to get directly to parts of the app we want to test.

We are done coding our files activities_android_accessibility_node.py, activities_android_sms_messaging.py, and activities_android_video
_player.py
. All we’re doing here is basically launching three activities buried deep within this app. But we’re launching them directly. We don’t have to navigate through the app in order to get immediately to these screens.

This is a powerful technique for speeding up our Android tests!

Let’s run the script to verify it works as expected.

% python3 activities_android_accessibility_node.py                                                lanabegunova@Lanas-iMac
The current app activity is: .accessibility.AccessibilityNodeProviderActivity.
The current app package is: io.appium.android.apis.

First, the AccessibilityNode activity is launched. And after it stays up for 3 seconds, we get our appPackage and appActivity printed out.

HD video: https://youtu.be/BzYeEK5hPkQ

There’s definitely more to learn about Activities, but that’s all we’re going to discuss for now. The Android developer documentation on Activities and Intents provides plenty more information.

Happy testing and debugging!

I welcome any comments and contributions to the subject. Connect with me on LinkedIn, X , GitHub, or Insta.

--

--

Lana Begunova

I am a QA Automation Engineer passionate about discovering new technologies and learning from it. The processes that connect people and tech spark my curiosity.