Android: Hide a view when the soft keyboard is showing

This took far too long to find, mostly because the documentation doesn’t make sense (to me) and because the one hit on the android-developers list looked like the wrong answer, when it fact it’s right.

On the NPR News for Android app, we have a player at the bottom of the screen. This is handled with a FrameLayout in activity layouts. For activities that had an EditText field, the keyboard would push the player up and cover the important parts of the screen. See the image at right for the ‘before’ issue.

I can see this being a major problem for apps that host a banner ad at the bottom of pages (and saw a post on StackOverflow to that effect). Clearly you don’t want the ad to cover the content when your user is trying to type.

The solution was simple enough for us: mark the activity with windowSoftInputMethod = adjustPan in the Manifest and this resolved it.

    <activity
      android:name="SearchResultsActivity"
      android:windowSoftInputMode="adjustPan"/>

Unfortunately, windowSoftInputMethod is not very well documented. The reference says that there are two possible choices, adjustPan and adjustResize and that activities should always select one or the other.

adjustResize
The activity’s main window is always resized to make room for the soft keyboard on screen.
adjustPan
The activity’s main window is not resized to make room for the soft keyboard. Rather, the contents of the window are automatically panned so that the current focus is never obscured by the keyboard and users can always see what they are typing. This is generally less desirable than resizing, because the user may need to close the soft keyboard to get at and interact with obscured parts of the window.

What should be made more clear is that “resize” doesn’t mean scale or zoom here, it means that the container for your layout is compressed and every element is realigned to try to make it fit best. While this may make sense for applications that have content above or below the edit field that they want visible while editing, it isn’t what we were looking for.

By using adjustPan the layout manager leaves the activity as it was and only shows a portion of it — the part that would be visible if the keyboard were laid over the activity. Exactly what we want.

Snapshots and the New Android AVD Manager

So one of the biggest pains to mobile development with emulators is that starting up the emulator takes forever. Android tools version 9 addressed this finally by letting you take a snapshot of the emulator, just like you would do with a virtual machine. Here’s how I got my emulators really snappy.

First on the initial launch make sure you select Save to snapshot. Get the emulator up and running and put it into the state you want. I like to have it start at the desktop (home screen) view. Now close the emulator. I found that it wasn’t very responsive when closing, so wait a bit, watch the mouse icon go to “busy” and let it write everything to disk.

Now, launch the emulator again and this time uncheck Save to snapshot but make sure Launch from snapshot is chosen. Voila! The emulator launches quickly. And when you save it it shuts down quickly because it’s not saving anymore.

One caveat I’ve found with the emulator. When moving from network to network (i.e. one wifi point to another) for some reason the emulator loses Internet access (returning “host cannot be null” for HTTP requests). Unfortunately this means I have to have a save emulator snapshot for each of my common network and launch a new one, not from snapshot, when I’m on a new network. Anyone have any idea how to fix this?

Subversion Revision in Android App Version with Eclipse

As developers, we like to know exactly what code our customers are running when they report a bug. For years, across several companies and many projects, we’ve put the source control revision number into the reported version number. This works well and is a common practice. Here’s how we’re doing it for Android apps.

Continue reading “Subversion Revision in Android App Version with Eclipse”

Location (GPS) and Automated Testing on Android

So I’ve been contributing to the NPR Android App, specifically by building a test suite and tests for outstanding bugs. While my specific interest is in the audio playback, I took on some sticky issues around the location lookup and found that testing location services isn’t as straightforward as I thought. So here’s a quick primer with some notes to get you started.

Getting the device’s location

Android devices can get location from the (cellular) network or the GPS chip (if they have them). Some devices may support other providers in the future. The emulator only has a GPS provider (emulated of course) registered on start-up.

The quickest way to get the location of the device is to call LocationManager#getLastKnownLocation.

LocationManager lm =(LocationManager) getSystemService(Context.LOCATION_SERVICE);
Location location = null;

List providers = lm.getAllProviders();
for (String provider : providers) {
  Location loc = lm.getLastKnownLocation(provider);
  if (loc != null) {
    location = loc;
    break;
  }
}

Note this call is synchronous but in order to not block the thread during a lookup, it just looks for a saved location from the given provider. Therefore, it does not actually do a network or GPS location lookup.

