A Quick Note on Assets for Notifications

I have recently spent some time getting all the appropriate drawable assets together for the notifications in an app, including support of Lollipop, Wear, and pre-Lollipop devices. Unfortunately, it took a lot of digging and a bit of trial and error to get everything right, so I figured I would document everything in one place in the hopes that it helps someone else (or, maybe it helps me next time I’m trying to remember how I did all this).

TL; DR

Devices Size Padding Color(s) Density
Small Icon
phone, tablet
24×24 dp
1 dp
#ffffff, transparent
any
Action Icon
phone, tablet
32×32 dp
4 dp
#ffffff, transparent
any
Action Icon
wear
64×64 dp
8 dp
#ffffff, transparent
hdpi
Background
wear
400×400 px or 600×400 px
any
any
nodpi

Small Icon

The small icon is in the lower right corner of a notification on Ice Cream Sandwich through KitKat and gets put inside a circle and brought over to the left on Lollipop. One version of the icon will be equally suited for use on all of those platform versions. The icon should be 24×24 dp with about 1 dp of padding on each side. It should be pure white (#ffffff) on a transparent background only, using the alpha channel as needed for blending. Provide as many different densities (e.g. xhdpi, xxhdpi, etc.) as needed.

Action Icon

Action icons are used with any actions that you may add to your notification, for example the Delete or Archive button that GMail adds to a new message notification. These icons should follow the Action Bar icon sizing parameters: 32×32 dp with 4 dp of padding on each side. These should also be only pure white (#ffffff) on a transparent background, using the alpha channel as needed for blending. These icons will be automatically re-colored on Lollipop devices, no additional assets required. Provide as many different densities (e.g. xhdpi, xxhdpi, etc.) as needed.

For Wear, you will want alternative versions of your action icons or you will end up with your icons being scaled up significantly and looking blurry on Wear devices. The Wear versions should be double the size of the phone and tablet version: 64×64 dp. However, currently you only need to provide hdpi versions for Wear. So, ultimately, you just need an hdpi asset that is 96×96 px. You must also name these Wear versions differently so that they do not collide with your phone/tablet version.

Background

The Background is used only for Wear devices. It should be 400×400 px or 640×400 px, density independent. It can be full color. A 640×400 version will parallax more than a 400×400 version, which may not parallax at all, depending on the device being used. Since they are density independent, these assets should be placed in your drawable-nodpi directory.

Working with Baselines in RelativeLayout

On initial glance, the layout_alignBaseline attribute that can be added to children of a RelativeLayout seems like a handy little tool that is used just as easily as the more common layout_alignTop and layout_alignBottom. The good news is that if you’ve only got a couple of views, it works great! The not so good news is that if your layout gets just a bit more complicated, things fall apart in a hurry.

Let’s say you have a nice design that you need to implement:

The desired result.

I’m not here to argue about what layout is the absolute best for this situation. But, for argument’s sake, let’s say you decide you can easily build this with a RelativeLayout with three children. Nice, flat hierarchy. You might end up with something like this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333"
    android:padding="16dp" >

    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/mi"
        android:textColor="#f3f3f3"
        android:textSize="96sp" />

    <TextView
        android:id="@+id/country"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@id/code"
        android:layout_marginLeft="16dp"
        android:layout_toRightOf="@id/code"
        android:text="@string/usa"
        android:textColor="#f3f3f3"
        android:textSize="22sp" />

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/country"
        android:layout_above="@id/country"
        android:text="@string/michigan"
        android:textColor="#f3f3f3"
        android:textSize="22sp" />

</RelativeLayout>

So, “United States of America” lines its baseline up with the baseline of “MI” and then “Michigan” sits on top. Perfect. But then you look at the rendered output of that layout:

RelativeLayout fails challenge.

RelativeLayout fails challenge.

So, what went wrong? Did you mess up one of the layout attributes? Turns out it is actually a limitation of RelativeLayout. If you dig into the source (line 519), you will notice that baseline alignment is performed after all other vertical alignment has already been performed. In other words, “Michigan” is lined up above “United States of America” when it is still in its default position at the top of the RelativeLayout container. Then, later on, “United States of America” is re-aligned with the baseline of “MI,” leaving “Michigan” just out of view, above the top of the RelativeLayout.

One potential solution to this problem is to wrap the two smaller TextViews in a LinearLayout that can then be properly baseline aligned with the larger TextView on the left. For example:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333"
    android:padding="16dp" >

    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/mi"
        android:textColor="#f3f3f3"
        android:textSize="96sp" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@id/code"
        android:layout_marginLeft="16dp"
        android:layout_toRightOf="@id/code"
        android:baselineAlignedChildIndex="1"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/michigan"
            android:textColor="#f3f3f3"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/country"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/usa"
            android:textColor="#f3f3f3"
            android:textSize="22sp" />
    </LinearLayout>

</RelativeLayout>

Voila! We get the desired result at the expense of an extra level in the View hierarchy. Oh, and did you notice that neat, relatively rarely used attribute on the LinearLayout? In case you missed it, baselineAlignedChildIndex is a cool little trick to tell the LinearLayout which of its children should be considered when lining up the entire LinearLayout with another View. Very handy!

A Quick Note on SMS Behavior

You probably already know that when sending an SMS you have a relatively small number of characters available for each message, somewhere in the 140-160 character range. What you may not know (particularly if English is your native language) is that the character limit can be cut down to just 70 if you include Unicode characters in your message.

This problem has bitten us in two different apps over the past year or so. A sharing message, carefully crafted to fit well within a 140-character limit for simple sharing via SMS (or Twitter, or some other medium) while developing in English was being chopped into two or even three separate messages in other languages.

ascii_text_message

A simple ASCII-only message of about 140 characters.

Let’s just ignore additional issues that can arise from the fact that many carriers have decided to handle text messages in their own, sometimes proprietary, way and the additional issues that come from those carriers trying to interoperate with each other and instead focus on the SMS standard which seems to apply in lots of situations. SMS was designed to have a data payload of 140 octets (140 8-bit bytes). So, depending on what character set you are using, you can squeeze 160 characters (7-bit ASCII), 140 characters (8-bit GSM), or 70 characters (16-bit Unicode) into that payload. Pretty quickly, we can see why we might get truncated messages in languages that use characters outside the 7-bit ASCII alphabet. So, we might expect major issues in Russian or Japanese, but maybe not so much in French or Italian.

unicode_text_message

Just one Unicode character completely changes the result.

But even text that appears to be harmless 7-bit ASCII at first glance can cause issues. Just one Unicode character in a message will cause the entire message to be sent in Unicode, slicing the character count in half. And that single Unicode character doesn’t have to be Russian, Japanese, Chinese, Greek, Tagalog or some other obvious standout. Something as simple as a curly apostrophe (’) instead of a straight apostrophe (‘) can break your messages and lead to much confusion and hair pulling. Even more difficult to diagnose is the non-breaking space (you know, good old &nbsp;). We have received translations with non-breaking space Unicode characters hiding innocently alongside normal space characters, basically impossible to detect without a specific script checking for that character.

So, the moral of the story is this: if you have strings that you are getting localized and that you know are supposed to be used in an SMS, make sure you know exactly which characters are going into the message so that you can make proper adjustments for character counts.

doublemeasure_relative_layout

Aligning Views Based on the Center of a RelativeLayout

In an effort to keep my View hierarchies simple, I have sometimes found myself using a little trick to layout Views on one half of a RelativeLayout container. The trick is to use a zero-size, centered View and then align your other Views based on that. Something as simple as this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <View
        android:id="@+id/ctr"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_centerInParent="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/ctr"
        android:text="I am on the right half of the container!" />
</RelativeLayout>

Cool! However, there is a small caveat to this. If the RelativeLayout container has an inexact size (e.g. it has defined height/width as wrap_content), this layout trick will not work as expected. The TextView would end up aligned against the left edge of the container instead of in the right half of the container.

If we dig into the source code for RelativeLayout, we can find the cause of this problem in the onMeasure method. In a first pass through the views in the RelativeLayout, centered views are placed in the center of the container. However, if the RelativeLayout has a width/height that is wrap_content, centered views are placed along the left edge of the container. This is because we don’t yet know the width/height of the RelativeLayout container as we must wait for all of the child views to be measured. At the very end of the onMeasure method, child views that have requested to be centered are corrected since we now know the height/width of the RelativeLayout container. Unfortunately, child views that depend on the positioning of these centered views are not further adjusted. So, the child views that are dependent upon the positioning of a centered view end up being positioned based upon the centered view’s location when it was originally aligned against the left edge of the container.

Let’s see how this might look:

The mess on the left is caused by overlapping TextViews that were supposed to have been aligned (and not overlapped) based on a centered View.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="16dp"
    android:background="#333"
    android:padding="@dimen/activity_horizontal_margin" >

    <TextView
        android:id="@+id/house"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:includeFontPadding="false"
        android:text="@string/house_name"
        android:textColor="#d4d5d6"
        android:textSize="43sp" />

    <TextView
        android:id="@+id/castle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/house"
        android:layout_marginBottom="16dp"
        android:text="@string/castle"
        android:textColor="#727887"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/heir"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/castle"
        android:text="@string/heir1"
        android:textAllCaps="true"
        android:textColor="#727887"
        android:textSize="12sp" />

    <TextView
        android:id="@+id/heir_detail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/heir"
        android:layout_below="@id/heir"
        android:drawablePadding="4dp"
        android:text="@string/heir1_title"
        android:textColor="#d4d5d6"
        android:textSize="14sp"
        android:visibility="visible" />

    <View
        android:id="@+id/ctr"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_centerInParent="true" />

    <TextView
        android:id="@+id/heir2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@id/heir"
        android:layout_toRightOf="@id/ctr"
        android:text="@string/heir2"
        android:textAllCaps="true"
        android:textColor="#727887"
        android:textSize="12sp" />

    <TextView
        android:id="@+id/heir2_detail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/heir2"
        android:layout_below="@id/heir2"
        android:text="@string/heir2_title"
        android:textColor="#d4d5d6"
        android:textSize="14sp" />

</RelativeLayout>

There are several ways to fix this and get the output we really want, none of which are necessarily ideal.

The easy fix, if you can do it, is to simply change the wrap_content height/width to match_parent or a specific size (e.g. 100dp). Obviously, that will not always give you appropriate results.

A second option would be to change to some other container, like a LinearLayout. This will likely increase the depth of your view hierarchy, making your layout heavier and the layout pass take longer.

Finally, a simple extended version of RelativeLayout with an altered onMeasure method can give you the desired results at the expense of a second measure pass. By calling the super onMeasure method a second time, passing exact measurements, we can leverage the existing RelativeLayout code to do the hard work for us and give us the results we desire:

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
		if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY
				|| MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
			super.onMeasure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
					MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
		}
	}

