Custom Suggestion Provider Code Snippets

satya - Sun Dec 09 2012 16:23:16 GMT-0500 (Eastern Standard Time)

Listing 12-5. Building a UriMatcher with SearchManager Suggest URI Definitions


private static UriMatcher buildUriMatcher(String AUTHORITY) 
{
    UriMatcher matcher =  
         new UriMatcher(UriMatcher.NO_MATCH);
    matcher.addURI(AUTHORITY, 
          SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
    matcher.addURI(AUTHORITY, 
          SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
    matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, 
        SHORTCUT_REFRESH);
    matcher.addURI(AUTHORITY, 
          SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", 
          SHORTCUT_REFRESH);
    return matcher;
}

satya - Sun Dec 09 2012 16:27:05 GMT-0500 (Eastern Standard Time)

Listing 12-6. Outline of query() method using a UriMatcher


@Override
public Cursor query(Uri uri, String[] projection, String selection, 
         String[] selectionArgs, String sortOrder) 
{
    ...other stuff
    switch (sURIMatcher.match(uri)) 
    {
          case SEARCH_SUGGEST:
          //Return a series of suggestions
          case SHORTCUT_REFRESH:
          //Return the updated suggestion
    }//eof-switch
    ....other stuff
}

satya - Sun Dec 09 2012 16:28:31 GMT-0500 (Eastern Standard Time)

Listing 12-7. Implementing getType() for SuggestUrlProvider


?.other stuff
//Initialize the object below first 
private static URIMatcher sURIMatcher;
?.otherstuff

public String getType(Uri uri) {
    switch (sURIMatcher.match(uri)) {
        case SEARCH_SUGGEST:
            return SearchManager.SUGGEST_MIME_TYPE;
        case SHORTCUT_REFRESH:
            return SearchManager.SHORTCUT_MIME_TYPE;
        default:
         throw new IllegalArgumentException("Unknown URL " + uri);
    }
}

satya - Sun Dec 09 2012 16:29:02 GMT-0500 (Eastern Standard Time)

Listing 12-8. SearchManager Mime Type Constants


SearchManager.SUGGEST_MIME_TYPE
SearchManager.SHORTCUT_MIME_TYPE

satya - Sun Dec 09 2012 16:29:26 GMT-0500 (Eastern Standard Time)

Listing 12-9. SearchManager Mime Type Constant Values


vnd.android.cursor.dir/vnd.android.search.suggest
vnd.android.cursor.item/vnd.android.search.suggest

satya - Sun Dec 09 2012 16:31:29 GMT-0500 (Eastern Standard Time)

Listing 12-10. Implementing query() method for SuggestUrlProvider


public Cursor query(Uri uri, String[] projection, 
         String selection, 
         String[] selectionArgs, String sortOrder) 
{
   Log.d(tag,"query called with uri:" + uri);
   Log.d(tag,"selection:" + selection);
   
   String query = selectionArgs[0];
   Log.d(tag,"query:" + query);
   
    switch (sURIMatcher.match(uri)) {

        case SEARCH_SUGGEST:
           Log.d(tag,"search suggest called");
           return getSuggestions(query);

        case SHORTCUT_REFRESH:
           Log.d(tag,"shortcut refresh called");
           return null;

        default:
          throw new IllegalArgumentException("Unknown URL " + uri);
    }
}

satya - Sun Dec 09 2012 16:32:10 GMT-0500 (Eastern Standard Time)

Listing 12-11. CustomSuggestionProvider Searchableinfo Metadata XML file


//xml/searchable.xml
<searchable 
  xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/search_label"
    android:hint="@string/search_hint" 
    android:searchMode="showSearchLabelAsBadge"
    android:searchSettingsDescription="suggests urls"
    android:includeInGlobalSearch="true"
    android:queryAfterZeroResults="true"
    
    android:searchSuggestAuthority=
           "com.androidbook.search.custom.suggesturlprovider"

    android:searchSuggestIntentAction=
           "android.intent.action.VIEW"
    android:searchSuggestSelection=" ? "
/>

satya - Sun Dec 09 2012 16:33:18 GMT-0500 (Eastern Standard Time)

Listing 12-12. Defining Suggestion Cursor Columns for SuggestUrlProvider


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
}

