package edu.mit.mitmobile2; import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.content.SearchRecentSuggestionsProvider; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; import edu.mit.mitmobile2.objs.SearchResults; public abstract class SearchActivity<ResultItem> extends NewModuleActivity { public static final String EXTRA_AUTHORITY = "authority"; protected abstract ArrayAdapter<ResultItem> getListAdapter(SearchResults<ResultItem> results); protected abstract void initiateSearch(String searchTerm, Handler uiHandler); protected abstract String getSuggestionsAuthority(); protected static int SUGGESTIONS_MODE = SearchRecentSuggestionsProvider.DATABASE_MODE_QUERIES; private ListView mSearchListView; protected SearchResultsHeader mSearchResultsHeader; protected FullScreenLoader mLoadingView; protected TwoLineActionRow mLoadMore; private static final String LOAD_MORE = "Load more"; private static final String LOADING = "Loading..."; private static final String TRY_AGAIN = "Loading Fail (Try Again)"; protected boolean mSearching = true; private Long mLastFailedSearchTime = null; private final static long MINIMUM_TRY_AGAIN_WAIT = 5000; // 5 seconds protected boolean mResultsDisplayed = false; private boolean mResetSearchScheduled = false; private String mSearchTerm; private SearchResults<ResultItem> mSearchResults; protected static void launchSearch(Context context, String query, Class<? extends SearchActivity<?>> searchActivity) { Intent intent = new Intent(context, searchActivity); intent.setAction(Intent.ACTION_SEARCH); intent.putExtra(SearchManager.QUERY, query); context.startActivity(intent); } private Handler searchHandler(final String searchTerm) { return new Handler() { @Override public void handleMessage(Message msg) { if(searchTerm.equals(mSearchTerm)) { mLoadingView.setVisibility(View.GONE); mSearching = false; if(msg.arg1 == MobileWebApi.SUCCESS) { mResultsDisplayed = true; // use a temp variable (to allow use of @SuppressWarnings on variable) @SuppressWarnings("unchecked") final SearchResults<ResultItem> tempSearchResults = (SearchResults<ResultItem>) msg.obj; mSearchResults = tempSearchResults; if(mSearchResults.getResultsList().size() == 0) { resetBackToSearch(); Toast.makeText(SearchActivity.this, "No matches found", Toast.LENGTH_SHORT).show(); } mSearchTerm = mSearchResults.getSearchTerm(); showSummaryView(); if(supportsMoreResult() && mSearchResults.isPartialResult()) { mSearchListView.addFooterView(mLoadMore); } mSearchListView.setAdapter(getListAdapter(mSearchResults)); mSearchListView.setVisibility(View.VISIBLE); } else if(msg.arg1 == MobileWebApi.ERROR) { mResultsDisplayed = false; resetBackToSearch(); } else if(msg.arg1 == MobileWebApi.CANCELLED) { mResultsDisplayed = false; resetBackToSearch(); } } } }; } private void doSearch(Intent searchIntent) { if(Intent.ACTION_SEARCH.equals(searchIntent.getAction())) { String searchTerm = searchIntent.getStringExtra(SearchManager.QUERY); // save the search to the suggestions list Log.d(this.getClass().toString(), getSuggestionsAuthority()); MITSearchRecentSuggestions suggestions = new MITSearchRecentSuggestions(this, getSuggestionsAuthority(), SUGGESTIONS_MODE); suggestions.saveRecentQuery(searchTerm.toLowerCase(), null); // hide the list view and header view mSearchListView.setVisibility(View.GONE); mSearchResultsHeader.setVisibility(View.GONE); // show the search indicator // do the search mLoadingView.setVisibility(View.VISIBLE); mLoadingView.showLoading(); mResultsDisplayed = false; mSearching = true; mResetSearchScheduled = false; mSearchTerm = searchTerm; initiateSearch(searchTerm, searchHandler(searchTerm)); } } private void doContinueSearch() { mLoadMore.setEnabled(false); mLoadMore.setTitle(LOADING); mSearching = true; mLastFailedSearchTime = null; final SearchResults<ResultItem> currentSearchResults = mSearchResults; continueSearch(currentSearchResults, new Handler() { @SuppressWarnings("unchecked") @Override public void handleMessage(Message msg) { if(currentSearchResults == mSearchResults) { mLoadMore.setEnabled(true); if (msg.arg1 == MobileWebApi.SUCCESS) { final SearchResults<ResultItem> tempSearchResults = (SearchResults<ResultItem>) msg.obj; mSearchResults = tempSearchResults; mLoadMore.setTitle(LOAD_MORE); showSummaryView(); if(!mSearchResults.isPartialResult()) { mSearchListView.removeFooterView(mLoadMore); } } else { mLoadMore.setTitle(TRY_AGAIN); mLastFailedSearchTime = System.currentTimeMillis(); } // the list views are not properly responding to data being changed. // requesting layout seems to fix this (my only guess is the special // magic used for footerViews is buggy mSearching = false; mSearchListView.requestLayout(); } } }); } @Override public void onNewIntent(Intent newIntent) { super.onNewIntent(newIntent); doSearch(newIntent); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_results); mSearchListView = (ListView) findViewById(R.id.searchResultsList); mSearchResultsHeader = (SearchResultsHeader) findViewById(R.id.searchResultsSummaryView); mLoadingView = (FullScreenLoader) findViewById(R.id.searchResultsLoading); mLoadMore = new TwoLineActionRow(this); mLoadMore.setTitle(LOAD_MORE); mSearchListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if(view == mLoadMore) { doContinueSearch(); } else { @SuppressWarnings("unchecked") ResultItem item = (ResultItem) parent.getItemAtPosition(position); SearchActivity.this.onItemSelected(mSearchResults, item); } } }); // add a scroll listener to retrieve more results prememptively if(supportsMoreResult()) { mSearchListView.setOnScrollListener(new OnScrollListener() { private static final int MINIMUM_REMAINING_ROWS = 10; @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { continueSearchFromScroll(); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { continueSearchFromScroll(); } private void continueSearchFromScroll() { if(mSearchResults != null && mSearchResults.isPartialResult()) { if(mSearchResults.getResultsList().size() - mSearchListView.getFirstVisiblePosition() < MINIMUM_REMAINING_ROWS) { if(!mSearching) { if((mLastFailedSearchTime == null) || (mLastFailedSearchTime - System.currentTimeMillis() > MINIMUM_TRY_AGAIN_WAIT)) { doContinueSearch(); } } } } } }); } doSearch(getIntent()); } private void showSummaryView() { String summaryText; int resultsCount = mSearchResults.getResultsList().size(); if(resultsCount == 0) { summaryText = "No matches for \"" + mSearchTerm + "\""; } else if(resultsCount == 1) { summaryText = "Results for "+ mSearchTerm; } else if(!mSearchResults.isPartialResult() || supportsMoreResult()) { /*String totalCount; if (mSearchResults.totalResultsCount() != null) { totalCount = "" + mSearchResults.totalResultsCount(); } else { totalCount = "" + mSearchResults.getResultsList().size(); } summaryText = totalCount + " results";*/ summaryText = "Results for "+ mSearchTerm; } else { if(mSearchResults.totalResultsCount() != null) { // total known summaryText = mSearchResults.totalResultsCount() + " "; } else { // total unknown summaryText = "Many "; } summaryText += searchItemPlural() + " found showing " + resultsCount; } mSearchResultsHeader.setText(summaryText); mSearchResultsHeader.setVisibility(View.VISIBLE); } private void resetBackToSearch() { if (hasWindowFocus()) { mResetSearchScheduled = false; mResultsDisplayed = false; startSearch(mSearchTerm, false, null, false); } else { mResetSearchScheduled = true; } } @Override public void onWindowFocusChanged(boolean hasFocus) { if (hasFocus) { if (mResetSearchScheduled) { resetBackToSearch(); } else if(!mSearching && !mResultsDisplayed) { finish(); } } super.onWindowFocusChanged(hasFocus); } @Override public boolean isModuleHomeActivity() { return false; } protected SearchResults<ResultItem> getSearchResults() { return mSearchResults; } protected ListView getListView() { return mSearchListView; } abstract protected String searchItemPlural(); abstract protected String searchItemSingular(); protected boolean supportsMoreResult() { return false; } protected void continueSearch(SearchResults<ResultItem> previousResults, Handler uiHandler) {} abstract protected void onItemSelected(SearchResults<ResultItem> results, ResultItem item); }