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