blob: a632136ed64cd95599abeb15404329e17ab2e3df [file] [log] [blame]
Aga Wronska8788dad2016-01-15 17:30:15 -08001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
Steve McKayd9caa6a2016-09-15 16:36:45 -070019import static com.android.documentsui.base.Shared.DEBUG;
Ben Kwa543a2922016-03-22 11:11:46 -070020
Aga Wronskaaf5ace52016-02-17 13:50:42 -080021import android.annotation.Nullable;
22import android.os.Bundle;
Aga Wronska8788dad2016-01-15 17:30:15 -080023import android.provider.DocumentsContract.Root;
24import android.text.TextUtils;
25import android.util.Log;
Aga Wronska8e21daa2016-03-24 18:22:09 -070026import android.view.Menu;
Aga Wronska8788dad2016-01-15 17:30:15 -080027import android.view.MenuItem;
Aga Wronska8e21daa2016-03-24 18:22:09 -070028import android.view.MenuItem.OnActionExpandListener;
Aga Wronska8788dad2016-01-15 17:30:15 -080029import android.view.View;
30import android.view.View.OnClickListener;
31import android.view.View.OnFocusChangeListener;
32import android.widget.SearchView;
33import android.widget.SearchView.OnQueryTextListener;
34
Steve McKayd0805062016-09-15 14:30:38 -070035import com.android.documentsui.base.RootInfo;
Steve McKayd9caa6a2016-09-15 16:36:45 -070036import com.android.documentsui.base.Shared;
Garfield, Tan11d23482016-08-05 09:33:29 -070037import com.android.documentsui.sorting.SortModel;
Aga Wronska8788dad2016-01-15 17:30:15 -080038
39/**
40 * Manages searching UI behavior.
41 */
Ben Lin7c35b032016-05-31 13:24:01 -070042public class SearchViewManager implements
Aga Wronska8e21daa2016-03-24 18:22:09 -070043 SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener,
44 OnActionExpandListener {
Aga Wronska8788dad2016-01-15 17:30:15 -080045
46 public interface SearchManagerListener {
Aga Wronskaaf5ace52016-02-17 13:50:42 -080047 void onSearchChanged(@Nullable String query);
Aga Wronska8e21daa2016-03-24 18:22:09 -070048 void onSearchFinished();
Aga Wronska8788dad2016-01-15 17:30:15 -080049 }
50
Ben Lin7c35b032016-05-31 13:24:01 -070051 private static final String TAG = "SearchManager";
Aga Wronska8788dad2016-01-15 17:30:15 -080052
53 private SearchManagerListener mListener;
Aga Wronska8788dad2016-01-15 17:30:15 -080054 private boolean mSearchExpanded;
Aga Wronskaaf5ace52016-02-17 13:50:42 -080055 private String mCurrentSearch;
Aga Wronska8788dad2016-01-15 17:30:15 -080056 private boolean mIgnoreNextClose;
Aga Wronska8e21daa2016-03-24 18:22:09 -070057 private boolean mFullBar;
Aga Wronska8788dad2016-01-15 17:30:15 -080058
Steve McKay18d01e82016-02-03 11:15:57 -080059 private DocumentsToolbar mActionBar;
Aga Wronska8e21daa2016-03-24 18:22:09 -070060 private MenuItem mMenuItem;
61 private SearchView mSearchView;
Aga Wronska8788dad2016-01-15 17:30:15 -080062
Garfield, Tan11d23482016-08-05 09:33:29 -070063 // We need to disable sorting during search.
64 private SortModel mSortModel;
65
66 public SearchViewManager(
67 SearchManagerListener listener, @Nullable Bundle savedState, SortModel sortModel) {
Aga Wronska8788dad2016-01-15 17:30:15 -080068 mListener = listener;
Aga Wronskaaf5ace52016-02-17 13:50:42 -080069 mCurrentSearch = savedState != null ? savedState.getString(Shared.EXTRA_QUERY) : null;
Garfield, Tan11d23482016-08-05 09:33:29 -070070 mSortModel = sortModel;
Aga Wronska8788dad2016-01-15 17:30:15 -080071 }
72
73 public void setSearchMangerListener(SearchManagerListener listener) {
74 mListener = listener;
75 }
76
Aga Wronska8e21daa2016-03-24 18:22:09 -070077 public void install(DocumentsToolbar actionBar, boolean isFullBarSearch) {
Aga Wronska8788dad2016-01-15 17:30:15 -080078 mActionBar = actionBar;
Aga Wronska8e21daa2016-03-24 18:22:09 -070079 mMenuItem = actionBar.getSearchMenu();
80 mSearchView = (SearchView) mMenuItem.getActionView();
Aga Wronska8788dad2016-01-15 17:30:15 -080081
Aga Wronska8e21daa2016-03-24 18:22:09 -070082 mSearchView.setOnQueryTextListener(this);
83 mSearchView.setOnCloseListener(this);
84 mSearchView.setOnSearchClickListener(this);
85 mSearchView.setOnQueryTextFocusChangeListener(this);
86
87 mFullBar = isFullBarSearch;
88 if (mFullBar) {
89 mMenuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
90 | MenuItem.SHOW_AS_ACTION_ALWAYS);
91 mMenuItem.setOnActionExpandListener(this);
92 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -080093
94 restoreSearch();
Aga Wronska8788dad2016-01-15 17:30:15 -080095 }
96
97 /**
Aga Wronska8e21daa2016-03-24 18:22:09 -070098 * Used to hide menu icons, when the search is being restored. Needed because search restoration
99 * is done before onPrepareOptionsMenu(Menu menu) that is overriding the icons visibility.
100 */
101 public void updateMenu() {
102 if (isSearching() && mFullBar) {
103 Menu menu = mActionBar.getMenu();
104 menu.setGroupVisible(R.id.group_hide_when_searching, false);
105 }
106 }
107
108 /**
Aga Wronska8788dad2016-01-15 17:30:15 -0800109 * @param root Info about the current directory.
110 */
111 void update(RootInfo root) {
Aga Wronska8e21daa2016-03-24 18:22:09 -0700112 if (mMenuItem == null) {
Ben Kwa543a2922016-03-22 11:11:46 -0700113 if (DEBUG) Log.d(TAG, "update called before Search MenuItem installed.");
Aga Wronska8788dad2016-01-15 17:30:15 -0800114 return;
115 }
116
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800117 if (mCurrentSearch != null) {
Aga Wronska8e21daa2016-03-24 18:22:09 -0700118 mMenuItem.expandActionView();
Aga Wronska8788dad2016-01-15 17:30:15 -0800119
Aga Wronska8e21daa2016-03-24 18:22:09 -0700120 mSearchView.setIconified(false);
121 mSearchView.clearFocus();
122 mSearchView.setQuery(mCurrentSearch, false);
Aga Wronska8788dad2016-01-15 17:30:15 -0800123 } else {
Aga Wronska8e21daa2016-03-24 18:22:09 -0700124 mSearchView.clearFocus();
125 if (!mSearchView.isIconified()) {
Aga Wronska8788dad2016-01-15 17:30:15 -0800126 mIgnoreNextClose = true;
Aga Wronska8e21daa2016-03-24 18:22:09 -0700127 mSearchView.setIconified(true);
Aga Wronska8788dad2016-01-15 17:30:15 -0800128 }
129
Aga Wronska8e21daa2016-03-24 18:22:09 -0700130 if (mMenuItem.isActionViewExpanded()) {
131 mMenuItem.collapseActionView();
Aga Wronska8788dad2016-01-15 17:30:15 -0800132 }
133 }
134
135 showMenu(root != null
136 && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
137 }
138
Garfield Tan25676032016-09-19 13:52:35 -0700139 public void showMenu(boolean visible) {
Aga Wronska8e21daa2016-03-24 18:22:09 -0700140 if (mMenuItem == null) {
Ben Kwa543a2922016-03-22 11:11:46 -0700141 if (DEBUG) Log.d(TAG, "showMenu called before Search MenuItem installed.");
Aga Wronska8788dad2016-01-15 17:30:15 -0800142 return;
143 }
144
Aga Wronska8788dad2016-01-15 17:30:15 -0800145 if (!visible) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800146 mCurrentSearch = null;
Aga Wronska8788dad2016-01-15 17:30:15 -0800147 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800148
Aga Wronska8e21daa2016-03-24 18:22:09 -0700149 mMenuItem.setVisible(visible);
Aga Wronska8788dad2016-01-15 17:30:15 -0800150 }
151
152 /**
153 * Cancels current search operation. Triggers clearing and collapsing the SearchView.
154 *
155 * @return True if it cancels search. False if it does not operate search currently.
156 */
157 boolean cancelSearch() {
158 if (isExpanded() || isSearching()) {
159 // If the query string is not empty search view won't get iconified
Aga Wronska8e21daa2016-03-24 18:22:09 -0700160 mSearchView.setQuery("", false);
161
162 if (mFullBar) {
163 onClose();
164 } else {
165 // Causes calling onClose(). onClose() is triggering directory content update.
166 mSearchView.setIconified(true);
167 }
Aga Wronska8788dad2016-01-15 17:30:15 -0800168 return true;
169 }
170 return false;
171 }
172
Aga Wronska8e21daa2016-03-24 18:22:09 -0700173 /**
174 * Sets search view into the searching state. Used to restore state after device orientation
175 * change.
176 */
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800177 private void restoreSearch() {
178 if (isSearching()) {
Aga Wronska8e21daa2016-03-24 18:22:09 -0700179 if(mFullBar) {
180 mMenuItem.expandActionView();
181 } else {
182 mSearchView.setIconified(false);
183 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800184 onSearchExpanded();
Aga Wronska8e21daa2016-03-24 18:22:09 -0700185 mSearchView.setQuery(mCurrentSearch, false);
186 mSearchView.clearFocus();
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800187 }
188 }
189
190 private void onSearchExpanded() {
191 mSearchExpanded = true;
Aga Wronska8e21daa2016-03-24 18:22:09 -0700192 if(mFullBar) {
193 Menu menu = mActionBar.getMenu();
194 menu.setGroupVisible(R.id.group_hide_when_searching, false);
195 }
Garfield, Tan11d23482016-08-05 09:33:29 -0700196
197 mSortModel.setSortEnabled(false);
Aga Wronska8788dad2016-01-15 17:30:15 -0800198 }
199
200 /**
Aga Wronska8e21daa2016-03-24 18:22:09 -0700201 * Clears the search. Triggers refreshing of the directory content.
Aga Wronska8788dad2016-01-15 17:30:15 -0800202 * @return True if the default behavior of clearing/dismissing SearchView should be overridden.
203 * False otherwise.
204 */
205 @Override
206 public boolean onClose() {
207 mSearchExpanded = false;
208 if (mIgnoreNextClose) {
209 mIgnoreNextClose = false;
210 return false;
211 }
212
Aga Wronska8788dad2016-01-15 17:30:15 -0800213 // Refresh the directory if a search was done
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800214 if (mCurrentSearch != null) {
215 mCurrentSearch = null;
Aga Wronska8788dad2016-01-15 17:30:15 -0800216 if (mListener != null) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800217 mListener.onSearchChanged(mCurrentSearch);
Aga Wronska8788dad2016-01-15 17:30:15 -0800218 }
219 }
Aga Wronska8e21daa2016-03-24 18:22:09 -0700220
221 if(mFullBar) {
222 mMenuItem.collapseActionView();
223 }
224 mListener.onSearchFinished();
225
Garfield, Tan11d23482016-08-05 09:33:29 -0700226 mSortModel.setSortEnabled(true);
227
Aga Wronska8788dad2016-01-15 17:30:15 -0800228 return false;
229 }
230
231 /**
Aga Wronska8e21daa2016-03-24 18:22:09 -0700232 * Called when owning activity is saving state to be used to restore state during creation.
233 * @param state Bundle to save state too
234 */
235 public void onSaveInstanceState(Bundle state) {
236 state.putString(Shared.EXTRA_QUERY, mCurrentSearch);
237 }
238
239 /**
240 * Sets mSearchExpanded. Called when search icon is clicked to start search for both search view
241 * modes.
Aga Wronska8788dad2016-01-15 17:30:15 -0800242 */
243 @Override
244 public void onClick(View v) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800245 onSearchExpanded();
Aga Wronska8788dad2016-01-15 17:30:15 -0800246 }
247
248 @Override
249 public boolean onQueryTextSubmit(String query) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800250 mCurrentSearch = query;
Aga Wronska8e21daa2016-03-24 18:22:09 -0700251 mSearchView.clearFocus();
Aga Wronska8788dad2016-01-15 17:30:15 -0800252 if (mListener != null) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800253 mListener.onSearchChanged(mCurrentSearch);
Aga Wronska8788dad2016-01-15 17:30:15 -0800254 }
255 return true;
256 }
257
Aga Wronska8e21daa2016-03-24 18:22:09 -0700258 /**
259 * Used to detect and handle back button pressed event when search is expanded.
260 */
Aga Wronska8788dad2016-01-15 17:30:15 -0800261 @Override
262 public void onFocusChange(View v, boolean hasFocus) {
263 if (!hasFocus) {
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800264 if (mCurrentSearch == null) {
Aga Wronska8e21daa2016-03-24 18:22:09 -0700265 mSearchView.setIconified(true);
266 } else if (TextUtils.isEmpty(mSearchView.getQuery())) {
Aga Wronska8788dad2016-01-15 17:30:15 -0800267 cancelSearch();
268 }
269 }
270 }
271
272 @Override
273 public boolean onQueryTextChange(String newText) {
274 return false;
275 }
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800276
Aga Wronska8e21daa2016-03-24 18:22:09 -0700277 @Override
278 public boolean onMenuItemActionCollapse(MenuItem item) {
279 Menu menu = mActionBar.getMenu();
280 menu.setGroupVisible(R.id.group_hide_when_searching, true);
281
282 // Handles case when search view is collapsed by using the arrow on the left of the bar
283 if (isExpanded() || isSearching()) {
284 cancelSearch();
285 return false;
286 }
287 return true;
288 }
289
290 @Override
291 public boolean onMenuItemActionExpand(MenuItem item) {
292 return true;
293 }
294
Aga Wronskaaf5ace52016-02-17 13:50:42 -0800295 String getCurrentSearch() {
296 return mCurrentSearch;
297 }
298
Steve McKay16e0c1f2016-09-15 12:41:13 -0700299 public boolean isSearching() {
Aga Wronska8e21daa2016-03-24 18:22:09 -0700300 return mCurrentSearch != null;
301 }
302
303 boolean isExpanded() {
304 return mSearchExpanded;
305 }
Aga Wronska8788dad2016-01-15 17:30:15 -0800306}