Thursday, November 22, 2012

Android: Jelly Bean forward locks paid apps

Today, a Monkey Write user complained that sound wasn't working on the workbooks he bought. I sell workbooks as separate paid apps on Google Play, which supplies data to the main app. Most of the data is retrieved via a ContentProvider, but for the sound the main app simply reaches out to the other apk and loads the sound file from its assets:

Context workbookContext = context.createPackageContext(
    packageName, 0);
AssetFileDescriptor afd = workbookContext.getAssets().openFd(
    "pronunciations/" + soundFile + ".ogg");

This depends on the resources and assets of an app being world-readable. But from the logcat, it seems that the main app could not find the sound file in the asset folder of the workbook app:

java.io.FileNotFoundException: pronunciations/kou3.ogg
 at android.content.res.AssetManager.openAssetFd(Native Method)
 at android.content.res.AssetManager.openFd(AssetManager.java:331)

I searched and searched on the internet to no avail, until I verified with the user that sound works fine for the free workbooks he downloaded. By including the word "paid" in my queries, I found a StackOverflow post with this critical bit of information:

Paid apps on JB devices are 'forward locked'. This means that the APK is split in two parts -- one with public resources, and one with private ones and code, which is not readable by other apps. I haven't looked into how files are split in detail, but the problem you are seeing suggests that assets are part of the private APK.

More importantly, he provided a way to test forward locking without going through Google Play: adb install -l myapp.apk

With that, I managed to reproduce and fix the problem. Since the assets of the paid app is no longer world-readable, I use a ContentProvider to read the sound file and have the main app query for it. The user has downloaded the updates from Google Play and verified that sound now works for paid workbooks. Yay!

1 comment:

  1. Another option would have been to have all your apps share the same userid by defining android:sharedUserId in their manifest.

    ReplyDelete