Using Android Search Dialog. Part 3 - Custom Suggestions

image

This is the final article on the use of Android Search Dialog (previous are here and here ). In it, I'll show you how to add dynamic dialogue in the search for clues, as well as how to integrate search into your application system Quick Search Box (QSB). QSB advantage that it can be used to obtain information from virtually anywhere in the OS.

Theory


Tips for searching the data generated by your application that are subject to search. When the user selects one of them, the Search Manager sends Intent to Activity, which is responsible for the search. Normally, when a user clicks an icon in the search dialog box, then sent Intent type Search, however, the choice of tips in this case we can define another type of Intent, so that we can intercept it and take appropriate action, such as creating a new dialogue, or call Activity to display information, etc.
The data are transferred via a search query Intent as before, but now we will use the URI, to determine the type of request by the content provider.

Again, we need not perform any action on the rendering of dialogue, is engaged in Search Manager, all that is required of us - to provide a configuration xml file.

So, when the Search Manager determines our Activity as responsible for finding and providing clues to the search, the following sequence of actions occurs:
  1. When the Search Manager receives the search request, it sends a request to the content provider, provides tips.
  2. Content provider returns a cursor that points to clues that coincide with the text search.
  3. Search Manager displays a prompt, using the cursor

Once a list of tips has been mapped, it may be the following:
  • If the user changes the text of the query, then all of the above steps are repeated.
  • If the user starts the search, the hints are ignored, and sent to Activity Intent type Search.
  • If the user selects a clue what to Activity Intent delivered another type (the type is defined in the configuration file) that transfers data as a URI. URI will be used to search for records in the table corresponding to the selected tip.

So, we modify our application (that which was seen in part 1 ) so as to add dynamic prompts, and to develop a mechanism, the choice tips will cause a new Activity, which will display information on request. For the implementation will require:
  • Edit the configuration file dialog by adding information about the content provider and the type of Intent, used for clues
  • Create a table in the database SQLite, which will provide the columns needed for Manager'om Search Tips
  • Create a new content provider that has access to a table hint, and define it in the manifesto
  • Add Activity, which will display the information in the choice tips


Change the configuration file


I remind you that the configuration file (res / xml / searchable.xml) is required to display the dialog and change it, for example, to use voice search. To use dynamic prompts, you must add a file parameter: android: searchSuggestAuthority. It will coincide with a string of authorization content provider. Besides adding a android: searchMode = «queryRewriteFromText», its value indicates that the search string in the dialog will be overwritten when navigating through the prompts, for example using the trackball. Also add the parameters that define the operator selection, the type of Intent sent to the selection tips, and the minimum number of characters printed in the dialogue necessary to query the content provider.

File res / xml / searchable.xml 

<?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:searchSettingsDescription="@string/settings_description"
    android:searchMode="queryRewriteFromText"
    android:includeInGlobalSearch="true"
    android:searchSuggestAuthority="com.example.search.SuggestionProvider" 
    android:searchSuggestIntentAction="android.intent.action.VIEW"
    android:searchSuggestIntentData="content://com.example.search.SuggestionProvider/records"
    android:searchSuggestThreshold="1"
    android:searchSuggestSelection=" ?">
</searchable>


Create a content provider


In fact, our content provider is no different from others. But to do so for each row of the table prompts to select the correct columns, those that require Search Manager. We will request data on the prompts using the content provider's query (). And he will be called whenever the user types a new character in the dialogue. Thus, query () method must return a pointer to the table entries that match the query, and then Search Manager will display a prompt. See the description of the method in the code comments.
The text itself will be appended to the request URI, so that it will not give problems, you just use the standard method getLastPathSegment ().

Create a table hints


When the cursor gets Search Manager, pointing to the record, it expects a certain set of columns for each record. Binding are two: _ID - unique identifier for each clue and SUGGEST_COLUMN_TEXT_1 - help text.
There are many optional columns, such as using SUGGEST_COLUMN_ICON_1, you can define for each record, the icon shown on the left side tips (very useful, for example, to search for contacts).