satya - Sun Dec 09 2012 16:33:55 GMT-0500 (Eastern Standard Time)

Listing 12-13. Using a MatrixCursor


MatrixCursor cursor = new MatrixCursor(COLUMNS);
string[] rowData;
//insert values for each column in rowData
cursor.addRow(rowData);

satya - Sun Dec 09 2012 16:40:08 GMT-0500 (Eastern Standard Time)

Listing 12-14. Custom Suggestion Provider Full Source Code


public class SuggestUrlProvider extends ContentProvider 
{
    private static final String tag = "SuggestUrlProvider"; 
    public static String AUTHORITY = 
        "com.androidbook.search.custom.suggesturlprovider";

    private static final int SEARCH_SUGGEST = 0;
    private static final int SHORTCUT_REFRESH = 1;
    private static final UriMatcher sURIMatcher = buildUriMatcher();

    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
    };
    
    private static UriMatcher buildUriMatcher() 
    {
        UriMatcher matcher =  
             new UriMatcher(UriMatcher.NO_MATCH);

        matcher.addURI(AUTHORITY, 
              SearchManager.SUGGEST_URI_PATH_QUERY, 
              SEARCH_SUGGEST);
        matcher.addURI(AUTHORITY, 
              SearchManager.SUGGEST_URI_PATH_QUERY + 
               "/*", 
              SEARCH_SUGGEST);
        matcher.addURI(AUTHORITY, 
              SearchManager.SUGGEST_URI_PATH_SHORTCUT, 
            SHORTCUT_REFRESH);
        matcher.addURI(AUTHORITY, 
              SearchManager.SUGGEST_URI_PATH_SHORTCUT + 
            "/*", 
            SHORTCUT_REFRESH);
        return matcher;
    }

    @Override
    public boolean onCreate() {
        //lets not do anything in particular
        Log.d(tag,"onCreate called");
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, 
                        String selection, String[] selectionArgs, 
                        String sortOrder) 
    {
       Log.d(tag,"query called with uri:" + uri);
       Log.d(tag,"selection:" + selection);
       
       String query = selectionArgs[0];
       Log.d(tag,"query:" + query);
       
        switch (sURIMatcher.match(uri)) {
            case SEARCH_SUGGEST:
               Log.d(tag,"search suggest called");
               return getSuggestions(query);
            case SHORTCUT_REFRESH:
               Log.d(tag,"shortcut refresh called");
               return null;
            default:
             throw new IllegalArgumentException("Unknown URL " + uri);
        }
    }

    private Cursor getSuggestions(String query) 
    {
       if (query == null) return null;
       String word = getWord(query);
       if (word == null)
          return null;
       
       Log.d(tag,"query is longer than 3 letters");
       
       MatrixCursor cursor = new MatrixCursor(COLUMNS);
       cursor.addRow(createRow1(word));
       cursor.addRow(createRow2(word));
       return cursor;
    }
    private Object[] createRow1(String query)
    {
        return columnValuesOfQuery(query,
              "android.intent.action.VIEW",
              "http://www.thefreedictionary.com/" + query,
              "Look up in freedictionary.com for",
              query);
    }

    private Object[] createRow2(String query)
    {
        return columnValuesOfQuery(query,
             "android.intent.action.VIEW",
 "http://www.google.com/search?hl=en&source=hp&q=define%3A/" 
           + query,
              "Look up in google.com for",
              query);
    }
    private Object[] columnValuesOfQuery(String query,
                                         String intentAction,
                                         String url, 
                                         String text1, 
                                         String text2) 
    {
        return new String[] {
                query,     // _id
                text1,     // text1
                text2,     // text2
                url,       
                // intent_data (included when clicking on item)
                intentAction, //action
                SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT
        };
    }

    private Cursor refreshShortcut(String shortcutId, 
                                   String[] projection) {
        return null;
    }

    public String getType(Uri uri) {
        switch (sURIMatcher.match(uri)) {
            case SEARCH_SUGGEST:
                return SearchManager.SUGGEST_MIME_TYPE;
            case SHORTCUT_REFRESH:
                return SearchManager.SHORTCUT_MIME_TYPE;
            default:
             throw 
             new IllegalArgumentException("Unknown URL " + uri);
        }
    }

    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException();
    }

    public int delete(Uri uri, String selection, 
                      String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

    public int update(Uri uri, ContentValues values, 
                      String selection, 
                      String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }
    
    private String getWord(String query)
    {
       int dotIndex = query.indexOf('.'); 
       if (dotIndex < 0)
          return null;
       return query.substring(0,dotIndex);
    }
}

