2

Extending Android’s Chronometer to get Elapsed Time

In revamping one of my Games on the Android Market, I wanted to add an onscreen timer. The built-in Chronometer class does 95% of what I needed, but getting that extra 5% was annoying enough that I decided to post my solution.

The OOTB Chronometer keeps tracks of time, but it can’t tell you the number of seconds elapsed since it started. This is easily remedied, as I discovered from the fine people on StackOverflow. However, simply getting the elapsed time is not enough because the Chronometer will reset itself everytime the app is Paused or Killed. The two cases are quite different. When the app is Paused, it is still alive and may resume (ex. an incoming call). When the app is Killed, it needs to store the elapsed time somewhere so it can be recovered when the app is Restored (ex. orientation change, or b/c the OS needs memory).

So here is a stripped down solution that seems to do the job:

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.Menu;
import android.widget.Chronometer;
 
public class CustomChronometerActivity extends Activity {
	private static final String TAG = "CustomChronometerActivity";
	private static final String MS_ELAPSED = "com.etc.etc.MsElapsed";
 
	private MyChronometer chrono;
 
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        //start the chronometer
        chrono = new MyChronometer(this);
        chrono.start();
        setContentView(chrono);
    }
 
	@Override
	protected void onPause() {
		Log.i(TAG, "onPause()");
		super.onPause();
		chrono.stop();
	}
 
	@Override
	protected void onResume() {
		Log.i(TAG, "onResume()");
		super.onResume();
		chrono.start();
	}
 
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		Log.i(TAG, "onSaveInstanceState()");
		chrono.stop();
		outState.putInt(MS_ELAPSED, chrono.getMsElapsed());
	}
 
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
		Log.i(TAG, "onRestoreInstanceState()");
		int ms = savedInstanceState.getInt(MS_ELAPSED);
		chrono.setMsElapsed(ms);
		chrono.start();
	}
 
	class MyChronometer extends Chronometer {
 
		public int msElapsed;
		public boolean isRunning = false;
 
		public MyChronometer(Context context) {
			super(context);
		}
 
		public int getMsElapsed() {
			return msElapsed;
		}
 
		public void setMsElapsed(int ms) {
			setBase(getBase() - ms);
			msElapsed  = ms;
		}
 
		@Override
		public void start() {
			super.start();
			setBase(SystemClock.elapsedRealtime() - msElapsed);
			isRunning = true;
		}
 
		@Override
		public void stop() {
			super.stop();
			if(isRunning) {
				msElapsed = (int)(SystemClock.elapsedRealtime() - this.getBase());
			}
			isRunning = false;
		}
	}
}

Certainly there are other ways to do this – either by implementing your own timer using Threads or Handlers, or perhaps by implementing an OnChronometerTickListener and subscribing to events. I rather like this solution, but if you’re the clever sort and see some situation where this doesn’t work or some reason why it might be a bad idea, please let me know.

3

Restyling Android ImageButtons

Discovered an interesting thing about Android today: the fairly ugly default ImageButton can be replaced with a custom drawable while retaining the very user-friendly highlighting behavior.

I was trying to create three buttons for an application I am working on: zoom-in, zoom-out, and next. For development purposes, I had just been using regular (text-only) Buttons, but for the final product I wanted something a little snazzier. While it is certainly possible to create your own button functionality, I’m guessing it is wise to stick to the defaults when possible – enter the ImageButton class. It works the same as a Button, but shows an image instead of text. The problem is, the default look was gonna be kinda ugly in my application.

My first thought was – well, maybe I can just set the background to black. That works, but you lose the highlighting that makes the ImageButton nice in the first place. So, a few searches later, here is what you do – this example is for a two-state (“normal” and “pressed”) button called “Next”:

1. Create two image files (one for the “normal” state and one for the “highlighted” state

2. Create an XML Drawable (that is, a .xml file in your /resources/drawable/ directory

3. Paste the following XML:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/btn_next_normal" />
<item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/btn_next_normal" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_next_pressed" />
<item android:state_focused="true" android:state_enabled="true"
android:drawable="@drawable/btn_next_pressed" />
<item android:state_enabled="true"
android:drawable="@drawable/btn_next_normal" />
<item android:state_focused="true"
android:drawable="@drawable/btn_next_normal" />
<item android:drawable="@drawable/btn_next_normal" />
</selector>

4. In your Java code (should also work in XML), use it just like any other Drawable:

ImageButton next = new ImageButton(this);
  next.setImageResource(R.drawable.btn_next);
  next.setScaleType(ScaleType.CENTER_INSIDE);
  next.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    //do stuff here
  }
});