Data type definition for Intent


Since we are passing the data on demand through the URI, then we need a mechanism to determine which clue was chosen. There are two ways. The first is to define a column SUGGEST_COLUMN_INTENT_DATA, which will be unique information for each record, then you can retrieve data from across Intent getData () or getDataString (). The second option - the data type for all of Intent in the configuration file (res / xml / searchable.xml) and then append to the URI unique data for each of Intent, using the column SUGGEST_COLUMN_INTENT_DATA_ID.
We will use the second option, with separate columns in the table, I did not create, because you can simply create a map of SUGGEST_COLUMN_INTENT_DATA_ID rowId in the table. I will add that for fun in SQLite to search for used FTS3 , that is, had to create a virtual table that you can not impose restrictions on the columns (constraints), such as a PRIMARY KEY or NULL / NOT NULL. But at the virtual table has a unique ID string, and set it on the map. That is, data for Intent will be as follows: the URI will be appended to "/" and rowId row in the table.

Create Activity to display information


The interface is in res / layout / record_activity.xml. All what the Activity - getting data from Intent, a query cursor over a content provider and display records in text box.

File res / layout / record_activity.xml 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:padding="10dp">
    <TextView
            android:id="@+id/record_header"
            android:textSize="25dp"
            android:textColor="?android:textColorPrimary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
</LinearLayout>


Now fill out the information on the content provider and a new Activity to the manifest, too, since we now have two Activity, then point out that, by default, is responsible for the search.

File AndroidManifest.xml 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.search"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Main"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <meta-data 
                android:name="android.app.searchable"
                android:resource="@xml/searchable"
            />
        </activity>

        <activity android:name=".RecordActivity"
                  android:theme="@android:style/Theme.NoTitleBar" />

        <provider android:name=".SuggestionProvider"
                  android:authorities="com.example.search.SuggestionProvider" />

        <meta-data android:name="android.app.default_searchable"
                   android:value=".Main" />
    
    </application>
    <uses-sdk android:minSdkVersion="5" />
</manifest> 

Intent to Intercept Activity, responsible for search


After all these steps need to be processed in the main Intent Activity, which is responsible for the search. Since we have determined the type of Intent for clues as View, then you just need to add a check on him. If the condition is satisfied, then starts RecordActivity, using the Intent, in which data is recorded URI + "/" + id tips in the table.

Integration with the Quick Search Box


Once you have modified your application to use custom suggestions, you can add it to the system search. To do this, file searchable.xml need to add two parameters:
  1. android: includeInGlobalSearch = «true» - indicates that QSB can search for your application.
  2. android: searchSettingsDescription = "@ string / settings_description" - points to a description of your application to be displayed when configuring the Quick Search Box. These settings are in settings-> search. 

These options are available with version Android 1.6, that is for versions below you can not configure your application to QSB.

Source


Presenting the complete source code for all required classes. Main.java - main Activity, responsible for search and for sending requests to the content provider, RecordActivity.java - Intent receives data on a particular record, gets a reference to record and display information. SuggestionProvider.java - the content provider will handle the requests from the Search Manager'a to the table hints. RecordsDbHelper.java - responsible for creating the table, filling it, the establishment of the necessary maps, and for myself «matching» records.

File Main.java 
package com.example.search;

import android.app.ListActivity;
import android.app.SearchManager;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SimpleCursorAdapter;
import android.widget.Toast;

public class Main extends ListActivity {
 
    private EditText text;
    private Button add;
    private RecordsDbHelper mDbHelper;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 setContentView(R.layout.main);
 mDbHelper = new RecordsDbHelper(this);
  
 Intent intent = getIntent();
  
