February 4, 2014
During our work porting Android to new devices we found some troubles when trying to install some apps from Play Store caused by the device not having certain features. This is a good opportunity to talk about Android permissions, capabilities and how the Play Store filters applications depending on the device.
We received some reports from users of our client’s device saying they couldn’t install the Germanwings app because they were not able to find it in the Play Store.
After trying it ourselves we confirmed the problem and quickly started investigating it. It turned out the app requested GPS access which, for security reasons, our device doesn’t currently support and that was the reason it is hidden from us in the Play Store.
Having GPS is not actually needed for the app to work so the fact that it’s hidden for us is probably an unintended misuse of Android permissions.
First, we’ll start by talking briefly about Android permissions and then we’ll describe Android features and how they both play together to allow requested features if they’re available without making them mandatory.
Some functionality the apps need (like making a phone call, reading the contacts list or using the camera) needs to be protected from unauthorized uses and that’s why Android Permissions were created. Each application declares a set of permissions the user has to accept at install time. These are defined by the developer using
<uses-permission> element in the AndroidManifest.xml.
Android provides a set of permissions that can be found at Manifest.permission and you can create your own custom permissions to protect your app’s activities or data providers. Regardless of who defines the permissions, one needs to be aware that these can affect feature requirements.
Not every Android device has the same capabilities, for example some devices support cellular data networks while others only support Wi-Fi. Android deals with this variety using “features”.
A device declares the features it supports in its build configuration. Applications use
<uses-feature> elements to declare the features they would like, these need not be hard requirements, though.
For example, an application that requires a camera will declare this in its AndroidManifest.xml:
<uses-feature android:name="android.hardware.camera" android:required="true" />
If it uses a camera but it’s not mandatory for the application to work, it will declare:
<uses-feature android:name="android.hardware.camera" android:required="false" />
This allows Google Play Store to filter out apps for devices that don’t have their required features.
Note: If an
android:required attribute is not included in
uses-feature element it is assumed that the feature is a hard requirement.
As mentioned above, certain permissions also imply certain features. Google Play uses these to filter out apps just as it would with explicit requirements. Developers should NOT rely on this implicit behavior, they should always declare explicitly every feature their app needs.
Some examples of permissions that imply features:
RECORD_AUDIO permission implies
ACCESS_WIFI_STATE permission implies
ACCESS_FINE_LOCATION permission implies
That’s it for the overview. Now let’s look at how one can detect which features are being used by an app.
aapt allows us, among other things, to see the contents of an app’s manifest. This is not as easy as simply unpacking the apk and reading the manifest as you’ll find it’s in a binary format. Here is the result of running the SDK-provided
aapt tool in its apk:
$ aapt dump badging com.germanwings.android-1.apk package: name='com.germanwings.android' versionCode='3' versionName='1.0.2' sdkVersion:'10' targetSdkVersion:'17' uses-permission:'android.permission.WRITE_EXTERNAL_STORAGE' uses-permission:'android.permission.INTERNET' uses-permission:'android.permission.ACCESS_FINE_LOCATION' uses-permission:'android.permission.ACCESS_COARSE_LOCATION' uses-permission:'com.germanwings.android.permission.C2D_MESSAGE' uses-permission:'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS' uses-permission:'android.permission.INTERNET' uses-permission:'android.permission.GET_ACCOUNTS' uses-permission:'android.permission.WAKE_LOCK' uses-permission:'android.permission.READ_PHONE_STATE' uses-permission:'com.google.android.c2dm.permission.RECEIVE' uses-permission:'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS' uses-permission:'android.permission.ACCESS_MOCK_LOCATION' uses-permission:'android.permission.ACCESS_NETWORK_STATE' uses-permission:'android.permission.ACCESS_GPS' uses-permission:'android.permission.ACCESS_LOCATION' uses-permission:'android.permission.READ_EXTERNAL_STORAGE' application-label:'Germanwings' application-icon-120:'res/drawable-ldpi/ic_launcher.png' application-icon-160:'res/drawable-mdpi/ic_launcher.png' application-icon-240:'res/drawable-hdpi/ic_launcher.png' application-icon-320:'res/drawable-xhdpi/ic_launcher.png' application: label='Germanwings' icon='res/drawable-mdpi/ic_launcher.png' launchable-activity: name='com.germanwings.android.presentation.activity.DashboardActivity' label='Germanwings' icon='' uses-feature:'android.hardware.location' uses-implied-feature:'android.hardware.location','requested a location access permission' uses-feature:'android.hardware.location.gps' uses-implied-feature:'android.hardware.location.gps','requested android.permission.ACCESS_FINE_LOCATION permission' uses-feature:'android.hardware.location.network' uses-implied-feature:'android.hardware.location.network','requested android.permission.ACCESS_COURSE_LOCATION permission' uses-feature:'android.hardware.touchscreen' uses-implied-feature:'android.hardware.touchscreen','assumed you require a touch screen unless explicitly made optional' uses-feature:'android.hardware.screen.portrait' uses-implied-feature:'android.hardware.screen.portrait','one or more activities have specified a portrait orientation' main other-activities other-receivers other-services supports-screens: 'small' 'normal' 'large' 'xlarge' supports-any-density: 'true' locales: '--_--' densities: '120' '160' '240' '320'
As we said before, our device didn’t have GPS capability and we can see that they requested
android.permission.ACCESS_FINE_LOCATION which implies
android.hardware.location.gps feature. We can also see that they rely on implied features instead of using explicit features as recommended.
How to fix it
In this case, the application was completely functional without a GPS connection so by simply adding to its AndroidManifest.xml
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
the application would have showed in Play Store for our device and we could have used it perfectly fine.
If you’re an app developer please make sure you add a
<uses-feature> element for every feature your app needs, adding the attribute
if your app can work without it.