Scrolling Tabs in Android

Posted in Android by Dan on April 18th, 2011

Perhaps this is obvious but it wasn’t immediately clear to me so maybe it’s worth documenting for the benefit of future Google searchers. If you use a TabActivity in an Android application you can probably fit four or maybe five tabs on a standard screen in portrait orientation before things become too cramped. To display more tabs than this and still be able to read the text on each it would be ideal to have a scrolling tab widget like many UI toolkits do. But how can you do this in an Android app?

Reading the patchy Android API documentation is rarely an illuminating experience at the best of times and it doesn’t help in this instance either because the possibility of scrolling tabs is not even mentioned in the entries for TabHost and TabWidget. In the absence of any better ideas you might speculatively attempt to wrap the TabWidget in a ScrollView. Don’t bother, it won’t do anything, ScrollView only provides vertical scrolling.

Android scrollable tabsIf, like me, you thought that ScrollView was the last word in scrolling Android widgets, you may have failed to notice android.widget.HorizontalScrollView. The name fails to conceal what this class is about. It’s the solution to your non-scrolling tab woes.

Wrap your TabWidget with a HorizontalScrollView and the tab set will be able to expand beyond the width of the screen and the user can drag the tabs left and right as required:

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@android:id/tabhost"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent">
  <LinearLayout android:orientation="vertical"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">
    <HorizontalScrollView android:layout_width="fill_parent"
                          android:layout_height="wrap_content"
                          android:fillViewport="true"
                          android:scrollbars="none">
      <TabWidget android:id="@android:id/tabs"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"/>
    </HorizontalScrollView>
    <FrameLayout android:id="@android:id/tabcontent"
                 android:layout_width="fill_parent"
                 android:layout_height="fill_parent" />
  </LinearLayout>
</TabHost>

Set android:fillViewport to true to ensure that the tabs stretch the full width like they would without a scroller and set android:scrollbars to none to avoid having a scrollbar displayed between the tabs and the content. The scrollable tabs will be rendered with faded edges on the right and/or left depending on which direction you can scroll (see image above).

Thoughts on Amazon’s Android App Store

Posted in Android by Dan on March 9th, 2011

I’ve posted some thoughts about Amazon’s upcoming Android Appstore over at the Rectangular Blog. There has been a lot of discussion about Amazon requiring control over app pricing, most of it focusing on the potential negatives for developers. However, one thing that has been overlooked is that if Amazon decides to give your app away for free it might actually be the most lucrative way for your app to be distributed:

The developer will be paid 70% of the purchase price of the app, or 20% of the list price, whichever is greater.

The important thing about this last point is that the developer gets paid even if the app is given away for free. At first glance, having your paid app given away for free and only getting 20% of the list price for each user, rather than 70%, seems like a bad deal but in reality it could be lucrative for the developer. On the Android Market, free apps typically get somewhere in the region of 50 times as many downloads as paid apps. If that ratio translates to Amazon’s store then instead of selling 200 copies at $2 and getting $280 out of it, the same developer could see 10,000 copies given away free and yet earn $4,000.

Not everyone will be a winner, but with Amazon’s other obvious advantages, this new store could potentially be the first alternative marketplace to seriously challenge Google’s Android Market.

ReportNG 1.1.3

Posted in Java by Dan on February 14th, 2011

I’ve just pushed a new version of ReportNG (an HTML reporting plug-in for TestNG) to GitHub. This is a minor enhancement release that incorporates a few patches submitted by users. Thanks to Jeff Weiss, ReportNG should now load custom stylesheets from the classpath as well as the file system and it should have less broken dependencies if you are using it with Maven. By default ReportNG will now also not generate the annoying Velocity log file (if you want the log file you can re-enable it via a system property). This change is thanks to C. Baldwin.

Using ReportNG with Maven

Posted in Java by Dan on January 23rd, 2011

