OSDN Git Service

Remove actions menu icon (issue #21)
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / activities / SearchActivity.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod 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.cyanogenmod.filemanager.activities;
18
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.SearchManager;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.ActivityInfo;
29 import android.os.Bundle;
30 import android.os.Parcelable;
31 import android.preference.PreferenceActivity;
32 import android.provider.SearchRecentSuggestions;
33 import android.text.Html;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.view.KeyEvent;
37 import android.view.MenuItem;
38 import android.view.View;
39 import android.widget.AdapterView;
40 import android.widget.AdapterView.OnItemClickListener;
41 import android.widget.AdapterView.OnItemLongClickListener;
42 import android.widget.ListAdapter;
43 import android.widget.ListView;
44 import android.widget.ProgressBar;
45 import android.widget.TextView;
46 import android.widget.Toast;
47
48 import com.cyanogenmod.filemanager.FileManagerApplication;
49 import com.cyanogenmod.filemanager.R;
50 import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences;
51 import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences.SearchPreferenceFragment;
52 import com.cyanogenmod.filemanager.adapters.SearchResultAdapter;
53 import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
54 import com.cyanogenmod.filemanager.commands.AsyncResultListener;
55 import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
56 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
57 import com.cyanogenmod.filemanager.model.Directory;
58 import com.cyanogenmod.filemanager.model.FileSystemObject;
59 import com.cyanogenmod.filemanager.model.Query;
60 import com.cyanogenmod.filemanager.model.SearchResult;
61 import com.cyanogenmod.filemanager.model.Symlink;
62 import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable;
63 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
64 import com.cyanogenmod.filemanager.preferences.Preferences;
65 import com.cyanogenmod.filemanager.providers.RecentSearchesContentProvider;
66 import com.cyanogenmod.filemanager.tasks.SearchResultDrawingAsyncTask;
67 import com.cyanogenmod.filemanager.ui.dialogs.ActionsDialog;
68 import com.cyanogenmod.filemanager.ui.dialogs.MessageProgressDialog;
69 import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy;
70 import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
71 import com.cyanogenmod.filemanager.util.CommandHelper;
72 import com.cyanogenmod.filemanager.util.DialogHelper;
73 import com.cyanogenmod.filemanager.util.ExceptionUtil;
74 import com.cyanogenmod.filemanager.util.FileHelper;
75 import com.cyanogenmod.filemanager.util.StorageHelper;
76
77 import java.io.FileNotFoundException;
78 import java.util.ArrayList;
79 import java.util.List;
80
81 /**
82  * An activity for search files and folders.
83  */
84 public class SearchActivity extends Activity
85     implements AsyncResultListener, OnItemClickListener,
86                OnItemLongClickListener, OnRequestRefreshListener {
87
88     private static final String TAG = "SearchActivity"; //$NON-NLS-1$
89
90     private static boolean DEBUG = false;
91
92     /**
93      * An {@link Intent} action for restore view information.
94      */
95     public static final String ACTION_RESTORE =
96             "com.cyanogenmod.filemanager.activities.SearchActivity#Restore"; //$NON-NLS-1$
97
98     /**
99      * Intent extra parameter for search in the selected directory on enter.
100      */
101     public static final String EXTRA_SEARCH_DIRECTORY = "extra_search_directory";  //$NON-NLS-1$
102
103     /**
104      * Intent extra parameter for pass the restore information.
105      */
106     public static final String EXTRA_SEARCH_RESTORE = "extra_search_restore";  //$NON-NLS-1$
107
108
109     //Minimum characters to allow query
110     private static final int MIN_CHARS_SEARCH = 3;
111
112     private final BroadcastReceiver mOnSettingChangeReceiver = new BroadcastReceiver() {
113         @Override
114         public void onReceive(Context context, Intent intent) {
115             if (intent != null &&
116                 intent.getAction().compareTo(FileManagerSettings.INTENT_SETTING_CHANGED) == 0) {
117
118                 // The settings has changed
119                 String key = intent.getStringExtra(FileManagerSettings.EXTRA_SETTING_CHANGED_KEY);
120                 if (key != null) {
121                     if (SearchActivity.this.mSearchListView.getAdapter() != null &&
122                        (key.compareTo(
123                                FileManagerSettings.SETTINGS_HIGHLIGHT_TERMS.getId()) == 0 ||
124                         key.compareTo(
125                                 FileManagerSettings.SETTINGS_SHOW_RELEVANCE_WIDGET.getId()) == 0 ||
126                         key.compareTo(
127                                 FileManagerSettings.SETTINGS_SORT_SEARCH_RESULTS_MODE.getId()) == 0)) {
128
129                         // Recreate the adapter
130                         int pos = SearchActivity.this.mSearchListView.getFirstVisiblePosition();
131                         drawResults();
132                         SearchActivity.this.mSearchListView.setSelection(pos);
133                         return;
134                     }
135                 }
136             }
137         }
138     };
139
140     /**
141      * @hide
142      */
143     MessageProgressDialog mDialog = null;
144     /**
145      * @hide
146      */
147     AsyncResultExecutable mExecutable = null;
148
149     /**
150      * @hide
151      */
152     ListView mSearchListView;
153     /**
154      * @hide
155      */
156     ProgressBar mSearchWaiting;
157     /**
158      * @hide
159      */
160     TextView mSearchFoundItems;
161     /**
162      * @hide
163      */
164     TextView mSearchTerms;
165     private View mEmptyListMsg;
166
167     private String mSearchDirectory;
168     /**
169      * @hide
170      */
171     List<FileSystemObject> mResultList;
172     /**
173      * @hide
174      */
175     Query mQuery;
176
177     /**
178      * @hide
179      */
180     SearchInfoParcelable mRestoreState;
181
182     private SearchResultDrawingAsyncTask mDrawingSearchResultTask;
183
184     /**
185      * @hide
186      */
187     boolean mChRooted;
188
189
190     /**
191      * {@inheritDoc}
192      */
193     @Override
194     protected void onCreate(Bundle state) {
195         if (DEBUG) {
196             Log.d(TAG, "NavigationActivity.onCreate"); //$NON-NLS-1$
197         }
198
199         // Check if app is running in chrooted mode
200         this.mChRooted = !FileManagerApplication.isAdvancedMode();
201
202         // Register the broadcast receiver
203         IntentFilter filter = new IntentFilter();
204         filter.addAction(FileManagerSettings.INTENT_SETTING_CHANGED);
205         registerReceiver(this.mOnSettingChangeReceiver, filter);
206
207         //Request features
208         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
209         //Set in transition
210         overridePendingTransition(R.anim.translate_to_right_in, R.anim.hold_out);
211
212         //Set the main layout of the activity
213         setContentView(R.layout.search);
214
215         //Restore state
216         if (state != null) {
217             restoreState(state);
218         }
219
220         //Initialize action bars and search
221         initTitleActionBar();
222         initComponents();
223         if (this.mRestoreState != null) {
224             //Restore activity from cached data
225             loadFromCacheData();
226         } else {
227             //New query
228             if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
229                 initSearch();
230             } else if (ACTION_RESTORE.equals(getIntent().getAction())) {
231                 restoreState(getIntent().getExtras());
232                 loadFromCacheData();
233             }
234         }
235
236         //Save state
237         super.onCreate(state);
238     }
239
240     /**
241      * {@inheritDoc}
242      */
243     @Override
244     protected void onNewIntent(Intent intent) {
245         //New query
246         if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
247             initSearch();
248         }
249     }
250
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     protected void onPause() {
256         //Set out transition
257         overridePendingTransition(R.anim.hold_in, R.anim.translate_to_left_out);
258         super.onPause();
259     }
260
261     /**
262      * {@inheritDoc}
263      */
264     @Override
265     protected void onDestroy() {
266         super.onDestroy();
267         try {
268             unregisterReceiver(this.mOnSettingChangeReceiver);
269         } catch (Throwable ex) {/**NON BLOCK**/}
270     }
271
272     /**
273      * {@inheritDoc}
274      */
275     @Override
276     protected void onSaveInstanceState(Bundle outState) {
277         if (DEBUG) {
278             Log.d(TAG, "SearchActivity.onSaveInstanceState"); //$NON-NLS-1$
279         }
280         saveState(outState);
281         super.onSaveInstanceState(outState);
282     }
283
284     /**
285      * Method that save the instance of the activity.
286      *
287      * @param state The current state of the activity
288      */
289     private void saveState(Bundle state) {
290         try {
291             if (this.mSearchListView.getAdapter() != null) {
292                 state.putParcelable(EXTRA_SEARCH_RESTORE, createSearchInfo());
293             }
294         } catch (Throwable ex) {
295             Log.w(TAG, "The state can't be saved", ex); //$NON-NLS-1$
296         }
297     }
298
299     /**
300      * Method that restore the instance of the activity.
301      *
302      * @param state The previous state of the activity
303      */
304     private void restoreState(Bundle state) {
305         try {
306             if (state.containsKey(EXTRA_SEARCH_RESTORE)) {
307                 this.mRestoreState = state.getParcelable(EXTRA_SEARCH_RESTORE);
308             }
309         } catch (Throwable ex) {
310             Log.w(TAG, "The state can't be restored", ex); //$NON-NLS-1$
311         }
312     }
313
314     /**
315      * Method that initializes the titlebar of the activity.
316      */
317     private void initTitleActionBar() {
318         //Configure the action bar options
319         getActionBar().setBackgroundDrawable(
320                 getResources().getDrawable(R.drawable.bg_holo_titlebar));
321         getActionBar().setDisplayOptions(
322                 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
323         getActionBar().setDisplayHomeAsUpEnabled(true);
324         View customTitle = getLayoutInflater().inflate(R.layout.simple_customtitle, null, false);
325
326         TextView title = (TextView)customTitle.findViewById(R.id.customtitle_title);
327         title.setText(R.string.search);
328         title.setContentDescription(getString(R.string.search));
329         ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
330         configuration.setImageResource(R.drawable.ic_holo_light_config);
331         configuration.setVisibility(View.VISIBLE);
332
333         getActionBar().setCustomView(customTitle);
334     }
335
336     /**
337      * Method that initializes the component of the activity.
338      */
339     private void initComponents() {
340         //Empty list view
341         this.mEmptyListMsg = findViewById(R.id.search_empty_msg);
342         //The list view
343         this.mSearchListView = (ListView)findViewById(R.id.search_listview);
344         this.mSearchListView.setOnItemClickListener(this);
345         this.mSearchListView.setOnItemLongClickListener(this);
346
347         //Other components
348         this.mSearchWaiting = (ProgressBar)findViewById(R.id.search_waiting);
349         this.mSearchFoundItems = (TextView)findViewById(R.id.search_status_found_items);
350         setFoundItems(0, ""); //$NON-NLS-1$
351         this.mSearchTerms = (TextView)findViewById(R.id.search_status_query_terms);
352         this.mSearchTerms.setText(
353                 Html.fromHtml(getString(R.string.search_terms, ""))); //$NON-NLS-1$
354     }
355
356     /**
357      * Method invoked when an action item is clicked.
358      *
359      * @param view The button pushed
360      */
361     public void onActionBarItemClick(View view) {
362         switch (view.getId()) {
363             case R.id.ab_button1:
364                 //Settings
365                 Intent settings = new Intent(this, SettingsPreferences.class);
366                 settings.putExtra(
367                         PreferenceActivity.EXTRA_SHOW_FRAGMENT,
368                         SearchPreferenceFragment.class.getName());
369                 startActivity(settings);
370                 break;
371
372             default:
373                 break;
374         }
375     }
376
377     /**
378      * Method that initializes the titlebar of the activity.
379      */
380     private void initSearch() {
381         //Stop any pending action
382         try {
383             if (SearchActivity.this.mDrawingSearchResultTask != null
384                     && SearchActivity.this.mDrawingSearchResultTask.isRunning()) {
385                 SearchActivity.this.mDrawingSearchResultTask.cancel(true);
386             }
387         } catch (Throwable ex2) {
388             /**NON BLOCK**/
389         }
390         try {
391             if (SearchActivity.this.mDialog != null) {
392                 SearchActivity.this.mDialog.dismiss();
393             }
394         } catch (Throwable ex2) {
395             /**NON BLOCK**/
396         }
397
398         //Recovery the search directory
399         Bundle bundle = getIntent().getBundleExtra(SearchManager.APP_DATA);
400         //If data is not present, use root directory to do the search
401         this.mSearchDirectory = FileHelper.ROOT_DIRECTORY;
402         if (bundle != null) {
403             this.mSearchDirectory =
404                     bundle.getString(EXTRA_SEARCH_DIRECTORY, FileHelper.ROOT_DIRECTORY);
405         }
406
407         //Retrieve the query Â¿from voice recognizer?
408         boolean voiceQuery = true;
409         List<String> userQueries =
410                 getIntent().getStringArrayListExtra(android.speech.RecognizerIntent.EXTRA_RESULTS);
411         if (userQueries == null || userQueries.size() == 0) {
412             //From input text
413             userQueries = new ArrayList<String>();
414             //Recovers and save the last term search in the memory
415             Preferences.setLastSearch(getIntent().getStringExtra(SearchManager.QUERY));
416             userQueries.add(Preferences.getLastSearch());
417             voiceQuery = false;
418         }
419
420         //Filter the queries? Needed if queries come from voice recognition
421         final List<String> filteredUserQueries =
422                 (voiceQuery) ? filterQuery(userQueries) : userQueries;
423
424         //Create the queries
425         this.mQuery = new Query().fillSlots(filteredUserQueries);
426         List<String> queries = this.mQuery.getQueries();
427
428         //Check if some queries has lower than allowed, in this case
429         //request the user for stop the search
430         boolean ask = false;
431         int cc = queries.size();
432         for (int i = 0; i < cc; i++) {
433             if (queries.get(i).trim().length() < MIN_CHARS_SEARCH) {
434                 ask = true;
435                 break;
436             }
437         }
438         if (ask) {
439             askUserBeforeSearch(voiceQuery, this.mQuery, this.mSearchDirectory);
440         } else {
441             doSearch(voiceQuery, this.mQuery, this.mSearchDirectory);
442         }
443
444     }
445
446     /**
447      * Method that ask the user before do the search.
448      *
449      * @param voiceQuery Indicates if the query is from voice recognition
450      * @param query The terms of the search
451      * @param searchDirectory The directory of the search
452      */
453     private void askUserBeforeSearch(
454             final boolean voiceQuery, final Query query, final String searchDirectory) {
455         //Show a dialog asking the user
456         AlertDialog dialog =
457                 DialogHelper.createYesNoDialog(
458                         this, R.string.search_few_characters_msg,
459                         new DialogInterface.OnClickListener() {
460                             @Override
461                             public void onClick(DialogInterface alertDialog, int which) {
462                                 if (which == DialogInterface.BUTTON_NEGATIVE) {
463                                     doSearch(voiceQuery, query, searchDirectory);
464                                     return;
465                                 }
466
467                                 //Close search activity
468                                 back(true, null, false);
469                             }
470                        });
471         dialog.show();
472     }
473
474     /**
475      * Method that do the search.
476      *
477      * @param voiceQuery Indicates if the query is from voice recognition
478      * @param query The terms of the search
479      * @param searchDirectory The directory of the search
480      * @hide
481      */
482     void doSearch(
483             final boolean voiceQuery, final Query query, final String searchDirectory) {
484
485         // Recovers the user preferences about save suggestions
486         boolean saveSuggestions = Preferences.getSharedPreferences().getBoolean(
487                 FileManagerSettings.SETTINGS_SAVE_SEARCH_TERMS.getId(),
488                 ((Boolean)FileManagerSettings.SETTINGS_SAVE_SEARCH_TERMS.
489                         getDefaultValue()).booleanValue());
490         if (saveSuggestions) {
491             //Save every query for use as recent suggestions
492             SearchRecentSuggestions suggestions =
493                     new SearchRecentSuggestions(this,
494                             RecentSearchesContentProvider.AUTHORITY,
495                             RecentSearchesContentProvider.MODE);
496             if (!voiceQuery) {
497                 List<String> queries = query.getQueries();
498                 int cc = queries.size();
499                 for (int i = 0; i < cc; i++) {
500                     suggestions.saveRecentQuery(queries.get(i), null);
501                 }
502             }
503         }
504
505         //Set the listview
506         this.mResultList = new ArrayList<FileSystemObject>();
507         SearchResultAdapter adapter =
508                 new SearchResultAdapter(this,
509                         new ArrayList<SearchResult>(), R.layout.search_item, this.mQuery);
510         this.mSearchListView.setAdapter(adapter);
511
512         //Set terms
513         this.mSearchTerms.setText(
514                 Html.fromHtml(getString(R.string.search_terms, query.getTerms())));
515
516
517         //Now, do the search in background
518         this.mSearchListView.post(new Runnable() {
519             @Override
520             public void run() {
521                 try {
522                     //Retrieve the terms of the search
523                     String label = getString(R.string.searching_action_label);
524
525                     //Show a dialog for the progress
526                     SearchActivity.this.mDialog =
527                             new MessageProgressDialog(
528                                     SearchActivity.this,
529                                     R.drawable.ic_holo_light_search,
530                                     R.string.searching, label, true);
531                     // Initialize the
532                     setProgressMsg(0);
533
534                     // Set the cancel listener
535                     SearchActivity.this.mDialog.setOnCancelListener(
536                             new MessageProgressDialog.OnCancelListener() {
537                                 @Override
538                                 public boolean onCancel() {
539                                     //User has requested the cancellation of the search
540                                     //Broadcast the cancellation
541                                     if (!SearchActivity.this.mExecutable.isCancelled()) {
542                                         if (SearchActivity.this.mExecutable.cancel()) {
543                                             ListAdapter listAdapter =
544                                                     SearchActivity.
545                                                         this.mSearchListView.getAdapter();
546                                             if (listAdapter != null) {
547                                                 SearchActivity.this.toggleResults(
548                                                         listAdapter.getCount() > 0, true);
549                                             }
550                                             return true;
551                                         }
552                                         return false;
553                                     }
554                                     return true;
555                                 }
556                             });
557                     SearchActivity.this.mDialog.show();
558
559                     //Execute the query (search are process in background)
560                     SearchActivity.this.mExecutable =
561                             CommandHelper.findFiles(
562                                     SearchActivity.this,
563                                     searchDirectory,
564                                     SearchActivity.this.mQuery,
565                                     SearchActivity.this,
566                                     null);
567
568                 } catch (Throwable ex) {
569                     //Remove all elements
570                     try {
571                         SearchActivity.this.removeAll();
572                     } catch (Throwable ex2) {
573                         /**NON BLOCK**/
574                     }
575                     try {
576                         if (SearchActivity.this.mDialog != null) {
577                             SearchActivity.this.mDialog.dismiss();
578                         }
579                     } catch (Throwable ex2) {
580                         /**NON BLOCK**/
581                     }
582
583                     //Capture the exception
584                     Log.e(TAG, "Search failed", ex); //$NON-NLS-1$
585                     DialogHelper.showToast(
586                             SearchActivity.this,
587                             R.string.search_error_msg, Toast.LENGTH_SHORT);
588                     SearchActivity.this.mSearchListView.setVisibility(View.GONE);
589                 }
590             }
591         });
592     }
593
594     /**
595      * Method that restore the activity from the cached data.
596      */
597     private void loadFromCacheData() {
598         this.mSearchListView.post(new Runnable() {
599             @Override
600             public void run() {
601                 //Toggle results
602                 List<SearchResult> list = SearchActivity.this.mRestoreState.getSearchResultList();
603                 String directory = SearchActivity.this.mRestoreState.getSearchDirectory();
604                 SearchActivity.this.toggleResults(list.size() > 0, true);
605                 setFoundItems(list.size(), directory);
606
607                 //Set terms
608                 Query query = SearchActivity.this.mRestoreState.getSearchQuery();
609                 String terms =
610                         TextUtils.join(" | ",  //$NON-NLS-1$;
611                                 query.getQueries().toArray(new String[]{}));
612                 if (terms.endsWith(" | ")) { //$NON-NLS-1$;
613                     terms = ""; //$NON-NLS-1$;
614                 }
615                 SearchActivity.this.mSearchTerms.setText(
616                         Html.fromHtml(getString(R.string.search_terms, terms)));
617
618                 try {
619                     if (SearchActivity.this.mSearchWaiting != null) {
620                         SearchActivity.this.mSearchWaiting.setVisibility(View.VISIBLE);
621                     }
622
623                     //Add list to the listview
624                     if (SearchActivity.this.mSearchListView.getAdapter() != null) {
625                         ((SearchResultAdapter)SearchActivity.this.
626                                 mSearchListView.getAdapter()).clear();
627                     }
628                     SearchResultAdapter adapter =
629                             new SearchResultAdapter(
630                                                 SearchActivity.this.mSearchListView.getContext(),
631                                                 list,
632                                                 R.layout.search_item,
633                                                 query);
634                     SearchActivity.this.mSearchListView.setAdapter(adapter);
635                     SearchActivity.this.mSearchListView.setSelection(0);
636
637                 } catch (Throwable ex) {
638                     //Capture the exception
639                     ExceptionUtil.translateException(SearchActivity.this, ex);
640
641                 } finally {
642                     //Hide waiting
643                     if (SearchActivity.this.mSearchWaiting != null) {
644                         SearchActivity.this.mSearchWaiting.setVisibility(View.GONE);
645                     }
646                 }
647             }
648         });
649     }
650
651
652     /**
653      * Method that filter the user queries for valid queries only.<br/>
654      * <br/>
655      * Only allow query strings with more that 3 characters
656      *
657      * @param original The original user queries
658      * @return List<String> The list of queries filtered
659      */
660     @SuppressWarnings("static-method")
661     private List<String> filterQuery(List<String> original) {
662         List<String> dst = new ArrayList<String>(original);
663         int cc = dst.size();
664         for (int i = cc - 1; i >= 0; i--) {
665             String query = dst.get(i);
666             if (query == null || query.trim().length() < MIN_CHARS_SEARCH) {
667                 dst.remove(i);
668             }
669         }
670         return dst;
671     }
672
673     /**
674      * Method that removes all items and display a message.
675      * @hide
676      */
677     void removeAll() {
678         SearchResultAdapter adapter = (SearchResultAdapter)this.mSearchListView.getAdapter();
679         adapter.clear();
680         adapter.notifyDataSetChanged();
681         this.mSearchListView.setSelection(0);
682         toggleResults(false, true);
683     }
684
685     /**
686      * Method that toggle the views when there are results.
687      *
688      * @param hasResults Indicates if there are results
689      * @param showEmpty Show the empty list message
690      * @hide
691      */
692     void toggleResults(boolean hasResults, boolean showEmpty) {
693         this.mSearchListView.setVisibility(hasResults ? View.VISIBLE : View.INVISIBLE);
694         this.mEmptyListMsg.setVisibility(!hasResults && showEmpty ? View.VISIBLE : View.INVISIBLE);
695     }
696
697     /**
698      * Method that display the number of found items.
699      *
700      * @param items The number of items
701      * @param searchDirectory The search directory path
702      * @hide
703      */
704     void setFoundItems(final int items, final String searchDirectory) {
705         if (this.mSearchFoundItems != null) {
706             this.mSearchFoundItems.post(new Runnable() {
707                 @Override
708                 public void run() {
709                     String directory = searchDirectory;
710                     if (SearchActivity.this.mChRooted &&
711                             directory != null && directory.length() > 0) {
712                         directory = StorageHelper.getChrootedPath(directory);
713                     }
714
715                     String foundItems =
716                             getResources().
717                                 getQuantityString(
718                                     R.plurals.search_found_items, items, Integer.valueOf(items));
719                     SearchActivity.this.mSearchFoundItems.setText(
720                                             getString(
721                                                 R.string.search_found_items_in_directory,
722                                                 foundItems,
723                                                 directory));
724                 }
725             });
726         }
727     }
728
729     /**
730      * {@inheritDoc}
731      */
732     @Override
733     public boolean onKeyUp(int keyCode, KeyEvent event) {
734         switch (keyCode) {
735             case KeyEvent.KEYCODE_BACK:
736                 back(true, null, false);
737                 return true;
738             default:
739                 return super.onKeyUp(keyCode, event);
740         }
741     }
742
743     /**
744      * {@inheritDoc}
745      */
746     @Override
747     public boolean onOptionsItemSelected(MenuItem item) {
748        switch (item.getItemId()) {
749           case android.R.id.home:
750               back(true, null, false);
751               return true;
752           default:
753              return super.onOptionsItemSelected(item);
754        }
755     }
756
757     /**
758      * {@inheritDoc}
759      */
760     @Override
761     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
762         try {
763             SearchResult result = ((SearchResultAdapter)parent.getAdapter()).getItem(position);
764             FileSystemObject fso = result.getFso();
765             if (fso instanceof Directory) {
766                 back(false, fso, false);
767             } else if (fso instanceof Symlink) {
768                 Symlink symlink = (Symlink)fso;
769                 if (symlink.getLinkRef() != null && symlink.getLinkRef() instanceof Directory) {
770                     back(false, symlink.getLinkRef(), false);
771                 }
772             } else {
773                 // Open the file with the preferred registered app
774                 back(false, fso, false);
775             }
776         } catch (Throwable ex) {
777             ExceptionUtil.translateException(this.mSearchListView.getContext(), ex);
778         }
779     }
780
781     /**
782      * {@inheritDoc}
783      */
784     @Override
785     public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
786         // Different actions depending on user preference
787
788         // Get the adapter, the search result and the fso
789         SearchResultAdapter adapter = ((SearchResultAdapter)parent.getAdapter());
790         SearchResult searchResult = adapter.getItem(position);
791         FileSystemObject fso = searchResult.getFso();
792
793         // Open the actions menu
794         onRequestMenu(fso);
795         return true; //Always consume the event
796     }
797
798     /**
799      * Method invoked when a request to show the menu associated
800      * with an item is started.
801      *
802      * @param item The item for which the request was started
803      */
804     public void onRequestMenu(FileSystemObject item) {
805         // Prior to show the dialog, refresh the item reference
806         FileSystemObject fso = null;
807         try {
808             fso = CommandHelper.getFileInfo(this, item.getFullPath(), false, null);
809             if (fso == null) {
810                 throw new NoSuchFileOrDirectory(item.getFullPath());
811             }
812
813         } catch (Exception e) {
814             // Notify the user
815             ExceptionUtil.translateException(this, e);
816
817             // Remove the object
818             if (e instanceof FileNotFoundException || e instanceof NoSuchFileOrDirectory) {
819                 removeItem(item);
820             }
821             return;
822         }
823
824         ActionsDialog dialog = new ActionsDialog(this, fso, false, true);
825         dialog.setOnRequestRefreshListener(this);
826         dialog.show();
827     }
828
829     /**
830      * Method that removes the {@link FileSystemObject} reference
831      *
832      * @param fso The file system object
833      */
834     private void removeItem(FileSystemObject fso) {
835         SearchResultAdapter adapter =
836                 (SearchResultAdapter)this.mSearchListView.getAdapter();
837         if (adapter != null) {
838             int pos = adapter.getPosition(fso);
839             if (pos != -1) {
840                 SearchResult sr = adapter.getItem(pos);
841                 adapter.remove(sr);
842             }
843
844             // Toggle resultset?
845             toggleResults(adapter.getCount() > 0, true);
846             setFoundItems(adapter.getCount(), this.mSearchDirectory);
847         }
848     }
849
850     /**
851      * {@inheritDoc}
852      */
853     @Override
854     public void onRequestRefresh(Object o) {
855         // Refresh only the item
856         SearchResultAdapter adapter =
857                 (SearchResultAdapter)this.mSearchListView.getAdapter();
858         if (adapter != null) {
859             if (o instanceof FileSystemObject) {
860
861                 FileSystemObject fso = (FileSystemObject)o;
862                 int pos = adapter.getPosition(fso);
863                 if (pos >= 0) {
864                     SearchResult sr = adapter.getItem(pos);
865                     sr.setFso(fso);
866                 }
867             } else if (o == null) {
868                 // Refresh all
869                 List<SearchResult> results = adapter.getData();
870                 this.mResultList = new ArrayList<FileSystemObject>(results.size());
871                 int cc = results.size();
872                 for (int i = 0; i < cc; i++) {
873                     this.mResultList.add(results.get(i).getFso());
874                 }
875                 drawResults();
876             }
877         }
878     }
879
880     /**
881      * {@inheritDoc}
882      */
883     @Override
884     public void onRequestRemove(Object o) {
885         if (o instanceof FileSystemObject) {
886             removeItem((FileSystemObject)o);
887         }
888     }
889
890     /**
891      * {@inheritDoc}
892      */
893     @Override
894     public void onNavigateTo(Object o) {
895         if (o instanceof FileSystemObject) {
896             back(false, (FileSystemObject)o, true);
897         }
898     }
899
900     /**
901      * Method that returns to previous activity.
902      *
903      * @param cancelled Indicates if the activity was cancelled
904      * @param item The fso
905      * @hide
906      */
907     void back(final boolean cancelled, FileSystemObject item, boolean isChecked) {
908         Intent intent =  new Intent();
909         if (cancelled) {
910             if (SearchActivity.this.mDrawingSearchResultTask != null
911                     && SearchActivity.this.mDrawingSearchResultTask.isRunning()) {
912                 SearchActivity.this.mDrawingSearchResultTask.cancel(true);
913             }
914             if (this.mRestoreState != null) {
915                 intent.putExtra(
916                         NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
917                         (Parcelable)this.mRestoreState);
918             }
919             setResult(RESULT_CANCELED, intent);
920         } else {
921             // Check that the bookmark exists
922             try {
923                 FileSystemObject fso = item;
924                 if (!isChecked) {
925                     fso = CommandHelper.getFileInfo(this, item.getFullPath(), null);
926                 }
927                 if (fso != null) {
928                     if (FileHelper.isDirectory(fso)) {
929                         intent.putExtra(NavigationActivity.EXTRA_SEARCH_ENTRY_SELECTION, fso);
930                         intent.putExtra(
931                                 NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
932                                 (Parcelable)createSearchInfo());
933                         setResult(RESULT_OK, intent);
934                     } else {
935                         // Open the file here, so when focus back to the app, the search activity
936                         // its in top of the stack
937                         IntentsActionPolicy.openFileSystemObject(this, fso, false, null, null);
938                         return;
939                     }
940                 } else {
941                     // The fso not exists, delete the fso from the search
942                     try {
943                         removeItem(item);
944                     } catch (Exception ex) {/**NON BLOCK**/}
945                 }
946
947             } catch (Exception e) {
948                 // Capture the exception
949                 ExceptionUtil.translateException(this, e);
950                 if (e instanceof NoSuchFileOrDirectory || e instanceof FileNotFoundException) {
951                     // The fso not exists, delete the fso from the search
952                     try {
953                         removeItem(item);
954                     } catch (Exception ex) {/**NON BLOCK**/}
955                 }
956                 return;
957             }
958         }
959         finish();
960     }
961
962     /**
963      * {@inheritDoc}
964      */
965     @Override
966     public void onAsyncStart() {
967         runOnUiThread(new Runnable() {
968             @Override
969             public void run() {
970                 SearchActivity.this.toggleResults(false, false);
971             }
972         });
973     }
974
975     /**
976      * {@inheritDoc}
977      */
978     @Override
979     public void onAsyncEnd(boolean cancelled) {
980         this.mSearchListView.post(new Runnable() {
981             @Override
982             public void run() {
983                 try {
984                     //Dismiss the dialog
985                     if (SearchActivity.this.mDialog != null) {
986                         SearchActivity.this.mDialog.dismiss();
987                     }
988
989                     // Resolve the symlinks
990                     FileHelper.resolveSymlinks(
991                                 SearchActivity.this, SearchActivity.this.mResultList);
992
993                     // Draw the results
994                     drawResults();
995
996                 } catch (Throwable ex) {
997                     Log.e(TAG, "onAsyncEnd method fails", ex); //$NON-NLS-1$
998                 }
999             }
1000         });
1001     }
1002
1003     /**
1004      * {@inheritDoc}
1005      */
1006     @Override
1007     @SuppressWarnings("unchecked")
1008     public void onPartialResult(final Object partialResults) {
1009         //Saved in the global result list, for save at the end
1010         if (partialResults instanceof FileSystemObject) {
1011             SearchActivity.this.mResultList.add((FileSystemObject)partialResults);
1012         } else {
1013             SearchActivity.this.mResultList.addAll((List<FileSystemObject>)partialResults);
1014         }
1015
1016         //Notify progress
1017         this.mSearchListView.post(new Runnable() {
1018             @Override
1019             public void run() {
1020                 if (SearchActivity.this.mDialog != null) {
1021                     int progress = SearchActivity.this.mResultList.size();
1022                     setProgressMsg(progress);
1023                 }
1024             }
1025         });
1026     }
1027
1028     /**
1029      * {@inheritDoc}
1030      */
1031     @Override
1032     public void onAsyncExitCode(int exitCode) {/**NON BLOCK**/}
1033
1034     /**
1035      * {@inheritDoc}
1036      */
1037     @Override
1038     public void onException(Exception cause) {
1039         //Capture the exception
1040         ExceptionUtil.translateException(this, cause);
1041     }
1042
1043     /**
1044      * Method that draw the results in the listview
1045      * @hide
1046      */
1047     void drawResults() {
1048         //Toggle results
1049         this.toggleResults(this.mResultList.size() > 0, true);
1050         setFoundItems(this.mResultList.size(), this.mSearchDirectory);
1051
1052         //Create the task for drawing the data
1053         this.mDrawingSearchResultTask =
1054                                 new SearchResultDrawingAsyncTask(
1055                                         this.mSearchListView,
1056                                         this.mSearchWaiting,
1057                                         this.mResultList,
1058                                         this.mQuery);
1059         this.mDrawingSearchResultTask.execute();
1060     }
1061
1062     /**
1063      * Method that creates a {@link SearchInfoParcelable} reference from
1064      * the current data.
1065      *
1066      * @return SearchInfoParcelable The search info reference
1067      */
1068     private SearchInfoParcelable createSearchInfo() {
1069         SearchInfoParcelable parcel = new SearchInfoParcelable();
1070         parcel.setSearchDirectory(this.mSearchDirectory);
1071         parcel.setSearchResultList(
1072                 ((SearchResultAdapter)this.mSearchListView.getAdapter()).getData());
1073         parcel.setSearchQuery(this.mQuery);
1074         return parcel;
1075     }
1076
1077     /**
1078      * Method that set the progress of the search
1079      *
1080      * @param progress The progress
1081      * @hide
1082      */
1083     void setProgressMsg(int progress) {
1084         String msg =
1085                 getResources().getQuantityString(
1086                         R.plurals.search_found_items,
1087                         progress,
1088                         Integer.valueOf(progress));
1089         SearchActivity.this.mDialog.setProgress(Html.fromHtml(msg));
1090     }
1091 }
1092