 if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
     //Берем строку запроса из экстры
     String query = intent.getStringExtra(SearchManager.QUERY);
     //Выполняем поиск
     showResults(query);
 } else if (Intent.ACTION_VIEW.equals(intent.getAction())){
         //Создаем Intent для открытия RecordActivity
                Intent recordIntent = new Intent(this, RecordActivity.class);
                recordIntent.setData(intent.getData());
                startActivity(recordIntent);
                finish();
 }
  
 add = (Button) findViewById(R.id.add);
 text = (EditText) findViewById(R.id.text);
 add.setOnClickListener(new View.OnClickListener() {
     public void onClick(View view) {
         String data = text.getText().toString();
  if (!data.equals("")) {
      saveTask(data);
      text.setText("");
  }
     }
 });
  
    }

    private void saveTask(String data) {
        mDbHelper.createRecord(data);
    }
 
    private void showResults(String query) {

     //Запрашиваем у контент-провайдера курсор на записи 
        Cursor cursor = managedQuery(SuggestionProvider.CONTENT_URI, null, null,
                                new String[] {query}, null);
        if (cursor == null) {
            Toast.makeText(this, "There are no results", Toast.LENGTH_SHORT).show();
        } else {
         //Обновляем адаптер
            String[] from = new String[] { RecordsDbHelper.KEY_DATA };
            int[] to = new int[] { R.id.text1 };
            SimpleCursorAdapter records = new SimpleCursorAdapter(this, R.layout.record, cursor, from, to);
            getListView().setAdapter(records);            
        }        
    }
 

    //Создаем меню для вызова поиска (интерфейс в res/menu/main_menu.xml)
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main_menu, menu);
        return true; 
    }

    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.search_record:
          onSearchRequested();
         return true;
            default:
            return super.onOptionsItemSelected(item);
        }
    }
}


Файл RecordActivity.java
package com.example.search;

import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;

public class RecordActivity extends Activity {

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

     //Получаем URI с данными из Intent и запрашиваем данные через контент-провайдер
     Uri uri = getIntent().getData();
            Cursor cursor = managedQuery(uri, null, null, null, null);

        if (cursor == null) {
            finish();
        } else {
            //Устанавливаем данные в текстовое поле
            cursor.moveToFirst();

            TextView record = (TextView) findViewById(R.id.record_header);
            int rIndex = cursor.getColumnIndexOrThrow(RecordsDbHelper.KEY_DATA);

            record.setText(cursor.getString(rIndex));
        }
    }
 
 //Создаем меню для вызова диалога поиска из этого активити
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.search_record:
                onSearchRequested();
                return true;
            default:
                return false;
        }
    }

}

package com.example.search;

import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;

public class SuggestionProvider extends ContentProvider{

    private RecordsDbHelper mDbHelper;
    
    public static String AUTHORITY = "com.example.search.SuggestionProvider";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/records");
    
    //MIME типы для getType()
    public static final String RECORDS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
                                                  "/vnd.example.search";
    public static final String RECORD_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE +
                                                  "/vnd.example.search";

    //Для матчера разных URI
    private static final int SEARCH_RECORDS = 0;
    private static final int GET_RECORD = 1;
    private static final int SEARCH_SUGGEST = 2;
    private static final UriMatcher sURIMatcher = makeUriMatcher();
               
    @Override
    public boolean onCreate() {
        mDbHelper = new RecordsDbHelper(getContext());
        return true;
    }

