Home | History | Annotate | Download | only in queries
      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