2 * Copyright (C) 2012 The CyanogenMod Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.cyanogenmod.filemanager.activities;
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.graphics.Color;
30 import android.graphics.PorterDuff;
31 import android.os.Bundle;
32 import android.os.Parcelable;
33 import android.preference.PreferenceActivity;
34 import android.provider.SearchRecentSuggestions;
35 import android.text.Html;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.view.KeyEvent;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.widget.AdapterView;
42 import android.widget.AdapterView.OnItemClickListener;
43 import android.widget.AdapterView.OnItemLongClickListener;
45 import android.widget.ArrayAdapter;
46 import android.widget.ImageView;
47 import android.widget.ListView;
48 import android.widget.ProgressBar;
49 import android.widget.Spinner;
50 import android.widget.TextView;
51 import android.widget.Toast;
52 import com.cyanogenmod.filemanager.FileManagerApplication;
53 import com.cyanogenmod.filemanager.R;
54 import com.cyanogenmod.filemanager.activities.preferences.SearchPreferenceFragment;
55 import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences;
56 import com.cyanogenmod.filemanager.adapters.SearchResultAdapter;
57 import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
58 import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
59 import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
60 import com.cyanogenmod.filemanager.console.RelaunchableException;
61 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
62 import com.cyanogenmod.filemanager.model.Directory;
63 import com.cyanogenmod.filemanager.model.FileSystemObject;
64 import com.cyanogenmod.filemanager.model.ParentDirectory;
65 import com.cyanogenmod.filemanager.model.Query;
66 import com.cyanogenmod.filemanager.model.SearchResult;
67 import com.cyanogenmod.filemanager.model.Symlink;
68 import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable;
69 import com.cyanogenmod.filemanager.preferences.AccessMode;
70 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
71 import com.cyanogenmod.filemanager.preferences.Preferences;
72 import com.cyanogenmod.filemanager.providers.RecentSearchesContentProvider;
73 import com.cyanogenmod.filemanager.ui.ThemeManager;
74 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
75 import com.cyanogenmod.filemanager.ui.dialogs.ActionsDialog;
76 import com.cyanogenmod.filemanager.ui.policy.DeleteActionPolicy;
77 import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy;
78 import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
79 import com.cyanogenmod.filemanager.ui.widgets.FlingerListView;
80 import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerListener;
81 import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder;
82 import com.cyanogenmod.filemanager.util.CommandHelper;
83 import com.cyanogenmod.filemanager.util.DialogHelper;
84 import com.cyanogenmod.filemanager.util.ExceptionUtil;
85 import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult;
86 import com.cyanogenmod.filemanager.util.FileHelper;
87 import com.cyanogenmod.filemanager.util.MimeTypeHelper;
88 import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
89 import com.cyanogenmod.filemanager.util.SearchHelper;
90 import com.cyanogenmod.filemanager.util.StorageHelper;
92 import java.io.FileNotFoundException;
93 import java.io.Serializable;
94 import java.util.ArrayList;
95 import java.util.HashSet;
96 import java.util.List;
99 * An activity for search files and folders.
101 public class SearchActivity extends Activity
102 implements OnItemClickListener, OnItemLongClickListener, OnRequestRefreshListener,
103 AdapterView.OnItemSelectedListener {
105 private static final String TAG = "SearchActivity"; //$NON-NLS-1$
107 private static boolean DEBUG = false;
110 * An {@link Intent} action for restore view information.
112 public static final String ACTION_RESTORE =
113 "com.cyanogenmod.filemanager.activities.SearchActivity#Restore"; //$NON-NLS-1$
116 * Intent extra parameter for search in the selected directory on enter.
118 public static final String EXTRA_SEARCH_DIRECTORY = "extra_search_directory"; //$NON-NLS-1$
121 * Intent extra parameter for pass the restore information.
123 public static final String EXTRA_SEARCH_RESTORE = "extra_search_restore"; //$NON-NLS-1$
126 * Intent extra parameter for passing the target mime type information.
128 public static final String EXTRA_SEARCH_MIMETYPE = "extra_search_mimetype"; //$NON-NLS-1$
130 //Minimum characters to allow query
131 private static final int MIN_CHARS_SEARCH = 3;
133 private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
135 public void onReceive(Context context, Intent intent) {
136 if (intent != null) {
137 if (intent.getAction().compareTo(
138 FileManagerSettings.INTENT_SETTING_CHANGED) == 0) {
140 // The settings has changed
141 String key = intent.getStringExtra(
142 FileManagerSettings.EXTRA_SETTING_CHANGED_KEY);
144 if (SearchActivity.this.mSearchListView.getAdapter() != null &&
147 SETTINGS_HIGHLIGHT_TERMS.getId()) == 0 ||
150 SETTINGS_SHOW_RELEVANCE_WIDGET.getId()) == 0 ||
153 SETTINGS_SORT_SEARCH_RESULTS_MODE.getId()) == 0)) {
155 // Recreate the adapter
156 int pos = SearchActivity.
157 this.mSearchListView.getFirstVisiblePosition();
158 mAdapter.notifyDataSetChanged();
159 SearchActivity.this.mSearchListView.setSelection(pos);
163 } else if (intent.getAction().compareTo(
164 FileManagerSettings.INTENT_THEME_CHANGED) == 0) {
172 * A listener for flinging events from {@link FlingerListView}
174 private final OnItemFlingerListener mOnItemFlingerListener = new OnItemFlingerListener() {
177 public boolean onItemFlingerStart(
178 AdapterView<?> parent, View view, int position, long id) {
180 // Response if the item can be removed
181 SearchResultAdapter adapter = (SearchResultAdapter)parent.getAdapter();
182 SearchResult result = adapter.getItem(position);
183 if (result != null && result.getFso() != null) {
184 if (result.getFso() instanceof ParentDirectory) {
185 // This is not possible ...
190 } catch (Exception e) {
191 ExceptionUtil.translateException(SearchActivity.this, e, true, false);
197 public void onItemFlingerEnd(OnItemFlingerResponder responder,
198 AdapterView<?> parent, View view, int position, long id) {
201 // Response if the item can be removed
202 SearchResultAdapter adapter = (SearchResultAdapter)parent.getAdapter();
203 SearchResult result = adapter.getItem(position);
204 if (result != null && result.getFso() != null) {
205 DeleteActionPolicy.removeFileSystemObject(
214 // Cancels the flinger operation
217 } catch (Exception e) {
218 ExceptionUtil.translateException(SearchActivity.this, e, true, false);
224 private ConcurrentAsyncResultListener mAsyncListener = new ConcurrentAsyncResultListener() {
229 public void onConcurrentAsyncStart() {
230 mSearchInProgress = true;
231 mAdapter.startStreaming();
238 public void onConcurrentAsyncEnd(boolean cancelled) {
239 mSearchInProgress = false;
240 mSearchListView.post(new Runnable() {
244 mAdapter.stopStreaming();
245 int resultsSize = mAdapter.resultsSize();
246 mStreamingSearchProgress.setVisibility(View.INVISIBLE);
247 if (mMimeTypeCategories.size() > 1) {
248 mMimeTypeSpinner.setVisibility(View.VISIBLE);
250 mSearchListView.setVisibility(resultsSize > 0 ? View.VISIBLE : View.GONE);
251 mEmptyListMsg.setVisibility(resultsSize > 0 ? View.GONE : View.VISIBLE);
253 } catch (Throwable ex) {
254 Log.e(TAG, "onAsyncEnd method fails", ex); //$NON-NLS-1$
264 @SuppressWarnings("unchecked")
265 public void onConcurrentPartialResult(final Object partialResults) {
266 //Saved in the global result list, for save at the end
267 FileSystemObject result = null;
268 if (partialResults instanceof FileSystemObject) {
269 FileSystemObject fso = (FileSystemObject) partialResults;
270 if (mMimeTypeCategories == null || mMimeTypeCategories.contains(MimeTypeHelper
271 .getCategory(SearchActivity.this, fso))) {
272 SearchActivity.this.mResultList.add((FileSystemObject) partialResults);
273 showSearchResult((FileSystemObject) partialResults);
276 List<FileSystemObject> fsoList = (List<FileSystemObject>) partialResults;
277 for (FileSystemObject fso : fsoList) {
278 if (mMimeTypeCategories == null || mMimeTypeCategories.contains(MimeTypeHelper
279 .getCategory(SearchActivity.this, fso))) {
280 SearchActivity.this.mResultList.add(fso);
281 showSearchResult(fso);
287 mSearchListView.post(new Runnable() {
290 int progress = mAdapter.resultsSize();
294 R.plurals.search_found_items, progress,
295 Integer.valueOf(progress) );
296 mSearchFoundItems.setText(
298 R.string.search_found_items_in_directory,
309 public void onConcurrentAsyncExitCode(int exitCode) {/**NON BLOCK**/}
315 public void onConcurrentException(Exception cause) {
316 //Capture the exception
317 ExceptionUtil.translateException(SearchActivity.this, cause);
324 AsyncResultExecutable mExecutable = null;
329 ListView mSearchListView;
333 ProgressBar mSearchWaiting;
337 TextView mSearchFoundItems;
341 TextView mSearchTerms;
342 private View mEmptyListMsg;
347 Spinner mMimeTypeSpinner;
349 private String mSearchDirectory;
353 List<FileSystemObject> mResultList;
362 SearchInfoParcelable mRestoreState;
372 HashSet<MimeTypeCategory> mMimeTypeCategories;
374 private SearchResultAdapter mAdapter;
375 private ProgressBar mStreamingSearchProgress;
376 private boolean mSearchInProgress;
382 protected void onCreate(Bundle state) {
384 Log.d(TAG, "SearchActivity.onCreate"); //$NON-NLS-1$
387 // Check if app is running in chrooted mode
388 this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0;
390 // Register the broadcast receiver
391 IntentFilter filter = new IntentFilter();
392 filter.addAction(FileManagerSettings.INTENT_SETTING_CHANGED);
393 filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
394 registerReceiver(this.mNotificationReceiver, filter);
396 // Set the theme before setContentView
397 Theme theme = ThemeManager.getCurrentTheme(this);
398 theme.setBaseTheme(this, false);
401 overridePendingTransition(R.anim.translate_to_right_in, R.anim.hold_out);
403 //Set the main layout of the activity
404 setContentView(R.layout.search);
411 //Initialize action bars and searc
412 initTitleActionBar();
415 // Apply current theme
418 if (this.mRestoreState != null) {
419 //Restore activity from cached data
423 if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
425 } else if (ACTION_RESTORE.equals(getIntent().getAction())) {
426 restoreState(getIntent().getExtras());
432 super.onCreate(state);
439 protected void onDestroy() {
441 Log.d(TAG, "SearchActivity.onDestroy"); //$NON-NLS-1$
444 // Unregister the receiver
446 unregisterReceiver(this.mNotificationReceiver);
447 } catch (Throwable ex) {
451 //All destroy. Continue
459 public void onConfigurationChanged(Configuration newConfig) {
460 super.onConfigurationChanged(newConfig);
467 protected void onNewIntent(Intent intent) {
469 if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
478 protected void onPause() {
480 overridePendingTransition(R.anim.hold_in, R.anim.translate_to_left_out);
488 protected void onSaveInstanceState(Bundle outState) {
490 Log.d(TAG, "SearchActivity.onSaveInstanceState"); //$NON-NLS-1$
493 super.onSaveInstanceState(outState);
497 * Method that save the instance of the activity.
499 * @param state The current state of the activity
501 private void saveState(Bundle state) {
503 if (this.mSearchListView.getAdapter() != null) {
504 state.putParcelable(EXTRA_SEARCH_RESTORE, createSearchInfo());
506 } catch (Throwable ex) {
507 Log.w(TAG, "The state can't be saved", ex); //$NON-NLS-1$
512 * Method that restore the instance of the activity.
514 * @param state The previous state of the activity
516 private void restoreState(Bundle state) {
518 if (state.containsKey(EXTRA_SEARCH_RESTORE)) {
519 this.mRestoreState = state.getParcelable(EXTRA_SEARCH_RESTORE);
521 } catch (Throwable ex) {
522 Log.w(TAG, "The state can't be restored", ex); //$NON-NLS-1$
527 * Method that initializes the titlebar of the activity.
529 private void initTitleActionBar() {
530 //Configure the action bar options
531 // Set up the action bar to show a dropdown list.
532 final ActionBar actionBar = getActionBar();
533 actionBar.setDisplayShowTitleEnabled(false);
534 actionBar.setBackgroundDrawable(
535 getResources().getDrawable(R.drawable.bg_material_titlebar));
536 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
537 actionBar.setDisplayHomeAsUpEnabled(true);
539 View customTitle = getLayoutInflater().inflate(R.layout.simple_customtitle, null, false);
541 TextView title = (TextView)customTitle.findViewById(R.id.customtitle_title);
542 title.setText(R.string.search);
543 title.setContentDescription(getString(R.string.search));
544 ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
545 configuration.setImageResource(R.drawable.ic_material_light_config);
546 configuration.setVisibility(View.VISIBLE);
547 actionBar.setCustomView(customTitle);
551 * Method that initializes the component of the activity.
553 private void initComponents() {
555 this.mEmptyListMsg = findViewById(R.id.search_empty_msg);
557 this.mSearchListView = (ListView)findViewById(R.id.search_listview);
558 this.mSearchListView.setOnItemClickListener(this);
559 this.mSearchListView.setOnItemLongClickListener(this);
561 // If we should set the listview to response to flinger gesture detection
563 Preferences.getSharedPreferences().getBoolean(
564 FileManagerSettings.SETTINGS_USE_FLINGER.getId(),
565 ((Boolean)FileManagerSettings.
566 SETTINGS_USE_FLINGER.
567 getDefaultValue()).booleanValue());
569 ((FlingerListView)this.mSearchListView).
570 setOnItemFlingerListener(this.mOnItemFlingerListener);
574 this.mSearchWaiting = (ProgressBar)findViewById(R.id.search_waiting);
575 mStreamingSearchProgress = (ProgressBar) findViewById(R.id.streaming_progress_bar);
576 mStreamingSearchProgress.getIndeterminateDrawable()
577 .setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
579 this.mSearchFoundItems = (TextView)findViewById(R.id.search_status_found_items);
580 setFoundItems(0, ""); //$NON-NLS-1$
581 this.mSearchTerms = (TextView)findViewById(R.id.search_status_query_terms);
582 this.mSearchTerms.setText(
583 Html.fromHtml(getString(R.string.search_terms, ""))); //$NON-NLS-1$
585 // populate Mime Types spinner for search results filtering
586 mMimeTypeSpinner = (Spinner) findViewById(R.id.search_status_type_spinner);
588 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
589 R.layout.search_spinner_item,
590 MimeTypeHelper.MimeTypeCategory.getFriendlyLocalizedNames(this));
591 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
592 mMimeTypeSpinner.setAdapter(adapter);
593 mMimeTypeSpinner.setOnItemSelectedListener(this);
597 * Method invoked when an action item is clicked.
599 * @param view The button pushed
601 public void onActionBarItemClick(View view) {
602 switch (view.getId()) {
603 case R.id.ab_button1:
605 Intent settings = new Intent(this, SettingsPreferences.class);
607 PreferenceActivity.EXTRA_SHOW_FRAGMENT,
608 SearchPreferenceFragment.class.getName());
609 startActivity(settings);
618 * Method that initializes the titlebar of the activity.
620 private void initSearch() {
621 Serializable mimeTypeExtra = getIntent().getSerializableExtra(EXTRA_SEARCH_MIMETYPE);
623 if (mimeTypeExtra != null) {
624 MimeTypeCategory[] categories = (MimeTypeCategory[]) getIntent()
625 .getSerializableExtra(EXTRA_SEARCH_MIMETYPE);
626 // setting load factor to 1 to avoid the backing map's resizing
627 mMimeTypeCategories = new HashSet<MimeTypeCategory>(categories.length, 1);
628 for (MimeTypeCategory category : categories) {
629 mMimeTypeCategories.add(category);
633 //If data is not present, use root directory to do the search
634 this.mSearchDirectory = FileHelper.ROOT_DIRECTORY;
635 String searchDirectory = getIntent()
636 .getStringExtra(SearchActivity.EXTRA_SEARCH_DIRECTORY);
638 if (!TextUtils.isEmpty(searchDirectory)) {
639 this.mSearchDirectory = searchDirectory;
642 //Retrieve the query ¿from voice recognizer?
643 boolean voiceQuery = true;
644 List<String> userQueries =
645 getIntent().getStringArrayListExtra(android.speech.RecognizerIntent.EXTRA_RESULTS);
646 if (userQueries == null || userQueries.size() == 0) {
648 userQueries = new ArrayList<String>();
649 //Recovers and save the last term search in the memory
650 Preferences.setLastSearch(getIntent().getStringExtra(SearchManager.QUERY));
651 userQueries.add(Preferences.getLastSearch());
655 //Filter the queries? Needed if queries come from voice recognition
656 final List<String> filteredUserQueries =
657 (voiceQuery) ? filterQuery(userQueries) : userQueries;
660 this.mQuery = new Query().fillSlots(filteredUserQueries);
661 List<String> queries = this.mQuery.getQueries();
664 // Mime type search uses '*' which needs to bypass
666 if (mMimeTypeCategories == null) {
667 //Check if some queries has lower than allowed, in this case
668 //request the user for stop the search
669 int cc = queries.size();
670 for (int i = 0; i < cc; i++) {
671 if (queries.get(i).trim().length() < MIN_CHARS_SEARCH) {
676 mMimeTypeSpinner.setVisibility(View.VISIBLE);
678 mMimeTypeSpinner.setVisibility(View.INVISIBLE);
681 askUserBeforeSearch(voiceQuery, this.mQuery, this.mSearchDirectory);
683 doSearch(voiceQuery, this.mQuery, this.mSearchDirectory);
689 * Method that ask the user before do the search.
691 * @param voiceQuery Indicates if the query is from voice recognition
692 * @param query The terms of the search
693 * @param searchDirectory The directory of the search
695 private void askUserBeforeSearch(
696 final boolean voiceQuery, final Query query, final String searchDirectory) {
697 //Show a dialog asking the user
699 DialogHelper.createYesNoDialog(
701 R.string.search_few_characters_title,
702 R.string.search_few_characters_msg,
703 new DialogInterface.OnClickListener() {
705 public void onClick(DialogInterface alertDialog, int which) {
706 if (which == DialogInterface.BUTTON_POSITIVE) {
707 doSearch(voiceQuery, query, searchDirectory);
711 //Close search activity
712 back(true, null, false);
715 DialogHelper.delegateDialogShow(this, dialog);
719 * Method that do the search.
721 * @param voiceQuery Indicates if the query is from voice recognition
722 * @param query The terms of the search
723 * @param searchDirectory The directory of the search
727 final boolean voiceQuery, final Query query, final String searchDirectory) {
729 // Recovers the user preferences about save suggestions
730 boolean saveSuggestions = Preferences.getSharedPreferences().getBoolean(
731 FileManagerSettings.SETTINGS_SAVE_SEARCH_TERMS.getId(),
732 ((Boolean) FileManagerSettings.SETTINGS_SAVE_SEARCH_TERMS.
733 getDefaultValue()).booleanValue());
734 if (saveSuggestions) {
735 //Save every query for use as recent suggestions
736 SearchRecentSuggestions suggestions =
737 new SearchRecentSuggestions(this,
738 RecentSearchesContentProvider.AUTHORITY,
739 RecentSearchesContentProvider.MODE);
741 List<String> queries = query.getQueries();
742 int cc = queries.size();
743 for (int i = 0; i < cc; i++) {
744 suggestions.saveRecentQuery(queries.get(i), null);
750 if (this.mSearchListView.getAdapter() != null) {
751 ((SearchResultAdapter)this.mSearchListView.getAdapter()).dispose();
753 this.mResultList = new ArrayList<FileSystemObject>();
755 new SearchResultAdapter(this,
756 new ArrayList<SearchResult>(), R.layout.search_item, this.mQuery);
757 this.mSearchListView.setAdapter(mAdapter);
760 if (mMimeTypeCategories == null) {
761 this.mSearchTerms.setText(
762 Html.fromHtml(getString(R.string.search_terms, query.getTerms())));
764 ArrayList<String> localizedNames = new ArrayList<String>(mMimeTypeCategories.size());
765 for (MimeTypeCategory category : mMimeTypeCategories) {
767 .add(NavigationActivity.MIME_TYPE_LOCALIZED_NAMES[category.ordinal()]);
769 this.mSearchTerms.setText(
770 Html.fromHtml(getString(R.string.search_terms, localizedNames)));
773 //Now, do the search in background
774 this.mSearchListView.post(new Runnable() {
778 // Execute the query (search in background)
779 SearchActivity.this.mExecutable =
780 CommandHelper.findFiles(
787 } catch (Throwable ex) {
788 //Remove all elements
790 SearchActivity.this.removeAll();
791 } catch (Throwable ex2) {
795 //Capture the exception
796 Log.e(TAG, "Search failed", ex); //$NON-NLS-1$
797 DialogHelper.showToast(
799 R.string.search_error_msg, Toast.LENGTH_SHORT);
800 SearchActivity.this.mSearchListView.setVisibility(View.GONE);
807 * Ensures the search result meets user preferences and passes it to the adapter for display
809 * @param result FileSystemObject that matches the search result criteria
811 private void showSearchResult(FileSystemObject result) {
812 // check against user's display preferences
813 if ( !FileHelper.compliesWithDisplayPreferences(result, null, mChRooted) ) {
818 FileHelper.resolveSymlink(this, result);
820 // convert to search result
821 SearchResult searchResult = SearchHelper.convertToResult(result, mQuery);
824 mAdapter.addNewItem(searchResult);
828 * Method that restore the activity from the cached data.
830 private void loadFromCacheData() {
831 this.mSearchListView.post(new Runnable() {
835 List<SearchResult> list = SearchActivity.this.mRestoreState.getSearchResultList();
836 String directory = SearchActivity.this.mRestoreState.getSearchDirectory();
837 SearchActivity.this.toggleResults(list.size() > 0, true);
838 setFoundItems(list.size(), directory);
841 Query query = SearchActivity.this.mRestoreState.getSearchQuery();
843 TextUtils.join(" | ", //$NON-NLS-1$;
844 query.getQueries().toArray(new String[]{}));
845 if (terms.endsWith(" | ")) { //$NON-NLS-1$;
846 terms = ""; //$NON-NLS-1$;
848 SearchActivity.this.mSearchTerms.setText(
849 Html.fromHtml(getString(R.string.search_terms, terms)));
852 if (SearchActivity.this.mSearchWaiting != null) {
853 SearchActivity.this.mSearchWaiting.setVisibility(View.VISIBLE);
856 //Add list to the listview
857 if (SearchActivity.this.mSearchListView.getAdapter() != null) {
858 ((SearchResultAdapter)SearchActivity.this.
859 mSearchListView.getAdapter()).clear();
861 SearchResultAdapter adapter =
862 new SearchResultAdapter(
863 SearchActivity.this.mSearchListView.getContext(),
865 R.layout.search_item,
867 SearchActivity.this.mSearchListView.setAdapter(adapter);
868 SearchActivity.this.mSearchListView.setSelection(0);
870 SearchActivity.this.mQuery = query;
871 SearchActivity.this.mSearchDirectory = mRestoreState.getSearchDirectory();
873 } catch (Throwable ex) {
874 //Capture the exception
875 ExceptionUtil.translateException(SearchActivity.this, ex);
879 if (SearchActivity.this.mSearchWaiting != null) {
880 SearchActivity.this.mSearchWaiting.setVisibility(View.GONE);
888 * Method that filter the user queries for valid queries only.<br/>
890 * Only allow query strings with more that 3 characters
892 * @param original The original user queries
893 * @return List<String> The list of queries filtered
895 @SuppressWarnings("static-method")
896 private List<String> filterQuery(List<String> original) {
897 List<String> dst = new ArrayList<String>(original);
899 for (int i = cc - 1; i >= 0; i--) {
900 String query = dst.get(i);
901 if (query == null || query.trim().length() < MIN_CHARS_SEARCH) {
909 * Method that removes all items and display a message.
913 SearchResultAdapter adapter = (SearchResultAdapter)this.mSearchListView.getAdapter();
915 this.mSearchListView.setSelection(0);
916 toggleResults(false, true);
920 * Method that toggle the views when there are results.
922 * @param hasResults Indicates if there are results
923 * @param showEmpty Show the empty list message
926 void toggleResults(boolean hasResults, boolean showEmpty) {
927 this.mSearchListView.setVisibility(hasResults ? View.VISIBLE : View.INVISIBLE);
928 this.mEmptyListMsg.setVisibility(!hasResults && showEmpty ? View.VISIBLE : View.INVISIBLE);
932 * Method that display the number of found items.
934 * @param items The number of items
935 * @param searchDirectory The search directory path
938 void setFoundItems(final int items, final String searchDirectory) {
939 if (this.mSearchFoundItems != null) {
940 this.mSearchFoundItems.post(new Runnable() {
943 String directory = searchDirectory;
944 if (SearchActivity.this.mChRooted &&
945 directory != null && directory.length() > 0) {
946 directory = StorageHelper.getChrootedPath(directory);
952 R.plurals.search_found_items, items, Integer.valueOf(items));
953 SearchActivity.this.mSearchFoundItems.setText(
955 R.string.search_found_items_in_directory,
967 public boolean onKeyUp(int keyCode, KeyEvent event) {
969 case KeyEvent.KEYCODE_BACK:
970 back(true, null, false);
973 return super.onKeyUp(keyCode, event);
981 public boolean onOptionsItemSelected(MenuItem item) {
982 switch (item.getItemId()) {
983 case android.R.id.home:
984 back(true, null, false);
987 return super.onOptionsItemSelected(item);
995 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
996 // cancel search query if in progress
997 // *need* to do this as the async query holds a lock on the Console and we need the Console
998 // to gather additional file info in order to process the click event
999 if (mSearchInProgress) mExecutable.cancel();
1002 SearchResult result = ((SearchResultAdapter)parent.getAdapter()).getItem(position);
1003 FileSystemObject fso = result.getFso();
1004 if (fso instanceof Directory) {
1005 back(false, fso, false);
1007 } else if (fso instanceof Symlink) {
1008 Symlink symlink = (Symlink)fso;
1009 if (symlink.getLinkRef() != null && symlink.getLinkRef() instanceof Directory) {
1010 back(false, symlink.getLinkRef(), false);
1013 fso = symlink.getLinkRef();
1016 // Open the file with the preferred registered app
1017 back(false, fso, false);
1019 } catch (Throwable ex) {
1020 ExceptionUtil.translateException(this.mSearchListView.getContext(), ex);
1028 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
1029 // Different actions depending on user preference
1031 // Get the adapter, the search result and the fso
1032 SearchResultAdapter adapter = ((SearchResultAdapter)parent.getAdapter());
1033 SearchResult searchResult = adapter.getItem(position);
1034 FileSystemObject fso = searchResult.getFso();
1036 // Open the actions menu
1038 return true; //Always consume the event
1042 * Method invoked when a request to show the menu associated
1043 * with an item is started.
1045 * @param item The item for which the request was started
1047 public void onRequestMenu(FileSystemObject item) {
1048 // Prior to show the dialog, refresh the item reference
1049 FileSystemObject fso = null;
1051 fso = CommandHelper.getFileInfo(this, item.getFullPath(), false, null);
1053 throw new NoSuchFileOrDirectory(item.getFullPath());
1056 } catch (Exception e) {
1058 ExceptionUtil.translateException(this, e);
1060 // Remove the object
1061 if (e instanceof FileNotFoundException || e instanceof NoSuchFileOrDirectory) {
1067 ActionsDialog dialog = new ActionsDialog(this, null, fso, false, true);
1068 dialog.setOnRequestRefreshListener(this);
1073 * Method that removes the {@link FileSystemObject} reference
1075 * @param fso The file system object
1077 private void removeItem(FileSystemObject fso) {
1078 SearchResultAdapter adapter =
1079 (SearchResultAdapter)this.mSearchListView.getAdapter();
1080 if (adapter != null) {
1081 int pos = adapter.getPosition(fso);
1083 SearchResult sr = adapter.getItem(pos);
1087 // Toggle resultset?
1088 toggleResults(adapter.getCount() > 0, true);
1089 setFoundItems(adapter.getCount(), this.mSearchDirectory);
1097 public void onRequestRefresh(Object o, boolean clearSelection) {
1098 // Refresh only the item
1099 SearchResultAdapter adapter =
1100 (SearchResultAdapter)this.mSearchListView.getAdapter();
1101 if (adapter != null) {
1102 if (o instanceof FileSystemObject) {
1104 FileSystemObject fso = (FileSystemObject)o;
1105 int pos = adapter.getPosition(fso);
1107 SearchResult sr = adapter.getItem(pos);
1110 } else if (o == null) {
1112 List<SearchResult> results = adapter.getData();
1113 this.mResultList = new ArrayList<FileSystemObject>(results.size());
1114 int cc = results.size();
1115 for (int i = 0; i < cc; i++) {
1116 this.mResultList.add(results.get(i).getFso());
1126 public void onRequestBookmarksRefresh() {
1134 public void onRequestRemove(Object o, boolean clearSelection) {
1135 if (o instanceof FileSystemObject) {
1136 removeItem((FileSystemObject) o);
1144 public void onNavigateTo(Object o) {
1145 if (o instanceof FileSystemObject) {
1146 back(false, (FileSystemObject) o, true);
1151 * Method that returns to previous activity.
1153 * @param cancelled Indicates if the activity was cancelled
1154 * @param item The fso
1155 * @param isChecked If the fso was fully retrieve previously to this call. Otherwise, a
1156 * getFileInfo call is done to complete the fso information
1159 void back(final boolean cancelled, FileSystemObject item, boolean isChecked) {
1160 final Context ctx = SearchActivity.this;
1161 boolean finish = true;
1163 final Intent intent = new Intent();
1164 if (this.mRestoreState != null) {
1165 Bundle bundle = new Bundle();
1166 bundle.putParcelable(NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
1167 (Parcelable)this.mRestoreState);
1168 intent.putExtras(bundle);
1170 setResult(RESULT_CANCELED, intent);
1172 // Check that the bookmark exists
1173 FileSystemObject fso = item;
1176 fso = CommandHelper.getFileInfo(ctx, item.getFullPath(), null);
1178 finish = navigateTo(fso);
1180 } catch (Exception e) {
1181 // Capture the exception
1182 final FileSystemObject fFso = fso;
1183 final OnRelaunchCommandResult relaunchListener = new OnRelaunchCommandResult() {
1185 public void onSuccess() {
1186 if (navigateTo(fFso)) {
1191 public void onFailed(Throwable cause) {
1192 ExceptionUtil.translateException(ctx, cause, false, false);
1195 public void onCancelled() { /** NON BLOCK**/}
1197 ExceptionUtil.translateException(ctx, e, false, true, relaunchListener);
1198 if (!(e instanceof RelaunchableException)) {
1199 if (e instanceof NoSuchFileOrDirectory || e instanceof FileNotFoundException) {
1200 // The fso not exists, delete the fso from the search
1203 } catch (Exception ex) {/**NON BLOCK**/}
1210 // End this activity
1217 * Method invoked when the activity needs to exit
1219 private void exit() {
1220 if (this.mSearchListView.getAdapter() != null) {
1221 ((SearchResultAdapter)this.mSearchListView.getAdapter()).dispose();
1227 * Method that navigate to the file system used the intent (NavigationActivity)
1229 * @param fso The file system object to navigate to
1230 * @param intent The intent used to navigate to
1231 * @return boolean If the action implies finish this activity
1233 boolean navigateTo(FileSystemObject fso) {
1235 if (FileHelper.isDirectory(fso)) {
1236 final Intent intent = new Intent();
1237 Bundle bundle = new Bundle();
1238 bundle.putSerializable(NavigationActivity.EXTRA_SEARCH_ENTRY_SELECTION, fso);
1239 bundle.putParcelable(NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
1240 (Parcelable)createSearchInfo());
1241 intent.putExtras(bundle);
1242 setResult(RESULT_OK, intent);
1246 // Open the file here, so when focus back to the app, the search activity
1247 // its in top of the stack
1248 IntentsActionPolicy.openFileSystemObject(this, fso, false, null, null);
1250 // The fso not exists, delete the fso from the search
1253 } catch (Exception ex) {/**NON BLOCK**/}
1259 * Method that creates a {@link SearchInfoParcelable} reference from
1262 * @return SearchInfoParcelable The search info reference
1264 private SearchInfoParcelable createSearchInfo() {
1265 SearchInfoParcelable parcel = new SearchInfoParcelable(
1267 ((SearchResultAdapter)this.mSearchListView.getAdapter()).getData(),
1273 * Method that applies the current theme to the activity
1277 Theme theme = ThemeManager.getCurrentTheme(this);
1278 theme.setBaseTheme(this, false);
1281 View v = getActionBar().getCustomView().findViewById(R.id.customtitle_title);
1282 theme.setTextColor(this, (TextView)v, "action_bar_text_color"); //$NON-NLS-1$
1283 v = findViewById(R.id.ab_button1);
1284 theme.setImageDrawable(this, (ImageView)v, "ic_config_drawable"); //$NON-NLS-1$
1286 theme.setBackgroundDrawable(
1287 this, getWindow().getDecorView(), "background_drawable"); //$NON-NLS-1$
1289 v = findViewById(R.id.search_status);
1290 theme.setBackgroundDrawable(this, v, "statusbar_drawable"); //$NON-NLS-1$
1291 v = findViewById(R.id.search_status_found_items);
1292 theme.setTextColor(this, (TextView)v, "action_bar_text_color"); //$NON-NLS-1$
1293 v = findViewById(R.id.search_status_query_terms);
1294 theme.setTextColor(this, (TextView)v, "action_bar_text_color"); //$NON-NLS-1$
1297 if (this.mSearchListView.getAdapter() != null) {
1298 ((SearchResultAdapter)this.mSearchListView.getAdapter()).notifyDataSetChanged();
1300 this.mSearchListView.setDivider(
1301 theme.getDrawable(this, "horizontal_divider_drawable")); //$NON-NLS-1$
1302 this.mSearchListView.invalidate();
1306 public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
1307 String category = MimeTypeHelper.MimeTypeCategory.names()[i];
1308 SearchResultAdapter adapter = ((SearchResultAdapter) this.mSearchListView.getAdapter());
1309 if (adapter != null) {
1310 adapter.setMimeFilter(category);
1315 public void onNothingSelected(AdapterView<?> adapterView) {