I’m not a Maven user, so I’ve never made any effort to use ReportNG from Maven. Maven users were left to figure it out for themselves. Well, Marcin Zajączkowski has figured it out for himself and has documented what’s involved.

Adjusting the Opacity of an Android Bitmap

Posted in Android by Dan on January 12th, 2011

This took me a lot longer to figure out than it should have done. I’m documenting it here in case it’s useful for somebody else searching for a similar solution.

If you’re displaying the Bitmap in an ImageView, you can probably use the ImageView.setAlpha(int) method. However, if the ImageView is accesed via RemoteViews, as is the case when updating a widget, you won’t be able to invoke this method (if you try to call it via RemoteViews.setInt(int, String, int) you’ll get an exception telling you as much).

After much searching I was unable to find a widget-friendly mechanism for adjusting an ImageView‘s transparency, or any obvious way of manipulating a Bitmap‘s alpha channel.

Eventually I stumbled upon Kevin Dion’s excellent answer to a different but related question on StackOverflow. From this I was able to figure out that I needed to use the DST_IN Porter-Duff mode to modify the alpha channel.

/**
 * @param bitmap The source bitmap.
 * @param opacity a value between 0 (completely transparent) and 255 (completely
 * opaque).
 * @return The opacity-adjusted bitmap.  If the source bitmap is mutable it will be
 * adjusted and returned, otherwise a new bitmap is created.
 */
private Bitmap adjustOpacity(Bitmap bitmap, int opacity)
{
    Bitmap mutableBitmap = bitmap.isMutable()
                           ? bitmap
                           : bitmap.copy(Bitmap.Config.ARGB_8888, true);
    Canvas canvas = new Canvas(mutableBitmap);
    int colour = (opacity & 0xFF) << 24;
    canvas.drawColor(colour, PorterDuff.Mode.DST_IN);
    return mutableBitmap;
}

Addendum (13 February 2013): Balazs Balazs e-mailed me to point out the following:

This might not work if the original bitmap is mutable but is not created with a config Bitmap.Config.ARGB_8888.

For example Samsung Galaxy S2 by default returns a mutable bitmap from BitmapFactory.decodeResource with a config of RGB565.

So it would be safer to leave out the bitmap.isMutable() check and always copy the bitmap.

Analysing Android Application Sales

Posted in Android by Dan on October 27th, 2010

One of the reasons that I’ve chosen to get involved in mobile app development in recent months is because I wanted to experiment with the economics of selling software. The new world of mobile app stores provides an ideal opportunity to do just that. For an eye-watering 30% of your revenue, Google and Apple will provide all of the distribution and payment-processing infrastructure so that you can concentrate on making and marketing software. Given that I’m already experienced with Java and that Google puts fewer barriers in the way of developers than Apple, Android was the obvious platform choice (I have since also started exploring iOS development, perhaps I’ll talk about that in a later post).

I have already related details of my initial forays into Android app development, the results of which were my first two (very basic) paid-for applications. The Beep Test app has sold a decent number of units considering its modest functionality and that it was only written as a “my first app” introduction to Android. We’re talking hundreds rather than thousands but that comfortably beats the “one genuine non-refunded sale” target that I set for it. The second app on the other hand has been, by all financial metrics at least, a total failure.

Try again. Fail again. Fail better.

Fortunately I was not banking on getting rich from these simple applications. They were a means to an end, an Android familiarisation exercise, phase 1 in a longer term plan that has more than one possible successful outcome.

Since then I’ve released a couple more apps, though much of my time has been occupied generating real revenue for Rectangular Software through paid development work. Like the vast majority of Android developers, I’m still awaiting the promised riches of the smartphone revolution. I have no sense of entitlement in this respect. I don’t expect to make a year’s salary from an app that took two weeks to write. A more realistic goal is two weeks’ salary for two weeks’ work, acknowledging that an app will not generate all of its revenue in the first month – it may take a year or more to make a decent return. Of course, like most apps, it may never make a decent return.

