Diagnostics for Android apps

This entry has been cross-posted in Codolution’s blog.

Let’s assume you have got a great Android app, how do you go about diagnosing problems users have with it? One big problem with mobile apps is the fact that it is gone out of your hands and you can’t see what’s happening when things go wrong.

broken-android

Ideally, you would ask the user to send you some logs, but it is not as easy as it should be. Some time ago you could install an app that would read logs for you, but in a fairly recent versions of Android they can only see their own logs which is not very useful. You could ask users to root their device, or install developer toolkit and use ADB. Good luck trying. This is when a simple diagnostic tools could be helpful, if it allowed users to send your application’s logs back to you. Please meet diagnostic-tools.

Here you have a jar file you should to your project, which will give you a LogCollector. This class can read application logs and send them in an email with some information about the runtime environment. It actually doesn’t send email, it prepares it and lets user decide how to send it. Give it a try by installing the demo app or building it from source code.

2015-06-18 12.43.03

What you need to do is (refer to the demo app if something is not clear):

Add this jar file to your Android project. You can also build it yourself from source code. However, the demo app defines a project dependency instead.

Add a provider as shown below to your application. This is needed by FileProvider to share the log file with the email sending app as an attachment.

Add the XML file mentioned in the configuration above, which specifies the path for the files exported by this provider.

And finally, when you want to send logs, call the log collector.

mLogCollector.collect();
mLogCollector.sendLogs("info@codolutions.com", "Error Log", "Hey there, here is your log!",
"Some extra information, just because you wanted.",
"com.codolutions.diagnostics.demo.fileprovider");

Just one more thing that could be useful: If you are lucky enough to spot the issue and fix it, you go ahead and upload a new version, but there is no guarantee users will opt-in to your fix. One thing that can help with that is a kill switch. Using the kill switch you can remotely disable particular versions of your app and force users to upgrade to the latest. I’d recommend you to have at least a very basic kill switch from day 1, you never know when you are going to use it.

Credits: I have borrowed some code from https://code.google.com/p/android-log-collector/.

Catch UP

This entry has been cross-posted in Codolution’s blog.

Background

catchup-screenshotI have a couple of friends in Facebook, Google+, Twitter, Foursquare, etc. I try to catch up with them once in a while, but I always miss important stuff and life events because I don’t check frequently enough. If this describes you as a social network user, you will probably want to check Catch UP out. Catch UP aims to bring the most important stuff from different networks in a single place, allowing you to make sure you don’t miss updates from your family members or best friends. There are a few similar applications out there, but we thought we can give it a try and learn more about social networks along the way.

 

How it works
  • You connect your social accounts, via OAuth (no password entered in the app)
  • You select a list of people you would like to filter your feed with
  • In separate dashboards you will see your own posts, and updates coming from your list
  • You will see the hot items, those who attracted a lot of attention, in a separate dashboard
  • You can see how connections in your list have been engaged with your own or each others’ posts
  • You can get notifications about new updates from your list
Considerations
  • We plan to provide this for free, for all the users. The base functionality will be free of advertisement and free of charge, forever.
  • We will provide some extra features for a price, expecting a percentage of users to take them up, covering some of the development and running costs.
  • In order to provide an acceptable experience for everyone, we might impose some limits in the app, since we have some infrastructure constraints and usage quota imposed by networks we connect to. We try our best to give users a better experience as we continue working on this.
  • It allows multiple accounts of the same network linked in the application. One of the problems of using multiple accounts is logging in and out all the time. Once these accounts are linked, they can be used at the same time with no switching required.
  • It uses OAuth so that users authorize the access to their accounts without compromising their security (revealing their password).
  • We have made a decision to use browser-based OAuth flow, so that users can make sure they are securely connected to their social network website using their trusted browser. It eliminates the possibility of phishing attacks like this one.
  • It is not aiming to replace client apps for the social networks it connects to. It won’t end up implementing all the features of each social network either. All the activities displayed in the app link back to the original update and open in the native client of that network if one is already installed, which can be used for anything that’s missing here.
Final thoughts

catchup-logo

Catch UP is currently available on Android and Windows Phone only. iOS and web client is being considered, but nothing is planned as of now.

Feel free to give us your feedback in order to make this a better product. What do you care about most? What do you want to see added to this next?

You might ask, what is about the logo? It talks about the infinite cycle that you have to break somehow to get a living 😉

Migration to Google Play In-app Billing

So your Android app has a free version and also a paid/pro version with some extra features and the whole thing isn’t easy to maintain, and you heard about this new (now pretty old) billing API version 3 (which is a whole lot better than version 2) and you think maybe I can use this to bring everything in a single app for good. Now what? I’m going to talk you through my requirements and concerns in a similar situation, and tell you what I have done about them. I had a very limited time to spend and I’m sure there are better ways doing this, that’s what the comment box down the page is for 😉

Everything should be in a single app

I had to stop users buying the pro app and communicate to the existing users that it has been deprecated. I did that by updating the Play Store listing and increasing the price so that people don’t buy it. Then I merged the common library module with the free version of the app and integrated with in-app billing so that users of free app can get pro features via in-app purchases. Now is time to let the existing users know they can migrate to the new app. There are a few things to take care of, for a smooth transition.

Current users of pro app shouldn’t need to pay again

