Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I need the WRITE_EXTERNAL_STORAGE permission with getExternalCacheDir() on Android Lollipop?

My app writes (and reads) cache files to the getExternalCacheDir() location. Before Android Lollipop (API 21) I've been using this permission with success:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />

The maxSdkVersion is there because this permission shouldn't be needed after API v18: http://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE

But on Android Lollipop (5.0) I'm getting an access permission like so (with my log output to show the actual path used):

11-19 13:01:59.257    4462-4541/com.murrayc.galaxyzoo.app E/android-galaxyzoo﹕     createCacheFile(): IOException for filename=/storage/emulated/0/Android/data/com.murrayc.galaxyzoo.app/cache/52
    java.io.IOException: open failed: EACCES (Permission denied)
            at java.io.File.createNewFile(File.java:941)
            at com.murrayc.galaxyzoo.app.provider.ItemsContentProvider.createCacheFile(ItemsContentProvider.java:528)

I see this both in the emulator and on my Nexus 4. Has something changed, or was I doing something wrong all along? I just want to access my own app's cache.

Update: I now see this only on my device (Nexus 4 running the standard Android 5.1.1, which has even had a fresh Android reflash since I first had this problem). I no longer see this in the emulator - of course the system images have been updated several times.

like image 990
murrayc Avatar asked Nov 19 '14 12:11

murrayc


1 Answers

We've seen same behaviour on API 21 (Lollipop) on a Nexus 5:

java.io.FileNotFoundException: /storage/emulated/0/Android/data/[package name]/cache/http/journal.tmp: open failed: EACCES (Permission denied)
   at libcore.io.IoBridge.open(IoBridge.java:456)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:87)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:72)
   at com.android.okhttp.internal.DiskLruCache.rebuildJournal(DiskLruCache.java:346)
   at com.android.okhttp.internal.DiskLruCache.open(DiskLruCache.java:239)
   at com.android.okhttp.HttpResponseCache.<init>(HttpResponseCache.java:140)
   at android.net.http.HttpResponseCache.install(HttpResponseCache.java:199)
...
   at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1011)
   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4518)
   at android.app.ActivityThread.access$1500(ActivityThread.java:144)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1339)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5221)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
   at libcore.io.Posix.open(Posix.java)
   at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
   at libcore.io.IoBridge.open(IoBridge.java:442)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:87)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:72)
   at com.android.okhttp.internal.DiskLruCache.rebuildJournal(DiskLruCache.java:346)
   at com.android.okhttp.internal.DiskLruCache.open(DiskLruCache.java:239)
   at com.android.okhttp.HttpResponseCache.<init>(HttpResponseCache.java:140)
   at android.net.http.HttpResponseCache.install(HttpResponseCache.java:199)
...
   at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1011)
   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4518)
   at android.app.ActivityThread.access$1500(ActivityThread.java:144)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1339)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5221)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

Until Google push Android 5.0 to the AOSP we won't be able to work out if its a bug or deliberate (but undocumented) change, but I've raised this bug regardless: https://code.google.com/p/android/issues/detail?id=81357

Adding WRITE_EXTERNAL_STORAGE permission prevents the above exception being thrown, but will require end user permission to upgrade existing apps. Since our app doesn't use this permission and we don't want to add it, we're falling back to using internal cache for all except KitKat devices.

As an aside, I found this an interesting background to the changes introduced in KitKat: http://www.doubleencore.com/2014/03/android-external-storage/

like image 117
fingertricks Avatar answered Oct 18 '22 03:10

fingertricks