Note that this button only shows two images “normal” and “pressed” because that seems sufficient for my situation, but the XML allows 7 views depending on the state of the button.

For more information, see:

http://stackoverflow.com/questions/606694/android-different-image-for-rollover-on-imagebutton

http://groups.google.com/group/android-developers/msg/ef6014f46c35a824

0

Bridging Android’s ListPreference and Database

Long time, no post.. I’ve been waay too busy programming Android for the last few weeks to get anything useful done. As mentioned a few days ago, I’m getting deluged by weird comment spam, so comments are turned off for the moment – sorry.

Today I was trying to mix Android’s two storage mechanisms. The application needs to store a small number of settings which a user can create/update/delete -  a database. Additionally, one of these settings is considered to be “selected” – probably SharedPreferences, though one could argue for the database here too. My first intuition was to specialize a subclass of ListPreference to override the getEntries() and getEntryValues() methods – nope. The next idea was to call setEntries(CharSequence[]) and setEntryValues(CharSequence[]) in the constuctor – that seems to work fine. However, since I was reading from a database, I wanted the Cursor to be managed by an activity. Since I was subclassing ListPreference, that wasn’t gonna work. Ultimately, I settled on subclassing PreferenceActivity and building the menus in code instead of XML. Adapting the sample database code from Notepadv3, here is a custom Preferences menu:

public class EditPreferences extends PreferenceActivity {
 
public static final String SELECTED_TARGET_KEY = "SelectedTargetKey";
public static final String NO_SELECTION  = "0";
 
private TargetDbAdapter mDbHelper;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setPreferenceScreen(createPreferenceHierarchy());
}
 
private PreferenceScreen createPreferenceHierarchy() {
PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
PreferenceCategory dialogBasedPrefCat = new PreferenceCategory(this);
dialogBasedPrefCat.setTitle(R.string.pref_cat_title);
root.addPreference(dialogBasedPrefCat);
 
//builds list from DB
mDbHelper = new TargetDbAdapter(this);
mDbHelper.open();
Cursor c = mDbHelper.fetchAllNotes();
startManagingCursor(c);
 
int count = c.getCount();
CharSequence[] entries = new CharSequence[count];
CharSequence[] entryValues = new CharSequence[count];
 
c.moveToFirst();
for(int i=0; i&lt;count; i++) {
entries[i] = c.getString(c.getColumnIndexOrThrow(TargetDbAdapter.KEY_TITLE));
entryValues[i] = c.getString(c.getColumnIndexOrThrow(TargetDbAdapter.KEY_ROWID));
c.moveToNext();
}
 
ListPreference targets = new ListPreference(this);
targets.setEntries(entries);
targets.setEntryValues(entryValues);
targets.setDefaultValue(NO_SELECTION);
targets.setDialogTitle(R.string.pref_dialog_title);
targets.setKey(SELECTED_TARGET_KEY);
targets.setTitle(R.string.pref_title);
targets.setSummary(R.string.pref_summary);
dialogBasedPrefCat.addPreference(targets);
 
//add other preference screens
 
return root;
}
 
}
0

Java Applet Instrumentation

Java 6 has a neat feature that has received little attention: the Attach API

Using this API, you can hook your own code into a process already running on the JVM. It’s meant to build code-profilers and things of that sort, but with a little fooling around, you can attach to a running Applet and play with the live objects. I’ll describe my method – if you know a better way to do this, please let me know.

First, you need to turn off Applet Security. I’m lazy and went with a shotgun approach. Create a new .policy file:

grant { permission java.security.AllPermission; };

Then, set the browser plugin to use this policy. On Windows, try this:

  1. Open the Control Panel
  2. [doubleclick] Java Control Panel
  3. [click] Java tab
  4. [click] Java Applet Runtime Settings
  5. [click] View
  6. [click] Java Runtime Parameters
  7. [type] -Djava.security.policy=C:path_toyour.policy
  8. [click] OK

