If we type in adb shell
(get to the Android shell), we get access to the interactive CLI inside of Android.
We have access to CLI utilities provided by toybox
. We can also interact with Android system services and filesystem.
toybox
is where all the command-line utilities come from.
When we have access to shell we can also interact with the framework utilities, like Activity Manager. We can start an activity using Activity Manager.
We can also interact with logcat
, for example redirect its output to some file and then get that file from the device to our dev machine and email it to someone.
Also, when we have access to shell, we can interact with runtime utilities. For example, with Dalvik Virtual Machine we can ask it to run a random Java app, which is not an Android app but was complied to Dalvik byte code. We can ask Android to run such an app, and it can be a server, a web server for example if we compile it through Dalvik.
There is an interesting property that we can access through shell
, which is boot_completed
. When setting up a CI pipeline involving Android emulators, we come across this command. This is a flag that we can periodically query for, and wait until its state changes to True. Therefore we can be sure that our system is fully booted and proceed with installing apps on it, and perform automated tests.
We can also set some system properties like a Debug Layout. It shows layout bounds per developer options.
We can also do pm hide
or pm unhide
, which is a command for Package Manager. It allows us to hide certain packages, and therefore we cannot open them. For example, if we’re developing a custom launcher for our device we can hide the default launcher, and therefore the system will not ask us which launcher we want to choose if we click on Home.
All of the installed APKs from user space are inside the /data/app folder. We can access the app at the path /data/app/com.my_app.demo which includes the package name. It’ll output the APK file which corresponds to the package name.
generic_x86:/data/app # cd com.my_app.demo
generic_x86:/data/app/com.my_app.demo # ls
base.apk lib
- installation paths
- permissions
- user id/shared user id (system apps)
We can also extract the file called packages.xml
which contains all the installation paths for all the installed apps. It contains all the permissions for the installed apps, including system apps (pre-installed ones). This is the thing that we can’t usually access through UI. In most cases we do not see what permissions the system apps have. They may have some permissions that we don’t want them to have, for example, camera or audio record.
If we are curious we can view the user ids assigned to certain apps and we find out that system apps usually share the user id because they are run by a user called System.
- All background jobs (from system and user apps)
- Package names
- Constraints (idle / charging)
We can take a look at the jobs.xml file which contains all background jobs that were scheduled both by system and user apps. We can look at package names. For example, if our device’s battery life is not doing well, we can look in this place and see if any apps are triggering too many background jobs. Or, if we are using background jobs extensively, we can debug it this way. If we are debugging an app which uses some jobs, we can force our jobs to be executed with the using of the -f
flag.
/system
/system/app
/system/priv-app
/system/framework
If we look at the file system, we can find interesting things there as well. For example, print those apps that belongs to /system/app or /system/priv-app. ‘priv’ means privileged, not private. Privileged means that these apps can access non-public hidden APIs that we don’t see in Android Studio, and system apps can’t access them because they are privileged.
In /system/framework we can find the framework.jar
which is indeed our whole framework.
So this is the file that gets pre-loaded in the zygote
from which every Android app is forking its initial process. And it contains other files like View.java, Context, Activity, all the other files that we are not shipping together with our app. Instead they are taken from framework.
If we are curious and stubborn at the same time, we can compare how these files are changed by OEMs, because we can browse how this file looks in AOSP (Android Open Source Project). We can browse Activity or View code, but the code that actually ships on some devices may be changed by the manufacturers. For example, it commonly happens that some tiny little details will look different on Samsung or Huawei, because these differences reside in files like View.java which are shipped on the device.
And if we extract the framework.jar
file, we can also take a look at internal Android classes which we cannot access through SDK.
For example, if we open Activity.java file in Android Studio, there’s a field called AutofillClient() which we cannot view because it’s not meant to be used by app developers or testers. But if we’re curious, we can take a look at what looks like an AOSP. Or we can look at it on the device that we are developing/testing our app against by extracting the framework.jar
file.
In /system/bin we can see all the binaries we can interact with through command line.
And also view some generic system images of an emulator, there are 27K of them.
And we can see things like Activity Manager or audio server. And things like grep
, ps
, ls
, pwd
- they are all here as well.
/system/xbin is another folder that contains such binaries. On an emulator we can find a binary called SwitchUser (su) which we cannot find on a production build. For example, on my OnePlus this folder is empty.