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