Shahzad Bhatti Welcome to my ramblings and rants!

August 17, 2011

Implementing Application Search and Custom Suggestions in Android

Filed under: Android — admin @ 2:44 pm

Being a search company, Google provides search button on most Android devices and allows a simple API to create search bar in the Android application. In addition, you can also use suggestions API to add your own suggestions either from the database or remote service. In this blog, I will demonstrate how to add the search and suggestions to your Android application.

Searchable configuration

The first thing you would need to configure is searchable.xml in res/xml directory. Here is an example file:

 <?xml version="1.0" encoding="utf-8"?>
 <searchable xmlns:android="http://schemas.android.com/apk/res/android"
     android:label="@string/app_name" android:hint="@string/search_hint"
     android:searchMode="showSearchLabelAsBadge"
     android:searchSettingsDescription="suggests symbols"
     android:queryAfterZeroResults="true"
     android:searchSuggestAuthority="com.plexobject.service.SearchSuggestionsProvider"
     android:searchSuggestSelection=" ? " android:searchSuggestIntentAction="android.intent.action.VIEW">
     >
 </searchable>
 

Search Activity

Next you would create your search activity, which would be automatically started when a user hits the hard search button or search option from menu/actionbar.

 package com.plexobject.activity;
 
 import android.app.Activity;
 import android.app.SearchManager;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 
 import com.plexobject.R;
 
 public class SearchActivity extends Activity {
     private static final String TAG = SearchActivity.class.getSimpleName();
 
     public SearchActivity() {
     }
 
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.search_activity);
         this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_LOCAL);
 
         final Intent queryIntent = getIntent();
 
         final String queryAction = queryIntent.getAction();
         if (Intent.ACTION_SEARCH.equals(queryAction)) {
             this.doSearchQuery(queryIntent);
         } else if (Intent.ACTION_VIEW.equals(queryAction)) {
             this.doView(queryIntent);
         } else {
             Log.d(TAG, "Create intent NOT from search");
         }
 
     }
 
     @Override
     public void onNewIntent(final Intent queryIntent) {
         super.onNewIntent(queryIntent);
         final String queryAction = queryIntent.getAction();
         if (Intent.ACTION_SEARCH.equals(queryAction)) {
             this.doSearchQuery(queryIntent);
         } else if (Intent.ACTION_VIEW.equals(queryAction)) {
             this.doView(queryIntent);
         }
     }
 
     private void doSearchQuery(final Intent queryIntent) {
         String queryString = queryIntent.getDataString(); // from suggestions
         if (query == null) {
             query = intent.getStringExtra(SearchManager.QUERY); // from search-bar
         }
 
         // display results here
         bundle.putString("user_query", queryString);
         intent.setData(Uri.fromParts("", "", queryString));
 
         intent.setAction(Intent.ACTION_SEARCH);
         queryIntent.putExtras(bundle);
         startActivity(intent);
     }
 
     private void doView(final Intent queryIntent) {
         Uri uri = queryIntent.getData();
         String action = queryIntent.getAction();
         Intent intent = new Intent(action);
         intent.setData(uri);
         startActivity(intent);
         this.finish();
     }
 }
 
 

Note that you would need to implement viewing logic after the search in doSearchQuery method. Also, if your search service is being called from the search bar, you would get query string from SearchManager.QUERY parameter in the intent and if your search service is being called from the suggestion, the query string would be in getData or getDataString. You would then declare the activity in your AndroidManifest.xml such as:

         <activity android:name=".activity.SearchActivity"
             android:configChanges="orientation|keyboardHidden" android:label="@string/app_name"
             android:launchMode="singleTop">
             <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>
 

For all the activities that would be using search activity would declare SearchActivity in their definition inside AndroidManifest.xml such as:

         <activity android:name=".activity.MyActivity"
             android:configChanges="orientation|keyboardHidden" android:label="@string/app_name"
             android:launchMode="singleTop">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
             <meta-data android:name="android.app.default_searchable"
                 android:value=".activity.SearchActivity" />
         </activity>
 
 

