Archive for the ‘Android’ Category

TextView Inner Shadows with EmbossMaskFilter

A common request from designers that I work with is to draw text in an application that contains an inner shadow. An example of what this looks like is provided below (top un-styled, bottom with shadow added):

Basically we’re talking about a shadow that appears inside the drawn text, rather than slightly outside it.  The Android APIs don’t directly provide a simple method of doing this; unfortunately the only effect that can be applied without working directly with the TextView’s Paint object is a drop shadow.  Luckily, this effect can be achieved with a little bit of trickery using the EmbossMaskFilter that Android does provide.  The disadvantage with this filter is it’s not very well documented as to what it’s parameters really do, so let’s see if we can clear that up and create a usable inner shadow.

What is EmbossMaskFilter?

EmbossMaskFilter can be applied to any Paint object to generate an “embossed” look to the content being drawn, whether it be text or any other shape/content that can be drawn to a Canvas.  By definition, an emboss effect is designed to make the content look like it is slightly raised up.  This is accomplished by placing highlights on one side of the element and corresponding lowlights (shadows) on the opposite side.  While this is not entirely the look we want, it turns out the configurable parameters of EmbossMaskFilter allow us to dial down the highlights almost completely so we are left only with the inner shadow effect.  First, let’s take a look at the options you can use to construct an EmbossMaskFilter.  The constructor signature looks like the following:

public EmbossMaskFilter (float[] direction, float ambient, float specular,
        float blurRadius)

Let’s look at each parameter:

  • Direction: Where is the light source coming from?
    • This is a x/y/z combo to place the light source in relation to the content.
    • The x-coordinate denotes the horizontal placement
    • The y-coordinate denotes the vertical placement
    • The z-coordinate denotes how far above the screen the theoretical light source exists.
    • For instance, (0, 0, 0) would be smack on top in the center and no real help.  At  (0, 0, 1) the light would be directly above the content and cast an even shadow (think, high noon); while (1, 1, 1) would locate the light source slightly to the top and to the left of the content, while being equally as high above the screen (z-coordinate).
  • Ambient: How bright is the light?
    • This is a value between 0.0 and 1.0 to show the brightness of the light source.  Its net effect is is to lighten or darken the shadow overlay, although setting the value all the way to either extreme will usually make the shadow disappear.
  • Specular: How reflective is the content?
    • This value controls how reflective the material is. Specular highlights are the shiny spots that show up on reflective materials, and this value represents the coefficient used in the Phong Model of determining their size and shape.  Its net effect is that higher values create narrow highlights that are visible only at direct viewing angles.  We want to keep this value high for an inner shadow so that the reflective highlights are narrow enough to be basically invisible.  A value larger than 15 usually does the trick.
  • Blur Radius: How sharp is the edge?
    • This controls the sharpness of the edge that the created artifacts (highlights and shadows) have.  It must be set to something (won’t draw properly with a value of zero), but for our shadow we want to keep this low so the shadow is subtle and crisp.  Increasing it can add some more dimension, but anything above 5.0 usually starts to look sloppy.

Creating the Effect

So know that we’ve discussed what the effect parameters mean, how can we combine them to create the shadow effect.  The parameter of primary importance is the light source.  We want the light source be “very close” to the drawing surface, as this will make it easier to minimize out the highlight reflection, this is accomplished by a low “Z” value for the light direction; we’ll use 0.5.  The other two parameters can be combined to establish where you want the shadow to occur.  Most often, an inner shadow should come down from the top, so we are going to place the X/Y coordinates a 0.0 and -1.0:

float[] direction = new float[] {0.0f, -1.0f, 0.5f};

This may seem a little counterintuitive since the idea of and inner shadow at the top should correlate to the light source coming from above, but since we are really capitalizing on the back side of the emboss, this direction will be reversed.  As for the secondary parameters:

  • A good starting point for the ambient light is around 0.6
  • Specularity should be >15.0 to eliminate the highlights usually found in an emboss effect.  You probably won’t see much difference between 15 and 50 unless you adjust the light source angle to be higher.
  • Blur radius should usually be a a value between 0.5 and 5.0.  We’ll use 1.0 for this example.

So a good starting filter for an inner shadow effect would look like:

MaskFilter filter = new EmbossMaskFilter(direction, 0.8f, 15f, 1f);

Adding to TextView

Using this filter on TextView is quite simple, and you can do so in one of two ways…

Subclass TextView

If you plan to use this style a lot throughout an application, I would recommend a simple subclass of TextView to encapsulate the functionality.  Something like this would do the trick:

public class ShadowTextView extends StyledTextView {

    public ShadowTextView(Context context) {
        super(context);
        init();
    }

    public ShadowTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ShadowTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        float[] direction = new float[]{0f, -1.0f, 0.5f};
        MaskFilter filter = new EmbossMaskFilter(direction, 0.8f, 15f, 1f);
        getPaint().setMaskFilter(filter);
    }
}

The filter is created with the view and attached to its Paint object so it will be used for all future text drawings.

Modify an Existing Paint

Because TextView provides a public method to access its Paint object, you can also apply the filter directly without a subclass like so:

TextView tv;
MaskFilter filter = new EmbossMaskFilter(new float[]{0f, -1.0f, 0.5f}, 0.8f, 15f, 1f);
tv.getPaint().setMaskFilter(filter);

Depending on when you do this in the layout/drawing cycle of your application, you may want to also call invalidate() on that view to ensure it redraws with your new filter.

Keep Tweaking

Applying this filter is not quite an exact science.  Especially since we are using this filter in a way slightly other than it was originally intended.  Spend some time tweaking the secondary parameters of the filter and see how they affect your application results!

The following link includes a test application you can run on your device to play with the different parameter settings and see how they affect the TextView output: TextMaskFilter.apk

Monitoring EditText Sessions

Something that has bothered many Android developers (including myself) since the dawn of the soft keyboard is having a way to track the simple event of when a user is done editing the text in a text field.  Depending on your application, there may be actions you need to take when a user has completed text entry in a field; and these actions are too granular to wait the Activity state to change and not granular enough to act with a TextWatcher on every keystroke the user makes.  Sadly, there are no discreet callbacks that an application can register for to determine when Android’s InputMethodManager and the associated InputConnection used to feed keyboard data into any given TextView changes state between active and inactive.

