Wednesday, July 15, 2015

Custom Tiled Background Theme in Android Activity

Making a tiled background in Android can be made simply by xml.

But, what if you need enough flexibility to manage a background theme where you can change color and pattern by user interaction, as in our free secure instant messaging app SHADO?

Since custom themes are immutable in Android, you can use our approach as a starting point for building your own runtime changeable themed UI with less code and xml resources, too.

First of all, you need a handful of patterns to use as backgrounds. They must be PNG images with transparency otherwise you won't see any change in the background color. Here are some examples:


Name them as you prefer (background_pattern_0.png, background_pattern_1.png, and so on) and put your patterns under the appropriate res/drawable resolution folder (res/drawable-hdpi, for example).
The second step would be to prepare your custom drawable resource using LayerList (and put it under the folder res/drawable) as follows:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/shape_color">
        <shape android:shape="rectangle" >
            <solid android:color="@color/black" />
        </shape>
    </item>
    <item android:id="@+id/shape_pattern"
        android:bottom="0px"
        android:left="0px"
        android:right="0px"
        android:top="0px">
        <bitmap
            android:antialias="true"
            android:src="@drawable/background_pattern_0"
            android:tileMode="repeat" />
    </item>
</layer-list>

Third, define your colors in the res/values/colors.xml file and, fourth, define the layout for your Activity as in the following skeleton:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_layout_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background_activity" >

    <!-- Here you can put your further layouts and controls -->

</RelativeLayout>

Fifth, in your Activity source code (or in an helper Java class), you can set the background theme programmatically as below:

private void setBackgroundTheme(RelativeLayout rlActivity) {
    LayerDrawable ldTheme = (LayerDrawable) rlActivity.getBackground();
    GradientDrawable shape = (GradientDrawable) 
        ldTheme.findDrawableByLayerId(R.id.shape_color);
    shape.setColor(getResources().getColor(BACKGROUND_COLORS[currentColor]));
    BitmapDrawable bmp = (BitmapDrawable) ResourcesCompat.getDrawable(getResources(), 
        BACKGROUND_PATTERNS[currentPattern], null);
    bmp.setAntiAlias(true);
    bmp.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
    ldTheme.setDrawableByLayerId(R.id.shape_pattern, bmp);
}

To use ResourceCompat you must import the Android Support API v4.

currentColor and currentPattern are two static variabiles you read promptly when the user make a selection on a Color or on a Pattern spinner, for example. Remember to save the user choice in SharedPreferences as soon as possible to keep track of it when app is restarted.

BACKGROUND_COLORS and BACKGROUND_PATTERNS are two final static int arrays linking to appropriate resources, for example:

private static final int[] BACKGROUND_COLORS = {
    R.color.blue_dark,
    R.color.green_dark,
    R.color.red_dark
};
private static final int[] BACKGROUND_PATTERNS = {
    R.drawable.background_pattern_0,
    R.drawable.background_pattern_1,
    R.drawable.background_pattern_2
};

Finally, you have to call setBackgroundTheme in the Activity's onCreate method and pass it the RelativeLayout root of the Activity layout, for example in this way:

setBackgroundTheme((RelativeLayout) findViewById(R.id.activity_layout_root));

Following is a background theme example based on the above code. You can download the complete code for this example here.



Have fun!

Tuesday, March 31, 2015

Service Intent Must Be Explicit

Soon after releasing our new secure instant messaging app called SHADO on the Google Play Store we got a discrete number of this exception which caused the app to crash on Android Lollipop and up:

java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.android.vending.licensing
.ILicensingService }

This exception concerns the new way how service intents are started or bound on the Android API 21+. Implicit service intents are no longer supported and indeed raise an exception. In this specific case, which is very common, the problem is with the Google Play Licensing API, which, for some strange reasons, Google didn't update to explicit intents since October 2014, when the bug was reported first.

To quickly solve it, you must go with your own rebuilt version of the API, as I will explain below.

First, when you imported the API from <Android-SDK>\extras\google
\play_licensing\library in Eclipse you should have checked it to make a workspace copy of the library. If not, delete the project on Eclipse but do not delete it on the disk, otherwise you will remove it from the Android-SDK local repository, and import again the API checking for making a workspace copy of it.

Second, find the class LicenseChecker.java under the package com.google.android.vending.licensing.

Third, find the following lines of code:

boolean bindResult = mContext
  .bindService(
    new Intent(
      new String(
        Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),
    this, // ServiceConnection.
    Context.BIND_AUTO_CREATE);

And replace them with the following:

boolean bindResult;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
  bindResult = mContext
    .bindService(
      new Intent(
        new String(
          Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")))
          .setPackage("com.android.vending"),
      this, // ServiceConnection.
      Context.BIND_AUTO_CREATE);
} else {
  bindResult = mContext
    .bindService(
      new Intent(
        new String(
          Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),
      this, // ServiceConnection.
      Context.BIND_AUTO_CREATE);
}

At the end, Lint will alert you that LOLLIPOP is not a valid value for android.os.Build.VERSION_CODES. Don't worry. You must open the Properties window of the API project and change the Project Build Target under the Android tab to Android 5.1 to have it rebuilt without problems.

That's all. If you added the API reference to the Properties of your app project, it will import the rebuilt version of the Google Play Licensing API without pain.

Have fun!