Monday, November 9, 2009

Droid

A solid smartphone on Verizon running Android? I couldn't resist, so I was one of the first in line on release day, and so far I love it. It's quite an upgrade and I've been impressed with how solidly it was put together, both in terms of hardware and software. But there are plenty of glowing reviews on the web, and so rather than rehashing what everyone else is saying, I thought I'd lay out my nitpicks and feature requests, both in terms of the user-experience and SDK features.

User Experience

Okay, so we'll start off with a common complaint from reviews. The camera blows, and it seems to be a software problem. Fix it. The flash is actually impressive for the size, but what's the point if the camera can't hold a focus.

Voice search is excellent. So is voice dial. But why are they not better integrated? I should be able to search for "dial 4085559210" to call that number. Right now, it picks up the text very accurately, but then searches google for the result. Bummer. Also, voice search should search over the nicknames I've entered. I don't want to say "Call Barbara", I want to say "Call Mom".

Easy access to more power modes. I love airplane mode. It's easy to enable and puts my phone in a more-silent-than-silent state, which I prefer during meetings and sleep. It would be great to support other profiles, specifically a "power saving" mode that enables only basic voice features, shuts down most background services, and keeps the screen dim. Good for long weekend outings.

Lost phones: Let me specify a list of contacts that can be called without entering the unlock code. This can help people return my phone if they feel so inclined. It's probably good enough to just let them call my favorites.

Long pressing the home screen brings up the list of recent apps. Why can't I long press one of those to kill it? Or restart it? The GUI should be updated to provide these options for the focused activity with one click.

Panning between home screens: make it faster. It should be powerful enough, and we see other plenty smooth animations. The pixels between screens are almost totally static, save for the widgets, so they should be cacheable in a framebuffer and the animation should be silky smooth. Of course this is probably a complicated issue, but it's the first thing people notice when they play with the phone.

SDK

Named intents. Intents are really great, but I have some issues. The information I should need to know to call an explicit intent are: (1) the name of an action and (2) the name of a package. I don't care which class within that package handles my request- that's for them to figure out.

Furthermore, intents should be manageable in the Manifest:

<namedIntent label="ScanQR">
   <action>com.google.zxing.client.android.SCAN</action>
   <package>com.google.zxing.client.android</package>
</namedIntent>
Later, in code:
Intent intent = new NamedIntent("ScanQR");

Additionally, we can use this (optional) package information to determine dependencies of our application, which the user can be requested to install. We can add a <mandate>optional/required</mandate> field as well.