Oh, there are methods you can query such as InputMethodManager.isActive() to get this information, but no real cues from the system as to when a good time to call these methods might be. Also, these methods do not return information directly related to whether the soft input method is active (the isActive() implementation will return true as long as there is an active InputConnection (blinking cursor) in a given View).

A Simple Widget Solution

In lieu of good session tracking APIs; to solve this problem, I created a simple little widget that I call SessionMonitorEditText.  It’s a subclass of EditText that operates on the premise that there are three main events under which you can expect the user is done editing the data in the field:

  1. The user has pressed the action button of the keyboard (Done, Next, etc.)
  2. The user has pressed the back button to dismiss the keyboard
  3. The user has tapped another EditText or focusable item to start editing that

SessionMonitorEditText is designed to track each of these events, and fire a single callback in any of the events to notify the application that the user has finished editing that field.  The full source for this widget is available at the end of this article, but here is a short discussion of the salient points.  First, a new interface is created so the application can register to be notified of these “session complete” events.

public interface OnEditSessionCompleteListener {
  public void onEditSessionComplete(TextView v);
}

To handle the first user case, we have the EditText implement OnEditorActionListener, which will tell us when the user hits the action button on their keyboard. We must also handle the special case where the action is simply a return to move to the next line in Multi-Line fields:

@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
  //Forward the event, if necessary
  boolean ret = false;
  if(mActionListener != null) {
    ret = mActionListener.onEditorAction(v, actionId, event);
  }

  //Any default editor action typically ends the session,
  // except for returns in a multi-line field
  boolean returnAction = (actionId == EditorInfo.IME_NULL);
  boolean multiLine = (getInputType() & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
  if(!returnAction || !multiLine) {
    notifySessionListener();
  }

  return ret;
}

To handle the second user case, we rely on overriding the TextView.onKeyPreIme() method.  This method notifies us of a key event before it is dispatched to the InputMethod, and we use this to look for BACK presses:

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
  //If it's a BACK key, this will end the session
  if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
    notifySessionListener();
  }

  return super.onKeyPreIme(keyCode, event);
}

Finally, the third user case is managed by monitoring the focus of the EditText.  If the user taps on another EditText or focusable item, this view will lose focus:

@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
  //View will lose focus if use taps another field to edit
  if(!focused) {
    notifySessionListener();
  }

  super.onFocusChanged(focused, direction, previouslyFocusedRect);
}

All of these tracking methods call into the same place, which sends a notification to the registered listener that this particular view is no longer the active editing view.

private void notifySessionListener() {
  if(mSessionListener != null) {
    mSessionListener.onEditSessionComplete(this);
  }
}

Note that in all of these cases the callbacks do not consume the events and attempt to forward them on to the default implementations.  We do not want to intercept normal behavior, which reduces the ubiquity of our custom widget, we simply want to monitor for certain user events to fire another event of our own.

Link: SessionMonitorEditText.java

Simplifying Interaction with Contacts

With the introduction of the new ContactsContract to replace the previous Contacts API in Android 2.0, came a drastic increase in both functional capability and API complexity for applications wishing to interact with the Contacts Provider.  This level of access allows applications to make very detailed and specific additions and updates to the Contacts/People database on the device; however it can make some of the common simpler operations seem like they require an unnecessary amount of code.

We are going to look at some ways to try to simplify some common interactions that an application may need to have with the Contacts Provider through the use of a few Intents that ContactsContract provides.  In this article, we are going to use Intents to let the existing device Contacts UI handle the following operations, simply returning a result to our application where appropriate:

  • Selecting a contact
  • Adding a new contact
  • Searching for an existing contact to edit

The ContactsContract API certainly provides all the functionality necessary for you to build your own UI to manage the data your application is interested in by way of making queries, updates, and inserts on the URIs for each table in the Content Provider.  However, in many cases the system-defined UI for these tasks is not only adequate, but helps provide a consistent experience to the user.

Selecting (Picking) A Contact

This operation involves displaying a list of the the users contacts so they can select one to perform further actions on (perhaps to send that person and email or call them).  Rather than creating your own picker UI, you can use the following code to create an Intent and launch the Contacts application’s existing picker:

Intent intent = new Intent(Intent.ACTION_PICK);
intent.setData(ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT);

Since we have launched the new Activity expecting a result, onActivityResult() will be called when the user is finished, and the data Uri will contain a lookup URI that can be used to inspect the details of the Contact selected by the user:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if(requestCode == PICK_CONTACT && resultCode == RESULT_OK) {
    //returns a lookup URI to the contact just selected
    Uri selectedContact = data.getData();
  }
}

Adding A New Contact

This operation involves inserting a new contact on the user’s device.  Due to the large number of tables involved in storing all the common kinds of data (phone, email, address, etc.), constructing this insert using the raw provider queries can be quite daunting.  ContactsContracts provides a series of actions and extras in the ContactsContract.Intents.Insert utility class.

Intent intent = new Intent(Intent.ACTION_INSERT);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);

// Just two examples of information you can send to pre-fill data
intent.putExtra(ContactsContract.Intents.Insert.NAME, "Dave Smith");
intent.putExtra(ContactsContract.Intents.Insert.COMPANY, "Xcellent Creations, Inc.");
startActivityForResult(intent, ADD_CONTACT);

There is a host of extras defined in ContactsContract.Intents.Insert that can be used to pre-fill data in the add form, but here is a list of some more common choices:

  • ContactsContract.Intents.Insert.NAME
    • Full name of the new contact
  • ContactsContract.Intents.Insert.COMPANY
    • Company/Organization of the new contact
  • ContactsContract.Intents.Insert.EMAIL
    • Email address of the new contact
  • ContactsContract.Intents.Insert.PHONE
    • Phone number of the new contact
  • ContactsContract.Intents.Insert.POSTAL
    • Address for the new contact

As with the picker, launching the new Activity with a result expectation will call onActivityResult() with a lookup Uri for the newly created Contact record, so that your application can display the details of the insert if you wish.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if(requestCode == ADD_CONTACT && resultCode == RESULT_OK) {
    //returns a lookup URI to the contact just inserted
    Uri newContact = data.getData();
  }
}

Search For A Contact

Finally, the ContactContract API includes another convenience intent to look for a specific contact record by telephone number or email address.  The Intent action ContactsContract.Intents.SHOW_OR_CREATE_CONTACT can be passed with a mailto: or tel: scheme Uri, and it will search for a matching contact.  If a match is found, the contacts details are displayed for further review or editing (all within the standard Contacts application UI).  If no match is found, by default a dialog will be displayed asking if the user would like to create a contact with the given email or phone.

