/** * Copyright 2009 Joe LaPenna */ package com.joelapenna.foursquared; import com.joelapenna.foursquare.Foursquare; import com.joelapenna.foursquare.error.FoursquareException; import com.joelapenna.foursquare.types.Group; import com.joelapenna.foursquare.types.Venue; import com.joelapenna.foursquared.error.LocationException; import com.joelapenna.foursquared.location.LocationUtils; import com.joelapenna.foursquared.providers.VenueQuerySuggestionsProvider; import com.joelapenna.foursquared.util.Comparators; import com.joelapenna.foursquared.util.NotificationsUtil; import com.joelapenna.foursquared.util.TabsUtil; import com.joelapenna.foursquared.widget.SeparatedListAdapter; import com.joelapenna.foursquared.widget.VenueListAdapter; import android.app.Activity; import android.app.SearchManager; import android.app.TabActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.Location; import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcelable; import android.provider.SearchRecentSuggestions; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.AdapterView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TabHost; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; import java.io.IOException; import java.util.Collections; import java.util.Observable; /** * @author Joe LaPenna (joe@joelapenna.com) */ public class SearchVenuesActivity extends TabActivity { static final String TAG = "SearchVenuesActivity"; static final boolean DEBUG = FoursquaredSettings.DEBUG; public static final String QUERY_NEARBY = null; public static SearchResultsObservable searchResultsObservable; private static final int MENU_SEARCH = 0; private static final int MENU_REFRESH = 1; private static final int MENU_NEARBY = 2; private static final int MENU_ADD_VENUE = 3; private static final int MENU_GROUP_SEARCH = 0; private SearchTask mSearchTask; private SearchHolder mSearchHolder = new SearchHolder(); private ListView mListView; private LinearLayout mEmpty; private TextView mEmptyText; private ProgressBar mEmptyProgress; private TabHost mTabHost; private SeparatedListAdapter mListAdapter; private BroadcastReceiver mLoggedOutReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "onReceive: " + intent); finish(); } }; private boolean mIsShortcutPicker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.search_venues_activity); setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_LOCAL); registerReceiver(mLoggedOutReceiver, new IntentFilter(Foursquared.INTENT_ACTION_LOGGED_OUT)); searchResultsObservable = new SearchResultsObservable(); initTabHost(); initListViewAdapter(); // Watch to see if we've been called as a shortcut intent. mIsShortcutPicker = Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction()); if (getLastNonConfigurationInstance() != null) { if (DEBUG) Log.d(TAG, "Restoring state."); SearchHolder holder = (SearchHolder) getLastNonConfigurationInstance(); if (holder.results != null) { mSearchHolder.query = holder.query; setSearchResults(holder.results); putSearchResultsInAdapter(holder.results); } } else { onNewIntent(getIntent()); } } @Override public void onDestroy() { super.onDestroy(); unregisterReceiver(mLoggedOutReceiver); } @Override public void onResume() { super.onResume(); ((Foursquared) getApplication()).requestLocationUpdates(true); if (mSearchHolder.results == null && mSearchTask == null) { mSearchTask = (SearchTask) new SearchTask().execute(); } } @Override public void onPause() { super.onPause(); ((Foursquared) getApplication()).removeLocationUpdates(); if (isFinishing()) { mListAdapter.removeObserver(); } } @Override public void onStop() { super.onStop(); if (mSearchTask != null) { mSearchTask.cancel(true); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); // Always show these. menu.add(MENU_GROUP_SEARCH, MENU_SEARCH, Menu.NONE, R.string.search_label) // .setIcon(R.drawable.ic_menu_search) // .setAlphabeticShortcut(SearchManager.MENU_KEY); menu.add(MENU_GROUP_SEARCH, MENU_NEARBY, Menu.NONE, R.string.nearby_label) // .setIcon(R.drawable.ic_menu_places); menu.add(MENU_GROUP_SEARCH, MENU_REFRESH, Menu.NONE, R.string.refresh_label) // .setIcon(R.drawable.ic_menu_refresh); menu.add(MENU_GROUP_SEARCH, MENU_ADD_VENUE, Menu.NONE, R.string.nearby_menu_add_venue) // .setIcon(R.drawable.ic_menu_add); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_SEARCH: onSearchRequested(); return true; case MENU_NEARBY: executeSearchTask(null); return true; case MENU_REFRESH: executeSearchTask(mSearchHolder.query); return true; case MENU_ADD_VENUE: Intent intent = new Intent(SearchVenuesActivity.this, AddVenueActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } @Override public void onNewIntent(Intent intent) { if (intent != null) { String action = intent.getAction(); String query = intent.getStringExtra(SearchManager.QUERY); if (DEBUG) Log.d(TAG, "New Intent: " + action + ", " + query); if (TextUtils.isEmpty(action) || Intent.ACTION_VIEW.equals(action) && query != null) { startSearch(query, false, null, false); } else if (Intent.ACTION_SEARCH.equals(action) && query != null) { if (DEBUG) Log.d(TAG, "onNewIntent received search intent and saving."); SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, VenueQuerySuggestionsProvider.AUTHORITY, VenueQuerySuggestionsProvider.MODE); suggestions.saveRecentQuery(query, null); executeSearchTask(query); } else { onSearchRequested(); } } else { executeSearchTask(""); } } @Override public Object onRetainNonConfigurationInstance() { return mSearchHolder; } public void putSearchResultsInAdapter(Group<Group<Venue>> searchResults) { mListAdapter.removeObserver(); mListAdapter = new SeparatedListAdapter(this); int groupCount = searchResults.size(); for (int groupsIndex = 0; groupsIndex < groupCount; groupsIndex++) { Group<Venue> group = searchResults.get(groupsIndex); if (group.size() > 0) { VenueListAdapter groupAdapter = new VenueListAdapter(this, ((Foursquared) getApplication()).getRemoteResourceManager()); groupAdapter.setGroup(group); if (DEBUG) Log.d(TAG, "Adding Section: " + group.getType()); mListAdapter.addSection(group.getType(), groupAdapter); } } mListView.setAdapter(mListAdapter); } public void setSearchResults(Group<Group<Venue>> searchResults) { if (DEBUG) Log.d(TAG, "Setting search results."); mSearchHolder.results = searchResults; searchResultsObservable.notifyObservers(); } void executeSearchTask(String query) { if (DEBUG) Log.d(TAG, "sendQuery()"); mSearchHolder.query = query; // not going through set* because we don't want to notify search result // observers. mSearchHolder.results = null; // If a task is already running, don't start a new one. if (mSearchTask != null && mSearchTask.getStatus() != AsyncTask.Status.FINISHED) { if (DEBUG) Log.d(TAG, "Query already running attempting to cancel: " + mSearchTask); if (!mSearchTask.cancel(true) && !mSearchTask.isCancelled()) { if (DEBUG) Log.d(TAG, "Unable to cancel search? Notifying the user."); Toast.makeText(this, getString(R.string.search_already_in_progress_toast), Toast.LENGTH_SHORT); return; } } mSearchTask = (SearchTask) new SearchTask().execute(); } void startItemActivity(Venue venue) { Intent intent = new Intent(SearchVenuesActivity.this, VenueActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.putExtra(Foursquared.EXTRA_VENUE_ID, venue.getId()); startActivity(intent); } private void ensureSearchResults() { if (mListAdapter.getCount() > 0) { mEmpty.setVisibility(LinearLayout.GONE); mListView.setVisibility(ViewGroup.VISIBLE); } else { mEmpty.setVisibility(LinearLayout.VISIBLE); mEmptyProgress.setVisibility(ViewGroup.GONE); mEmptyText.setText(R.string.no_search_results); mListView.setVisibility(ViewGroup.GONE); } } private void ensureTitle(boolean finished) { if (finished) { if (mSearchHolder.query == QUERY_NEARBY) { setTitle(getString(R.string.title_search_finished_noquery)); } else { setTitle(getString(R.string.title_search_finished, mSearchHolder.query)); } } else { if (mSearchHolder.query == QUERY_NEARBY) { setTitle(getString(R.string.title_search_inprogress_noquery)); } else { setTitle(getString(R.string.title_search_inprogress, mSearchHolder.query)); } } } private void initListViewAdapter() { if (mListView != null) { throw new IllegalStateException("Trying to initialize already initialized ListView"); } mEmpty = (LinearLayout) findViewById(R.id.empty); mEmptyText = (TextView) findViewById(R.id.emptyText); mEmptyProgress = (ProgressBar) findViewById(R.id.emptyProgress); mListView = (ListView) findViewById(R.id.list); mListAdapter = new SeparatedListAdapter(this); mListView.setAdapter(mListAdapter); mListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Venue venue = (Venue) parent.getAdapter().getItem(position); if (mIsShortcutPicker) { setupShortcut(venue); finish(); } else { startItemActivity(venue); } } }); } protected void setupShortcut(Venue venue) { // First, set up the shortcut intent. For this example, we simply create // an intent that // will bring us directly back to this activity. A more typical // implementation would use a // data Uri in order to display a more specific result, or a custom // action in order to // launch a specific operation. Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); shortcutIntent.setClassName(this, VenueActivity.class.getName()); shortcutIntent.putExtra(Foursquared.EXTRA_VENUE_ID, venue.getId()); // Then, set up the container intent (the response to the caller) Intent intent = new Intent(); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, venue.getName()); Parcelable iconResource = Intent.ShortcutIconResource.fromContext(this, R.drawable.venue_shortcut_icon); intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); // Now, return the result to the launcher setResult(RESULT_OK, intent); } private void initTabHost() { if (mTabHost != null) { throw new IllegalStateException("Trying to intialize already initializd TabHost"); } mTabHost = getTabHost(); TabsUtil.addNativeLookingTab(this, mTabHost, "results", getString(R.string.search_venues_label), R.drawable.places_tab, R.id.listviewLayout); TabsUtil.addNativeLookingTab(this, mTabHost, "map", getString(R.string.map_label), R.drawable.map_tab, new Intent(this, SearchVenuesMapActivity.class)); mTabHost.setCurrentTab(0); } private class SearchTask extends AsyncTask<Void, Void, Group<Group<Venue>>> { private Exception mReason = null; @Override public void onPreExecute() { if (DEBUG) Log.d(TAG, "SearchTask: onPreExecute()"); setProgressBarIndeterminateVisibility(true); ensureTitle(false); } @Override public Group<Group<Venue>> doInBackground(Void... params) { try { return search(); } catch (Exception e) { mReason = e; } return null; } @Override public void onPostExecute(Group<Group<Venue>> groups) { try { if (groups == null) { NotificationsUtil.ToastReasonForFailure(SearchVenuesActivity.this, mReason); } else { setSearchResults(groups); putSearchResultsInAdapter(groups); } } finally { setProgressBarIndeterminateVisibility(false); ensureTitle(true); ensureSearchResults(); } } public Group<Group<Venue>> search() throws FoursquareException, LocationException, IOException { Foursquare foursquare = ((Foursquared) getApplication()).getFoursquare(); Location location = ((Foursquared) getApplication()).getLastKnownLocationOrThrow(); Group<Group<Venue>> groups = foursquare.venues(LocationUtils .createFoursquareLocation(location), mSearchHolder.query, 30); for (int i = 0; i < groups.size(); i++) { Collections.sort(groups.get(i), Comparators.getVenueDistanceComparator()); } return groups; } } private static class SearchHolder { Group<Group<Venue>> results; String query; } class SearchResultsObservable extends Observable { @Override public void notifyObservers(Object data) { setChanged(); super.notifyObservers(data); } public Group<Group<Venue>> getSearchResults() { return mSearchHolder.results; } public String getQuery() { return mSearchHolder.query; } }; }