Disabling (and removing) code on release builds

Repo updated on 2017-03-13: added additional debug configurations to check different options to enable/disable minification, optimization and obfuscation. See the discussion in Optimize without obfuscate your debug build, including this comment.

Sometimes you have to add code to your applications that is used for debugging purposes. This can be very useful, and sometimes is keep there as it helps in the development and debugging of different parts of the application. But, some of this code can have unintended consequences:

  • it can reveal sensitive data to a potential attacker (internal URLs, session cookies, etc.)
  • it can have a performance impact in your application (excessive logging, performing operations not needed for release builds, etc.)
  • it can lower the security of your application (backdoor-like features to help while debugging, that can disable certain security features, or completely bypass them, etc.)

Continue reading Disabling (and removing) code on release builds

Jenkins builds with a different Java version

We have a Jenkins server taking care of CI for an Android project. In the server we were using Java 7, but since we updated a few dependencies we needed Java 8 to run some Gradle plugins. After the change, we suddenly started to get this error in the builds:

* What went wrong:
A problem occurred evaluating project ':my-project-app'.
> java.lang.UnsupportedClassVersionError: com/android/build/gradle/AppPlugin : Unsupported major.minor version 52.0

After googling it, I found the following SO answer, pointing us to the need of Java 8 (major version 52): How to fix java.lang.UnsupportedClassVersionError: Unsupported major.minor version.

So, given that:

  1. server has both Java 7 and Java 8 installed
  2. Jenkins is configured to run using Java 7, and changing may pose some problems (and currently any failure could put our schedule in jeopardy)

we decided to just run Gradle script using Java 8. To configure it, the following change was done to the configuration of the job: in section Build Environment, enable Inject environment variables to the build process and add the following to Properties Content:

JAVA_HOME=(path_to_jdk)

in our case:

JAVA_HOME=/usr/lib/jvm/java-8-oracle

And hit Rebuild!

Problems with Instant Run (Android Studio 2.0 beta 4) and Retrofit

This morning I was playing with a toy app I have which uses Retrofit, and I’ve found the following problem with it (and Instant Run):

02-16 07:41:55.550 2976-2976/com.test.android A/art: art/runtime/thread.cc:1329] Throwing new exception 'length=227; index=1017' with unexpected pending exception: java.lang.ArrayIndexOutOfBoundsException: length=227; index=1017

The exception was in the call itself, so it was either Retrofit doing strange things or something deeper. I found the problem to be because of some interaction between ART and Instant Run, and it was already reported.

The original reporter has opened a bug in the AOSP and it’s still unsolved. See also (Retrofit).

All in all, currently there is no fully reliable solution. So, if you find this problem in a call using Retrofit, the best course of action is to disable Instant Run and rebuild. Preferences -> Build, Execution, Deployment -> Instant Run -> (uncheck) Enable Instant Run to hot swap code/resource changes on deploy (default enabled)

Disable Instant Run

Kotlin RC and kotlinx

I’ve been toying with Kotlin for a while. Yesterday RC was released, making it one step closer to have a stable version.

One small change that has made me scratch my head a little, even if I read the solution, has been the changes regarding Kotlin Extensions. This had been integrated with the main Kotlin plugin (instead of being a separated plugin) but on RC you need to change your Gradle file a little to keep using it.

The symptom is that you find an “Unresolved reference: kotlinx” plus others that come from the same namespace. Resolution is easy: in the announcement for the release Kotlin 1.0 Release Candidate is Out! in the section Tooling, it reads (bold is mine):

To enable Android Extensions in Gradle in a more idiomatic way, we now say:

apply plugin: 'kotlin-android-extensions'

in the build.gradle file (individually for each project).

The old way doesn’t work any more and prints fixing instructions to the output.

So, I haven’t found this instructions, but the solution is easy: apply the new kotlin-android-extensions plugin right after the kotlin-android plugin in your gradle file. Usually, the end result will be like this:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

After that you just need to sync your Gradle configuration and rebuild the whole project.

In-depth article on Android touch events handling

Sometimes you need to intercept touches for a given ViewGroup, to temporarily change or disable it’s behavior. For example, recently I had to do this to temporarily intercept events for a RecyclerView during a tutorial, instructing the user how to select a particular item.

In this case, I could simply add an overlaid Layout on top of everything, but to keep as close to the real thing as possible, I decided to intercept the touch events at the RecyclerView level. The problem is, how to detect if the touch event needs to be intercepted or not. Two possible solutions came to my mind:

  • Intercept (or somehow disable) the events on each individual item, except on the one we’re interested on receiving it. For me it’s a bit messy to have this logic scattered.
  • Intercept the events on the RecyclerView, and check if the event belongs to the child, to allow further processing.

