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