1 /*
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 
17 package com.android.documentsui;
18 
19 import static com.android.documentsui.Shared.DEBUG;
20 
21 import android.annotation.Nullable;
22 import android.os.Bundle;
23 import android.provider.DocumentsContract.Root;
24 import android.text.TextUtils;
25 import android.util.Log;
26 import android.view.Menu;
27 import android.view.MenuItem;
28 import android.view.MenuItem.OnActionExpandListener;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.view.View.OnFocusChangeListener;
32 import android.widget.SearchView;
33 import android.widget.SearchView.OnQueryTextListener;
34 
35 import com.android.documentsui.model.RootInfo;
36 
37 /**
38  * Manages searching UI behavior.
39  */
40 final class SearchViewManager implements
41         SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener,
42         OnActionExpandListener {
43 
44     public interface SearchManagerListener {
onSearchChanged(@ullable String query)45         void onSearchChanged(@Nullable String query);
onSearchFinished()46         void onSearchFinished();
47     }
48 
49     public static final String TAG = "SearchManger";
50 
51     private SearchManagerListener mListener;
52     private boolean mSearchExpanded;
53     private String mCurrentSearch;
54     private boolean mIgnoreNextClose;
55     private boolean mFullBar;
56 
57     private DocumentsToolbar mActionBar;
58     private MenuItem mMenuItem;
59     private SearchView mSearchView;
60 
SearchViewManager(SearchManagerListener listener, @Nullable Bundle savedState)61     public SearchViewManager(SearchManagerListener listener, @Nullable Bundle savedState) {
62         mListener = listener;
63         mCurrentSearch = savedState != null ? savedState.getString(Shared.EXTRA_QUERY) : null;
64     }
65 
setSearchMangerListener(SearchManagerListener listener)66     public void setSearchMangerListener(SearchManagerListener listener) {
67         mListener = listener;
68     }
69 
install(DocumentsToolbar actionBar, boolean isFullBarSearch)70     public void install(DocumentsToolbar actionBar, boolean isFullBarSearch) {
71         mActionBar = actionBar;
72         mMenuItem = actionBar.getSearchMenu();
73         mSearchView = (SearchView) mMenuItem.getActionView();
74 
75         mSearchView.setOnQueryTextListener(this);
76         mSearchView.setOnCloseListener(this);
77         mSearchView.setOnSearchClickListener(this);
78         mSearchView.setOnQueryTextFocusChangeListener(this);
79 
80         mFullBar = isFullBarSearch;
81         if (mFullBar) {
82             mMenuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
83                     | MenuItem.SHOW_AS_ACTION_ALWAYS);
84             mMenuItem.setOnActionExpandListener(this);
85         }
86 
87         restoreSearch();
88     }
89 
90     /**
91      * Used to hide menu icons, when the search is being restored. Needed because search restoration
92      * is done before onPrepareOptionsMenu(Menu menu) that is overriding the icons visibility.
93      */
updateMenu()94     public void updateMenu() {
95         if (isSearching() && mFullBar) {
96             Menu menu = mActionBar.getMenu();
97             menu.setGroupVisible(R.id.group_hide_when_searching, false);
98         }
99     }
100 
101     /**
102      * @param root Info about the current directory.
103      */
update(RootInfo root)104     void update(RootInfo root) {
105         if (mMenuItem == null) {
106             if (DEBUG) Log.d(TAG, "update called before Search MenuItem installed.");
107             return;
108         }
109 
110         if (mCurrentSearch != null) {
111             mMenuItem.expandActionView();
112 
113             mSearchView.setIconified(false);
114             mSearchView.clearFocus();
115             mSearchView.setQuery(mCurrentSearch, false);
116         } else {
117             mSearchView.clearFocus();
118             if (!mSearchView.isIconified()) {
119                 mIgnoreNextClose = true;
120                 mSearchView.setIconified(true);
121             }
122 
123             if (mMenuItem.isActionViewExpanded()) {
124                 mMenuItem.collapseActionView();
125             }
126         }
127 
128         showMenu(root != null
129                 && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
130     }
131 
showMenu(boolean visible)132     void showMenu(boolean visible) {
133         if (mMenuItem == null) {
134             if (DEBUG) Log.d(TAG, "showMenu called before Search MenuItem installed.");
135             return;
136         }
137 
138         if (!visible) {
139             mCurrentSearch = null;
140         }
141 
142         mMenuItem.setVisible(visible);
143     }
144 
145     /**
146      * Cancels current search operation. Triggers clearing and collapsing the SearchView.
147      *
148      * @return True if it cancels search. False if it does not operate search currently.
149      */
cancelSearch()150     boolean cancelSearch() {
151         if (isExpanded() || isSearching()) {
152             // If the query string is not empty search view won't get iconified
153             mSearchView.setQuery("", false);
154 
155             if (mFullBar) {
156                onClose();
157             } else {
158                 // Causes calling onClose(). onClose() is triggering directory content update.
159                 mSearchView.setIconified(true);
160             }
161             return true;
162         }
163         return false;
164     }
165 
166     /**
167      * Sets search view into the searching state. Used to restore state after device orientation
168      * change.
169      */
restoreSearch()170     private void restoreSearch() {
171         if (isSearching()) {
172             if(mFullBar) {
173                 mMenuItem.expandActionView();
174             } else {
175                 mSearchView.setIconified(false);
176             }
177             onSearchExpanded();
178             mSearchView.setQuery(mCurrentSearch, false);
179             mSearchView.clearFocus();
180         }
181     }
182 
onSearchExpanded()183     private void onSearchExpanded() {
184         mSearchExpanded = true;
185         if(mFullBar) {
186             Menu menu = mActionBar.getMenu();
187             menu.setGroupVisible(R.id.group_hide_when_searching, false);
188         }
189     }
190 
191     /**
192      * Clears the search. Triggers refreshing of the directory content.
193      * @return True if the default behavior of clearing/dismissing SearchView should be overridden.
194      *         False otherwise.
195      */
196     @Override
onClose()197     public boolean onClose() {
198         mSearchExpanded = false;
199         if (mIgnoreNextClose) {
200             mIgnoreNextClose = false;
201             return false;
202         }
203 
204         // Refresh the directory if a search was done
205         if (mCurrentSearch != null) {
206             mCurrentSearch = null;
207             if (mListener != null) {
208                 mListener.onSearchChanged(mCurrentSearch);
209             }
210         }
211 
212         if(mFullBar) {
213             mMenuItem.collapseActionView();
214         }
215         mListener.onSearchFinished();
216 
217         return false;
218     }
219 
220     /**
221      * Called when owning activity is saving state to be used to restore state during creation.
222      * @param state Bundle to save state too
223      */
onSaveInstanceState(Bundle state)224     public void onSaveInstanceState(Bundle state) {
225         state.putString(Shared.EXTRA_QUERY, mCurrentSearch);
226     }
227 
228     /**
229      * Sets mSearchExpanded. Called when search icon is clicked to start search for both search view
230      * modes.
231      */
232     @Override
onClick(View v)233     public void onClick(View v) {
234         onSearchExpanded();
235     }
236 
237     @Override
onQueryTextSubmit(String query)238     public boolean onQueryTextSubmit(String query) {
239         mCurrentSearch = query;
240         mSearchView.clearFocus();
241         if (mListener != null) {
242             mListener.onSearchChanged(mCurrentSearch);
243         }
244         return true;
245     }
246 
247     /**
248      * Used to detect and handle back button pressed event when search is expanded.
249      */
250     @Override
onFocusChange(View v, boolean hasFocus)251     public void onFocusChange(View v, boolean hasFocus) {
252         if (!hasFocus) {
253             if (mCurrentSearch == null) {
254                 mSearchView.setIconified(true);
255             } else if (TextUtils.isEmpty(mSearchView.getQuery())) {
256                 cancelSearch();
257             }
258         }
259     }
260 
261     @Override
onQueryTextChange(String newText)262     public boolean onQueryTextChange(String newText) {
263         return false;
264     }
265 
266     @Override
onMenuItemActionCollapse(MenuItem item)267     public boolean onMenuItemActionCollapse(MenuItem item) {
268         Menu menu = mActionBar.getMenu();
269         menu.setGroupVisible(R.id.group_hide_when_searching, true);
270 
271         // Handles case when search view is collapsed by using the arrow on the left of the bar
272         if (isExpanded() || isSearching()) {
273             cancelSearch();
274             return false;
275         }
276         return true;
277     }
278 
279     @Override
onMenuItemActionExpand(MenuItem item)280     public boolean onMenuItemActionExpand(MenuItem item) {
281         return true;
282     }
283 
getCurrentSearch()284     String getCurrentSearch() {
285         return mCurrentSearch;
286     }
287 
isSearching()288     boolean isSearching() {
289         return mCurrentSearch != null;
290     }
291 
isExpanded()292     boolean isExpanded() {
293         return mSearchExpanded;
294     }
295 
296 }
297