Android Permissions, Features and Google Play Store

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.

Our problem

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.

Android Permissions

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.

Android Features

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”.

Explicit 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.

Implicit Features

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 android.hardware.microphone

ACCESS_WIFI_STATE permission implies android.hardware.wifi

ACCESS_FINE_LOCATION permission implies android.hardware.location.gps and

android.hardware.location

That’s it for the overview. Now let’s look at how one can detect which features are being used by an app.

Using aapt

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.

Lessons learned

If you’re an app developer please make sure you add a <uses-feature> element for every feature your app needs, adding the attribute

android:required="false"

if your app can work without it.

References

  1. <uses-feature> | Android Developers
  2. Permissions | Android Developers
  3. Image credits: Yuko Honda, Android Latte, license