OSDN Git Service

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