Android Phone-Top Programming, Part 2

Creating a simple application debug tool out of an App Widget

Debug widget for Android

Most software programmers my age began their career with a program that looks similar to this:

10 Print "Frank"
20 Goto 10

Unless you and I share a first name, your program probably looked a little different.

And for those not old enough to have heard the term “supply-side” or “trickle-down”, perhaps your first program looked more like this:

public class MyClass
{
    public static void main(String[] args)
    {
        System.out.println("Your name");
    }
}

Either way, we have been “printing” stuff forever.

When it comes to trouble-shooting an application printing things to the screen, or to a file, is not as full-featured as attaching a debugger to a running device, but there is a time and place for printing a simple message to the screen.

When debugging an application during development writing to the LogCat is often your best bet — and every Android programmer should make sure they are comfortable with the LogCat tool. But getting access to a small piece of information from a user who has purchased your application can be a bit trickier because they are not likely to have the Android development tools installed on their computer — if indeed they have one!

So let’s move forward on the premise that it would be useful to see some small pieces of information at runtime.

We can add this functionality to many Android applications, provided they have a user interface. But what if your application is a Service or a Content Provider only? Or what if a hard-to-find problem occurs when you are sleeping during an over-night update process? Or something happens on the phone while you are in the middle of a 200 foot plunge from a bridge with a bungee-cord? You get the idea.

To meet this basic objective of seeing what is happening inside of one of our applications, let’s add some functionality to our AppWidget.

In a previous post we reviewed the basics of an AppWidget. In this installment we will add some incremental functionality by building an AppWidget that can receive messages from other applications and display the message to the user.

Application Description

The AppWidget we are constructing has the capability of displaying two important pieces of information:

  • msg – A textual message of arbitrary composition.
  • sender – Which application or routine sent the message.

The ability to display a “sender” helps to distinguish the source of the message. The application may receive a message either from multiple, distinct applications and/or from distinct methods within a single application. The “sender” field is logically equivalent to the “tag” attribute found in the android.util.Log methods.

Because this widget lets us “see” into an application — sort of — it is named “LM App Eyes”.

For now, the AppWidget only displays the most recent message received. In a future post we will expand this further to show a full history of debugging messages received.

Let’s have a look at the code.

The AppWidget code

The code for this AppWidget looks very similar to our previous example, however we have made some refinements to accommodate the specific functionality we are looking for.

The class has two new variables, one to hold the “sender” and one to hold the “message”:

	private String lastMessage = "Waiting on Message";
	private String lastSender = "";

In the onReceive() method we are now on the look-out for a particular type of Intent, namely an Intent with an action value of “com.msi.linuxmagazine.TRACEME”. If the application encounters this Intent, we need to do some additional processing.

		if (intent.getAction().equals("com.msi.linuxmagazine.TRACEME")) {
			lastMessage = intent.getStringExtra("msg");
			lastSender = intent.getStringExtra("sender");
			Log.i(tag,lastMessage + " from " + lastSender);
			AppWidgetManager appManager = AppWidgetManager.getInstance(context);
			int [] ids = appManager.getAppWidgetIds(new ComponentName(context,LMAppEyes.class));
			onUpdate(context,appManager,ids);
		}

In this code snippet, taken from the onReceive() method, we are extracting two pieces of information which were supplied as “extras” in the Intent. We’ll examine how the Intent is constructed later in this article.

Once we have extracted the relevant data elements, we want the AppWidget instance(s) to update. To accomplish this we get a reference to the AppWidgetManager and obtain a list of the instantiated widgets. From here a call to the onUpdate() method causes the widget to update and show our most recent message.

	public void onUpdate(Context context,AppWidgetManager appWidgetManager,int[] appWidgetIds ) {

		super.onUpdate(context, appWidgetManager, appWidgetIds);
		int count = appWidgetIds.length;
		Log.i(tag,"onUpdate::" + count);
		// we may have multiple instances of this widget ... make sure we hit each one ...
		for (int i=0;i<count;i++) {
			Log.i(tag,"Updating text view ....");
			RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.main);
			if (lastSender.equals(""))
				views.setTextViewText(R.id.senderName, lastSender);
			else
				views.setTextViewText(R.id.senderName, lastSender + " says:");
			views.setTextViewText(R.id.lastMessage, lastMessage);

			appWidgetManager.updateAppWidget(appWidgetIds[i],views);
		}
	}

The Tester code

In order to get a message to our widget, we are going to employ the sendBroadcast() method from another application. For this purpose we’ve got a simple application named “GenericTester” — here is the code:

package com.msi.linuxmagazine.gt;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Button;

public class GenericTester extends Activity {

	private int counter = 0;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final Button btnSendMessage = (Button) findViewById(R.id.SendMessage);
        final EditText etInputText = (EditText) findViewById(R.id.inputText);

        // default some text here
        etInputText.setText("some message");

        btnSendMessage.setOnClickListener(new View.OnClickListener(){
        	public void onClick(View v){
        		Log.i(GenericTester.class.getName(),"Button Clicked! " + counter);
        		try {
        		    // setup the Intent
        			Intent traceIntent = new Intent();
        			traceIntent.setAction("com.msi.linuxmagazine.TRACEME");
        			counter++;
        			// store the message and the sender
        			traceIntent.putExtra("msg", etInputText.getText().toString());
        			traceIntent.putExtra("sender",GenericTester.class.getSimpleName());
        			// shoot this off asynchronously
        			sendBroadcast(traceIntent);
        		} catch (Exception e) {
        			Log.e(GenericTester.class.getName(),"Error occured [" + e.getMessage() + "]");
        		}
        	}
        });
    }
}

In order for the AppWidget to receive this broadcast we must setup an Intent Filter in the AppWidget’s AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.msi.linuxmagazine"
      android:versionCode="1"
      android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="LM App Eyes">
	<receiver android:name=".LMAppEyes">
		<intent-filter>
			<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
		</intent-filter>
		<intent-filter>
			<action android:name="com.msi.linuxmagazine.TRACEME" />
		</intent-filter>

		<meta-data android:name="android.appwidget.provider"
			android:resource="@xml/lmappeyes" />
	</receiver>
	</application>
</manifest>

Demo

To test the application, we first need an instance of the AppWidget on the home screen of the phone.

Insert Widget to Home Screen
Insert Widget to Home Screen

Select the LM App Eyes Widget

Choose LM App Eyes
Choose LM App Eyes

When the widget first loads, it is in the “waiting” stage — i.e., no messages have been received just yet.

Waiting for a message
Waiting for a message

Now, let’s send some data via the testing application.

Simulate a message
Simulate a message

We can see that the GenericTester application has sent a message!

Display a message
Display a message

Further thoughts

At this point, our AppWidget is functional. However, there are a couple of features that would be nice to add:

  • This code updates any and all instances of the widget with the same content. It would be nice to have the choice to create multiple instances of the widget, each “tuned” into a specific sender or source.
  • The widget displays only the most recent message, meaning that we might have missed one or more messages depending on when we most recently observed the widget.

In future articles we will add these pieces of functionality. For now, you can download the code from the Linux Magazine Google Code hosting site.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62