Monday, September 7, 2015

The Unexpected Permission

Recently I've noticed an interesting trend where apps are suddenly asking for permission to write to external storage. Considering starting with Android 4.4, an application is no longer required to have this permission to read the application-specific directories, you would expect more apps to remove the permission instead of add it. With Android 6.0 on the horizon where each app was going to have to "justify" why they need each restricted permission and then be able to justify it to the user.


The Potential Cause


While the new influx of the permission was strange, it wasn't until I was recently testing the new Android 6.0 permission api that I noticed something odd. When I went to the app settings to "switch" off a permission that was granted, I noticed the app I was working on also had registered for the external storage permissions. This was especially troubling since the application had only declared the course location permission, beyond a few normal permissions such as internet in the manifest. 

I double checked the manifest file to ensure someone didn't leave the external storage permission configured by accident.  However, as expected, only the permissions I remembered were listed in the manifest. Needless to say I was momentarily confused. 

I then remembered a post by Ian Lake I had read a while back talking about how the Google Play Services libraries were automatically adding certain content to the manifests such as registering the Google Play Services version. Perhaps a library was adding the permission on the apps behalf. Sure enough, the final merged manifest for the application had these lines merged into the application's manifest:

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<android:uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature
    android:glEsVersion="0x00020000" android:required="true" />


I immediately recognized the write external storage permission and the OpenGL v2 requirement as part of the Google Maps V2 API. However, my app was not using the Maps api, so I ran the following Gradle command to see which library was potentially including Maps:


./gradlew androidDependencies

which showed this snippit:

\--- com.google.android.gms:play-services-location:7.8.0
     +--- com.google.android.gms:play-services-base:7.8.0
     |    \--- com.android.support:support-v4:23.0.0
     |         \--- LOCAL: internal_impl-23.0.0.jar
     \--- com.google.android.gms:play-services-maps:7.8.0
          \--- com.google.android.gms:play-services-base:7.8.0
               \--- com.android.support:support-v4:23.0.0
                    \--- LOCAL: internal_impl-23.0.0.jar

As you can see Google Play Location now depends on Maps V2. After a little research I determined that the smaller Google Play Service libraries are utilizing manifest merging to automatically add permissions and other attributes developers used to have to add manually. By updating to a newer version of the Location API, all applications that just use the location API will require new permissions added by its dependency on the maps API. Since most of the apps I had notice adding the external storage permission did use location in some form, I wonder if this is where the influx of external storage permissions is coming from.

Since the application I was testing wasn’t using Maps V2, what should be done about the extra permission? For Android 6.0 marshmallow, the permission would never be activated since the app wouldn’t directly request it. However, for Lollipop and below, the permission would have to be accepted by the user to just to install or update the app. This would potentially hold back automatic updates because they user would have to accept the new permissions in order to get the latest version. This is a tough ask when the new permissions are not even required for the app to operate correctly. 


The Solution


After a little research, I discovered it is possible to prevent the inclusion of permissions by adding some lines to the application’s manifest file. Essentially the lines ask the development tools to "remove" the listed permissions as Gradle is generating the merged manifest.

<uses-permission 
    android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove"/>
<uses-permission 
    android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove"/>

Notice that the application is removing both the write and read external storage permissions. This is due to the fact that manifest merging will automatically add the read permission if the write permission was added. You can read more about implicit permissions in the manifest merge guide.

After adding these lines I rechecked the application and everything was back to the explicit list of permissions in the manifest. Of course, you may want to comment why these lines exist to help remind you that you are removing the permission due to an implicit dependency to the maps library.


Update Nov 5, 2015


As of Google Play Services 8.3, the Maps API configuration indicates the WRITE_EXTERNAL_STORAGE permission is no longer required which solves this issue with respect to the Location API. If you are able to update to 8.3 I highly recommend it. This version is also recommended if you are targeting Android 6.0.

The solution would still apply for other 3rd party libraries that may add permissions that are not required for your application.