OSDN Git Service

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