• Helped in design and technology direction for next-generation LIMS product. Included technology discovery and evaluation.
• Provided new technology leadership on migration of legacy application from VB6 to C#.
• Implemented automated build system for the team with continuous integration and testing.
• Worked in Scanning, Parsing and Imaging team on designing and implementing advanced text processing algorithms.
• Involved in creation and maintenance of full-fetured testing application, written in C#, for evaluation of parsing algorithms.
• Took part in architecture re-design of parsing system that provides core functionality of the NeatReceipts product.
• Principal web developer for an online event management system used by non-profit groups.
• Designed and implemented multiple applications in PHP, including a full administration interface for employees and a complete testing framework in Java.
• Maintained workflow management system.
• Created process automation software for workflow system.
• Established general programming standards for my group, as well as
learning and then teaching the Oracle OO4O standard for PL/SQL.
• Created Excel macro programs to facilitate organization-wide reporting.
(NOTE: This was summer 2002 and May-Dec 2003)
One of the biggest complaints I get about WallSwitch is that the random switching sucks. It repeats images too frequently and just doesn't 'feel right'. I couldn't agree more, so when I ran across this article, I figured I could do something about it.
The problem is that random numbers don't work how the human brain expects. The article above explains it in detail, but suffice it to say that pure/pseudo random numbers suck for most things a user interacts with. What I wanted was a way to provide selections that are still nondeterministic, but closer to what users expect.
The method itself is nothing more than random selection using weighted items. I wanted to keep a feeling of randomness, but make sure more recent selections were less likely to appear. This being Java, the first thing I did was make an interface to hold the object and it's priority:
public interface BetterRandomItem {
int getPriority();
void setPriority(int newPriority);
}
public static BetterRandomItem choice(List items) {
if(items == null || items.size() == 0)
throw new IllegalArgumentException("items is null or empty");
// Sum all the priority values
int itemSpace = 0;
for(int i = 0; i = target)
return items.get(i);
}
// If that failed, return the last in the list, I guess
return items.get(items.size() - 1);
}
WallSwitch 2.0 was released a couple days ago. It was quickly followed by 2.0.1 with some bug fixes for Droid users. There are more blog posts and updates to come. I'll be working on a couple more bug fixes and a new, smaller, widget for the next version.
I'm incredibly happy to finally announce that WallSwitch 1.5 has been published to the Android Market! It's been a significant amount of work to add some of the new features and fix the outstanding bugs. Here's what you can expect to find in the new version:
Released tonight with a couple more bug fixes.
I'm going to start work on v1.1 soon, which will add some of my more frequently requested features.
Just a quick note on a release that happened a couple days ago. Some bugs were found very quickly and I had to do a quick release so it would work on more phones.
Other than that, working on a couple more features. Right now, I'm thinking checkboxes instead of a radio button for choosing the folder for your wallpapers will be the first thing I do. It seems to be the more requested feature at the moment and is a really good idea.
Creating a service that will receive TIME_TICK, but also start on boot isn't immediately obvious. It took me a bit to figure out the best way to do it, so I figure it's worth sharing.
To start, you can't register your receiver to get TIME_TICK in the android manifest. The registration for TIME_TICK has to happen in code using Intent.registerReceiver. You can, however, define that your receiver has android.permission.RECEIVE_BOOT_COMPLETED and set your receiver to handle it, which will give you some control on boot. Here's the relevant portion of AndroidManifest.xml:
<application android:icon="@drawable/icon" android:label="@string/app_name" android:enabled="true" android:debuggable="false"> <activity android:name=".AndroidMain" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="DemoService"></service> <receiver android:name="DemoReceiver" android:permission="android.permission.RECEIVE_BOOT_COMPLETED"> <intent-filter> <action android:name="android.intent.action.TIME_TICK"></action> <action android:name="android.intent.action.BOOT_COMPLETED"></action> </intent-filter> </receiver> </application> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
TIME_TICK. Intent.registerReceiver registers for the lifetime of the Intent it's called from. If we register it using the intent created for BOOT_COMPLETED, it's dead as soon as it exits the handler. Therefore, we need to start a background service that will keep running. This service then creates a new instance of the receiver and registers that for TIME_TICK.
public class DemoReceiver extends BroadcastReceiver {
static final String LOGGING_TAG = "MyDemo";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().compareTo(Intent.ACTION_BOOT_COMPLETED) == 0){
Log.v(LOGGING_TAG, "DemoReceiver.onReceive(ACTION_BOOT_COMPLETED)");
context.startService(new Intent(context, DemoService.class));
}else if(intent.getAction().compareTo(Intent.ACTION_TIME_TICK) == 0)
Log.v(LOGGING_TAG, "DemoReceiver.onReceive(ACTION_TIME_TICK)");
else
Log.v(LOGGING_TAG, "DemoReceiver.onReceive(" + intent.getAction() + ")");
}
}
public class DemoService extends Service {
static final String LOGGING_TAG = "MyDemo";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onStart(Intent intent, int startId){
super.onStart(intent, startId);
Log.v(LOGGING_TAG, "DemoService.onStart()");
}
@Override
public void onCreate(){
super.onCreate();
Log.v(LOGGING_TAG, "DemoService.onCreate()");
registerReceiver(
new DemoReceiver(),
new IntentFilter(Intent.ACTION_TIME_TICK));
}
}
04-25 21:04:22.580: VERBOSE/MyDemo(182): DemoReceiver.onReceive(ACTION_BOOT_COMPLETED) 04-25 21:04:22.630: VERBOSE/MyDemo(182): DemoService.onCreate() 04-25 21:04:22.650: VERBOSE/MyDemo(182): DemoService.onStart() 04-25 21:05:00.140: VERBOSE/MyDemo(182): DemoReceiver.onReceive(ACTION_TIME_TICK)
While working on WallSwitch, I ran into a number of hairy problems with image processing. The worst, by far, was this:
04-15 02:20:34.693: ERROR/dalvikvm-heap(165): 50331648-byte external allocation too large for this process. 04-15 02:20:34.693: ERROR/(165): VM won't let us allocate 50331648 bytes 04-15 02:20:38.233: ERROR/AndroidRuntime(165): java.lang.OutOfMemoryError: bitmap size exceeds VM budgetI got it when trying to do a simple
BitmapFactory.decodeFile() on a jpeg I had on the sd card. The jpeg was huge (~4MB) but that should exceed the 16MB heap I get, right? I found some decent help here and here, but nothing that really explained what was going on. I knew I was hitting the limit, but had no idea why.
inSampleSize on your BitmapFactory.Options to something useful. It'll sample the image you get back, returning something 1/2, 1/4, ... the size. The key here is to try to keep the sample size a power of two. It isn't necessary, but it makes the processing faster and it'll make sure the image you get back keeps the same proportions.
// First, get the dimensions of the image
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// Only scale if we need to
// (16384 buffer for img processing)
Boolean scaleByHeight = Math.abs(options.outHeight - targetHeight) >= Math.abs(options.outWidth - targetWidth);
if(options.outHeight * options.outWidth * 2 >= 16384){
// Load, scaling to smallest power of 2 that'll get it
It's still a pretty slow operation to perform, but this at least makes it possible!