Recently I’ve ended up spending quite some time trying to figure out why a test where I was trying to set permissions via adb and appops command on Android intermittently fails (the dreaded flaky test!). So in today’s post I want to share my findings with you.
The permissions I was setting were Usage access permission (android.permission.PACKAGE_USAGE_STATS), All files access permission (android.permission.MANAGE_EXTERNAL_STORAGE) and System alert window permisson (android.permission.SYSTEM_ALERT_WINDOW), and for the I was using
adb shell appops set <package_name> <permission> allow/deny command (you can read more about adb commands in my other post here). The weird behavior I was observing was that sometimes the app was behaving like the permission was denied despite the fact that in system setting it looked like the permission was granted.
Long story short, the root cause of this behavior hides here:
App-ops can either be controlled for each uid or for each package. Which one is used depends on the API provider maintaining this app-op. For any security or privacy related app-op the provider needs to control the app-op for per uid as all security and privacy is based on uid in Android.https://developer.android.com/reference/android/app/AppOpsManager
That means that depending on the permission we should either provide an option “–uid” or not. Note, that even if we provide “–uid” option, we should specify not an app’s process’s uid, but an app’s package name after it .
AppOps service (appops) commands:
Print this help text.
start [--user <USER_ID>] <PACKAGE | UID> <OP>
Starts a given operation for a particular application.
stop [--user <USER_ID>] <PACKAGE | UID> <OP>
Stops a given operation for a particular application.
set [--user <USER_ID>] <[--uid] PACKAGE | UID> <OP> <MODE>
Set the mode for a particular application and operation.
get [--user <USER_ID>] <PACKAGE | UID> [<OP>]
Return the mode for a particular application and optional operation.
query-op [--user <USER_ID>] <OP> [<MODE>]
Print all packages that currently have the given op in the given mode.
reset [--user <USER_ID>] [<PACKAGE>]
Reset the given application or all applications to default modes.
Immediately write pending changes to storage.
Read the last written settings, replacing current state in RAM.
<PACKAGE> an Android package name or its UID if prefixed by --uid
<OP> an AppOps operation.
<MODE> one of allow, ignore, deny, or default
<USER_ID> the user id under which the package is installed. If --user is not
specified, the current user is assumed.
So why was I having the issues? Because I mixed uid and package modes when setting the permissions, which is a big no-no as the following bit explains:
Warning: Do not usehttps://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/AppOps.md
setUidModefor the same app-op. Due to the way the internal storage for the mode works this can lead to very confusing behavior. If this ever happened by accident this needs to be cleaned up for any affected user as the app-op mode is retained over reboot.
So in order to set the permission correctly, first we need to find out whether this permission requires uid or package mode. I couldn’t find any way to do that other then manually enable the permission via system settings, and then inspect the output of get command – if it has words “Uid mode”, then this permission requires a uid mode, otherwise – a package mode.
appops get <package> MANAGE_EXTERNAL_STORAGE Uid mode: MANAGE_EXTERNAL_STORAGE: allow
To spare you some time, Usage access permission and System alert window permission require the package mode, and All files access permission – uid mode (it also can be seen in this command sample from Google). Correspondingly, the commands for granting these permissions would look like:
appops set --uid <package> MANAGE_EXTERNAL_STORAGE allow appops set <package> android:system_alert_window allow appops set <package> android:get_usage_stats allow
Note, that format of the permission name in the last two commands is different than in the first one. The reason is that names of those two permissions are stored in
AppOps.OPSTR_GET_USAGE_STATS constants, so if you are executing appops commands from the test script, it is better to reference those constants than hardcode the values.
If you mixed these modes, like I did, then this mess could be cleaned in the following ways:
- Reinstalling the app.
- Setting the non-relevant mode to default state (e.g. if the permission requires uid mode, than you should execute appops command without uid and set permission to “default” mode):
appops set <package> android:get_usage_stats default
And two more words of caution:
pm cleardoesn’t clear all permission, so if you use it for “reinstalling” your app between tests, you need to either stop doing that or to additionally revoke those permissions (e.g. neither of three permission I’ve written about in this post is cleared by
appopscommand has “–uid” options only since Android 10 (but I guess that permissions requiring that options didn’t exist before that).
Hope you will find this post about how to set permissions via adb and appops command on Android useful, and if you have any comments, leave them below.
By the way, my next post will be about tools for logcat log analysis, so stay tuned.