    /**
     * Обрабатывает запросы от Search Manager'a.
     * Когда запрашивается конкретный элемент, то требуется только URI.
     * Когда запрашивается поиск по всей таблице, то первый элемент параметра selectionArgs содержит строку запроса.
     * Остальные параметры не нужны.
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
       String[] selectionArgs, String sortOrder) {
        //Используем UriMatcher, чтобы узнать какой тип запроса получен. Далее формируем соответствующий запрос к БД
        switch (sURIMatcher.match(uri)) {
            case SEARCH_SUGGEST:
                if (selectionArgs == null) {
                    throw new IllegalArgumentException(
                        "selectionArgs must be provided for the Uri: " + uri);
                }
                return getSuggestions(selectionArgs[0]);
            case SEARCH_RECORDS:
                if (selectionArgs == null) {
                    throw new IllegalArgumentException(
                        "selectionArgs must be provided for the Uri: " + uri);
                }
                return search(selectionArgs[0]);
            case GET_RECORD:
                return getRecord(uri);
            default:
                throw new IllegalArgumentException("Unknown Uri: " + uri);
        }
 }
 
    private Cursor getSuggestions(String query) {
        query = query.toLowerCase();
        String[] columns = new String[] {
             BaseColumns._ID,
             RecordsDbHelper.KEY_DATA,
             SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
        return mDbHelper.getRecordMatches(query, columns);
    }
    
    private Cursor search(String query) {
        query = query.toLowerCase();
        String[] columns = new String[] {
            BaseColumns._ID,
            RecordsDbHelper.KEY_DATA};

        return mDbHelper.getRecordMatches(query, columns);
    }
    
    private Cursor getRecord(Uri uri) {
        String rowId = uri.getLastPathSegment();
        String[] columns = new String[] {
            RecordsDbHelper.KEY_DATA};

        return mDbHelper.getRecord(rowId, columns);
    }
    
    /**
     * Вспомогательный метод
     * нужен для сопоставления разным URI конкретных значений 
     */
    private static UriMatcher makeUriMatcher() {
        UriMatcher matcher =  new UriMatcher(UriMatcher.NO_MATCH);
        // Для записей
        matcher.addURI(AUTHORITY, "records", SEARCH_RECORDS);
        matcher.addURI(AUTHORITY, "records/#", GET_RECORD);
        // Для подсказок
        matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
        matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
        return matcher;
    }
 
    //Требуемые методы (наследуются от класса ContentProvider)
    @Override
    public String getType(Uri uri) {
        switch (sURIMatcher.match(uri)) {
            case SEARCH_RECORDS:
                return RECORDS_MIME_TYPE;
            case SEARCH_SUGGEST:
             return SearchManager.SUGGEST_MIME_TYPE;
            case GET_RECORD:
                return RECORD_MIME_TYPE;
            default:
                throw new IllegalArgumentException("Unknown URL " + uri);
        } 
    }
 
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                   String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

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

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }
 
}package com.example.search;

import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;

public class RecordActivity extends Activity {

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

     //Получаем URI с данными из Intent и запрашиваем данные через контент-провайдер
     Uri uri = getIntent().getData();
            Cursor cursor = managedQuery(uri, null, null, null, null);

        if (cursor == null) {
            finish();
        } else {
            //Устанавливаем данные в текстовое поле
            cursor.moveToFirst();

            TextView record = (TextView) findViewById(R.id.record_header);
            int rIndex = cursor.getColumnIndexOrThrow(RecordsDbHelper.KEY_DATA);

            record.setText(cursor.getString(rIndex));
        }
    }
 
 //Создаем меню для вызова диалога поиска из этого активити
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.search_record:
                onSearchRequested();
                return true;
            default:
                return false;
        }
    }

}

File RecordActivity.java
package com.example.search;

import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;

public class RecordActivity extends Activity {

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

     //Получаем URI с данными из Intent и запрашиваем данные через контент-провайдер
     Uri uri = getIntent().getData();
            Cursor cursor = managedQuery(uri, null, null, null, null);

        if (cursor == null) {
            finish();
        } else {
            //Устанавливаем данные в текстовое поле
            cursor.moveToFirst();

            TextView record = (TextView) findViewById(R.id.record_header);
            int rIndex = cursor.getColumnIndexOrThrow(RecordsDbHelper.KEY_DATA);

            record.setText(cursor.getString(rIndex));
        }
    }
 
 //Создаем меню для вызова диалога поиска из этого активити
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.search_record:
                onSearchRequested();
                return true;
            default:
                return false;
        }
    }

}

File SuggestionProvider.java 

package com.example.search;

import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;

public class SuggestionProvider extends ContentProvider{

    private RecordsDbHelper mDbHelper;
    
    public static String AUTHORITY = "com.example.search.SuggestionProvider";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/records");
    