Full source for this “DoubleMeasureRelativeLayout” is available.

By simply swapping out the RelativeLayout with this DoubleMeasureRelativeLayout, the desired output is easily obtained:

doublemeasure_relative_layout

With the DoubleMeasurRelativeLayout, the TextViews are lined up in the expected location and the result is much better.

Animations in Android at MobiDevDay 2013

I presented a modified version of my Animations in Android talk today at MobiDevDay 2013 in Detroit. This version was narrowed down to discussing only Animators.

You can grab the slides from this version of the talk if you like.

Overall, it was a good day. It was great to see so many folks from the Detroit area motivated to spend a beautiful Saturday learning more about mobile development. Lots of good presentations all around and an excellent keynote address by Mike Lee. Hope to see this event happen again next year.

Adding Click Listeners to Views in Adapters

Despite using Adapters to achieve complex results on a nearly daily basis, it didn’t really hit home with me just how complex and powerful they really are until I attended an “Intro to Adapters” presentation at GDG-A3 this past week.  Joe Blough did a nice job showing how to get started with Adapters, you can get his slides and code if you wish to take a look.  One particular point started a bit of discussion, and that was dealing with click listeners attached to views returned by the Adapter. Below are three approaches to solving this problem that came from that discussion. I’m sure there are others, and the solution that works for you will depend on your use case.