In order to support soft search, I added an option for search to the action-bar as well as menu item and then invoked following method when the search menu was clicked:

     @Override
     public boolean onSearchRequested() {
         startSearch("default-search", true, null, false);
         return true;
     }
 

This would start your search activity described above.

Suggestions Provider

I already added com.plexobject.service.SearchSuggestionsProvider in the searchable.xml file above, which provides custom implementation of suggestions.

 package com.plexobject.service;
 
 import java.util.List;
 
 import android.app.SearchManager;
 import android.content.ContentValues;
 import android.content.SearchRecentSuggestionsProvider;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.util.Log;
 
 public class SearchSuggestionsProvider extends SearchRecentSuggestionsProvider {
     static final String TAG = SearchSuggestionsProvider.class.getSimpleName();
     public static final String AUTHORITY = SearchSuggestionsProvider.class
             .getName();
     public static final int MODE = DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES;
     private static final String[] COLUMNS = {
             "_id", // must include this column
             SearchManager.SUGGEST_COLUMN_TEXT_1,
             SearchManager.SUGGEST_COLUMN_TEXT_2,
             SearchManager.SUGGEST_COLUMN_INTENT_DATA,
             SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
             SearchManager.SUGGEST_COLUMN_SHORTCUT_ID };
 
     public SearchSuggestionsProvider() {
         setupSuggestions(AUTHORITY, MODE);
     }
 
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
 
         String query = selectionArgs[0];
         if (query == null || query.length() == 0) {
             return null;
         }
 
         MatrixCursor cursor = new MatrixCursor(COLUMNS);
 
         try {
             List list = callmyservice(query);
             int n = 0;
             for (MyObj obj : list) {
                 cursor.addRow(createRow(new Integer(n), query, obj.getText1(),
                         obj.getText2()));
                 n++;
             }
         } catch (Exception e) {
             Log.e(TAG, "Failed to lookup " + query, e);
         }
         return cursor;
     }
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
         throw new UnsupportedOperationException();
     }
 
     @Override
     public int delete(Uri uri, String selection, String[] selectionArgs) {
         throw new UnsupportedOperationException();
     }
 
     @Override
     public int update(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
         throw new UnsupportedOperationException();
     }
 
     private Object[] createRow(Integer id, String text1, String text2,
             String name) {
         return new Object[] { id, // _id
                 text1, // text1
                 text2, // text2
                 text1, "android.intent.action.SEARCH", // action
                 SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT };
     }
 
 }
 
 

You may store your suggestions in the database and may need to query the database and return database cursor. However, above provider makes a remote call and then populate Cursor using MatrixCursor from the objects that are returned from the remote service. I omitted actual remote calls that return your suggestions as it can be very application specific. As our implementation does not add anything in the database, it throws exceptions in the insert, update and delete methods. The suggestions provider can support one-line or two-line suggestions and above implementation is using two-line suggestions.
Finally, you will need to add provider and default search activity to your AndroidManifest.xml:

         <provider android:name=".service.SearchSuggestionsProvider"
             android:authorities="com.plexobject.service.SearchSuggestionsProvider"></provider>
         <meta-data android:name="android.app.default_searchable"
             android:value=".activity.SearchsActivity" />
 

As you would be calling remote service you would need following permission in your AndroidManifest.xml: :

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

If you were using database suggesgtion provider, you can add suggestions to the database as:

         SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchSuggestionsProvider.AUTHORITY, SearchSuggestionsProvider.MODE);
         suggestions.saveRecentQuery(queryString, null);
 

and remote suggestions from the database as:

         SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
                 SearchSuggestionsProvider.AUTHORITY,
                 SearchSuggestionsProvider.MODE);
         suggestions.clearHistory();
 

Summary

As you can see with a few line of configuration and code you can create application wide search. You can also expose your search to default Android search and you can read Android documentation for more information.


Powered by WordPress