SearchFragment
– Android TV app hands on Tutorial 12

SearchFragment

In-app Search on Android TV application

One of the biggest difference between Android phone & Android TV is their input method. Since TV does not support touchpad, and we shouldn’t expect users to use keyboard for TV, inputting words for TV is troublesome.

Google suggests to use voice input for in Searching within TV Apps

In-app Search icon on BrowseFragment

BrowseFragment contains a design layout for search function as well, and showing in-app icon on your application is very easy. Just implement setOnSearchClickedListener, that’s all.

// Existence of this method make In-app search icon visible
        setOnSearchClickedListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                
            }
        });

setSearchAffordanceColor method can be used for specifying search icon color.

        // set search icon color
        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
setOnSerchClickedListener
In-app search icon will appear on the top-right of BrowseFragment when setOnSearchClickedListener is called.

SearchFragment

Search function need to be implemented to answer user’s search query. To show the search query input UI and search result UI, LeanbackLibrary provides SearchFragment. This time, when request search (by either voice search button or explicitly press search button icon), we invoke this SearchFragment from SearchActivity.

    private void setupEventListeners() {
        setOnItemViewSelectedListener(new ItemViewSelectedListener());
        setOnItemViewClickedListener(new ItemViewClickedListener());

        // Existence of this method make In-app search icon visible
        setOnSearchClickedListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getActivity(), SearchActivity.class);
                startActivity(intent);
            }
        });
    }

Start creating SearchActivity by right click on Package name, Create → New  Activity → Blank Activity → type “SearchActivity”. After SearchActivity class and activity_search.xml layout have created, modify res/acitivity_search.xml as follows.

<?xml version="1.0" encoding="utf-8"?>

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:name="com.corochann.androidtvapptutorial.SearchFragment"
    android:id="@+id/search_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

It attaches SearchFragment. Again, create SearchFragment by Create → New → Java class → SearchFragment, and make it a subclass of android.support.v17.leanback.app.SearchFragment.

If you build and run application here, application will crash because SearchFragment automatically start to get search query from internal speech recognizer.

Implement voice input/voice search – setSpeechRecognitionCallback

Official doc says,

If you do not supply a callback via setSpeechRecognitionCallback(SpeechRecognitionCallback), an internal speech recognizer will be used for which your application will need to request android.permission.RECORD_AUDIO.

So you need to do either

  • Implement setSpeechRecognitionCallback
  • Request android.permission.RECORD_AUDIO on AndroidManifest.xml

as follwing.

public class SearchFragment extends android.support.v17.leanback.app.SearchFragment {

    private static final String TAG = SearchFragment.class.getSimpleName();

    private static final int REQUEST_SPEECH = 0x00000010;

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

        if (!Utils.hasPermission(getActivity(), Manifest.permission.RECORD_AUDIO)) {
            // SpeechRecognitionCallback is not required and if not provided recognition will be handled
            // using internal speech recognizer, in which case you must have RECORD_AUDIO permission
            setSpeechRecognitionCallback(new SpeechRecognitionCallback() {
                @Override
                public void recognizeSpeech() {
                    Log.v(TAG, "recognizeSpeech");
                    try {
                        startActivityForResult(getRecognizerIntent(), REQUEST_SPEECH);
                    } catch (ActivityNotFoundException e) {
                        Log.e(TAG, "Cannot find activity for speech recognizer", e);
                    }
                }
            });
        }
    }
    public static boolean hasPermission(final Context context, final String permission) {
        return PackageManager.PERMISSION_GRANTED == context.getPackageManager().checkPermission(
                permission, context.getPackageName());
    }

Overriding onSearchRequeseted to activate in-app search

When user tries voice input search, onSearchRequested callback is executed and Google’s global contents search will be launched as default.

Voice search works with returning the result of Google’s default voice search.

It is necessary to override this method if you want to activate in-app application search.

<Ref> It is written in the description of startSearch method that 

It is typically called from onSearchRequested(), either directly from Activity.onSearchRequested() or from an overridden version in any given Activity. If your goal is simply to activate search, it is preferred to call onSearchRequested(), which may have been overridden elsewhere in your Activity. If your goal is to inject specific data such as context data, it is preferred to override onSearchRequested(), so that any callers to it will benefit from the override.

We override onSearchRequested method for both MainActivity & SearchActivity.

    @Override
    public boolean onSearchRequested() {
        startActivity(new Intent(this, SearchActivity.class));
        return true;
    }

Customize in-app search – SearchResultProvider

SearchResultProvider intereface is an intereface of Leanback library, used to listen search related event.  We need to override 3 methods.

  • getResultsAdapter – returns the adapter which includes the search results, to show search results on SearchFragment.
  • onQueryTextChange – event listener which is called when user changes search query text.
  • onQueryTextSubmit – event listener which is called when user submitted search query text. 

We need to register this SearchResultProvider by using setSearchResultProvider method,  minimum implementation is like this,

public class SearchFragment extends android.support.v17.leanback.app.SearchFragment
        implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider {

    private static final String TAG = SearchFragment.class.getSimpleName();

    private static final int REQUEST_SPEECH = 0x00000010;
    private ArrayObjectAdapter mRowsAdapter;

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

        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

        setSearchResultProvider(this);

        ...
    }

    ... 

    @Override
    public ObjectAdapter getResultsAdapter() {
        Log.d(TAG, "getResultsAdapter");
        Log.d(TAG, mRowsAdapter.toString());

        // It should return search result here,
        // but static Movie Item list will be returned here now for practice.
        ArrayList<Movie> mItems = MovieProvider.getMovieItems();
        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
        listRowAdapter.addAll(0, mItems);
        HeaderItem header = new HeaderItem("Search results");
        mRowsAdapter.add(new ListRow(header, listRowAdapter));

        return mRowsAdapter;
    }

    @Override
    public boolean onQueryTextChange(String newQuery){
        Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        Log.i(TAG, String.format("Search Query Text Submit %s", query));
        return true;
    }

Build and run

SearchActivity can be launched in 2 ways,

  1. Explicitly click search icon of BrowseFragment.
  2. When user starts voice input search from specific controller *1 (onSearchRequested will be called.)

*1 It is depending on the Android TV devices. For example, SONY BRAVIA provides touchpad remote controller and voice search can be done from this remote controller.

SONY’s one flick touchpad remote (cited from http://www.ebay.com/itm/NEW-IN-BOX-SONY-RMF-TX100E-One-Flick-Touchpad-Remote-BRAVIA-Android-TV-2015-NFC-/151709676991)

SearchFragment shows the mock search results now.

SearchFragment with Search results.

Source code is on github.

[Update on 1. 8. 2016]: I’m sorry that it seems the implementation is insufficient. We should implement  OnItemViewClickedListener to define the action of clicking these search results.This OnItemViewClickedListener can be set it by 

setOnItemViewClickedListener(new ItemViewClickedListener());

(in this case) in onCreate of BrowseFragment. Please refer Kim Johnsson’s helpful comment below.

Google’s AOSP implementation is explained in the next chapter.

Leave a Comment

Your email address will not be published. Required fields are marked *