OSDN Git Service

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