OSDN Git Service

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