So, for simplicity’s sake, let’s consider a simple ListView with an Adapter that has one type of View.  This view is pretty simple, containing just a TextView and a Button:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="@string/Click_Me" />

</RelativeLayout>

Now, in your Adapter, you will want to hook up a click listener to the button and perform an action when the user clicks. More than likely, what happens when the user clicks the button is going to depend upon which button is clicked. For example, perhaps the “Click Me” button has the always useful task of informing the user of the index of the row in which the clicked button resides.

Simple (Anti-)Pattern

A simple solution might result in a getView() method that looks something like this:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_simple, parent, false);
        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.text);
        holder.button = (Button) convertView.findViewById(R.id.button);
        convertView.setTag(holder);
    }
    else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(getContext(), "Row " + position + " was clicked!", Toast.LENGTH_SHORT).show();
        }
    });

    return convertView;
}

This code will certainly work. But consider for a moment the downsides. Every single time that getView() is called, a brand new OnClickListener object is created and the old one thrown away. This is potentially adding a lot of garbage to the heap. There has to be a better way.

(Before we move on, let’s consider a couple of things that are going right in this example: re-using the convertView and using the ViewHolder pattern. ListView is highly optimized for buttery-smooth scrolling. One of the optimizations is that it re-uses views so that they don’t have to be re-created or re-inflated hundreds or thousands of times, eating up extra memory and wasting CPU cycles. But this optimization relies on you properly making use of the convertView in your own implementation of getView(). The ViewHolder pattern is a further optimization that lets you avoid having to call the relatively expensive findViewById() method over and over again. But… you already knew all this anyway, right?)

A Better Way — A single OnClickListener

An easy change is to take advantage of the fact that the onClick() callback in the OnClickListener gets passed a reference to the View that was clicked, giving us an opportunity to pass some information into the listener. So rather than hundreds of unique and transient click listeners, we can instead have one single click listener that knows how to find the data it needs. In order to accomplish this task, we’ll do something similar to the ViewHolder pattern and take advantage of the tag on the View that is being clicked:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_simple, parent, false);
        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.text);
        holder.button = (Button) convertView.findViewById(R.id.button);
        holder.button.setOnClickListener(mMyButtonClickListener);
        convertView.setTag(holder);
    }
    else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.button.setTag(position);

    return convertView;
}