Intent intent = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT);
//PICK ONE of the following:
//For searching via telephone number
// intent.setData(Uri.fromParts("tel", "8001234567", null));
//For searching via email address
// intent.setData(Uri.fromParts("mailto", "johndoe@gmail.com", null));

//We'll use an email search for the example
intent.setData(Uri.fromParts("mailto", "johndoe@gmail.com", null));

startActivity(intent);

The same extra parameters may be passed along with the Intent to pre-fill the entry form in case the matched contact is edited or if the user opts to create a new contact.  In addition, a special extra is defined (ContactsContract.Intents.EXTRA_FORCE_CREATE) that allows you to skip over the user dialog and immediately open the create contact form if no matches are found.  Here is an example that includes some fields to be pre-filled and skips the dialog for creating a new contact:

Intent intent = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT);
intent.setData(Uri.fromParts("tel", "8001234567", null));

//If no match and we create, skip the user prompt dialog
intent.putExtra(ContactsContract.Intents.EXTRA_FORCE_CREATE, true);

//Values to tag along for an insert
intent.putExtra(ContactsContract.Intents.Insert.NAME, "David Smith");
intent.putExtra(ContactsContract.Intents.Insert.COMPANY, "Xcellent Creations, Inc.");

startActivity(intent);

Notice in this case that we aren’t interested in any result returned by the Contacts application. With the SHOW_OR_CREATE_CONTACT Intent, no interesting data is returned from the operation.

Hopefully these tips will help take the complexity out of using the Contacts API for those cases where your application does not need to utilize the full access of the Content Provider!

A Developer’s Adventures in Rooting

When I started developing applications for Android in early 2009, I never expected that I would ever own a rooted device.  I felt that, as a developer, I needed to be testing my applications in an environment that best emulated my users, and rooting would compromise that environment.  However, recently I have been forced to take a second look at that opinion in light of some unfortunate barriers Android developers face today.  Let me also go on record to say that these are made unfortunate because I believe that they should not exist at all, and would not exist with Google and device OEM support.  This is the story of my journey to solve a major problem, and how rooting was the only viable solution.

The Problem

When I set out on this journey, I did so because there were a number of vital debugging tasks that I simply could not do on any physical device; and I was tired of resorting to the emulator just to perform them.  As a goal, the primary tasks I wanted on my devices were:

  • Using DDMS File Explorer to view my application’s internal storage (the data directory where files and databases created by an application are stored)
  • Using the HierarchyViewer tool to debug issues with layouts
  • Running ADB in TCP/IP mode (I will explain the importance of this one more later)

I didn’t feel like these demands were unreasonable, they are all a necessary part of developing applications.  When I look back, it’s a bit of a shock to me that this is even a problem.  My first test phone was the ADP1; the first developer phone that could be purchased directly from Google by registered Android Market developers.  This phone set a great precedent because the device was completely unlocked and was running a userdebug build of Android; meaning that all the things I listed above were possible on the device without even a second thought.

Nowadays, the “developer phone” is a thing of the past, and you can no longer select and purchase phones already set up for userdebug in the Android Market portal.  Today, if you want to develop applications on a device, you must purchase a commercial device from a retail location.  This means that you can not perform any of these useful debugging functions directly on a device…where they really count.

Google instead now provides support via instructions for building userdebug images of the AOSP from source for their latest devices (the Nexus One and Nexus S at the time of this writing).  This seems helpful on the surface, except most application developers do not have the full AOSP build environment set up on their systems.  In the days of the ADP1, these images were at least available on the HTC dev site built and ready to be flashed!  But I digress…

The Journey Begins…

Just to establish context, I currently have the following devices that I use for developing applications:

  • HTC ADP1
  • Original Motorola Droid
  • HTC EVO 4G
  • Samsung Nexus S 4G
  • Motorola Droid 2

None of these devices, except the ADP1, are set up to allow proper access to use the debugging tools I wanted.  I decided that, since these issues were primarily due to lack of permissions on the device (like the inability for ADB to run in root mode), perhaps these problems could be solved simply by rooting one of my test devices.

Step 1: Root

I chose my OG Droid as the device to use.  It had been around the longest, so there should be plenty of information out in the community about rooting it.  Plus, if something happened to it in the process I wouldn’t be super sad.  I used a program for the PC name SuperOneClick to root the device and install SuperUser, an Android application that helps manage root access to installed applications.  I didn’t realize it at the time, but SuperOneClick also installed BusyBox on the system.  I’ll explain a bit later why this was actually a bad thing.

With my device rooted, I went to test the success of my efforts by attempting to run ADB over WiFi on my device…success!  I could now run the adb tcpip <portnum> command and subsequently adb connect <ipaddress:portnum> from my development machine to debug applications without being tethered to the actual device.  In fact, with root access I could now even install one of the many applications available on Android Market that would enable/disable this service for me so I didn’t need to drop into the shell every time.

I was so excited I couldn’t wait to try out HierarchyViewer and File Explorer…but for some reason, these tools still did not work on the device.  I was confused as to what other piece of the puzzle could be required to get things up and running.

ADB WiFi

Besides the simple convenience of the matter, the primary reason that running ADB over WiFi was so important to me is because oftentimes I’m developing with Android in a context where the USB connector is not accessible.  The two main examples of that are when I’m developing with an accessory device connected (like the ADK) or when the Android device is encased or embedded in some larger system where the mechanical enclosure obstructs the USB connector.  In these cases, the prospect of debugging wirelessly makes the development process infinitely easier.

Step 2: Userdebug

It didn’t take much research to find out that the reason these tools no longer work is because the necessary features required to run them are explicitly disabled in production or “user” builds of the Android OS (this statement is made directly by Googlers from the Android Team all over the web).  Instead, an engineering (“eng”) or “userdebug” build is required to be installed on the device.  One of the primary functions needed was the ability to run the ADB daemon as root (try typing <adb root> with your device plugged in and you’ll see what I mean).