Same thing for Linux, just figure out where your Applet Runtime settings are.

Now you need a way to run your Agent. Annoyingly, the Agent must live in a jar file. You can build it on the command line using JAR, but I chose to do it programmatically to avoid the extra step. This program takes a single command line argument, the PID (process id) of the JVM the applet is running on. There’s different ways to find it. On Windows, try running “tasklist”. On Linux, “ps -A”. Place this file at “com/stuff/Runner.java”:

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.sun.tools.attach.VirtualMachine;
public class Runner {
  public static void main(String args[]) throws Exception {
    //create .jar including all .class files under directory: com/stuff/
    List<String> jarFiles = new ArrayList<String>();
    for(String file : (new File("com/stuff")).list())
      if(file.endsWith(".class"))
        jarFiles.add("com/stuff/"+file);
    String[] filenames = jarFiles.toArray(new String[]{});
    String jarFile = System.getProperty("user.dir")+"/agent.jar";
    JarUtil.jar(filenames, jarFile);
    //find PID of process to monitor
    String pid = args[0];
    //attach agent.jar 
    if(Integer.parseInt(pid) > 0) {
      VirtualMachine vm = VirtualMachine.attach(pid);
      vm.loadAgent(jarFile, null);
    } else {
      System.out.println("Bad PID: " + pid);
    }
  }
}

Rad, so now for the Agent itself. When the JVM calls our Agent, it
passes an instance of Instrumentation, which has the interesting method
getAllLoadedClasses(). A Class isn’t very useful unless there are
static methods to get instances of the Class. Fortunately,
AppletPanelCache.getAppletPanels() comes to the rescue as a way to get
at Objects instead of Classes. Here’s MyAgent.java:

import java.lang.instrument.Instrumentation;
public class MyAgent {
  public static void agentmain(String agentArgs, Instrumentation inst) {
    for(Class klass : inst.getAllLoadedClasses()) {
      if(klass.getName().endsWith("AppletPanelCache")) {
        Method m = klass.getMethod("getAppletPanels", new Class[]{});
        Object[] panels = (Object[])m.invoke(null, new Object[]{});
        for(Object panel : panels) {
        //do something interesting with an instance of Panel 
        }
      }
    }
  }
}

What can you do with a Panel? Well, let’s see, for an instance to be of any use in an Applet, it is likely connected *somehow* to the rest of those top level Panels where everything is shown. With a heavy dose of Reflection, you can recursively explore each Panel’s children, ultimately getting a reference to most (is it most or all? does anyone know?) of the live objects. Once you have a live object, you can do whatever you want – call methods, inspect fields, etc. The methods getComponents() and getWindows() are a good place to start.

So, how to run it? Depends where you have Java installed. Make sure tools.jar is on your classpath. I use this invocation:

  1. compile javac -classpath /your/path/to/tools.jar:. com.stuff.*
  2. run (first, open an applet in your webbrowser)
  3. java -classpath /your/path/to/tools.jar:. com.stuff.Runner -Djava.security.policy=/home/path_to/your.policy

Annoyances – When an Agent calls System.out.println(), it gets the Applet’s PrintStream instead of printing to the Console. My solution? Well, Runner *is* still connected to the Console, so have Agent send all its output to Runner over a socket. There may be a better way. Also, I’ve been unable to attach twice without shutting down and restarting Firefox – not sure exactly why.

One more neat thing you might try – hooking your own AWTEventListener into the AWT Event Processing pipeline:

if(klass.getName().endsWith(Toolkit)) {
  long mask = Long.MAX_VALUE; //call our listener on ALL event types 
  Method m = klass.getMethod("getDefaultToolkit", new Class[]{});
  Toolkit toolkit = (Toolkit)m.invoke(null, new Object[]{});
  Method n = klass.getMethod("addAWTEventListener", new Class[]{AWTEventListener.class, long.class});
  n.invoke(toolkit, new Object[]{new MyAWTEventListener(), mask});
}

One cute idea – dispatch MouseEvent and KeyEvent to make a video game “play” itself.

Remember to reset your Java Applet Runtime Settings, if you are concerned about such things.

References: Hotpatching a Java 6 Application by Jack Shirazi