Hey, Where Did These Permissions Come From?
We are getting a wave of questions like this one on Stack Overflow recently. Developers ask for a few permissions in their manifest, then go to install their app (by means other than the IDE) and wonder where all these other permissions are coming from.
The vast majority of the time, they are coming from libraries.
Android library projects — both in source and AAR form —
can publish manifests. Those manifests can have <uses-permission>
elements in them. Those permissions will blend with the ones from your
manifest(s) and those of other libraries that you are using, and all
of them will be requested by your app.
This is one highly-visible impact of manifest merging.
To diagnose what is going on, you can turn to two resources:
-
The actual manifest in your app
-
The manifest merger report
Let’s see how this works.
If you create a brand-new Android Studio 1.2.2 project, using the Blank Activity template, you will wind up with a manifest that looks a bit like this:
<?xml version=”1.0” encoding=”utf-8”?>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Note: there are no <uses-permission>
elements here.
Your app module’s build.gradle
file will also be fairly bland:
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "23.0.0 rc2"
defaultConfig {
applicationId "com.commonsware.myapplication"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
}
But let’s make one change: adding compile 'com.google.android.gms:play-services:7.5.0'
to the dependencies
closure, to pull in all of Play Services.
Then, after building the project, we pull up
app/build/intermediates/manifests/full/debug/AndroidManifest.xml
. This
is the manifest that will actually go into our APK, representing
all the contributions from our own app and libraries.
As you can see, we have a few <uses-permission>
elements now, even
though our own manifest does not have any:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.myapplication"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="22" />
<!-- Include required permissions for Google Mobile Ads to run -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<android:uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.commonsware.myapplication.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Include the AdActivity and InAppPurchaseActivity configChanges and themes. -->
<activity
android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:theme="@android:style/Theme.Translucent" />
<activity
android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity"
android:theme="@style/Theme.IAPTheme" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.gms.wallet.api.enabled"
android:value="true" />
<receiver
android:name="com.google.android.gms.wallet.EnableWalletOptimizationReceiver"
android:exported="false" >
<intent-filter>
<action android:name="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION" />
</intent-filter>
</receiver>
</application>
</manifest>
That tells us what our app will ask for. It does not tell us exactly why it wants those permissions, other than that one XML comment hinting that Play Services is to blame.
To get a better sense of where those <uses-permission>
elements are
coming from, we can look at app/build/outputs/logs/manifest-merger-debug-report.txt
.
Since this file is 500+ lines long, here are some sample lines that
relate to our permission issue:
uses-permission#android.permission.INTERNET
ADDED from com.google.android.gms:play-services-ads:7.5.0:20:5
MERGED from com.google.android.gms:play-services-analytics:7.5.0:21:5
MERGED from com.google.android.gms:play-services-analytics:7.5.0:21:5
MERGED from com.google.android.gms:play-services-appinvite:7.5.0:19:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5
MERGED from com.google.android.gms:play-services-wallet:7.5.0:20:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5
android:name
ADDED from com.google.android.gms:play-services-ads:7.5.0:20:22
uses-permission#android.permission.ACCESS_NETWORK_STATE
ADDED from com.google.android.gms:play-services-ads:7.5.0:21:5
MERGED from com.google.android.gms:play-services-analytics:7.5.0:22:5
MERGED from com.google.android.gms:play-services-analytics:7.5.0:22:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5
MERGED from com.google.android.gms:play-services-nearby:7.5.0:19:5
MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5
android:name
ADDED from com.google.android.gms:play-services-ads:7.5.0:21:22
What adding compile 'com.google.android.gms:play-services:7.5.0'
really does is pull in a bunch of other Play Services artifacts
as transitive dependencies. So, we get play-services-maps
and play-services-ads
and play-services-analytics
and
so on. Many of those have manifests, asking for certain permissions.
In the case of ACCESS_NETWORK_STATE
, we see that play-services-ads
,
play-services-analytics
, play-services-maps
, and whatever
play-services-nearby
is all want that permission. So, it gets
added to our app’s manifest.
If you look at the generated manifest and the manifest merger report for your app, you too can see where these extra permission requirements are coming from.
Of course, the obvious follow-on question is “how do I make it stop?”
Maybe you don’t want all those <uses-permission>
requests, because
yours is a smaller brand and you don’t want to scare away prospective
users.
The best answer is: use fewer, more targeted dependencies.
For example, maybe you are using Play Services just for Google
Cloud Messaging (GCM). Rather than pulling in play-services
as a
dependency, you can pull in play-services-gcm
instead. Now, with
that one change, not only is your APK going to be smaller, but all
those rogue <uses-permission>
elements will go away from your
merged manifest, because GCM itself does not need any permissions.
It is possible that you will find yourself in a situation where you want a
dependency but you don’t want the permissions that the dependency wants.
You can try to block the permission from the merger process, by having
a <uses-permission>
element in your own manifest (e.g., in the
main
sourceset) with tools:node="remove"
:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" tools:node="remove" />
The above element would tell the build system, “yes, some dependencies
want ACCESS_COARSE_LOCATION
, but do not include it in my manifest”.
(note: you may need to add xmlns:tools="http://schemas.android.com/tools"
to the root <manifest>
element if it is not already there)
But at this point, you have one main thing to do: write the biggest
damn test suite you can. The author of the library is expecting to be
able to access location data, and some code in that library may fail
with a SecurityException
due to your manifest shenanigans. So long
as you never execute that code, life is good… at least until the
library gets an update that you want to take on. But the only way you can
feel comfortable that you are not executing that permission-dependent
code is to test, test, test.
Ideally, library authors will use PackageManager
to see whether or
not their app has the permission and either gracefully degrade or
fail fast. For many permissions, Android M is going to shake up the
world, particularly for library authors, and so this is a fine time to
go in and adding in that sort of logic.
So, in summary: if permissions are showing up in your app that you are not expecting, you have nobody to blame but yourself.
Well, yourself and the library authors for the libraries that you are using.
Well, OK, really it’s yourself, the library authors, and the Android tools team for offering this slick manifest merger process rather than forcing developers to hand-merge all of this stuff in.
But given the knowledge you gain from the merged manifest and the merger report, you can better tailor your library use and try to avoid permissions that you fear your users will not appreciate.