SearchFragment.java revision e5f2388b8d24876ebbd6daf302487bf452245d50
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.app;
15
16import android.app.Fragment;
17import android.graphics.drawable.Drawable;
18import android.os.Bundle;
19import android.os.Handler;
20import android.support.v17.leanback.widget.ObjectAdapter;
21import android.support.v17.leanback.widget.OnItemClickedListener;
22import android.support.v17.leanback.widget.OnItemSelectedListener;
23import android.support.v17.leanback.widget.Row;
24import android.support.v17.leanback.widget.SearchBar;
25import android.support.v17.leanback.widget.VerticalGridView;
26import android.util.Log;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.FrameLayout;
31import android.support.v17.leanback.R;
32
33import java.util.List;
34
35/**
36 * <p>A fragment to handle searches.</p>
37 *
38 * <p>Note: Your application will need to request android.permission.RECORD_AUDIO</p>
39 */
40public class SearchFragment extends Fragment {
41    private static final String TAG = SearchFragment.class.getSimpleName();
42    private static final boolean DEBUG = false;
43
44    private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName();
45    private static final String ARG_QUERY =  ARG_PREFIX + ".query";
46    private static final String ARG_TITLE = ARG_PREFIX  + ".title";
47
48    /**
49     * Search API exposed to application
50     */
51    public static interface SearchResultProvider {
52        /**
53         * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve
54         * an ObjectAdapter that will contain the results to future updates of the search query.</p>
55         *
56         * <p>As results are retrieved, the application should use the data set notification methods
57         * on the ObjectAdapter to instruct the SearchFragment to update the results.</p>
58         *
59         * @return ObjectAdapter The result object adapter.
60         */
61        public ObjectAdapter getResultsAdapter();
62
63        /**
64         * <p>Method invoked when the search query is updated.</p>
65         *
66         * <p>This is called as soon as the query changes; it is up to the application to add a
67         * delay before actually executing the queries if needed.</p>
68         *
69         * <p>This method might not always be called before onQueryTextSubmit gets called, in
70         * particular for voice input cases.</p>
71         *
72         * @param newQuery The current search query.
73         * @return whether the results changed or not.
74         */
75        public boolean onQueryTextChange(String newQuery);
76
77        /**
78         * Method invoked when the search query is submitted, either by dismissing the keyboard,
79         * pressing search or next on the keyboard or when voice has detected the end of the query.
80         *
81         * @param query The query.
82         * @return whether the results changed or not
83         */
84        public boolean onQueryTextSubmit(String query);
85    }
86
87    private RowsFragment mRowsFragment;
88    private final Handler mHandler = new Handler();
89
90    private SearchBar mSearchBar;
91    private SearchResultProvider mProvider;
92    private String mPendingQuery = null;
93
94    private OnItemSelectedListener mOnItemSelectedListener;
95    private OnItemClickedListener mOnItemClickedListener;
96    private ObjectAdapter mResultAdapter;
97
98    private String mTitle;
99    private Drawable mBadgeDrawable;
100
101    /**
102     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
103     */
104    public static Bundle createArgs(Bundle args, String query) {
105        return createArgs(args, query, null);
106    }
107
108    public static Bundle createArgs(Bundle args, String query, String title)  {
109        if (args == null) {
110            args = new Bundle();
111        }
112        args.putString(ARG_QUERY, query);
113        args.putString(ARG_TITLE, title);
114        return args;
115    }
116
117    /**
118     * Create a search fragment with a given search query to start with
119     *
120     * You should only use this if you need to start the search fragment with a pre-filled query
121     *
122     * @param query the search query to start with
123     * @return a new SearchFragment
124     */
125    public static SearchFragment newInstance(String query) {
126        SearchFragment fragment = new SearchFragment();
127        Bundle args = createArgs(null, query);
128        fragment.setArguments(args);
129        return fragment;
130    }
131
132    @Override
133    public void onCreate(Bundle savedInstanceState) {
134        super.onCreate(savedInstanceState);
135    }
136
137    @Override
138    public View onCreateView(LayoutInflater inflater, ViewGroup container,
139                             Bundle savedInstanceState) {
140        View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
141
142        FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame);
143        mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar);
144        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
145            @Override
146            public void onSearchQueryChange(String query) {
147                if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s", query));
148                if (null != mProvider) {
149                    retrieveResults(query);
150                } else {
151                    mPendingQuery = query;
152                }
153            }
154
155            @Override
156            public void onSearchQuerySubmit(String query) {
157                if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
158                mRowsFragment.setSelectedPosition(0);
159                mRowsFragment.getVerticalGridView().requestFocus();
160                if (null != mProvider) {
161                    mProvider.onQueryTextSubmit(query);
162                }
163            }
164
165            @Override
166            public void onKeyboardDismiss(String query) {
167                if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query));
168                mRowsFragment.setSelectedPosition(0);
169                mRowsFragment.getVerticalGridView().requestFocus();
170            }
171        });
172
173        readArguments(getArguments());
174        if (null != mBadgeDrawable) {
175            setBadgeDrawable(mBadgeDrawable);
176        }
177        if (null != mTitle) {
178            setTitle(mTitle);
179        }
180
181        // Inject the RowsFragment in the results container
182        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
183            mRowsFragment = new RowsFragment();
184            getChildFragmentManager().beginTransaction()
185                    .replace(R.id.lb_results_frame, mRowsFragment).commit();
186        } else {
187            mRowsFragment = (RowsFragment) getChildFragmentManager()
188                    .findFragmentById(R.id.browse_container_dock);
189        }
190        mRowsFragment.setOnItemSelectedListener(new OnItemSelectedListener() {
191            @Override
192            public void onItemSelected(Object item, Row row) {
193                int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
194                if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position));
195                mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE);
196                if (null != mOnItemSelectedListener) {
197                    mOnItemSelectedListener.onItemSelected(item, row);
198                }
199            }
200        });
201        mRowsFragment.setOnItemClickedListener(new OnItemClickedListener() {
202            @Override
203            public void onItemClicked(Object item, Row row) {
204                int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
205                if (DEBUG) Log.v(TAG, String.format("onItemClicked %d", position));
206                if (null != mOnItemClickedListener) {
207                    mOnItemClickedListener.onItemClicked(item, row);
208                }
209            }
210        });
211        mRowsFragment.setExpand(true);
212        if (null != mProvider) {
213            onSetSearchResultProvider();
214        }
215        return root;
216    }
217
218    @Override
219    public void onStart() {
220        super.onStart();
221
222        VerticalGridView list = mRowsFragment.getVerticalGridView();
223        int mContainerListAlignTop =
224                getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top);
225        list.setItemAlignmentOffset(0);
226        list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
227        list.setWindowAlignmentOffset(mContainerListAlignTop);
228        list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
229        list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
230    }
231
232    /**
233     * Set the search provider, which is responsible for returning items given
234     * a search term
235     *
236     * @param searchResultProvider the search provider
237     */
238    public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
239        mProvider = searchResultProvider;
240        onSetSearchResultProvider();
241    }
242
243    /**
244     * Sets an item selection listener.
245     * @param listener the item selection listener
246     */
247    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
248        mOnItemSelectedListener = listener;
249    }
250
251    /**
252     * Sets an item clicked listener.
253     */
254    public void setOnItemClickedListener(OnItemClickedListener listener) {
255        mOnItemClickedListener = listener;
256    }
257
258    /**
259     * Sets the title string to be be shown in an empty search bar
260     */
261    public void setTitle(String title) {
262        mTitle = title;
263        if (null != mSearchBar) {
264            mSearchBar.setTitle(title);
265        }
266    }
267
268    /**
269     * Returns the title set
270     */
271    public String getTitle() {
272        if (null != mSearchBar) {
273            return mSearchBar.getTitle();
274        }
275        return null;
276    }
277
278    /**
279     * Sets the badge drawable that will be shown inside the search bar, next to the hint
280     */
281    public void setBadgeDrawable(Drawable drawable) {
282        mBadgeDrawable = drawable;
283        if (null != mSearchBar) {
284            mSearchBar.setBadgeDrawable(drawable);
285        }
286    }
287
288    /**
289     * Returns the badge drawable
290     */
291    public Drawable getBadgeDrawable() {
292        if (null != mSearchBar) {
293            return mSearchBar.getBadgeDrawable();
294        }
295        return null;
296    }
297
298    /**
299     * Display the completions shown by the IME.
300     *
301     * @param completions list of completions shown in the IME, can be null or empty to clear them
302     */
303    public void displayCompletions(List<String> completions) {
304        mSearchBar.displayCompletions(completions);
305    }
306
307    private void retrieveResults(String searchQuery) {
308        if (DEBUG) Log.v(TAG, String.format("retrieveResults %s", searchQuery));
309        mProvider.onQueryTextChange(searchQuery);
310    }
311
312    private void onSetSearchResultProvider() {
313        mHandler.post(new Runnable() {
314            @Override
315            public void run() {
316                // Retrieve the result adapter
317                mResultAdapter = mProvider.getResultsAdapter();
318                if (null != mRowsFragment) {
319                    mRowsFragment.setAdapter(mResultAdapter);
320                    executePendingQuery();
321                }
322            }
323        });
324    }
325
326    private void executePendingQuery() {
327        if (null != mPendingQuery && null != mResultAdapter) {
328            String query = mPendingQuery;
329            mPendingQuery = null;
330            retrieveResults(query);
331        }
332    }
333
334    private void readArguments(Bundle args) {
335        if (null == args) {
336            return;
337        }
338        if (args.containsKey(ARG_QUERY)) {
339            setSearchQuery(args.getString(ARG_QUERY));
340        }
341
342        if (args.containsKey(ARG_TITLE)) {
343            setTitle(args.getString(ARG_TITLE));
344        }
345    }
346
347    private void setSearchQuery(String query) {
348        mSearchBar.setSearchQuery(query);
349    }
350
351
352
353}
354