    //MIME типы для getType()
    public static final String RECORDS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
                                                  "/vnd.example.search";
    public static final String RECORD_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE +
                                                  "/vnd.example.search";

    //Для матчера разных URI
    private static final int SEARCH_RECORDS = 0;
    private static final int GET_RECORD = 1;
    private static final int SEARCH_SUGGEST = 2;
    private static final UriMatcher sURIMatcher = makeUriMatcher();
               
    @Override
    public boolean onCreate() {
        mDbHelper = new RecordsDbHelper(getContext());
        return true;
    }

    /**
     * Обрабатывает запросы от Search Manager'a.
     * Когда запрашивается конкретный элемент, то требуется только URI.
     * Когда запрашивается поиск по всей таблице, то первый элемент параметра selectionArgs содержит строку запроса.
     * Остальные параметры не нужны.
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
       String[] selectionArgs, String sortOrder) {
        //Используем UriMatcher, чтобы узнать какой тип запроса получен. Далее формируем соответствующий запрос к БД
        switch (sURIMatcher.match(uri)) {
            case SEARCH_SUGGEST:
                if (selectionArgs == null) {
                    throw new IllegalArgumentException(
                        "selectionArgs must be provided for the Uri: " + uri);
                }
                return getSuggestions(selectionArgs[0]);
            case SEARCH_RECORDS:
                if (selectionArgs == null) {
                    throw new IllegalArgumentException(
                        "selectionArgs must be provided for the Uri: " + uri);
                }
                return search(selectionArgs[0]);
            case GET_RECORD:
                return getRecord(uri);
            default:
                throw new IllegalArgumentException("Unknown Uri: " + uri);
        }
 }
 
    private Cursor getSuggestions(String query) {
        query = query.toLowerCase();
        String[] columns = new String[] {
             BaseColumns._ID,
             RecordsDbHelper.KEY_DATA,
             SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
        return mDbHelper.getRecordMatches(query, columns);
    }
    
    private Cursor search(String query) {
        query = query.toLowerCase();
        String[] columns = new String[] {
            BaseColumns._ID,
            RecordsDbHelper.KEY_DATA};

        return mDbHelper.getRecordMatches(query, columns);
    }
    
    private Cursor getRecord(Uri uri) {
        String rowId = uri.getLastPathSegment();
        String[] columns = new String[] {
            RecordsDbHelper.KEY_DATA};

        return mDbHelper.getRecord(rowId, columns);
    }
    
    /**
     * Вспомогательный метод
     * нужен для сопоставления разным URI конкретных значений 
     */
    private static UriMatcher makeUriMatcher() {
        UriMatcher matcher =  new UriMatcher(UriMatcher.NO_MATCH);
        // Для записей
        matcher.addURI(AUTHORITY, "records", SEARCH_RECORDS);
        matcher.addURI(AUTHORITY, "records/#", GET_RECORD);
        // Для подсказок
        matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
        matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
        return matcher;
    }
 
    //Требуемые методы (наследуются от класса ContentProvider)
    @Override
    public String getType(Uri uri) {
        switch (sURIMatcher.match(uri)) {
            case SEARCH_RECORDS:
                return RECORDS_MIME_TYPE;
            case SEARCH_SUGGEST:
             return SearchManager.SUGGEST_MIME_TYPE;
            case GET_RECORD:
                return RECORD_MIME_TYPE;
            default:
                throw new IllegalArgumentException("Unknown URL " + uri);
        } 
    }
 
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                   String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

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

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

___


RecordsDbHelper.java


package com.example.search;


import java.util.HashMap;

import android.app.SearchManager;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.provider.BaseColumns;
import android.util.Log;

public class RecordsDbHelper {

    //Единственный столбец в таблице - данные
    public static final String KEY_DATA = SearchManager.SUGGEST_COLUMN_TEXT_1;

    private static final String TAG = "RecordsDbHelper";
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    private static final String DATABASE_NAME = "datas";
    private static final String DATABASE_TABLE = "records";
    private static final int DATABASE_VERSION = 2;
 
