OSDN Git Service

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