Quick Search Box is a feature introduced in Android 1.6 that I lauded in a previous post. If you are an Android developer, the good news is you can make QSB suggest results from within your applications.
The purpose of this post is to provide a human-understandable overview of the process that will allow you to integrate your application with QSB quickly and painlessly.
To get the job done you need to supply four things: an xml/searchable.xml file, a few entries in your manifest, a content provider that QSB will query for the data, and an activity to handle the taps on your suggestions. In addition to that you'll need to somehow communicate to your users that your application is searchable so that they enable it for search on their phones.
searchable.xml
This file, usually named as above and found in res/xml directory, seems to be the logical beginning of the story. It should look similar to this one:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/search_label"
android:searchSettingsDescription="@string/search_settings_description" android:includeInGlobalSearch="true"
android:searchSuggestIntentData="item" android:searchSuggestAuthority="com.burnayev.actioncomplete.search">
</searchable>
android:label defines the text that a user will see as the name of your application under More results... when QSB spews out its suggestions.
android:searchSettingsDescription is used as the description of your searchable items in Settings/Search/Searchable items (this is the place where your users will need to navigate to enable your app for QSB suggestions). This is what goes into the second line of the row. What goes into the first line (i.e. denotes the name of your application) you'll know in a moment.
android:includeInGlobalSearch="true" is the magic word that makes QSB care about your efforts.
android:searchSuggestAuthority tells "the system" what content provider to look for to get the search results from your application.
The Manifest Magic
You application's manifest has to declare two things: the content provider and the activity that's going to handle the taps on your suggestions in QSB.
The provider declaration looks like this:
<provider android:name=".SearchProvider" android:authorities="com.burnayev.actioncomplete.search"
android:syncable="false" />
and defines the provider class name (android:name) as well as the "authority" (android:authorities), which is an identifier that the system uses to figure out who provides what kind of data.
<activity android:name=".activity.GlobalSearchHandler" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
The snippet above says that GlobalSearchHandler (android:name) is going to handle QSB suggestion taps (intent filter android.intent.action.SEARCH).
Now the drum roll, please... android:label defines the name of your application in Settings/Search/Searchable items. And you thought it wasn't straightforward...
The Content Provider
This is the centerpiece of the story. It goes like this:
public class SearchProvider extends ContentProvider {
public static final String AUTHORITY = "com.burnayev.actioncomplete.search";
private static final String[] COLUMN_NAMES = new String[] { "_id", SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2, SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID };
private static final int SEARCH_SUGGEST = 0;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
}
@Override
public boolean onCreate() {
searchDAO = new SearchDAO(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String searchString = uri.getLastPathSegment();
MatrixCursor cursor = new MatrixCursor(COLUMN_NAMES);
List<Action> actions = // get your stuff, e.g. query a database or something
for (Action action : actions) {
Object[] rowObject = new Object[] { action.getId(), action.getName(), action.getProjectName(),
Constants.ACTION_ACTION_DETAILS, action.getId() };
cursor.addRow(rowObject);
}
return cursor;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}
Notice the AUTHORITY constant. QSB looks at authorities in searchable.xml and queries a matching provider.
COLUMN_NAMES array is used to define both the way your search suggestions are presented in QSB (SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_TEXT_2) and the manner a suggestion tap is processed by your application (SUGGEST_COLUMN_INTENT_ACTION, SUGGEST_COLUMN_INTENT_DATA_ID).
The query method is where you perform the real search within your application, build a Cursor, and return it for further processing by QSB.
You gotta decide on a communication mechanism between your search provider and suggestion taps handler. In this example I use SUGGEST_COLUMN_INTENT_ACTION to specify the intent and SUGGEST_COLUMN_INTENT_DATA_ID to specify the particular item id.
Handling Suggestion Taps
Last but not least you have to decide what to do with the taps on the QSB suggestions for your application. A possible approach is illustrated below:
public class GlobalSearchHandler extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String intentAction = intent.getAction();
Uri intentData = intent.getData();
Long id = null;
try {
id = intentData.getLastPathSegment() != null ? Long.valueOf(intentData.getLastPathSegment()) : null;
} catch (NumberFormatException e) {
// null id may be just fine or it may be not
}
if (id != null) {
Intent targetIntent = new Intent(intentAction);
// configure the intent based on the retrieved id
startActivity(targetIntent);
}
finish();
}
}
The noteworthy part of the code snippet above is this:
String intentAction = intent.getAction();
Uri intentData = intent.getData();
Both intent action and intent data are based on the cursor fields you set in your search provider. They allow you to properly delegate further processing to the appropriate part of your application.
By now you should have a working prototype of your application nicely integrated with Android Quick Search Box. It's time to tell your users to go to Settings/Search/Searchable items and enable your application for QSB search suggestions.
Monday, November 30, 2009
Friday, November 27, 2009
Google Maps Navigation vs Garmin Nuvi 265WT
I've been pleasantly surprised by a recent announcement about the back-port of Google Maps Navigation to Android 1.6, which all happy T-Mobile G1 owners are supposedly running.
For the few days since the announcement I've been test-driving the new application to get a feel of whether it's an epochal event or a blip on the technology radar.
I also happen to have Garmin Nuvi 265WT GPS unit at hand, which currently goes for $159.99 at Amazon, thus feeling to be in position to make comparisons and draw conclusions.
Without further ado I'm going to present my humble findings to the avid reader.
The best part of the deal with Google Maps Navigation is that it comes to your nearest Android phone at absolutely no cost, i.e. free no strings attached.
The worst part of the deal with Google Maps Navigation is that you get pretty much what you paid for.
Diving into the details now...
GMN (that's Google Maps Navigation) is nicely integrated with GM (that's Google Maps). You get your directions as usual, then choose Navigate option and you are in the game.
Soon after the device gets itself oriented a nice metallic female voice tells you something. After a little practice you come to realize that what is being said at this point is the direction to the start of the route such as "drive South-West ...". Garmin unit would just say something like "proceed to route", which is more succinct and understandable. Garmin's voice sounds much more human-like and is quite a bit more understandable.
Next goes the regular routine of voice commands that makes you feel good about your navigational abilities. I'm still trying to decide if I prefer quarter miles GMN style or decimal miles Garmin style. I do prefer "turn to Main Street" Garmin style announcement to "turn at Main Street" GMN style announcement.
I happen to live in an area where a lot of road construction is going on and was expecting GMN to beat Garmin to the punch with the accuracy of the maps. No such luck. Google maps are as inaccurate as one year old Garmin maps installed on my GPS unit.
My major expectation about a GPS navigation system is it's going to tell me where to go when I need it and that's exactly what my Garmin unit does. GMN does the same except when it doesn't feel like that... At one point on a test drive I realized that my phone was rebooting itself instead of telling me where to go.
GMN builds routes as quickly as Garmin but Garmin's routes are generally faster in terms of travel time and better in terms of convenience.
My Garmin GPS unit came with a nice dashboard mount and a car charger. My phone didn't. Unless you are voice-guiding your short office commute, which you probably know as the back of your hand, you'll want a charger. Unless you absolutely don't care about the route your GPS unit happened to choose, you'll want a dashboard mount. Both the charger and the mount are going to spoil that great "get-for-free" feeling for some.
There's been a lot of talk recently on how Google Maps Navigation is going to drive traditional providers of GPS navigation systems out of business. My brief testing makes me believe that as of now GMN is highly beneficial for Garmin, TomTom, and other GPS nav manufacturers as it essentially provides them with free marketing services demonstrating what one can expect from a GPS navigation system to a crowd that might have not ever considered such a system otherwise.
For the few days since the announcement I've been test-driving the new application to get a feel of whether it's an epochal event or a blip on the technology radar.
I also happen to have Garmin Nuvi 265WT GPS unit at hand, which currently goes for $159.99 at Amazon, thus feeling to be in position to make comparisons and draw conclusions.
Without further ado I'm going to present my humble findings to the avid reader.
The best part of the deal with Google Maps Navigation is that it comes to your nearest Android phone at absolutely no cost, i.e. free no strings attached.
The worst part of the deal with Google Maps Navigation is that you get pretty much what you paid for.
Diving into the details now...
GMN (that's Google Maps Navigation) is nicely integrated with GM (that's Google Maps). You get your directions as usual, then choose Navigate option and you are in the game.
Soon after the device gets itself oriented a nice metallic female voice tells you something. After a little practice you come to realize that what is being said at this point is the direction to the start of the route such as "drive South-West ...". Garmin unit would just say something like "proceed to route", which is more succinct and understandable. Garmin's voice sounds much more human-like and is quite a bit more understandable.
Next goes the regular routine of voice commands that makes you feel good about your navigational abilities. I'm still trying to decide if I prefer quarter miles GMN style or decimal miles Garmin style. I do prefer "turn to Main Street" Garmin style announcement to "turn at Main Street" GMN style announcement.
I happen to live in an area where a lot of road construction is going on and was expecting GMN to beat Garmin to the punch with the accuracy of the maps. No such luck. Google maps are as inaccurate as one year old Garmin maps installed on my GPS unit.
My major expectation about a GPS navigation system is it's going to tell me where to go when I need it and that's exactly what my Garmin unit does. GMN does the same except when it doesn't feel like that... At one point on a test drive I realized that my phone was rebooting itself instead of telling me where to go.
GMN builds routes as quickly as Garmin but Garmin's routes are generally faster in terms of travel time and better in terms of convenience.
My Garmin GPS unit came with a nice dashboard mount and a car charger. My phone didn't. Unless you are voice-guiding your short office commute, which you probably know as the back of your hand, you'll want a charger. Unless you absolutely don't care about the route your GPS unit happened to choose, you'll want a dashboard mount. Both the charger and the mount are going to spoil that great "get-for-free" feeling for some.
There's been a lot of talk recently on how Google Maps Navigation is going to drive traditional providers of GPS navigation systems out of business. My brief testing makes me believe that as of now GMN is highly beneficial for Garmin, TomTom, and other GPS nav manufacturers as it essentially provides them with free marketing services demonstrating what one can expect from a GPS navigation system to a crowd that might have not ever considered such a system otherwise.
Tuesday, November 17, 2009
Android Developer Tip: Regenerating R.java
I've just spent half an hour trying to fathom how to get back my auto-generated R.java that magically disappeared at some point.
The usual ways of resolving issues such as clean build, restarting IDE, and wiping the screen clean with a damp cloth didn't work.
Googling around did reveal a lot of similar complaints and revived a stark deja vu feeling. The hilarious thing is that once the R guy is gone, a good chunk of your code goes red thus adding to the insult.
Rather than continuing the discovery of all the conceivable strains of the issue found in the wild, I decided to leverage the tried-and-true approach of staring at the code. Having done so for a while I spotted a few red guys under res folder. They were a leftover of my current redesign work that I was about to delete before things went awry. Sure enough, as soon as I hit Del on them the darn R thing automagically reappeared.
The moral of the story is there's just so much magic Google can do for us. Bad resources (e.g. layouts with errors) are not treated gracefully by Google Android Eclipse plugin and can deceive it into deleting R.java with no sound reason.
The usual ways of resolving issues such as clean build, restarting IDE, and wiping the screen clean with a damp cloth didn't work.
Googling around did reveal a lot of similar complaints and revived a stark deja vu feeling. The hilarious thing is that once the R guy is gone, a good chunk of your code goes red thus adding to the insult.
Rather than continuing the discovery of all the conceivable strains of the issue found in the wild, I decided to leverage the tried-and-true approach of staring at the code. Having done so for a while I spotted a few red guys under res folder. They were a leftover of my current redesign work that I was about to delete before things went awry. Sure enough, as soon as I hit Del on them the darn R thing automagically reappeared.
The moral of the story is there's just so much magic Google can do for us. Bad resources (e.g. layouts with errors) are not treated gracefully by Google Android Eclipse plugin and can deceive it into deleting R.java with no sound reason.
Labels:
android
Subscribe to:
Posts (Atom)