If you can make a modest amount from one mobile app, you can make 20x a modest amount from twenty mobile apps. Applying the arithmetic of unspecific numbers, 20x a modest amount equals a reasonable amount. The logic is sound but the approach only scales so far. In the absence of a genuine hit among your 20 apps, substantial wealth may not be forthcoming. Adding more unfocused development effort is unlikely to be sufficient.

At this point I’ve written a page of text and not gotten to my point. My point is that, to stand a chance of financial success, the average mobile developer needs to figure out how to maximise the return on their development effort. Marketing, economics, and all the other non-technical stuff. The goal is to make sure that you leave no money on the table. You’ve written the software, sell it.

You can’t control what you can’t measure

I’m not the person to tell you how to increase your sales (maybe you should start with Andy Brice’s series of articles about promoting your software). I’ve already established that I am a very long way from making a sustainable income from mobile development. You should also know, if you haven’t deduced it already, that I am not an economist. As for marketing, I make that up as I go along. I do however have an extensive collection of quotations ready to deploy as the situation demands. Enter Benjamin Disraeli:

“As a general rule, the most successful man in life is the man who has the best information.”

When optimising computer software better information leads to better decisions and better outcomes. The same is true when optimising for financial performance. It’s why insider trading is so profitable (and so illegal).

If you were selling software from your website rather than somebody else’s app store, you would use Google Analytics or similar to record and digest the information that you needed to optimise sales. There are some partial equivalents in the Android world such as in-app analytics, which only tell you what happens to your software after it’s been bought, but a complete picture will not be possible until Google starts revealing more Android Market data to developers. For now we have to work with what we’ve got. What we’ve got is some fairly detailed information hidden away in Google Checkout. Again, it won’t tell you about the people who didn’t buy your apps but it does give some insight into who is buying.

You can download this information in CSV format. When I looked for a tool to process this data, I didn’t find anything. So I wrote my own. This has helped me to identify some interesting trends.

Sales by country

Perhaps most informative is where I’m making sales. The top five countries are all (predominately) English-speaking countries. This is to be expected since my software is currently only available in English. These five countries account for 94.8% of all my Android revenue. Take a close look at the figures for the UK and France. These are two neighbouring Western European countries with almost identical population levels and similar levels of wealth. Assuming similar levels of Android adoption (which seems reasonable but I don’t have any figures to back up this assumption), it should be possible for me to make as much money from France as from the UK. Instead I make over 50 times as much in the UK. This leads to the obvious hypothesis that I am leaving money on the table by not translating my applications into French. Germany, an even bigger market, is similarly under-represented. This is easily remedied – there are online services that will do the necessary translations for somewhere in the region of $20 – $50 per app for a single language.

The second interesting thing about this data is that the figures for the UK and the US are broadly similar despite the latter having five times the population. In this case the discrepancy cannot be due to language. The data is from the last 3 months or so. In the last few weeks I have seen a sustained jump in US sales and a corresponding dip in UK sales. I can only speculate that this is related to the recent Android Market change to display app prices in the user’s local currency. My figures for October to date are 52.5% for the US and only 25.5% for the UK. Australia is also slightly up.

Hour of the Day

There are several other ways to digest the Google Checkout data. The above chart shows how revenue is distributed throughout the day. Here you can see that my most productive period of the day is between 4pm and 6pm UK time. Filtering the orders by country shows that 4pm – 5pm generates more money from UK users than any other time of the day, which I find surprising, whereas 5pm to 6pm (12pm – 1pm Eastern / 9am – 10am Pacific) is peak time for US users.

The point in the article where thinly-veiled product placement becomes blatant advertisement