satya - Sun Dec 09 2012 16:41:22 GMT-0500 (Eastern Standard Time)

Listing 12-16. Responding to Action_View and Action_Search


//Body of onCreate

// get and process search query here
final Intent queryIntent = getIntent();
//query action
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");
}

satya - Sun Dec 09 2012 16:42:18 GMT-0500 (Eastern Standard Time)

Listing 12-17. Full source code of SearchActivity


//file: SearchActivity.java
public class SearchActivity extends Activity 
{
    private final static String tag ="SearchActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d(tag,"I am being created");
        setContentView(R.layout.layout_test_search_activity);
       
        // get and process search query here
        final Intent queryIntent = getIntent();
        
        //query action
        final String queryAction = queryIntent.getAction();
        Log.d(tag,"Create Intent action:"+queryAction);

        final String queryString = 
           queryIntent.getStringExtra(SearchManager.QUERY);
        Log.d(tag,"Create Intent query:"+queryString);
        
        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");
        }
        return;
    }
    
    @Override
    public void onNewIntent(final Intent newIntent) 
    {
        super.onNewIntent(newIntent);
        Log.d(tag,"new intent calling me");
        
        // get and process search query here
        final Intent queryIntent = newIntent;
        
        //query action
        final String queryAction = queryIntent.getAction();
        Log.d(tag,"New Intent action:"+queryAction);

        final String queryString = 
           queryIntent.getStringExtra(SearchManager.QUERY);
        Log.d(tag,"New Intent query:"+queryString);
        
        if (Intent.ACTION_SEARCH.equals(queryAction)) 
        {
           this.doSearchQuery(queryIntent);
        }
        else if (Intent.ACTION_VIEW.equals(queryAction)) 
        {
           this.doView(queryIntent);
        }
        else {
           Log.d(tag,"New intent NOT from search");
        }
        return;
    }
    private void doSearchQuery(final Intent queryIntent) 
    {
        final String queryString = 
           queryIntent.getStringExtra(SearchManager.QUERY);
        appendText("You are searching for:" + queryString);
    }
    private void appendText(String msg)
    {
        TextView tv = (TextView)this.findViewById(R.id.text1);
        tv.setText(tv.getText() + "\n" + msg);
    }
    private void doView(final Intent queryIntent) 
    {
        Uri uri = queryIntent.getData();
        String action = queryIntent.getAction();
        Intent i = new Intent(action);
        i.setData(uri);
        startActivity(i);
        this.finish();
    }
}

satya - Sun Dec 09 2012 16:42:43 GMT-0500 (Eastern Standard Time)

The intent that gets fired to invoke the search activity


launching Intent { 
act=android.intent.action.VIEW 
dat=http://www.google.com 
flg=0x10000000 
cmp=com.androidbook.search.custom/.SearchActivity (has extras) 
}

satya - Sun Dec 09 2012 16:43:39 GMT-0500 (Eastern Standard Time)

Listing 12-18. Responding to Action_Search and Action_View


if (Intent.ACTION_SEARCH.equals(queryAction)) 
{
   this.doSearchQuery(queryIntent);
}
else if (Intent.ACTION_VIEW.equals(queryAction)) 
{
   this.doView(queryIntent);
}

satya - Sun Dec 09 2012 16:45:37 GMT-0500 (Eastern Standard Time)

Listing 12-20. Finishing the Search Activity


