/* * Copyright (c) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.tvleanback.ui; import android.Manifest; import android.app.Activity; import android.app.LoaderManager; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.CursorLoader; import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.CursorObjectAdapter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ImageCardView; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; import android.support.v17.leanback.widget.ObjectAdapter; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.support.v17.leanback.widget.SpeechRecognitionCallback; import android.support.v4.app.ActivityOptionsCompat; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.example.android.tvleanback.BuildConfig; import com.example.android.tvleanback.R; import com.example.android.tvleanback.data.VideoContract; import com.example.android.tvleanback.model.Video; import com.example.android.tvleanback.model.VideoCursorMapper; import com.example.android.tvleanback.presenter.CardPresenter; /* * This class demonstrates how to do in-app search */ public class SearchFragment extends android.support.v17.leanback.app.SearchFragment implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider, LoaderManager.LoaderCallbacks<Cursor> { private static final String TAG = "SearchFragment"; private static final boolean DEBUG = BuildConfig.DEBUG; private static final boolean FINISH_ON_RECOGNIZER_CANCELED = true; private static final int REQUEST_SPEECH = 0x00000010; private final Handler mHandler = new Handler(); private ArrayObjectAdapter mRowsAdapter; private String mQuery; private final CursorObjectAdapter mVideoCursorAdapter = new CursorObjectAdapter(new CardPresenter()); private int mSearchLoaderId = 1; private boolean mResultsFound = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); mVideoCursorAdapter.setMapper(new VideoCursorMapper()); setSearchResultProvider(this); setOnItemViewClickedListener(new ItemViewClickedListener()); if (DEBUG) { Log.d(TAG, "User is initiating a search. Do we have RECORD_AUDIO permission? " + hasPermission(Manifest.permission.RECORD_AUDIO)); } if (!hasPermission(Manifest.permission.RECORD_AUDIO)) { if (DEBUG) { Log.d(TAG, "Does not have RECORD_AUDIO, using SpeechRecognitionCallback"); } // 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() { try { startActivityForResult(getRecognizerIntent(), REQUEST_SPEECH); } catch (ActivityNotFoundException e) { Log.e(TAG, "Cannot find activity for speech recognizer", e); } } }); } else if (DEBUG) { Log.d(TAG, "We DO have RECORD_AUDIO"); } } @Override public void onPause() { mHandler.removeCallbacksAndMessages(null); super.onPause(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_SPEECH: switch (resultCode) { case Activity.RESULT_OK: setSearchQuery(data, true); break; default: // If recognizer is canceled or failed, keep focus on the search orb if (FINISH_ON_RECOGNIZER_CANCELED) { if (!hasResults()) { if (DEBUG) Log.v(TAG, "Voice search canceled"); getView().findViewById(R.id.lb_search_bar_speech_orb).requestFocus(); } } break; } break; } } @Override public ObjectAdapter getResultsAdapter() { return mRowsAdapter; } @Override public boolean onQueryTextChange(String newQuery) { if (DEBUG) Log.i(TAG, String.format("Search text changed: %s", newQuery)); loadQuery(newQuery); return true; } @Override public boolean onQueryTextSubmit(String query) { if (DEBUG) Log.i(TAG, String.format("Search text submitted: %s", query)); loadQuery(query); return true; } public boolean hasResults() { return mRowsAdapter.size() > 0 && mResultsFound; } private boolean hasPermission(final String permission) { final Context context = getActivity(); return PackageManager.PERMISSION_GRANTED == context.getPackageManager().checkPermission( permission, context.getPackageName()); } private void loadQuery(String query) { if (!TextUtils.isEmpty(query) && !query.equals("nil")) { mQuery = query; getLoaderManager().initLoader(mSearchLoaderId++, null, this); } } public void focusOnSearch() { getView().findViewById(R.id.lb_search_bar).requestFocus(); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { String query = mQuery; return new CursorLoader( getActivity(), VideoContract.VideoEntry.CONTENT_URI, null, // Return all fields. VideoContract.VideoEntry.COLUMN_NAME + " LIKE ? OR " + VideoContract.VideoEntry.COLUMN_DESC + " LIKE ?", new String[]{"%" + query + "%", "%" + query + "%"}, null // Default sort order ); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { int titleRes; if (cursor != null && cursor.moveToFirst()) { mResultsFound = true; titleRes = R.string.search_results; } else { mResultsFound = false; titleRes = R.string.no_search_results; } mVideoCursorAdapter.changeCursor(cursor); HeaderItem header = new HeaderItem(getString(titleRes, mQuery)); mRowsAdapter.clear(); ListRow row = new ListRow(header, mVideoCursorAdapter); mRowsAdapter.add(row); } @Override public void onLoaderReset(Loader<Cursor> loader) { mVideoCursorAdapter.changeCursor(null); } private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof Video) { Video video = (Video) item; Intent intent = new Intent(getActivity(), VideoDetailsActivity.class); intent.putExtra(VideoDetailsActivity.VIDEO, video); Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation( getActivity(), ((ImageCardView) itemViewHolder.view).getMainImageView(), VideoDetailsActivity.SHARED_ELEMENT_NAME).toBundle(); getActivity().startActivity(intent, bundle); } else { Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT).show(); } } } }