Still reading? OK, that tool I mentioned for analysing Google Checkout data is my fourth Android application, Appmonger, which is on the Android Market for the bargain introductory price of £1.99. It automatically fetches the Google Checkout CSV data and generates various charts, which you can share via e-mail and social media. You can find out more about it by following the link or by watching the poorly shot video on the right. Appmonger has already been through a couple of iterations to incorporate feedback from early adopters and I hope to extend the functionality in future releases, so feel free to make suggestions.

Uncommons Maths 1.2.2

Posted in Java by Dan on October 20th, 2010

Yesterday I moved Uncommons Maths (random number generators, probability distributions, combinatorics and a few other numbery type things) from Java.net to GitHub. My other projects have been on GitHub for a while but this one hadn’t been moved mostly because it hasn’t seen any recent activity. I also took this opportunity to push out another release incorporating a couple minor fixes (serialisation and thread safety issues) that have been sitting in the Subversion repository for almost a year.

Download version 1.2.2, or get it from Java.net’s Maven repository.

Android LVL Obfuscation Pitfalls

Posted in Android, Java by Dan on September 13th, 2010

The recently-introduced Android License Verification Library (LVL) survived a disappointingly short time in the wild before somebody figured out how to circumvent its protection. Google responded to this development by stating that it was the lack of obfuscation that had made the crack so straightforward and then followed up with further advice on achieving security through obscurity, effectively acknowledging that there is no robust way of doing licence verification that can’t be bypassed.

Unfortunately, the LVL is pretty much the only option for protecting apps sold via the Android Market (other app stores have their own protection mechanisms). The choice for Android developers is mediocre protection or no protection at all (Google apparently intends to withdraw its previous copy protection mechanism).  You can thwart the most casual Android pirates and make things harder for the determined but that’s about as good as it gets.

Obfuscating LVL-protected Apps with Proguard

Assuming that you choose to add the LVL to your app, you’ll no doubt want to follow Google’s advice and obfuscate your code, probably using Proguard. If you do, you might hit a couple of problems that prevent the LVL from working properly.

I added the LVL source to my application and built it as a single entity but it is also possible to incorporate the LVL as a library project.

Disable Aggressive Overloading

The first problem I encountered was a limitation in the Dalvik VM. Unlike the JVM, it does not (did not?) permit static fields of different types to have the same name.  In normal Java development this situation never arises but Proguard has an option to aggressively overload names to make decompilation more difficult. For Android development this option should not be used otherwise you will get VerifyErrors.

Avoid Renaming the ILicensingService Intent

The checkAccess method of the LVL’s LicenseChecker class binds to the licensing service as follows:

boolean bindResult = mContext.bindService(
    new Intent(ILicensingService.class.getName()),
    this,  // ServiceConnection.
    Context.BIND_AUTO_CREATE);

The problem with calling ILicensingService.class.getName() is that, after obfuscation, the class has a different name and therefore the wrong intent is created. This will show up in the log as an error with the message “Could not bind to service” and the licence check will always fail.

You could fix this by modifying the Proguard configuration to avoid renaming the ILicensingService interface or, even more straightforwardly, you could just modify the code in LicenseChecker and hard-code the appropriate action:

