Samsung Shaped Spanner

Android devices come in many shapes, sizes and price points from many different manufacturers, for the most part Google have created an Android development environment that handles this well but every now and then a spanner falls into the works.

The One

We recently rolled out an Android build of School Days to a new school, everything was going well, users were coming onboard thick and fast, until we got word of one user who couldn’t launch the app. Instead, it would crash the instant they tapped the icon. But why? No one else had this problem.

SITREP

The first step was getting the user to submit a crash report, which gave us the following stack trace:

java.lang.NoClassDefFoundError: android.support.v7.internal.view.menu.MenuBuilder
    at android.support.v7.widget.ActionMenuView.getMenu(ActionMenuView.java:620)
    at android.support.v7.widget.Toolbar.ensureMenu(Toolbar.java:823)
    at android.support.v7.widget.Toolbar.getMenu(Toolbar.java:815)
    at android.support.v7.internal.app.ToolbarActionBar.getMenu(ToolbarActionBar.java:554)
    at android.support.v7.internal.app.ToolbarActionBar.setListMenuPresenter(ToolbarActionBar.java:558)
    at android.support.v7.app.ActionBarActivityDelegateBase.setSupportActionBar(ActionBarActivityDelegateBase.java:178)
    at android.support.v7.app.ActionBarActivity.setSupportActionBar(ActionBarActivity.java:92)

On its own this wasn’t helpful as it points to an error with a Google-built support library that we had no control over. It wasn’t until we combined the stack trace with some more data from the crash report that research began bearing fruit.

Samsung, Android 4.2.2 and the Support Library walk into a bar…

The bar promptly crashes. It turns out the user has a Galaxy Ace, a mid tier Samsung offering running Android 4.2.2. Adding those details into my research queries led to issue #78377 on the Android Open Source Project (AOSP). The issue contains a lengthy discussion on the issue which is concluded succinctly by a user named koush:

Leave it to Samsung to Samsung it up. Various Samsung phones are including older versions of the android support library in the framework or classpath.

If you use the new material support library, you'll see this crash on those Samsung devices:

java.lang.NoClassDefFoundError: android.support.v7.internal.view.menu.MenuBuilder

In simple terms, Samsung has included old library versions in their modified 4.2.2 release. When an app comes along with the latest in support libraries—most notably appcompat-v7 v21.0.0—it’s expecting one thing and getting another.

What they can’t find they can’t break

So now we know that appcompat-v7 v21.0.0 and Samsung devices running 4.2.2 don't play nicely, but what can we do about it?

Samsung are causing the issue by overriding an AOSP class with their own but they can only do this if they know the name of the class to target. We need a solution that will rename classes at compile time, providing a solution without relying on Samsung, AOSP or putting other devices at risk.

That solution is ProGuard, a part of the Android build system that, when configured, "shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names". The result of which is a "smaller sized.apk file that is more difficult to reverse engineer."

Implementation

To enable ProGuard in your project you need to uncomment the following line in the project's project.properties file found in the root directory the project:

 proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

A proguard-project.txt config file should exist in the above location. If not, just create an empty text file with that name.

Many people in the AOSP issue discussion found success with only the following line in their ProGuard config:

-keep class !android.support.v7.internal.view.menu.MenuBuilder,** {*;}

However, we were still seeing crashes on the affected device. Instead, we decided to add both MenuBuilder classes and the entire library to the config to be sure similar issues couldn't arise elsewhere:

-keep class !android.support.v7.internal.view.menu.MenuBuilder, !android.support.v7.internal.view.menu.SubMenuBuilder, android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }

ProGuard's options are deep and you could spend a lot of time just reading its manual. In our case, we're saying we want to keep these classes and the library interface as they are—which ProGuard will do by renaming the classes—ensuring the Samsung's bug can't interfere.

The Many

This is a specific bug that requires a specific combination of players to even witness. This is Samsung's fault but they aren’t likely to fix it and your users certainly don’t care who's to blame when it’s your app that's crashing.

The solution, while a minor pain to track down, was worth solving because even one user's suffering can make all the difference in a community-focused app.