Revealing why emails appear off-centre in Android 4.4 (KitKat)

3
android-kitkat

Recently some members of the Litmus Community noticed that email campaigns in Android 4.4 were no longer being centred to the device width. I was slightly puzzled by this as overall Android 4.4 has a pretty decent email experience, but sure enough I started seeing email content being cut off on the right hand side. Looking at my code I couldn’t see anything out of the ordinary that would cause this. Wanting to find out more, I dived into the source code of the Android 4.4 mail app to get some answers! Find out what I discovered.

The work that lead to this write up involved some technical steps, so rather than make you read all of that first, I’ll provide a summary of the fix and if you want, you can read on how I discovered it! Keep scrolling if you like source code!

We don’t need your margins Android!

The problem is simply margins, the Android 4.4 email client is applying a margin value on all sides (but its not actually visible on the right side), so even before your email message is rendered, its messed with overall viewport already. It applies the side margin to the body element and the top and bottom sides with a wrapping div which your email is placed within:

The email wrapper in Android 4.4 KitKat

<!--  Margin on the body, which is a variable value (more on that below) -->
<body style="margin: 0 ?px;">
    <!-- Wrapping div that applies margin to the top and bottom -->
    <div style="margin: 16px 0; font-size: 80%">
         <!-- Your email template goes here -->
    </div>
</body>

The margin applied on the body is the one that’s causing the most trouble as this is what the off-centre appearance is being caused by. The wrapping div is adding a bit more margin to the top and bottom, which isn’t necessarily that much of a bad thing, though if you’ve already applied padding in your email template its going to create further spacing that you might not want.

Normalise the margins

body { margin:0 !important; }
div[style*="margin: 16px 0"] { margin:0 !important; font-size:100% !important; }

Placing this in the head of your template in a style block should resolve the issue. The body margin is simple enough to override using !important, the div wrapper is a little more complicated because it has no id or class to specifically target, likewise the body tag doesn’t have any specific values either. So in order to target the div wrapper you have to use attribute selectors in a rather crazy way.

Targeting the div wrapper

The only property unique about that div wrapper is the margin property and its value, therefore we use that as the value to target conditionally. It has to be written exactly how its defined in the source code of the email client (note the space), otherwise it won’t evaluate as true conditionally.

The only caveat is if you have a div with the same margin property written with the same values somewhere else in your email template, it will also tag that as well, there really isn’t another way to target it however. You could just normalise the margin on the body and just accept the additional margin on the top and bottom and leave it at that I guess. Though from a boilerplate sense you want to try and remove all pre-defined client specific stuff to get the best foundation for your email campaigns.

There is also a font-size property declared on the wrapper. It doesn’t actually appear to cause any problems, you should be defining your font sizes inline on block elements anyway so you’ll never notice it, however if you want to reset you can.

Technical details

So if your interested on how I discovered all of the information above, look no further! It involved a bit of CSS debugging and reading the code of some specific Java classes that relate to the message view. I’ll admit I can’t actually write Java, but generally I can read code from a programmatic sense, you know being a web developer and all that!

Debugging layouts

First of all it became visually apparent that there was some form of “spacing” issue in the email client, but without being able to actually see the DOM/box model, its becomes harder to discover what the spacing actually is, cue a bit of CSS debug magic:

* { background-color: rgba(255,0,0,.2); }
* * { background-color: rgba(0,255,0,.2); }
* * * { background-color: rgba(0,0,255,.2); }
* * * * { background-color: rgba(255,0,255,.2); }
* * * * * { background-color: rgba(0,255,255,.2); }
* * * * * * { background-color: rgba(255,255,0,.2); }

This essentially turns layouts into different coloured zones so you can see margin/padding as well as other areas represented in block layers. Doing this in Android 4.4 was very telling:

Android 4.4 email client top left side

Pink outer layer: Unexpected Android 4.4 spacing
Green inner layer: Padding on the actual email template (10px)

This spacing is actually present on the top, left and bottom sides, in theory it should be on the right side as well, but I guess its not visible due to the email client message view cutting it off.

Knowing that this spacing is being added, it now became a case of finding out where its being applied, if its margin or padding and can it actually be removed? I took a complete stab in the dark and created the following CSS rule to see what would happen.

div { margin:0 !important; padding:0 !important; }

Sure enough the spacing on the top and bottom disappeared, but the spacing on the left side remained, obviously there is more than just margin/padding on a div at play. In order to see what was happening I dived into the Android 4.4 email app source code.

Tracking down the problem from the source

Because Android is open source, its source code can be viewed via the Android Git repo or on the GitHub mirror. I know I’m looking for the email client code on the KitKat release branches. I decided that I should download an .apk of the Android 4.4 email client and decompile it, which is what I did. After decompiling and throwing in some HTML based keywords to cherry pick and filter Java files of interest, I came across a specific class in the SecureConversationViewController.java file:

public void renderMessage(ConversationMessage conversationmessage)
    {
        mMessage = conversationmessage;
        conversationmessage = mWebView.getSettings();
        boolean flag;
        if (!mMessage.alwaysShowImages)
        {
            flag = true;
        } else
        {
            flag = false;
        }
        conversationmessage.setBlockNetworkImage(flag);
        conversationmessage = new StringBuilder(String.format("<body style=\"margin: 0 %spx;\"><div style=\"margin: 16px 0; font-size: 80%%\">", new Object[] {
            Integer.valueOf(mSideMarginInWebPx)
        }));
        conversationmessage.append(mMessage.getBodyAsHtml());
        conversationmessage.append("</div></body>");
        mWebView.loadDataWithBaseURL(mCallbacks.getBaseUri(), conversationmessage.toString(), "text/html", "utf-8", null);
        conversationmessage = ConversationViewAdapter.newMessageHeaderItem(null, mDateBuilder, mMessage, true, mMessage.alwaysShowImages);
        mMessageHeaderView.unbind();
        mMessageHeaderView.bind(conversationmessage, false);
        if (mMessage.hasAttachments)
        {
            mMessageFooterView.setVisibility(0);
            mMessageFooterView.bind(conversationmessage, mCallbacks.getAccountUri(), false);
        }
    }

Here the wrapper is exposed and it turns out margin is the problem. We have margin being applied on the body and via a wrapping div. What’s interesting is the margin on the body isn’t a fixed value, its actually variable as there is a placeholder (%s) on the margin value for the right and left side (expressed in shorthand). This value is actually calculated by this bit of code also part of the same Java file:

mSideMarginInWebPx = (int)((float)bundle.getDimensionPixelOffset(0x7f09001b) / bundle.getDisplayMetrics().density);

The calculation is an integer offset divided by the device density which will be obviously be variable based on different Android devices and their DPI. For more info on these functions see the Android SDK articles below:

getDimensionPixelOffset
getDisplayMetrics
getDisplayMetrics (density)

Conclusion

So after a bit of layout debugging and reading some source code we now know why Android 4.4 doesn’t like to centre your emails. I hope my CSS fix is useful to your email campaigns going forward!

Share This:

  • jtsurf1

    Amazing! Thank you!

  • jk

    Does this still work in 2017? There are people on the Litmus boards saying it doesn’t work anymore.

    • Gordy

      Why don’t you try and see? 😉 haha

      I just used it today and it works great! 2/2/17