    //Сценарий создания БД
    private static final String DATABASE_CREATE =
  "CREATE VIRTUAL TABLE " + DATABASE_TABLE +
                " USING fts3 (" + KEY_DATA + ");"; 

    private static final HashMap<String,String> mColumnMap = buildColumnMap();
 
    /**
     * Возвращает курсор, указывающий на запись с rowId
     * @param rowId id возвращаемой записи
     * @param columns возвращаемые столбцы записи; если null, то все 
     * @return курсор, указывающий на определенную запись, null - если не запись не найдена
     */
    public Cursor getRecord(String rowId, String[] columns) {
        String selection = "rowid = ?";
        String[] selectionArgs = new String[] {rowId};

        return query(selection, selectionArgs, columns);
    }
    
    /**
     * Возвращает курсор, указывающий на все записи, совпадающие с запросом
     * @param query текст поискового запроса
     * @param columns возвращаемые столбцы записи; если null, то все 
     * @return курсор, указывающий на записи, совпадающие с запросом, null - если не записи не найдена
     */
    public Cursor getRecordMatches(String query, String[] columns) {
        String selection = KEY_DATA + " MATCH ?";
        String[] selectionArgs = new String[] {query+"*"};

        return query(selection, selectionArgs, columns);
    }
    
    /**
     * Создает отображение всевозможных запрашиваемых столбцов.
     * Будет установлено как проекция в SQLiteQueryBuilder.
     * Нужно для того, чтобы назначить для каждой записи уникальные значения SUGGEST_COLUMN_INTENT_DATA_ID
     * которые используются для получения конкретной записи по URI.
     */
    private static HashMap<String,String> buildColumnMap() {
        HashMap<String,String> map = new HashMap<String,String>();
        map.put(KEY_DATA, KEY_DATA);
        map.put(BaseColumns._ID, "rowid AS " +
                BaseColumns._ID);
        map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
                SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
        return map;
    }

    /**
     * 
     * @param selection оператор выборки
     * @param selectionArgs аргументы, заменяющие "?" в запросе к БД
     * @param columns возвращаемые столбцы записи
     * @return курсор, указывающий на все записи, совпадающие с поисковым запросом
     */
    private Cursor query(String selection, String[] selectionArgs, String[] columns) {
        /* SQLiteBuilder предоставляет возможность создания отображения для всех
         * необходимых столбцов БД, что позволяет не сообщать контент-провайдеру
         * настоящие имена столбцов.
         */

        SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
        builder.setTables(DATABASE_TABLE);
        builder.setProjectionMap(mColumnMap);

        Cursor cursor = builder.query(mDbHelper.getReadableDatabase(),
                columns, selection, selectionArgs, null, null, null);
        if (cursor == null) {
            return null;
        } else if (!cursor.moveToFirst()) {
            cursor.close();
            return null;
        }
        return cursor;
    }    
     
    /**
     *Создает/открывает БД
     */
    private static class DatabaseHelper extends SQLiteOpenHelper {
  
        DatabaseHelper(Context context) {
  super(context, DATABASE_NAME, null, DATABASE_VERSION);
 }

 @Override
 public void onCreate(SQLiteDatabase db) {
     db.execSQL(DATABASE_CREATE);
 }

 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
     Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
   + newVersion + ", which will destroy all old data");
     db.execSQL("DROP TABLE IF EXISTS records");
     onCreate(db);
 }
    }

    public RecordsDbHelper(Context context) {
        mDbHelper = new DatabaseHelper(context);
    }

    /**
     * Добавляет запись в таблицу
     * @param data данные, сохраняемые в таблицу
     * @return id записи, или -1, если добавление не удалось
     */
    public long createRecord(String data) {
        mDb = mDbHelper.getWritableDatabase();
 ContentValues initialValues = new ContentValues();
 initialValues.put(KEY_DATA, data);
 return mDb.insert(DATABASE_TABLE, null, initialValues);
    }
 
}


The whole project can take on code.google.com
Thank you!