This meant I needed to install a custom system ROM image.  Luckily, my device had already been rooted, which is the first step needed to install said custom image.  However, finding the image to load would prove a bit more difficult.  For most production phones, a stock userdebug image doesn’t seem to exist, although if you own one of the Nexus phones it seems you can undertake the task of building one yourself from source (see the link at the beginning of this post).  I found the answer in the modding community via Cyanogenmod.  While I don’t believe most of the popular custom ROMs out there (Cyanogenmod, MIUI, etc.) are technically built as userdebug (the ro.build.type property is still set to “user” on devices I’ve seen), they are built to allow the processes like ADB the full system access I needed it to have.  I used the very helpful ROM Manager application (available in Android Market) to handle the necessary steps of flashing a proper recovery image and installing Cyanogenmod.

The initial attempt to load the Cyanogenmod binary resulted in a phone that would not boot past the initial animation, but a second attempt (manually going into the recovery menu and applying the update again) yielded successful results.  So how was I faring with my laundry list now?  With Cyanogenmod installed, I was finally successful in attaching to HierarchyViewer on the device!  However, I was still unable to read anything other than the SD Card via File Explorer.

I was 2 for 3, but completely puzzled about what else I could even customize on the device to allow me proper access inside of DDMS.  I could drop into the shell on the command line and view all the files in all the directories on the device…so why couldn’t DDMS?

ViewServer

I wanted to make a quick note about the solution that came out of the Android team recently with regards to using HierarchyViewer on devices.  The ViewServer project, hosted by Romain Guy, is a version of the internal Android ViewServer that can be dropped into individual applications in order to enable the use of HierarchyViewer on devices.  I greatly appreciate the effort put forth to provide this to the community, but I still believe it is the wrong solution.  Developers should be able to configure their devices to freely use these tools globally throughout the system.

Step 3: Replacing System Binaries

After doing even more research, I came across an obscure set of posts on a few forums indicating that the system Toolbox was the culprit.  I mentioned earlier that SuperOneClick installed BusyBox on the device, which replaced the default system Toolbox for all primary commands.  Cyanogenmod also relies on BusyBox, so even if the rooting hadn’t done it, I have a feeling I would have ended up with BusyBox by this point anyway.

What Are All These Boxes?

If you’re unfamiliar with what all these “boxes” are, let me explain.  Linux systems typically have a collection of individual utility programs in the system directories that are invoked from the command line.  Commands like “ls”, “cat”, “ssh”, etc. are actually binaries that get executed to perform those specific tasks.  When Linux found its way into mobile and embedded systems, this collection of tools took up far too much space.  Enter BusyBox and Toolbox.  BusyBox and Toolbox are examples of single binary applications that implement a host of these common commands in a very tiny package, making it suitable for embedded systems.  In short, one of these two binaries is where you get your command line capability from in the Android shell.

The hidden problem that this created is that the BusyBox version of “ls” (which is what DDMS File Explorer uses) does not support all the functions DDMS expects to find (I believe the primary missing feature is file sorting) in the Toolbox version of “ls”.  So in order to solve this problem, I had to manually copy a good version of Android’s Toolbox back onto the device and re-link “ls” to point to it instead of BusyBox.  I obtained the correct copy of Toolbox from an emulator instance running the same base OS version and transferred the file to the system/bin directory of the device.  I then had to enter the shell on the device in order to remap the symbolic link for “ls” in the system/xbin directory to point to system/bin/toolbox.

Finally, I now could browse the device’s file system properly from within the DDMS File Explorer!  I now had a device worthy of being used for debugging!

The Plea

Needless to say, this process was an absolute pain; especially considering my position that the whole thing should not have been necessary.  Android developers should not have to be linux gurus or developing at the firmware level of the AOSP in order to have access to the full set of SDK debugging tools on their devices.  They key missing element is that developers need easy access to flash-ready system images that enable the userdebug features of the OS and devices that easily allow these images to be flashed.  Devices like the Nexus One, Nexus S, and some of the newer HTC devices that allow easy unlocking of the bootloader is a step in the right direction, but we need Google and the OEMs to step up and help provide the tools to finish the job.  Your community is counting on you!

Update: November 2011

As a comparison study, I spent some time going through the process of converting a “commercial” Nexus S 4G device (originally released via Sprint) to a userdebug build from the AOSP code. Comparatively, the process was about the same amount of effort, just spread out over a different set of tasks. The primary advantage here is that I was walking through a fairly well documented set of steps instead of researching each step one after the other…but the process still took a full day’s work to get initialized.

The primary piece of heavy lifting required to go through this process was to set up the build environment for the AOSP. If I can provide any advice here it is to build your environment inside of Ubuntu 10.04…and nothing else. I started initializing the environment inside of an Ubuntu 8.04 instance, and ran into multiple issues in setting up the build chain that ultimately pointed to required packages I could not get without an upgrade. I imagine trying to set up the environment in something even more different would result in even more headaches…hardly worth it if your doing this for a simplistic purpose such as this.

Once the environment is initialized, the non-AOSP proprietary drivers and binaries that each device requires must be unpacked and loaded into the code base manually before the image is built. Again, this process is well documented and there are scripts to extract each driver into the proper location…but it still must be done individually for each device you plan to build for. Beyond that, the process is simple and requires little interaction, even though there is a lot of waiting (syncing the source and building the target code). With about 6 command-line commands you have built and flashed the image onto the device.

If I had to recommend a method, I would choose this avenue just because it caused less frustration. However, you will be limited to the 3-4 devices at any one time that has current support in a branch of the AOSP source tree.

Synchronizing ScrollView

Recently, a post was made by Kirill Grouchnikov on his blog providing details on how the Android Market app uses a synchronized scrolling technique to take a certain view from inside the ScrollView content and let it float to the top of the screen when it would have otherwise been scrolled off-screen.  This post is meant to elaborate on his details with a splash of sample code.  Here are some screenshots of what we’re looking to accomplish:

          

Doing this hinges primarily on a simple custom subclass of ScrollView that tracks the onScrollChanged() method callback to keep all views in sync.  We could use this class to expose these scroll changes as a public interface, but in this example we attach the views of interest and do all the tracking inside the custom class.

package com.examples.synchronizedscrolling;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;

public class SynchronizedScrollView extends ScrollView {

    private View mAnchorView;
    private View mSyncView;

    public SynchronizedScrollView(Context context) {
        super(context);
    }

    public SynchronizedScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SynchronizedScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Attach the appropriate child view to monitor during scrolling
     * as the anchoring space for the floating view.  This view MUST
     * be an existing child.
     *
     * @param v View to manage as the anchoring space
     */
    public void setAnchorView(View v) {
        mAnchorView = v;
        syncViews();
    }