Which brings up the next point... Improved dependencies in the manifest. We should be able to depend on other activities, services, as well as shared libraries (I'm sure there are much deeper issues that may make this very difficult..) For example, I've written several small apps, all of which require the SMACK XMPP library. This library is large, and it would be great to only require it once.

As a bonus, the system can integrate with Maven and other build systems in the future. This isn't work the android team should have to do, but it should be a consideration and left as a possibility for 3rd party work.

The Media Player sucks. Not just the interface, but the API as well. Why can't I send it an m3u to play? I want to take advantage of its widget and playlist GUI. Consistent view for the user, less work for me. And if the API were properly specified, they could drop in another player.

Timeline Layers: the map layers are cool, although I haven't seen whether or not they are customizable. Either way, I want to be able to add data to a timeline, and have a collective view. My note-taking apps should attach updates. My music player a play history. GPS tracks my steps. Put them all together in one timeline GUI.

ADT

The "sample" apps are great, and could be the start of something much bigger. But when I select one, I am set up with a project that edits the sample directly. This is just wrong- it should make a copy of the code and put it in my workspace so I don't ruin the original example.

Friday, October 16, 2009

Checking for Internet Availability on Android

I've been writing a good handful of net-enabled apps for Android and wanted a simple class to make sure the internet is available. If there is connectivity, the application should continue to run. Otherwise, the user should be prompted that the internet is not available, with the ability to "retry" or "cancel". If they choose to cancel, a different branch of code is executed. The behavior is seen in many of the built-in Android apps. My solution uses callbacks to get the job done. Hopefully the example speaks for itself, and the two required classes follow. You'll need an additional permission for the package to work- ACCESS_NETWORK_STATE. Also, Blogger is basically the worst possible way I could imagine posting code to the internet. If you have a better suggestion, I'd be happy to hear it.

Example

This snippet can be placed inside of an Activity, often in onCreate or similar lifecycle method.
WaitForInternetCallback callback =
 new WaitForInternetCallback(this) {
 public void onConnectionSuccess() {
  Log.d("test","We have internet!");
 }
 
 public void onConnectionFailure() {
  Log.d("test","failed to get internet connectivity...");
 }
 };
   
try {
 WaitForInternet.setCallback(callback);
} catch (SecurityException e) {
 Log.w("test","Could not check network state.", e);
 callback.onConnectionSuccess();
}
Here are the associated classes:

WaitForInternetCallback

package edu.stanford.prpl.junction.impl;

import android.app.Activity;

public abstract class WaitForInternetCallback {
 protected Activity mActivity;
 
 public WaitForInternetCallback(Activity activity) {
  mActivity=activity;
 }
 
 public abstract void onConnectionSuccess();
 public abstract void onConnectionFailure();
}

WaitForInternet

package edu.stanford.prpl.junction.impl;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.util.Log;

public class WaitForInternet {
 
 /**
  * Check for internet connectivity.
  * The calling context must have permission to
  * access the device's network state.
  *
  * If the calling context does not have permission, an exception is thrown.
  *
  * @param WaitForInternetCallback
  * @return
  */
 public static void setCallback(final WaitForInternetCallback callback) {  
  final ConnectivityManager connMan = (ConnectivityManager) callback.mActivity
    .getSystemService(Context.CONNECTIVITY_SERVICE);

  final MutableBoolean isConnected = new MutableBoolean(connMan
    .getActiveNetworkInfo() != null
    && connMan.getActiveNetworkInfo().isConnected());
  if (isConnected.value) {
   callback.onConnectionSuccess();
   return;
  }
  
  final MutableBoolean isRetrying = new MutableBoolean(true);

  /* dialog */
  final AlertDialog.Builder connDialog = new AlertDialog.Builder(callback.mActivity);
  connDialog.setTitle("Network not available");
  connDialog
    .setMessage("Your phone cannot currently access the internet.");
  connDialog.setPositiveButton("Retry",
    new DialogInterface.OnClickListener() {
     public void onClick(DialogInterface dialogInterface, int i) {
      synchronized (isRetrying) {
       isRetrying.notify();
      }
     }
    });
  connDialog.setNegativeButton("Cancel",
    new DialogInterface.OnClickListener() {
     public void onClick(DialogInterface dialogInterface, int i) {
      synchronized (isRetrying) {
       isRetrying.value = false;
       isRetrying.notify();
      }
     }
    });

  new Thread() {
   public void run() {
    while (!isConnected.value && isRetrying.value) {
     callback.mActivity.runOnUiThread(new Thread() {
      @Override
      public void run() {
       connDialog.show();
      }
     });

     synchronized (isRetrying) {
      try {
       isRetrying.wait();
      } catch (InterruptedException e) {
       Log.w("junction", "Error waiting for retry lock", e);
      }
     }

     isConnected.value = (connMan.getActiveNetworkInfo() != null && connMan
       .getActiveNetworkInfo().isConnected());
    }
    
    if (isConnected.value) {
     callback.onConnectionSuccess();
    } else {
     callback.onConnectionFailure();
    }
   }
  }.start();
 }
}

class MutableBoolean {
 public boolean value = true;

 public MutableBoolean(boolean v) {
  value = v;
 }
}

Wednesday, July 29, 2009

XMPP on Android using Smack

I just went through the experience of running XMPP on Android, using the excellent Smack library. I take very little credit for getting it working, and just wanted to document my research in one place. The end result: XMPP running on Android SDK 1.5, using Smack 3.0.4 (svn revision 10869), including full support for bundled extensions. Note that at the time of writing, Smack 3.1.0 is available, but 3.0.4 was sufficient for my needs.

The first useful post I found was from Davanum Srinivas, found here. I had mixed results, and looked for a more source-level approach.

Reading through the comments, I found a link to Peter Neubauer's post here. The smackdiff file was exactly what I was looking for.

To apply the patch and build your own .jar, you'll need three tools installed: svn, diff, and ant. Looking at the .diff, the patch is against revision 10869 of the smack repository: http://svn.igniterealtime.org/svn/repos/smack/trunk. I checked out the source and applied the patch like this:

$ svn co -r 10869 \
     http://svn.igniterealtime.org/svn/repos/smack/trunk smack-android
$ cd smack-android/source
$ patch -p0 -i /path/to/smack.diff
$ cd ../build
$ ant
$ cd ../target

If all went well, you should see smack.jar and smackx.jar, and a few others. These .jar's should work on android, but there's one more catch. My application is using the MultiUserChat extension. The smack extensions are loaded based on a file in smack.jar's META-INF directory. So, when I build my .apk, I simply copied the contents of both smack.jar/META-INF and smackx.jar/META-INF into my apk's META-INF, and everything worked!

Note that one important function of the patch is to remove SSL from Smack, so SSL will not work on this build.

An issue I have not yet worked through is how to automatically import the files from this META-INF directory. I am using Maven to build my .apk (using maven-android-plugin) and have not yet found the best practice for copying these files, so if you have ideas, please post them.

Many thanks to Peter Neubauer and the Smack/Android teams for their work!

Sunday, July 19, 2009

Inline Wiki Syntax using JQuery

I have been kicking this idea for a while and finally got to writing some demo code for it in the form of a JQuery plugin. The idea is to write Wiki code directly into an HTML element, and have it be converted on the fly in the browser.

There are a few benefits to this. First of all, you can use a simple wiki syntax in any webpage (a blog is a good candidate), without the need for a full-blown server-side wiki engine. Second, if you're building a wiki engine, you can have a real-time preview of wiki edits without the need for a server-driven refresh.

Currently, the plugin supports:

* Lists, both ordered (#) and unordered (*) * Basic text formatting- two single apostrophes for ''emphasis'' and three for '''strong''' (five for '''''both''''') * Section headers (!!, !!!, etc.)

For a demo of the code in action, view the source code for the above section (Pretty sneaky.) The JQuery plugin itself can also be found on this page. Here's a more complete example:

This is my standard HTML page. I can have inline wiki code:
<div class="wiki">
!! Welcome to my wiki!
!!! it's pretty simple.
You can have lists:
* pretty easy.
* it's unordered
## but the sublist
## it's ''ordered''!!
* so handy.
!!! it works.
So cool. and You can have ''some'' '''crazy''' '''''formatting'''''.
</div>

Becomes:
This is my standard HTML page. I can have inline wiki code:
!! Welcome to my wiki! !!! it's pretty simple. You can have lists: * pretty easy. * it's unordered ## but the sublist ## it's ''ordered''!! * so handy. !!! it works. So cool. and You can have ''some'' '''crazy''' '''''formatting'''''.

Sunday, July 12, 2009

Civic EX USB Audio Tools

A few months ago, I got myself a 2009 Civic EX, which I totally love. One of the cool features on it is a USB audio interface. You can plug in a USB stick full of music and have it play back. It works very well, but with a problem- it's pretty impossible to navigate 16GB of music with a one line display. I put together a little PHP script to print out the contents of my stick in the same order as is read by the Civic. It's a dead-simple script but might save someone a few minutes. I've tested it on Linux only, but it should work across platforms (but I believe it will require PHP5) I also came across a useful shell script that forces the directory ordering to be alphanumeric, which you can find at the bottom of this thread. Below is the code to print a directory listing. By default, it only prints folders. Two flags are supported: "-t" will print tracks, and "-n" will hide directory names. One last thing that might save you some hunting- according to the Civic manual, the system is capable of handling up to 700 folders and about 65,000+ tracks. I've only pushed it to 100 folders and about 1100 tracks so far.
#!/usr/bin/php
<?php

$media_types="mp3|m4a|wma";

// check options
$list_tracks=false;
$list_directories=true;

for ($i=1;$i<sizeof($argv)-1;$i++){
 if ($argv[$i][0]=='-') {
  if (false !== stripos($argv[$i],'t')) {
   $list_tracks=true; 
  }
  if (false !== stripos($argv[$i],'n')) {
   $list_directories=false; 
  }
 } else {
  die("Invalid option " + $argv[$i]);
 }
}



$path = realpath($argv[sizeof($argv)-1]);
$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);

$di=0;
$ti=1;
$media_dir=false;
$working=false;
$media_pattern='/\.'.$media_types.'$/i';
$tr_pad = ($list_tracks && $list_directories) ? '  ' : '';
foreach ($objects as $name => $object) {
 if ($object->isFile()) {
  if ($working != $object->getPath()) {
   // start new dir
   if ($media_dir && $list_tracks) {
    echo "\n";
   }
   $media_dir=false;
   $working = $object->getPath();
   $ti=1;
  }

  
  if (!$media_dir) {
   if (preg_match($media_pattern,$name)) {
    $media_dir=true;
    $di++;
    if ($list_directories) {
     $el = str_replace($path.DIRECTORY_SEPARATOR,'',$object->getPath());
     echo substr(str_pad($di,2,'0',STR_PAD_LEFT),-2) . ' ' . str_replace(DIRECTORY_SEPARATOR,' :: ', $el) . "\n";
    }
   }
  }
  

  if ($list_tracks) {
   if (preg_match($media_pattern,$name)) {
    echo $tr_pad . substr(str_pad($di,2,'0',STR_PAD_LEFT),-3) . '-' . str_pad(($ti++),3,'0',STR_PAD_LEFT) . ' ' . $object->getBaseName() . "\n";
   }
  }

 }
}
Search Terms: civic directory order, civic music, USB Audio directory order

First Post

Hello, World. I've finally caved in and started my very own blog. But don't worry, I won't be posting about my boring life (god willing). I just wanted a place to jot down solutions to problems I've come across, and let the almighty Google throw it in its index so others (you?) might find it useful. Enjoy. Or don't.