private View.OnClickListener mMyButtonClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        int position = (Integer) v.getTag();
        Toast.makeText(getContext(), "Row " + position + " was clicked!", Toast.LENGTH_SHORT).show();
    }
}

Notice how in this version, we have just a single click listener object (mMyButtonClickListener) that is connected to every instance of the button. We just tag a little bit of extra data on the button itself so that the click listener can figure out what to do.

An Alternate Better Way — A Click Listener in the ViewHolder

An alternate solution that Joe came up with and that I had not considered previously would be to put the click listener itself within the ViewHolder. This has the advantage that the click listener has easy access to the other View(s) within the row, making it very easy to change the state of those Views. For example, maybe you want to change the color of the text when the button is pressed:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_simple, parent, false);
        TextView tv = (TextView) convertView.findViewById(R.id.text);
        Button btn = (Button) convertView.findViewById(R.id.button);
        holder = new ViewHolder(tv, btn);
        convertView.setTag(holder);
    }
    else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.button.setTag(position);

    return convertView;
}

private static final int[] COLORS = new int[] { 0xffa00000, 0xff00a000, 0xff0000a0 };
private int mLastColor = 0;

private static class ViewHolder {
    public TextView text;
    public Button button;

    public ViewHolder(TextView tv, Button btn) {
        text = tv;
        button = btn;
        button.setOnClickListener(mMyLocalClickListener);
    }

    private View.OnClickListener mMyLocalClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            text.setColor(COLORS[mLastColor]);
            mLastColor = (mLastColor + 1) % COLORS.length;
        }
    }
}

So, there you have it, three ways to do the same thing, each with its own plusses and minuses. As I mentioned earlier, the solution that works best for you will be highly dependent upon what you are trying to accomplish. But hopefully now you’ve got a few more tools in your toolbox to help you the next time you run into this type of issue.

Animations in Android at Michigan Google Developers DevFest 2013

I spoke at the Michigan Google Developers DevFest 2013 today in downtown Detroit.  It was a great day with lots of good presentations and was well attended.  The presentation that I gave was an adapted version of the Animations in Android talk that I had given before, with a little less fluff and a little more detailed code.

You can grab the slides and also the code example for the Manual Animation technique presented.

Did you miss my talk?  No worries, I’ll be presenting again at MobiDevDay on May 4th.  That talk will be a more-focused version that digs deeper into Animators and leaves out pretty much everything else.  See you there!

Avoiding File Problems on Multi-User Devices

One of the many new features added in Android 4.2 was support for multiple users on a single device. This is a pretty cool feature from a user’s perspective. And from a developer’s perspective, the great news is that “there’s nothing different you need to do in order for your app to work properly with multiple users on a single device.

Well, not quite. If you keep reading that document, you will notice there are a couple of caveats. A very important one is to make sure that you are using the correct path to the user’s files. I have seen many examples around the web and elsewhere (including my own code) that uses hard-coded paths to access files in the private storage area. The code often looks something like this:

String filename = "/data/data/com.example.mycoolapp/databases/cool.db";

This code has worked great for a very long time, but if you want to support multiple users on a single device, this code will fail. Why? Well, the implementation of multiple user support separates the private files of each user from those of all other users on that device. This is a good thing as it means that users don’t have to worry about their files being exposed to those who shouldn’t see them. In order to do this, Android gives each user a unique space in the file system. So, while /data/data/com.example.mycoolapp might work for the administrator user on the device, another user attempting to access /data/data/com.example.mycoolapp will receive an error indicating that he or she does not have privileges to that location. That location is, in fact, owned by the administrative user and not the second user.

So, how to solve the problem? Google tells you right in the Android 4.2 APIs document, but ultimately the change is very simple. You just need to make sure that you have a Context reference available. To fix my example above, I would simply change that line as follows:

String filename = context.getFilesDir().getAbsolutePath() + "/databases/cool.db";

An easy, one-line code change that will save you the headaches and confusion that would have come when your users started reporting strange errors and the inability to use your app on their Android 4.2 tablets.

Animations in Android

On January 31, 2013, I gave an “Animations in Android” talk at the Google Developer Group Ann Arbor Android (GDG-A3).  I covered three ways to animate in Android: “Manual” where you do all the heavy lifting in the draw() method of your custom View or Drawable; using the Animation classes found in android.view.animation; and the Animator classes found in android.animation.  The major focus was on the Animator classes, including the use of NineOldAndroids to support versions prior to Honeycomb.

The slides for the presentation are available here.