For rapid response, this is what you want, and most of the time that will work well in your apps. Some other application (like Google Maps) has probably already found the location recently. However, when testing the application, it’s very likely the emulator will not have a location and this call will return null (which, according to the SDK docs, means there is no provider but it can also mean the provider hasn’t located the device yet).

To locate the device, you’ll have to set up a listener. There’s a small likelihood that your app is the first (location-based) app your customer started after turning on the phone. In that case it would be the same problem — no location. So what you probably want to do is have your initial activity launch a listener on one or more providers to record a location. This is asynchronous so it doesn’t block your app. Here’s an example of how to do that.

  private static final int MSG_CANCEL_LOCATION_LISTENERS = 2;
  private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
      case MSG_CANCEL_LOCATION_LISTENERS:
        cancelLocationListeners();
        break;
      }
    }
  };

  // This is public so that we can inspect if for testing
  public List locationListeners =  new ArrayList();

 /**
   * On start up, launch a location listener for each service. We need to do
   * this in order to ensure that getLastKnownLocation
   * will always find a value.
   */
  private void lauchLocationListeners() {
    LocationManager lm =
        (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    List providers = lm.getAllProviders();
    for (String provider : providers) {
      LocationListener listener = new LocationListener() {

        @Override
        public void onLocationChanged(Location location) {
          handler.sendEmptyMessage(MSG_CANCEL_LOCATION_LISTENERS);
        }

        @Override
        public void onProviderDisabled(String provider) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }

      };
      lm.requestLocationUpdates(provider, 60000, 0, listener);
      locationListeners.add(listener);
    }
  }

  /**
   * Remove all listeners.
   */
  private void cancelLocationListeners() {
    LocationManager lm =
        (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    // Synchronized because there may be multiple listeners running and
    // we don't want them to both try to alter the listeners collection
    // at the same time.
    synchronized (locationListeners) {
      for (LocationListener listener : locationListeners) {
        lm.removeUpdates(listener);
        locationListeners.remove(listener);
      }
    }
  }

Testing the location

The SDK provides methods for mocking location providers and location details for your testing. (You can also change the geo fix with DDMS in Eclipse or through the console, but I’m interested in automated testing here.) It wasn’t very clear from the documentation on how to use them so it took me a while to figure out that all the ‘test’ calls are not necessarily related.

addTestProvider

This method places a new provider (with the name you gave it) in the collection of providers returned by getAllProviders() (and other calls). You cannot add a mock provider with the same name as a provider that already exists, such as “gps”. This will cause an error. The mock provider doesn’t allow you to pass in a class, so it’s not very useful for inspecting actions called on it (which is why I usually use mocks). Instead it just lets you create a provider that will return in response to specific criteria.

The corresponding removeTestProvider removes this entry from the list. You should remove the provider as you clean up your test because otherwise your next test won’t be able to create it again. (The provider is retained in the emulator instance, even if the activity is destroyed and recreated between tests.) Note that you can actually remove any provider, even the built in “gps” one with this call, so be careful. If you do that you’ll have to restart your emulator to get the emulated GPS interface back.

setTestProviderLocation

I think this method should have been called “setTestLocation” because it is not directly related to a “test provider” made in the previous call. You can set a test location, with this call, for any provider. In fact, if all you want to do is test that your application can find a particular location, then set a location on the GPS provider and ignore the ‘addTestProvider’ method completely.

      LocationManager lm = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
      Location location = new Location(providerName);
      location.setLatitude(latitude);
      location.setLongitude(longitude);
      location.setTime(System.currentTimeMillis());
      lm.setTestProviderLocation(providerName, location);

Be sure to set the time on your location if you want the provider to think it’s a new update.

Also, all the testing requires that you have permission for mock location requested in your application under test (not in the test project). Add this to your manifest.

<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

If you are testing on a device, you’ll need to go into Settings | Applications | Developement | Allow Mock Locations and enable it, which is not set by default. This setting may not exist on all devices (but there’s a workaround).

setTestProviderEnabled

By now you should be getting the picture. This method doesn’t enable your test provider, it sets a value that indicates to your application that a provider is enabled whether it actually is or not.

Hopefully this will help some other folks who were confused about the terms in the SDK.

Testing Challenges

As mentioned above the SDK is not very conducive to dependency injection. What I’d like to do is inject a real mock provider object (one I create in my test) into the provider list for the app and when the app asks the GPS provider for a location I know the app is working. This isn’t possible as it’s currently built so I have to write silly tests with threads to start the activity and check when it collects and removes its listeners. Not very clean (and really testing the wrong thing). In many places in the SDK, I’d like to be able to inject mock objects into the system, but this isn’t thoroughly supported yet.

The second challenge is that you can’t access the location manager without access to the activity because it’s returned by the context of the activity. This makes it impossible to, say, inject a mock provider or location before the activity starts, which you might like to do in the case of, oh, looking up location in the onCreate call.

If anyone has any suggestions on this, I’d be excited to hear them. Perhaps I’m just missing a basic concept here.

Android Audio Streaming

Having spent hours and hours trying to figure out how to get MediaPlayer to work properly on a thread for my last video project and now an audio streaming app, I found there’s an easier way.

The AsyncPlayer class takes care of this for you.

player = new AsyncPlayer(TAG);
player.play(this, Uri.parse(stream), false, AudioManager.STREAM_MUSIC);

For some reason the PocketJourney streaming tutorial for Android 1.0 keeps ranking in all my searches, which is generally too bad because that’s way out of date (incidentally, here is the newer version for 1.5 and later). You don’t need to buffer or double buffer media for the media player anymore. However, the comments on there helped me find AsyncPlayer as well and the 3GP file that Anthony posted to test this.

Now, the next step is to figure out how to get MediaPlayer (which underlays AsyncPlayer) to play an MP3 stream. I don’t know why it won’t, as MP3 is a supported type, but it plays nothing for my test stream.

Update Removed the link to the old streaming article in the hopes that the new one will rank better.

Update Android SDK doesn’t support streaming MP3 (shoutcast) until 2.2 (FroYo). To stream an MP3 stream on earlier OS versions you need to double buffer it either using the PocketJourney code above (which is not very good — it stutters) or using a built-in proxy as the NPR News app does.

Loading Images Over HTTP on a Separate Thread on Android

My previous post about making a list view with lazy-loaded images on Android just had the code pertaining to the list view. As requested, I’m also adding the class that loads the images.

This class includes a queue so that only a single image is loaded at a time. I chose this because in building a list view with images it’s more important to start loading some images than to wait for all images to get loaded. Other implementations of this kind of thing launch a separate thread per image which means that the network connection would be clogged with all the image loads.

You’ll notice the use of SoftReference here, which I gleaned from Tom van Zummeren’s tutorial. While this appears to work well, I haven’t done any significant load or performance testing, so it may not be necessary.

There are some notable problems here in the design, so please adapt this to your need. Beyond the potential race condition noted below, there’s a basic problem in that the thread completes once the queue is done. So if the images happened to load faster than you add them to the queue you could end up with a queue that was emptied, the thread died and future items were never loaded. I’ve tried to work around this by capturing the TERMINATED state of the thread and relaunching it, but have not, as far as I know, tested this in production. And I’ve built no automated tests to test that case yet.

Continue reading “Loading Images Over HTTP on a Separate Thread on Android”

Loading Remote Images in a ListView on Android

So this appears to be a common challenge:

I have a list of items in my mobile application. Each item in the list contains an image and a title. The data for the list comes from a remote web service, RESTful interface or other Internet connection and the images are provided in a separate call. I want the list to be responsive, so I’ll load the list and then in a separate thread I will update the images in the list as they are loaded from the network.

I struggled with this on the BlackBerry where there was little similar code to go on, but got it working. I just finished an Android app and discovered that we had the same pattern. Trying to re-use my BlackBerry code I found that the UI patterns don’t translate (beyond frameworks, Android recycles views while it’s rendering to keep memory-use low).

There are lots of posts on the web about how to do this for the Android, such as this thread.

Unfortunately all of those ran into the same problem: Sometimes the images would load and sometimes they wouldn’t. Sometimes the images in the list would be the wrong one for the list item when rendered. And some solutions only rendered images that were off screen.

Continue reading “Loading Remote Images in a ListView on Android”