Androidアプリ上でのサーチハードウェアキーによる振る舞い機能を実装

2013-01-02T00:00:00+00:00 Android Java

まだ微妙だけどメモる。一応、テストケースとかで色々してませんので

概要

というような画面があって、サーチハードウェアキーを押して「h」を入力した後に

というようなフィルタリングを行ったり

というようにサーチハードウェアキーを押してキーワードでインクレメンタル検索のようなのを実装したい場合にはSearchRecentSuggestionProvider等を駆使する事で出来る模様

ただ結果をフィルタリングする機能自体はただのAdapterのFilterを実装するだけ

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="sample.test"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application android:icon="@drawable/ic_launcher" android:label="@string/app_name">
        <activity android:name=".MainActivity" android:label="@string/app_name" android:launchMode="singleTop">
            <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.default_searchable" android:value=".MainActivity" />
            <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
        </activity>
        <provider android:name=".SearchSuggestionProvider" android:authorities="sample.test.provider" />
    </application>
</manifest>

res/xml/searchable.xml

<?xml version="1.0" ?>
<searchable
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:searchSuggestAuthority="sample.test.provider"
    android:searchSuggestSelection=" ? "
    android:searchSuggestIntentAction="sample.test.action.SEARCH_SUGGEST" />

SampleArrayAdapter.java

ListViewの結果な部分をフィルタするのに必要なArrayAdapter

package sample.test;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;

import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;

public class SampleArrayAdapter extends ArrayAdapter<String> {

    private Filter filter;
    private List<String> items = new ArrayList<String>();

    public SampleArrayAdapter(Context ctx) {
        super(ctx, android.R.layout.simple_list_item_1);

        String[] items = ctx.getResources().getStringArray(R.array.values);

        // API Level11からならaddAllメソッドで対応可能かも

        for (String item : items) {
            add(item);

            this.items.add(item);
        }
    }

    @Override
    public Filter getFilter() {
        if (filter == null) {
            filter = new SampleFilter();
        }

        return filter;
    }

    private class SampleFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();

            // 検索クエリーから
            SortedSet<String> filterItems = new TreeSet<String>();

            if (constraint != null && constraint.toString().length() > 0) {
                StringTokenizer tokens = new StringTokenizer(constraint.toString());

                while(tokens.hasMoreTokens()) {
                    String token = tokens.nextToken();

                    for (String item : items) {
                        if (item.contains(token)) {
                            filterItems.add(item);
                        }
                    }
                }
            } else {
                filterItems = new TreeSet<String>(items);
            }

            results.values = filterItems;
            results.count = filterItems.size();

            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint,FilterResults results) {
            @SuppressWarnings("unchecked")
            SortedSet<String> filters = (SortedSet<String>)results.values;

            clear();

            for (String filter : filters) {
                add(filter);
            }
        }
    }
}

SearchSuggestionProvider.java

検索項目からフィルターしてサジェストする項目をCursorで出す

package sample.test;

import android.content.SearchRecentSuggestionsProvider;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;

public class SearchSuggestionProvider extends SearchRecentSuggestionsProvider {

    public SearchSuggestionProvider() {
        setupSuggestions("sample.test.provider", SearchSuggestionProvider.DATABASE_MODE_QUERIES);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        if (selectionArgs.length > 0) {
            MatrixCursor csr = new MatrixCursor(
                new String[] { "_id", "suggest_text_1", "suggest_intent_query" }
            );

            String[] values = getContext().getResources().getStringArray(R.array.values);

            String query = selectionArgs[0];
            String[] queries = query.split("s");

            for (int i = 0;i < values.length;i++) {
                String value = values[i];

                for (String splitedQuery : queries) {
                    if (value.contains(splitedQuery)) {
                        csr.addRow(new Object[] { i, value, value });
                    }
                }
            }

            return csr;
        }

        return super.query(uri,projection,selection,selectionArgs,sortOrder);
    }
}

MainActivity.java

package sample.test;

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Filter;
import android.widget.Toast;

import static android.app.SearchManager.QUERY;
import static android.app.SearchManager.APP_DATA;

public class MainActivity extends ListActivity {

    public static final String ACTION_SEARCH_SUGGEST = "sample.test.action.SEARCH_SUGGEST";

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);

        getListView().setTextFilterEnabled(true);
        setListAdapter(new SampleArrayAdapter(this));

        // onSearchRequested();
    }

    @Override
    public void onNewIntent(Intent intent) {
        String action = intent.getAction();

        // startSearch際に指定されたBundleを取得できる模様
        Bundle  bundle = intent.getBundleExtra(APP_DATA);

        if (Intent.ACTION_SEARCH.equals(action)) {
            final Filter textFilter = ((SampleArrayAdapter)getListAdapter()).getFilter();

            textFilter.filter(intent.getStringExtra(QUERY), new Filter.FilterListener() {
                public void onFilterComplete(int count) {
                    if (count <= 0) {
                        // もしマッチせず見つからなかった場合にListAdapterにバインドされてるフィルターを無効にしないと元のデータ一覧が表示されなくなる
                        textFilter.filter(null);
                    }
                }
            });
        } else if (ACTION_SEARCH_SUGGEST.equals(action)) {
            Bundle b = intent.getExtras();

            // サーチハードウェア上の項目をクリックした際の項目はsuggest_intent_queryから取れる模様
            String selectedValue = b.getString(QUERY);

            if (selectedValue != null) {
                Toast.makeText(this, "selected: " + selectedValue, Toast.LENGTH_LONG).show();
            }
        }
    }

    @Override
    public boolean onSearchRequested() {
        Bundle bundle = new Bundle();
        bundle.putString("message", "hoge fuga foobar");

        startSearch(null, false, bundle, false);

        return true;
    }
}

ACTION_SEARCHはサーチハードウェアを押してキーワードを入力して検索した際に、ACTION_SEARCH_SUGGEST(searchabel.xmlで設定したIntentAction)は検索候補をタッチした際に返ってくる模様

んまぁざっくり書いたけど、 http://y-anz-m.blogspot.jp/2010/03/android-searchmanager.html が参考になるんじゃねーかなって所

Mockito Linuxでのjava.util.prefs.Preferences