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