    /**
     * Attach the appropriate child view to managed during scrolling
     * as the floating view.  This view MUST be an existing child.
     *
     * @param v View to manage as the floating view
     */
    public void setSynchronizedView(View v) {
        mSyncView = v;
        syncViews();
    }

    //Position the views together
    private void syncViews() {
        if(mAnchorView == null || mSyncView == null) {
            return;
        }

        //Distance between the anchor view and the header view
        int distance = mAnchorView.getTop() - mSyncView.getTop();
        mSyncView.offsetTopAndBottom(distance);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //Calling this here attaches the views together if they were added
        // before layout finished
        syncViews();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if(mAnchorView == null || mSyncView == null) {
            return;
        }

        //Distance between the anchor view and the scroll position
        int matchDistance = mAnchorView.getTop() - getScrollY();
        //Distance between scroll position and sync view
        int offset = getScrollY() - mSyncView.getTop();
        //Check if anchor is scrolled off screen
        if(matchDistance < 0) {
            mSyncView.offsetTopAndBottom(offset);
        } else {
            syncViews();
        }
    }
}

This example keeps track of two views, which must be laid out as children of the ScrollView (we’ll see an example layout in a minute).  We’ll call these views the anchor view and the synchronized view.  The synchronized view is the view that will float to the top of the screen when scrolled.  The anchor view is a view that lives inside the list of content and represents the space and location where the synchronized view should be if it weren’t floating.

The two workhorse methods in this example are syncViews() and onScrollChanged().  The job of syncViews() is to align the position of the floating view with its anchor.  We use this to align the views when everything is initialized, as well as when scrolling occurs and the anchor view is fully visible.

The onScrollChanged() callback is overridden to track the user’s scrolling and slide the view accordingly.  We calculate two important distance values with each call.  The first is the distance between the anchor view and the current top visible scroll position.  We use this value to determine if the anchor view is visible or has been scrolled off-screen.  If this value is negative, then the anchor view is at least partially scrolled off-screen, and we need to set the position of the synchronized view as an offset.  The second calculated value is the distance between the current visible scroll position and the current position of the synchronized view.  This is the value we apply as the offset to the synchronized view using offsetTopAndBottom().  As previously mentioned, when the anchor distance is zero or greater, the view is fully visible and the floating view should be aligned with the anchor’s position.

The Layout

The key to making this custom scrolling work is that the two views we are tracking must be laid out as children.  Here is the layout used in the example:

main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <com.examples.synchronizedscrolling.SynchronizedScrollView
    android:id="@+id/scroll"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <FrameLayout
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
      <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <View
          android:layout_width="fill_parent"
          android:layout_height="65dip"
          android:background="#A00"/>
        <View
          android:id="@+id/anchor"
          android:layout_width="fill_parent"
          android:layout_height="65dip"
          android:background="#AAA"/>
        <View
          android:layout_width="fill_parent"
          android:layout_height="225dip"
          android:background="#0A0"/>
        <View
          android:layout_width="fill_parent"
          android:layout_height="225dip"
          android:background="#00A"/>
        <View
          android:layout_width="fill_parent"
          android:layout_height="225dip"
          android:background="#A00"/>
        <View
          android:layout_width="fill_parent"
          android:layout_height="225dip"
          android:background="#0A0"/>
      </LinearLayout>
      <TextView
        android:id="@+id/header"
        android:layout_width="fill_parent"
        android:layout_height="65dip"
        android:gravity="center"
        android:text="Heading View"
        android:background="#A555" />
    </FrameLayout>
  </com.examples.synchronizedscrolling.SynchronizedScrollView>
</FrameLayout>

Since ScrollView can only have one child, we use a FrameLayout to contain everything.  The traditionally scrollable list of items (here, represented as Views in assorted colors) are laid out in a vertical LinearLayout.  The View to be synchronized is laid out as a sibling of this list, but still contained in the FrameLayout

NOTE: The reason we do this instead of letting the LinearLayout be the sole child and let the floating view live outside the ScrollView completely has to do with scrollbars.  If all these views are not children of the ScrollView, then they will be drawn over the top of the scrollbars when they are visible.  This, as Kirill mentions in this post, is ugly.

Tie It Together

Finally, let’s take a look at the sample Activity that brings these pieces together to make the lovely screenshots we saw at the beginning of this article.

package com.examples.synchronizedscrolling;

import android.app.Activity;
import android.os.Bundle;

public class ScrollingActivity extends Activity {

    private SynchronizedScrollView mScrollView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mScrollView = (SynchronizedScrollView)findViewById(R.id.scroll);

        mScrollView.setAnchorView(findViewById(R.id.anchor));
        mScrollView.setSynchronizedView(findViewById(R.id.header));
    }
}

You can see there isn’t much to do here, since we do the heavy lifting inside our SynchronizedScrollView.  We simply need to attach the to views from our layout that represent the anchor and the floating view.

Multiple Clickable Zones in ListView Items

Developers often have a need to create rows in a ListView that have multiple interactive locations that the user can touch, instead of just one single clickable row.  This is a pattern that even Google has employed in apps like the DeskClock. DeskClock’s Alarm tab display each list item with a small toggle button inside, used to enable or disable each alarm.  In addition, the remainder of the list item is also touchable and takes the user to a screen to edit the alarm parameters.

                    

Taking a quick survey of questions and answers found on the internet, you may discover that many will say what I just described above is impossible.  The common method for adding a clickable item is to add a Button or ImageButton to a ListView row’s layout.  The side-effect of doing this is that the remainder of the list item is no longer selectable and this renders your OnItemClickListener useless!  But then, how is it that Google was able to accomplish this same task??  Luckily for us, Android is an open source project, so we can inspect what was done in DeskClock, and learn from it.

The Problem is Focus

As developers, we all have the same natural tendency to pick either a Button or ImageButton when we want to implement a clickable widget anywhere…including inside of ListView.  Sadly, this is where the downfall begins.  Button and ImageButton are not just subclasses of TextView and ImageView with their CLICKABLE flag enabled; they are subclasses with their CLICKABLE and FOCUSABLE flags enabled.  ListView, by design, does not pass perform click events on list items when those items contain FOCUSABLE views, regardless of how you configured any of its other flags (ListView actually protects the PerformClick method by first checking hasFocusable() on any list item).

Bottom line, in order to keep access to the default behavior of ListView, we need to use child views in our row layouts that are CLICKABLE without being FOCUSABLE.