So, I decided to go for the second solution. To do that, I investigated a bit more in depth how touch events are handled in Android. This a really interesting article on how this happens: Understanding Android Input Touch Events System Framework (dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent, OnTouchListener.onTouch). Also, it is really useful to see the real code on how a ViewGroup handles this. Even if I could download Android source code from the Android Open Source Project web site, I didn’t really felt like it. So I found this copy online.

The interesting part for us is ViewGroup#dispatchTouchEvent(MotionEvent), and specifically in the loop where it iterates all the child and checks if the touch event is within the bounds of this child.

Based on this, I extracted the relevant code, which boils down to retrieving the hit rectangle of the child we want to allow processing the event, and checking it against the touch target. This class has to be overridden and the getAllowedChildView() method implemented, returning the child we’re interested in allowing the touch events go through.

public class CustomRecyclerView extends RecyclerView {

    private static final String LOG_MARKER = CustomRecyclerView.class.getName();

    private boolean scrollEnabled = true;

    public CustomRecyclerView(Context context) {
        super(context);
    }

    public CustomRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setEnabledRecycleScroll(final boolean enable) {

        if (scrollEnabled == enable) {
            Logger.getInstance().debug(LOG_MARKER, "RecyclerView scrolling is already " +
                    (enable ? "enabled" : "disabled") + ", skipping");
            return;
        }

        scrollEnabled = enable;

        if (!enable) {
            Logger.getInstance().debug(LOG_MARKER, "Disabling RecyclerView scrolling");
            addOnItemTouchListener(disablerListener); // disables scrolling
        } else {
            Logger.getInstance().debug(LOG_MARKER, "Enabling RecyclerView scrolling");
            removeOnItemTouchListener(disablerListener); // scrolling is enabled again
        }

    }

    // Override this method to return the child we're interested in allowing touch events
    // to go through
    abstract protected View getAllowedChildView();

    private RecyclerView.OnItemTouchListener disablerListener =
            new RecyclerView.OnItemTouchListener() {
        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {

            // Inspired by ViewGroup#dispatchTouchEvent(MotionEvent). See
            // http://codetheory.in/understanding-android-input-touch-events/ and
            // http://www.netmite.com/android/mydroid/frameworks/base/core/java/android/view/ViewGroup.java
            final int action = ev.getAction();
            if (action == MotionEvent.ACTION_DOWN) {
                final float scrolledX = ev.getX() + CustomRecyclerView.this.getScrollX();
                final float scrolledY = ev.getY() + CustomRecyclerView.this.getScrollY();
                final Rect frame = new Rect();
                View allowedChildView = getAllowedChildView();
                if (allowedChildView != null) {
                    allowedChildView.getHitRect(frame);
                    if (frame.contains((int) scrolledX, (int) scrolledY)) {
                        // Do not intercept the touch events for this child
                        return false;
                    }
                }
            }

            return true;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        }
    };

}

 

Disabling logs on Android using ProGuard

A quick way of disabling the logs for release builds is using ProGuard to take care of it. To do it in our current project we’ve created two ProGuard configurations, the one that applies for all the builds and the one that only applies for the release build.

Then, we can configure the build.gradle file like this:

apply plugin: 'com.android.application'

// snip...