boolean bindResult = mContext.bindService(
    new Intent("com.android.vending.licensing.ILicensingService",
    this,  // ServiceConnection.
    Context.BIND_AUTO_CREATE);

ReportNG 1.1.2

Posted in Java by Dan on August 4th, 2010

ReportNG version 1.1.2 is available for download. ReportNG is an HTML/XML reporting plugin for TestNG.

There are only two small changes in this release.  The first is a fix for an avoidable NullPointerException if the target directory does not exist. The second updates ReportNG to work with this change in TestNG 5.13.1. Thanks to Nalin for providing a patch for this issue.

Building Two Versions of the Same Android App

Posted in Android, Ant by Dan on July 19th, 2010

There are good reasons to want to build two versions of the same Android application. The most common scenario is to produce a free demo/reduced-functionality version of a non-free app. You could achieve this by maintaining two separate source trees but the duplication would make most developers wince.  So the question is how to build two differing apps without duplicating the code and resources?

Building Android Apps with Custom Ant Scripts

The answer is Ant. Maybe it’s also possible with Eclipse. I doubt it but I don’t know and I don’t care to find out. As I mentioned previously, the Android Ant build system is fairly painless to use. If you want to customise it though you’ll have to do a fair bit of digging as it doesn’t appear to be documented anywhere. The first place to look is the <sdk_dir>/platforms/<target_dir>/templates/android_rules.xml file that is used by the build.xml that the Android tools generate for you. This will give you a good idea of the various stages of the build but if you want to experiment with the Android Ant tasks you’ll have to look at the source to see what attributes they support (making sure that the version you are looking at is the same as the version you are using). The AaptExecLoopTask is just a simple wrapper around the aapt tool but the ApkBuilderTask is non-trivial.

The outline of a minimal Ant build for an Android app looks something like this:

  1. Generate the R.java file from the resources using the the exec task and the aapt tool with the -J option.
  2. Compile all of the app source, including the class generated in step 1.
  3. Use the dx tool to convert the Java bytecode into a .dex (Dalvik executable) file.
  4. Use the aapt tool (either via the exec task or via the provided AaptExecLoopTask) to package the resources.
  5. Build the .apk package using the ApkBuilderTask.
  6. If you are building a release package rather than a debug package, sign the .apk file using the signjar task.
  7. Use exec to run the zipalign tool on the .apk file.

More advanced applications will involve additional steps, such as running the aidl tool.

Once you have the outline of your build in place, you can start to customise it using your standard issue Ant prowess.

Different Resources for Different Versions

In my scenario I don’t mind shipping all of the code with both versions of the application. The difference is just the initial Activity that each uses. It doesn’t add much bloat to the package and the code is useless without the full set of resources so there is no danger of somebody hacking the demo version into a fully functioning version. In other words, I don’t need to worry about excluding certain classes from one version or the other.

I do however only want to include a subset of the resources in the demo version.  The way I have achieved this is to replace the default res directory with three directories: res-common, res-demo and res-full. I then use the Ant copy task to construct the res directory from the appropriate pair of directories prior to building.

Different Manifests

Because I want to invoke different activities, I need to define the AndroidManifest.xml differently for each version. My first idea was to just have two different files with different names but I discovered that the Android tools get upset if the file is not called AndroidManifest.xml, even if you explicitly specify which file to use. This just means that I have to copy the chosen manifest so that it has the correct name.

The biggest problem with building two versions from the same tree is resolving conflicts in the application package name. Each Android application must have a unique application package. This is specified in the manifest. You can just use the same package name for both versions but users will run into problems if they ever try to install both versions at the same time. They will likely end up not being able to use either. Despite the drawbacks, this method seems to be widely used judging by the number of apps in the Android Market that warn users to uninstall the demo version first.

So we’ll just change the package name in one of the manifests then? Yeah, that would be nice. The problem is that the package name specified in the manifest is the package into which the R class is generated. If the two versions of your application have their R classes in different packages then common classes will not compile for both versions.

Android 2.1 introduces the --custom-package option for the aapt tool which allows you to over-ride where R.java will be generated. So the idea is to set the application package differently in one of the manifests but then to use this option to make sure that R.java is still in the same place as in the other version. I tried this and it resolved my compile problems but there were resource problems at runtime. I didn’t fully investigate what was wrong because I found another approach that appears to work.

The aapt tool also has the --rename-manifest-package option. Leaving the application package the same in both manifests and then using this option at step 4 above, I was able to generate a working demo version and full version from the same source tree and have them both functional when installed at the same time.

The whole custom Ant build exercise was not nearly as straightforward as I would have liked, mostly due to lack of documentation, but it is at least a working solution to the problem. Another, probably more recommended, way of achieving something similar would be to use library projects.

« Older Posts