Quick and Dirty

One option to circumvent this and still use typical buttons would be to create layouts that are nothing but Buttons, so that no area of the item layout exists that is not clickable.  This method, IMO, has one major drawback in that you completely lose the position tracking ListView provides you for any of the item clicks and you will have to manage that all yourself in the Button’s OnClickListener methods.  However, in some cases this method may be desirable and I would suggest that in those cases that you call setItemsCanFocus(true) on your ListView.  If you plan to fully obstruct the root layout with Buttons, this method will allow D-Pad and arrow focus changes to slide across all your buttons appropriately just like it was the list item itself.

Losing the Focus

So how do we implement clickable views that don’t also try to take focus?  Simple!  Any View object has the ability to be made clickable by calling setClickable(true) in Java or adding android:clickable=”true” in XML.  Consider replacing a Button with a TextView made clickable, or an ImageButton with an ImageView made clickable.  Make a composite button out of some widgets inside a LinearLayout and make the layout itself clickable.

NOTE: There is a shortcut here as well.  You may not be aware of the fact that, anytime you call setOnClickListener() on any View (even if the parameter is null), it will automatically set the clickable flag for the View for you.  Since, in most cases you are probably making something clickable so you can listen for the event, you don’t need to explicitly mark the flag on the views in your layout.

Here is an example of a layout that includes two ImageViews that we turn into clickable accessories by setting their clickable attribute:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="?android:attr/listPreferredItemHeight">
  <ImageView
    android:id="@+id/left"
    android:layout_width="?android:attr/listPreferredItemHeight"
    android:layout_height="fill_parent"
    android:src="@drawable/icon"
    android:scaleType="center"
    android:clickable="true"
    android:background="@drawable/mybutton" />
  <TextView
    android:id="@+id/text"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    android:gravity="center"
    android:textAppearance="?android:attr/textAppearanceLarge" />
  <ImageView
    android:id="@+id/right"
    android:layout_width="?android:attr/listPreferredItemHeight"
    android:layout_height="fill_parent"
    android:src="@drawable/icon"
    android:scaleType="center"
    android:clickable="true"
    android:background="@drawable/mybutton" />
</LinearLayout>

When this layout is used as the list item view, the item itself as well as each individual ImageView are tappable by the user.

          

There may still be cases where you want to use the traditional Button (to keep its styling, perhaps).  By removing the FOCUSABLE flag from the Buttons, they can be safely added to the row layout while keeping the list item itself interactive.  This can be accomplished by calling setFocusable(false) from Java or android:focusable=”false” in XML.  Here is an example of another list item layout that uses Buttons with their FOCUSABLE attribute removed:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="?android:attr/listPreferredItemHeight"
  android:gravity="center_vertical">
  <TextView
    android:id="@+id/text"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:textAppearance="?android:attr/textAppearanceLarge" />
  <Button
    android:id="@+id/left"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:focusable="false"
    android:text="Accessory1" />
  <Button
    android:id="@+id/right"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:focusable="false"
    android:text="Accessory2" />
</LinearLayout>

With both of these examples, the list item itself is still clickable and that event will register with the ListView’s OnItemClickListener.  The accessory views will need listeners attached to them to monitor for their click events.  Download the sample code attached to this post and you can see an example of attaching separate listeners to these views, including using tags to track the row position.

A Note About Drawables

You may have noticed in some of the screenshots of the examples above that the drawable states of the selectable buttons mirror that of the parent layout.  When the user presses the parent list item, the child buttons themselves are also highlighted.  Depending on your particular application, this may be an undesirable effect.  To find a solution to this problem, we turn again to the AOSP code for DeskClock.  The accessory in each list item does not highlight when the alarm time is pressed.  So how do we achieve this?

The key here is to define a custom widget that overrides the behavior of setPressed() to ignore the calls to set this state when they come from the parent layout.  Here is an example of a custom ImageView that we can use in our first example to handle this:

package com.examples.listzones;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;

public class NoParentPressImageView extends ImageView {

    public NoParentPressImageView(Context context) {
        this(context, null);
    }

    public NoParentPressImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setPressed(boolean pressed) {
        // If the parent is pressed, do not set to pressed.
        if (pressed && ((View) getParent()).isPressed()) {
            return;
        }
        super.setPressed(pressed);
    }
}

We can now insert this custom widget into the row layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="?android:attr/listPreferredItemHeight">
  <com.examples.listzones.NoParentPressImageView
    android:id="@+id/left"
    android:layout_width="?android:attr/listPreferredItemHeight"
    android:layout_height="fill_parent"
    android:src="@drawable/icon"
    android:scaleType="center"
    android:clickable="true"
    android:background="@drawable/mybutton" />
  <TextView
    android:id="@+id/text"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    android:gravity="center"
    android:textAppearance="?android:attr/textAppearanceLarge" />
  <com.examples.listzones.NoParentPressImageView
    android:id="@+id/right"
    android:layout_width="?android:attr/listPreferredItemHeight"
    android:layout_height="fill_parent"
    android:src="@drawable/icon"
    android:scaleType="center"
    android:clickable="true"
    android:background="@drawable/mybutton" />
</LinearLayout>

Now, the child views will only display their pressed states when actually pressed. They will no longer mirror the state of their parent.

          

Please feel free to download the sample code from this link to take a closer look at how all of this works together!

Quick Rounded Corners

Application developers often want to display images with rounded corners.  A common technique for accomplishing this in Android is to construct a new mutable Bitmap object, draw a rounded rectangle, and then draw in the image contents using transfer modes.  An example of a method that would accomplish this is below:

public Bitmap createRoundedCornerBitmap(float radius, Bitmap src) {
    //Create a *mutable* location, and a canvas to draw into it
    int width = src.getWidth();
    int height = src.getHeight();
    Bitmap result = Bitmap.createBitmap(width, height, Config.ARGB_8888);
    Canvas canvas = new Canvas(result);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

    RectF rect = new RectF(0, 0, width, height);
    paint.setColor(Color.BLACK);
    canvas.drawRoundRect(rect, radius, radius, paint);
    paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
    canvas.drawBitmap(src, 0, 0, paint);
    paint.setXfermode(null);

    return result;
}

This method is born out of a more generic way of applying solid mask shapes to images.  It just so happens that, in this case, the mask we are applying is a rounded rectangle shape.  However, this method does have one primary drawback in a lot of applications in that you must post-process each image to create another before displaying them to the user.  Perhaps there is a simpler way to do this that only affects the presentation layer?