private void doView(final Intent queryIntent) 
{
    Uri uri = queryIntent.getData();
    String action = queryIntent.getAction();
    Intent i = new Intent(action);
    i.setData(uri);
    startActivity(i);
    this.finish();
}

satya - Sun Dec 09 2012 16:46:12 GMT-0500 (Eastern Standard Time)

Listing 12-21. Custom Suggestion Provider Manifest File


//file:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.androidbook.search.custom"
      android:versionCode="1"
      android:versionName="1.0.0">
    <application android:icon="@drawable/icon" 
                     android:label="Custom Suggestions Provider">
<!--
****************************************************************
* Search related code: search activity 
**************************************************************** 
 -->                  
   <activity android:name=".SearchActivity"
                  android:label="Search Activity Label"
                  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>
   
<!-- Declare default search -->
   <meta-data android:name="android.app.default_searchable"
       android:value=".SearchActivity" />
            
<!-- Declare Suggestion Provider -->
    <provider android:name="SuggestUrlProvider"
        android:authorities=
        "com.androidbook.search.custom.suggesturlprovider" />
</application>
    <uses-sdk android:minSdkVersion="4" />
</manifest>

satya - Sun Dec 09 2012 16:46:52 GMT-0500 (Eastern Standard Time)

Listing 12-22. List of Action Key Codes


keycode_dpad_up
keycode_dpad_down
keycode_dpad_left
keycode_dpad_right
keycode_dpad_center
keycode_back
keycode_call
keycode_camera
keycode_clear
kecode_endcall
keycode_home
keycode_menu
keycode_mute
keycode_power
keycode_search
keycode_volume_up
keycode_volume_down

satya - Sun Dec 09 2012 16:47:08 GMT-0500 (Eastern Standard Time)

More on key codes

More on key codes

satya - Sun Dec 09 2012 16:47:39 GMT-0500 (Eastern Standard Time)

Listing 12-23. Action Key Definition Example


<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/search_label"
    android:hint="@string/search_hint" 
    android:searchMode="showSearchLabelAsBadge"
   
    android:includeInGlobalSearch="true"
    android:searchSuggestAuthority=
       "com.androidbook.search.simplesp.SimpleSuggestionProvider"
    android:searchSuggestSelection=" ? "
>
   <actionkey
       android:keycode="KEYCODE_CALL"
       android:queryActionMsg="call"
       android:suggestActionMsg="call"
       android:suggestActionMsgColumn="call_column" />

   <actionkey
       android:keycode="KEYCODE_DPAD_CENTER"
       android:queryActionMsg="doquery"
       android:suggestActionMsg="dosuggest"
       android:suggestActionMsgColumn="my_column" />
   .....
</searchable>

satya - Sun Dec 09 2012 16:48:20 GMT-0500 (Eastern Standard Time)

More info on action keys at google

More info on action keys at google

satya - Sun Dec 09 2012 16:49:56 GMT-0500 (Eastern Standard Time)

Listing 12-24. Example of Action Key Columns in the Suggestion Cursor


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,
        "call_column",
        "my_column"
};

satya - Sun Dec 09 2012 16:50:19 GMT-0500 (Eastern Standard Time)

Listing 12-25. Passing Additional Application data to Search Activity


public boolean onSearchRequested()
{
   Bundle applicationData = new Bundle();
   applicationData.putString("string_key","some string value");
   applicationData.putLong("long_key",290904);
   applicationData.putFloat("float_key",2.0f);
   
   startSearch(null,          // Initial Search search query string
      false,                        // don't "select initial query"
      applicationData,       // extra data
      false                         // don't force a global search 
      );
   return true;   
}

satya - Sun Dec 09 2012 16:51:36 GMT-0500 (Eastern Standard Time)

Listing 12-26. Retrieving Additional Application Data Context


Bundle applicationData = 
  queryIntent.getBundleExtra(SearchManager.APP_DATA);
if (applicationData != null)
{
  String s = applicationData.getString("string_key");
  long   l = applicationData.getLong("long_key");
  float  f = applicationData.getFloat("float_key");
}