OSDN Git Service

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