Enter ClipPath

It turns out, we can add just a few lines of code to the way ImageView draws its contents to obtain the same behavior.  Below is a simple custom subclass of ImageView that applies this technique.

package com.examples;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.ImageView;

public class RoundedImageView extends ImageView {

    public RoundedImageView(Context context) {
        super(context);
    }

    public RoundedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        float radius = 25.0f;

        Path clipPath = new Path();
        RectF rect = new RectF(0, 0, this.getWidth(), this.getHeight());
        clipPath.addRoundRect(rect, radius, radius, Path.Direction.CW);
        canvas.clipPath(clipPath);

        super.onDraw(canvas);
    }
}

Notice here that we apply a simple rounded rectangle Path as the clip region on the canvas before allowing ImageView to continue to draw the image contents.  The clip of a Canvas can be thought of as the specific region where you want to allow drawing to occur.

Using A Custom Widget

Because this method involves reconfiguring how ImageView draws the image to the screen, it pretty much requires that you create a subclass.  This may worry some who are not used to working with custom widgets and views in Android.  Basically, any View you want to place in your layout that does not exist in android.view or android.widget must be full referenced in the XML.  So, if you build your layouts in XML, then all you need to change is the tag name, replacing it with the fully-qualified version representing the new widget.  For example, a traditional ImageView such as

<ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/placeholder" />

Doesn’t need a package name because it lives in android.widget.  It can be replaced by our new rounded corners version by changing the XML to

<com.examples.RoundedImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/placeholder" />

Now we have a reusable widget that draws images with rounded corners without needing to manipulate the underlying image data.  I would urge you to experiment further with both of these techniques to create more creative masks and clips on your views.

Manufacturing Unique R.id Values

If you have been developing in Android for any length of time, you’ll most likely be aware that one of the most useful portions the resource framework is the fact that Views and other resources can be given an android:id tag in their XML declaration, and Android will make sure to compile all the ids into unique integer values that can be accessed from Java code by calling R.id.. This allows us to reduce the number of event listeners an application requires because it can uniquely identify the view that triggered the event by matching its id.  And, of course, we prefer using integer ids for this as opposed to matching Objects or String tags because the ids fit nicely into switch statements!

For example, a layout with two buttons, both pointing at the same action method:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <Button
    android:id="@+id/firstButton"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Click Me!"
    android:onClick="onButtonClick"
  />
  <Button
    android:id="@+id/secondButton"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Click Me Too!"
    android:onClick="onButtonClick"
  />
</LinearLayout>

We can determine which button was pressed in the Activity by checking the sender’s id:

public class MyActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    public void onButtonClick(View v) {
        switch(v.getId()) {
        case R.id.firstButton:
            //Handle first button click
            break;
        case R.id.secondButton:
            //Handle second button click
            break;
        default:
            break;
        }
    }
}

This is not breaking new, and is just one of many useful properties of android:id…
But what if we didn’t use XML to create our layouts/menus/etc.?  What if we created a series of Views in Java code?  Are we destined to go wanting without the assistance of the unique R.id generator?  Certainly not!

Making id Elements Yourself

To reserve a set of ids for your own use in application, simply create an ids.xml file in the res/values directory of your project.  The syntax coupled with creating this file is pretty self-explanatory

res/values/ids.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <item type="id" name="firstButton"/>
  <item type="id" name="secondButton"/>
  <item type="id" name="thirdButton"/>
</resources>

Now let’s take these fresh ids and add them to a series Buttons created in a dynamic layout:

public class MyActivity extends Activity implements View.OnClickListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);

        Button button;
        button = new Button(this);
        button.setOnClickListener(this);
        button.setText("Click Me First!");
        button.setId(R.id.firstButton);
        layout.addView(button);

        button = new Button(this);
        button.setOnClickListener(this);
        button.setText("Click Me Next!");
        button.setId(R.id.secondButton);
        layout.addView(button);

        button = new Button(this);
        button.setOnClickListener(this);
        button.setText("Then Click Me!");
        button.setId(R.id.thirdButton);
        layout.addView(button);

        setContentView(layout);
    }

    public void onClick(View v) {
        switch(v.getId()) {
        case R.id.firstButton:
            //Handle first button click
            break;
        case R.id.secondButton:
            //Handle second button click
            break;
        case R.id.thirdButton:
            //Handle third button click
            break;
        default:
            break;
        }
    }
}

That’s it!  Just another practical application of a little known resource type in the Android framework.

The Missing Manual: Android Bluetooth RFCOMM

Bluetooth communication has been a part of the Android SDK since the 2.0 release late last year. The APIs available in the android.bluetooth package are primarily focused around three application functions:

  1. Discovering other devices
  2. Connecting to devices
  3. Transferring data using the RFCOMM layer

With this capability, developers can write applications that transfer data between devices using peer-to-peer connections, or even communicate with other Bluetooth enabled electronics.  The SDK provides a very good sample project geared towards illustrating these basic functions in the context of a chat program.  But with all the assistance the SDK documentation provides, there are a few important pieces that are missing from the big picture of working in the RFCOMM connection stack.

A Little Bluetooth Background

Bluetooth is a very flexible wireless standard enabling devices in close proximity to discover, connect, and transfer information across miniature peer-to-peer networks.  As part of the discovery and connection process, the Bluetooth stack relies on a protocol called Service Discovery (SDP) to gather information about the devices it is discovering to determine whether they have the right capabilities to warrant connecting with.  As part of SDP, devices must publish a UUID for each service they have available.

A typical discover/connect operation would have the following steps:

  1. Scan for discoverable devices in range
  2. Use SDP on a specific device to find the UUID of the service you want to connect with
  3. Connect to the specific device referencing your preferred service

RFCOMM is a layer of the Bluetooth Protocol Stack that provides serial data transfer services (via “sockets”).  It is the base of many of the common profiles such as Dialup Networking (DUN) and Serial Port Profile (SPP).  In essence, though, it is a way for developers to create a full duplex serial data stream between two points.

And Now…Back to Android

As previously mentioned, in order to establish a connection to a remote device using Bluetooth, you must be able to discover and reference a specific service record on that device.  This gives rise to two possible paths by which this can be done:

  1. Know the UUID of the remote service ahead of time
  2. Read the SDP responses to determine the UUID fo what you need