android {

    // snip...

    buildTypes {
        release {

            // snip...

            // Enable ProGuard
            minifyEnabled true

            // Common release options
            zipAlignEnabled true
            debuggable false
            jniDebuggable false

            // Notice that the default ProGuard file (SDK-provided) also enables optimization
            // Here we also include a third file which disables the logging (see below)
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'proguard-rules-disable-logging.pro'
        }

        debug {
            // We enable ProGuard also for debug builds
            minifyEnabled true

            // Notice that the default ProGuard file (SDK-provided) differs from the release one
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

The file to disable logging is as simple as that:

##
## Disable logging
##

# Disable Android logging
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}

# This gets rid of System.out.println() and System.out.print()
# WARNING: if you're using this functions for other PrintStreams in your app, this can break things!
-assumenosideeffects class java.io.PrintStream {
    public void println(...);
    public void print(...);
}

Android compatibility with 32-bit libraries on a 64-bit device

Recently I run into a problem with an app we’re working on: suddenly it stopped to work on a Samsung Galaxy S6 Edge. It was still working on all the other, older devices. The first idea was: the app is using a JNI library, so it’s a 64-bit compatibility problem. And bingo! This was the reason… but in an unexpected way.

The strange thing was that the app used to work on this device, and with this library (SQLcipher, to keep data secure on rest). So we started to see what changed: the OS recently got an update, to 5.1.1. Maybe there was a compatibility provision on the OS, but after this OS upgrade this had been disabled.

After some investigation, I found the following post in stackoverflow.com: Use 32-bit jni libraries on 64-bit android. Basically the answer stated that as far as you use System.loadLibrary() to load your JNI libraries, a 64-bit OS will try to load the 32-bit version of your library if it doesn’t find a 64-bit version. But this was not happening any more!

After some further investigation, I found that recently we added a module provided by a third party, which had a dependency to another library that had compatibility to both 32 and 64-bit. I’ve added ** next to each of the offending files.

$ unzip -l myapp-development-debug.apk
...
  186244 09-10-15 11:31 lib/armeabi/libdatabase_sqlcipher.so
 2478904 09-10-15 11:31 lib/armeabi/libsqlcipher_android.so
  390456 09-10-15 11:31 lib/armeabi/libstlport_shared.so
  182156 09-10-15 11:31 lib/armeabi-v7a/libdatabase_sqlcipher.so
 2421584 09-10-15 11:31 lib/armeabi-v7a/libsqlcipher_android.so
  374076 09-10-15 11:31 lib/armeabi-v7a/libstlport_shared.so
 1580868 09-10-15 11:31 lib/x86/libdatabase_sqlcipher.so
 3897372 09-10-15 11:31 lib/x86/libsqlcipher_android.so
  563148 09-10-15 11:31 lib/x86/libstlport_shared.so
   34224 09-09-15 13:29 lib/arm64-v8a/libpl_droidsonroids_gif.so **
    9624 09-09-15 13:29 lib/arm64-v8a/libpl_droidsonroids_gif_surface.so **
   38064 09-09-15 13:29 lib/armeabi/libpl_droidsonroids_gif.so
   17572 09-09-15 13:29 lib/armeabi/libpl_droidsonroids_gif_surface.so
   29884 09-09-15 13:29 lib/armeabi-v7a/libpl_droidsonroids_gif.so
   13488 09-09-15 13:29 lib/armeabi-v7a/libpl_droidsonroids_gif_surface.so
   73488 09-09-15 13:29 lib/mips/libpl_droidsonroids_gif.so **
   71132 09-09-15 13:29 lib/mips/libpl_droidsonroids_gif_surface.so **
   41544 09-09-15 13:29 lib/mips64/libpl_droidsonroids_gif.so **
   10360 09-09-15 13:29 lib/mips64/libpl_droidsonroids_gif_surface.so **
   33868 09-09-15 13:29 lib/x86/libpl_droidsonroids_gif.so
    9280 09-09-15 13:29 lib/x86/libpl_droidsonroids_gif_surface.so
   34416 09-09-15 13:29 lib/x86_64/libpl_droidsonroids_gif.so **
    9816 09-09-15 13:29 lib/x86_64/libpl_droidsonroids_gif_surface.so **
   95397 09-14-15 15:31 META-INF/MANIFEST.MF
   95426 09-14-15 15:31 META-INF/CERT.SF
    1318 09-14-15 15:31 META-INF/CERT.RSA
 --------               -------
 44599565               947 files

 
By simply using unzip tool we can clearly see the directories for the arm64-v8a and x86_64 architectures, and also the mips and mips64. In this directories the only present library are libpl_droidsonroids_gif and libpl_droidsonroids_gif_surface.

A quick and dirty solution to get rid of this libraries is to manually use “zip -d” to remove the unwanted architectures, like this:

zip -d myapp-app-development-debug.apk "lib/x86_64/*"
zip -d myapp-app-development-debug.apk "lib/mips64/*"
zip -d myapp-app-development-debug.apk "lib/mips/*"
zip -d myapp-app-development-debug.apk "lib/arm64-v8a/*"

 
Of course, this is useful to test that this is the cause of the crash, but not something you want to do on each build.

So, to automate this steps in Gradle we can use packagingOptions.exclude in the android section, like this (parts omitted):

android {
    compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION)
    buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION

    defaultConfig {
        ...
    }

    ...

    packagingOptions {
        exclude 'META-INF/LICENSE.txt'

        exclude 'lib/arm64-v8a/libpl_droidsonroids_gif.so'
        exclude 'lib/arm64-v8a/libpl_droidsonroids_gif_surface.so'
        exclude 'lib/x86_64/libpl_droidsonroids_gif.so'
        exclude 'lib/x86_64/libpl_droidsonroids_gif_surface.so'
        exclude 'lib/mips/libpl_droidsonroids_gif.so'
        exclude 'lib/mips/libpl_droidsonroids_gif_surface.so'
        exclude 'lib/mips64/libpl_droidsonroids_gif.so'
        exclude 'lib/mips64/libpl_droidsonroids_gif_surface.so'
    }

}

 
Notice that exclude doesn’t allow globs or wildcards, and we have to specify each file manually.