The existence of the pro app can work as a proof of purchase and unlock the extra features in the new app, just like going through in-app purchase. I can rename the pro app to unlocker at this stage. It is important that users keep their unlocker app on their phone, until I come up with a more elegant way of dealing with this in the billing API. In order to verify that users have the unlocker, all I need to do is making sure the app exists and is signed with the same certificate that my new app is. This is how to do it:

    public boolean isUnlockerAppPresent()
    {
        try
        {
            PackageInfo info = context.getPackageManager().getPackageInfo("com.example.unlocker",
                    PackageManager.GET_META_DATA);
            return context.getPackageManager().checkSignatures("com.example.app",
                    "com.example.unlocker") == PackageManager.SIGNATURE_MATCH;
        }
        catch (NameNotFoundException e)
        {
            return false;
        }
    }

Users should be able to import settings from the old app

According to Android docs if my apps are signed with the same signature they can share a user ID and as a result run in the same process or access their data (share preferences for example). All I need to do is adding two tags to the manifest of both apps:

<manifest android:sharedUserId="com.example.shared.userid" android:sharedUserLabel="@string/shared_user_id" ... package="com.example.unlocker" />
<manifest android:sharedUserId="com.example.shared.userid" android:sharedUserLabel="@string/shared_user_id" ... package="com.example.app" />

Please note that sharedUserId has to be in a package-like structure, i.e. dot separated, and sharedUserLabel has to be a string resource not a hardcoded value. Once all this is in place in can do this in my app:

Context context = createPackageContext("com.example.unlocker", 0);
SharedPreferences unlockerAppPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// Read data from shared preferences of the old app.

Looks pretty easy and straightforward, right? WRONG!

You should’ve known this before

The problem is, the apps can’t get updated with this changes because I have changed the user id the apps will run as. I should’ve thought about this in day one! Now I have to implement a “real and proper” messaging mechanism between the apps.

What I came up with was a pretty simple content provider on top of shared preferences protected by the signature check, meaning only apps with the matching signature will be able to access it. This is how it is defined it in the manifest:

<provider android:name="com.example.unlocker.PreferencesContentProvider" android:authorities="com.example.unlocker.preferences" android:exported="true" android:protectionLevel="signature"/>

Here is the code for the content provider, of course simplified to fit the blog post:

public class PreferencesContentProvider extends ContentProvider
{
    @Override
    public boolean onCreate()
    {
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
    {
        String[] columns = new String[] { "NAME", "VALUE" };
        if (projection == null)
            projection = new String[] { "NAME", "VALUE" };
        if (!Arrays.asList(columns).containsAll(Arrays.asList(projection)))
            throw new IllegalArgumentException("Unknown columns requested.");

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        MatrixCursor matrixCursor = new MatrixCursor(columns);
        matrixCursor.addRow(new Object[] { "TEST_DATE", prefs.getString("TEST_DATE") });
        // ...
        return matrixCursor;
    }

    @Override
    public String getType(Uri uri)
    {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values)
    {
        throw new UnsupportedOperationException("Cannot insert into this content provider.");
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs)
    {
        throw new UnsupportedOperationException("Cannot delete from this content provider.");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
    {
        throw new UnsupportedOperationException("Cannot update this content provider.");
    }
}

This is how I will read data from the old app, again simplified:

    ContentResolver cr = getContentResolver();
    Cursor cur = cr.query(Uri.parse("content://com.example.unlocker.preferences/preferences"),
            null, null, null, null);
    int count = cur.getCount();
    cur.moveToFirst();
    String testData = null;
    for (int i = 0; i < count; i++)
    {
        String key = cur.getString(0);
        String value = cur.getString(1);
        if (key.equals("TEST_DATA"))
            testData = value;
        cur.moveToNext();
    }

What’s left?

There isn’t much left. It is all about making sure the user is properly guided through the migration process in both apps. I need to make sure the latest unlocker (with the content provider) is installed before trying to import settings. I also need to implement the update method for the content provider to be able to flag transition complete. I might need to do other things, but at least I’ve got a base in place.

Lesson learned

There are some parts of your application that can not change. User ID is also one of them.

Android app to "Google" images

Some time ago I wrote a simple Android app to search for images using Google Search API, I thought it is worth sharing in case someone else needs to do the same. I had to create a custom search engine (https://www.google.com/cse/) and create an API project (https://code.google.com/apis/console) to get set up. The rest is ordinary stuff you all know.

There is one thing to mention though: Using Java search API you have to specify the web sites you want to search in. You can’t search the whole Internet using this API. You could do this using image search API, but since it is deprecated it is not worth investing in.

The complete working code along with a pre-built .apk is also provided. I have implemented other interesting features too like search suggestions and infinite loading, which will become handy down for you the road.

Simply clone https://github.com/normanatashbar/imagesearch.git or download by clicking here. Please remember to change the search engine ID and API key with your own if you are using this code as a base.

A kind-of base Android app

I have just created a simple Android app that can be used as a starting point. This app uses ActionBarSherlock (ABS) and RoboGuice libraries. ABS gives your application new features (like action bar and fragments) while still supporting old API levels / devices. RoboGuice on the other hand helps you reduce the cluter of inter-dependencies among your application’s components. It has got a simple fragment based list-details view which takes care of different screen sizes by displaying a separated or combined view depending on the available screen size (again, a relatively new feature). It also provides a basic SQLite database and a content provider on top of it.

Simply fork/clone the code from https://github.com/normanatashbar/basedroid.git and start using it.