In the sample program provided in the SDK, the former option is used.  Because the application is a point to point link between two Android devices, and the sample works for both sides of the conversation, the UUID is hard-coded into the application.  This method does work, and in many cases is the proper way to handle things.  But what if the device your connecting to isn’t under your control, like an embedded sensor or media device?  Isn’t this why SDP exists in the first place?

It turns out, there are some holes in the current SDK API and documentation that make discovering unknown services a little difficult, but there are solutions.

Discovery via a Hidden Function

The Android SDK does include two methods in the BluetoothDevice class:

  • public boolean fetchUuidsWithSdp()
  • public ParcelUuid[] getUuids()

Both of these methods are designed to poke the underlying service and get references to the service records.  And in the current SDK (2.2 as of this writing) they are both hidden.  Niether of these methods can be called directly, even though they are in the code.  They have to be called using reflection, and there is no guarantee that they will stay the same in future version of the SDK since they are not public.

BluetoothDevice.getUuids() is a synchronous method, and will return an array of the UUID records to the caller directly.  BluetoothDevice.fetchUuidsWithSdp() is an asynchronous method, and you will have to register for the Broadcast Intent that the system fires for the available UUID that is found.

  String action = "android.bleutooth.device.action.UUID";
  IntentFilter filter = new IntentFilter(action);
  registerReceiver(mReceiver, filter);

Notice the misspelling of the word “bleutooth”, which is not a typo; that is the true Intent action string.  These Intents will include two extras that you can use to get the data you need

  • android.bluetooth.device.extra.DEVICE
    • Contains the BluetoothDevice that is being queried.
  • android.bluetooth.device.extra.UUID
    • Contains an array of ParcelUuid objects for the services.
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
    BluetoothDevice deviceExtra = intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
    Parcelable[] uuidExtra = intent.getParcelableArrayExtra("android.bluetooth.device.extra.UUID");
    //Parse the UUIDs and get the one you are interested in
  }
};

UUID via Failed Connect

The only currently exposed in the SDK that also calls fetchUuidsWithSdp() under the hood is BluetoothSocket.connect().  By creating and attempting to connect to a BluetoothSocket, you will get the same UUID Broadcast Intent, at which point you could read it, find the services you need, and then connect again.  This is a very counterintuitive method since a BluetoothSocket requires a service’s UUID to be created!

Closing Remarks

Hopefully, Android will clear up their documentation and expose the proper functions in future releases.  But for now, thanks to the visibility the AOSP gives us into the source code, we have a few workarounds to accomplish the goal of using Android to add rich connectivity into applications!

Adapting to Empty Views

Android has a pretty flexible framework for displaying data from a database, array, or other list structure using classes called Adapters.  There are plenty of great tutorials in the Android docs and throughout the web on how to use adapters to create custom behavior for display this data, so I’m going to focus on it’s less-discussed couterpart; the AdapterView.

Adapter Basics

Android uses an Adapter class to bind data from a data structure (like a database Cursor or an array) to a view that is specially designed to dynamically display the data as it moves and changes…this is the AdapterView.  There are many common subclasses of AdapterView (ListView, Spinner, Gallery, etc.), but they share the common function without any special overriding that I will discuss in detail today.

The Empty View

All AdapterView subclasses have a property that maintains a reference to a View object known as the empty view.  The point of this view is to be displayed when the Adapter bound to our data tells the AdapterView that the data structure has no data.  It does this either by just returning zero from getCount(), or through a more complex implementation of isEmpty().  Conceptually, it is then the role of the AdapterView to replace the view reserved for displaying content with this View, if it is not null.

Setting the Empty View for an AdapterView

You may call at any time AdapterView.setEmptyView(View v) on an AdapterView object to attach a view you would like to use as the empty view.  You may also call AdapterView.getEmptyView() at any time to verify if an empty view has been set.  That’s it…sort of.

There’s a trick…

As of this writing, one of the things the Android documentation doesn’t tell you is that the AdapterView simply manipulates the visibility properties of your content view and empty view based on the information it receives from the Adapter.  After determining which view to display, it simply calls setVisibility(View.VISIBLE) on that view, and calls setVisibility(View.GONE) on the other.  This is efficient and works just fine, but it requires that you create both views and insert them into a visible layout yourself.  AdapterView will not do this legwork for you.  You cannot assume that supplying an empty view to AdapterView means that it will insert that view in it’s place in the layout at runtime…

Example, please!

Let’s examine the basic ListView example to get a grip on things.  When you place a ListView in your layout per the SDK tutorials, it will look something like this:

File: main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
  <TextView
    android:id="@+id/empty1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:text="Nothing in the List!"
  />
</LinearLayout>

and the setup code in your activity would be like this:

ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

The key here is this works because both the ListView and it’s “empty view” are defined as children of the displayed root layout, so they are both laid out when the Activity is displayed.  Because of this, AdapterView’s method of simply manipulating visibility works perfectly.  Let’s modify the example now so that the layout XML looks like this:

File: main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
</LinearLayout>
File: empty.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
  android:id="@+id/empty1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:gravity="center_horizontal"
  android:text="Nothing in the List!"
/>

with the same setup code in our Activity.  Now, the layout process is not handling the setup and inflation of our “empty view”.  When our list is empty, AdapterView will manipulate the visibility of the two view objects…so the content view will become invisible.  However, with the empty view not attached to our layout, it will not show up anywhere on the screen (even though it has been set to View.VISIBLE)! In order for this second example to work properly, you would have to modify the Java code as follows:

ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  //New code to add the empty view to the visible layout
  LinearLayout rootLayout = (LinearLayout)findViewById(R.id.layout1);
  rootLayout.addView(empty, 0);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

Take-home reminder: AdapterView does nothing more for you than manage visibility between a content view and an empty view. You are responsible to making sure those views are laid out in the appropriate locations prior to utilizing this functionality. You could also subclass the AdapterView you are using to handle it, but many AdapterView classes are not as straightforward as you might think. I would highly recommend reading the docs and the Android source thoroughly before doing so.

Looking Forward

Knowing this about AdapterView is actually a bit exciting because it means we have yet even more flexibility at our fingertips.  This means that we are not forced to have the AdapterView content and empty views occupy the same space.  We could define our content at the top of the screen (like an image gallery), and display the empty view somewhere else, or even on top of the entire screen (like a popup saying “nothing to see here, move along”) restricting access to other UI elements in the view.