/* * Copyright (C) 2012 Paul Watts (paulcwatts@gmail.com) * and individual contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.android.ui; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.api.GoogleApiClient; import org.onebusaway.android.R; import org.onebusaway.android.app.Application; import org.onebusaway.android.io.ObaAnalytics; import org.onebusaway.android.io.ObaApi; import org.onebusaway.android.io.elements.ObaElement; import org.onebusaway.android.io.elements.ObaRoute; import org.onebusaway.android.io.elements.ObaStop; import org.onebusaway.android.io.request.ObaRoutesForLocationRequest; import org.onebusaway.android.io.request.ObaRoutesForLocationResponse; import org.onebusaway.android.io.request.ObaStopsForLocationRequest; import org.onebusaway.android.io.request.ObaStopsForLocationResponse; import org.onebusaway.android.util.ArrayAdapter; import org.onebusaway.android.util.LocationUtils; import org.onebusaway.android.util.UIUtils; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.location.Location; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * This implements the response that returns both stops and routes in one list. * * @author paulw */ final class SearchResponse { private final int mCode; private final List<ObaElement> mResults; SearchResponse(int code, List<ObaElement> r) { mCode = code; mResults = r; } int getCode() { return mCode; } List<ObaElement> getResults() { return mResults; } } public class SearchResultsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<SearchResponse> { //private static final String TAG = "SearchResultsFragment"; public static final String QUERY_TEXT = "query_text"; private MyAdapter mAdapter; /** * GoogleApiClient being used for Location Services */ GoogleApiClient mGoogleApiClient; @Override public void onAttach(Activity activity) { super.onAttach(activity); // Init Google Play Services as early as possible in the Fragment lifecycle to give it time GoogleApiAvailability api = GoogleApiAvailability.getInstance(); if (api.isGooglePlayServicesAvailable(getActivity()) == ConnectionResult.SUCCESS) { mGoogleApiClient = LocationUtils.getGoogleApiClientWithCallbacks(getActivity()); mGoogleApiClient.connect(); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mAdapter = new MyAdapter(getActivity()); setListAdapter(mAdapter); search(); } @Override public void onStart() { super.onStart(); ObaAnalytics.reportFragmentStart(this); // Make sure GoogleApiClient is connected, if available if (mGoogleApiClient != null && !mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); } } @Override public void onStop() { // Tear down GoogleApiClient if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { mGoogleApiClient.disconnect(); } super.onStop(); } private void search() { UIUtils.showProgress(this, true); Loader<?> loader = getLoaderManager().restartLoader(0, getArguments(), this); //loader.onContentChanged(); loader.forceLoad(); } @Override public Loader<SearchResponse> onCreateLoader(int id, Bundle args) { String query = args.getString(QUERY_TEXT); Location location = Application.getLastKnownLocation(getActivity(), mGoogleApiClient); if (location == null) { location = LocationUtils.getDefaultSearchCenter(); } return new MyLoader(getActivity(), query, location); } @Override public void onLoadFinished(Loader<SearchResponse> loader, SearchResponse response) { UIUtils.showProgress(this, false); //Log.d(TAG, "Loader finished"); final int code = response.getCode(); if (code == ObaApi.OBA_OK) { setEmptyText(getString(R.string.find_hint_noresults)); mAdapter.setData(response.getResults()); } else if (code != 0) { // If we get anything other than a '0' error, that means // the server actually returned something to us, // (even if it was an error) so we shouldn't show // a 'communication' error. Just fake no results. setEmptyText(getString(R.string.find_hint_noresults)); } else { setEmptyText(getString(R.string.generic_comm_error)); } } @Override public void onLoaderReset(Loader<SearchResponse> loader) { mAdapter.clear(); } @Override public void onListItemClick(ListView l, View v, int position, long id) { ListAdapter adapter = l.getAdapter(); ObaElement e = (ObaElement) adapter.getItem(position - l.getHeaderViewsCount()); if (e instanceof ObaRoute) { clickRoute((ObaRoute) e); } else if (e instanceof ObaStop) { clickStop((ObaStop) e); } } private void clickRoute(ObaRoute route) { final String routeId = route.getId(); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(UIUtils.getRouteDescription(route)); builder.setItems(R.array.search_route_options, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: // Show on list RouteInfoActivity.start(getActivity(), routeId); break; case 1: // Show on map HomeActivity.start(getActivity(), routeId); break; } } }); AlertDialog dialog = builder.create(); dialog.setOwnerActivity(getActivity()); dialog.show(); } private void clickStop(final ObaStop stop) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(UIUtils.formatDisplayText(stop.getName())); builder.setItems(R.array.search_stop_options, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: ArrivalsListActivity.start(getActivity(), stop.getId()); break; case 1: HomeActivity.start(getActivity(), stop.getId(), stop.getLatitude(), stop.getLongitude()); break; } } }); AlertDialog dialog = builder.create(); dialog.setOwnerActivity(getActivity()); dialog.show(); } // // Adapter // private static final class MyAdapter extends ArrayAdapter<ObaElement> { public MyAdapter(Context context) { super(context, R.layout.route_list_item); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; LayoutInflater inflater = getLayoutInflater(); ObaElement elem = getItem(position); // The layout we use depends on the element... // so unfortunately we can't actually re-use views... if (elem instanceof ObaRoute) { view = inflater.inflate(R.layout.route_list_item, parent, false); UIUtils.setRouteView(view, (ObaRoute) elem); } else if (elem instanceof ObaStop) { view = inflater.inflate(R.layout.stop_list_item, parent, false); initStop(view, (ObaStop) elem); } else { view = null; } return view; } private void initStop(View view, ObaStop stop) { TextView nameView = (TextView) view.findViewById(R.id.stop_name); nameView.setText(UIUtils.formatDisplayText(stop.getName())); UIUtils.setStopDirection(view.findViewById(R.id.direction), stop.getDirection(), true); } @Override protected void initView(View view, ObaElement t) { throw new AssertionError("Should never be called"); } } // // Loader // private static final class MyLoader extends AsyncTaskLoader<SearchResponse> { private final String mQueryText; private final Location mCenter; public MyLoader(Context context, String query, Location center) { super(context); mQueryText = query; mCenter = center; } private ObaRoutesForLocationResponse getRoutes() { ObaRoutesForLocationResponse response = new ObaRoutesForLocationRequest.Builder(getContext(), mCenter) .setRadius(LocationUtils.DEFAULT_SEARCH_RADIUS) .setQuery(mQueryText) .build() .call(); // If there is no results from the user-centered query, // open a wider next in some "default" location //Log.d(TAG, "Server returns: " + response.getCode()); if (response.getCode() == ObaApi.OBA_OK) { ObaRoute[] routes = response.getRoutesForLocation(); if (routes.length != 0) { return response; } } Location center = LocationUtils.getDefaultSearchCenter(); if (center != null) { return new ObaRoutesForLocationRequest.Builder(getContext(), center) .setRadius(LocationUtils.DEFAULT_SEARCH_RADIUS) .setQuery(mQueryText) .build() .call(); } // I suppose we just return what was there... return response; } private ObaStopsForLocationResponse getStops() { return new ObaStopsForLocationRequest.Builder(getContext(), mCenter) .setRadius(LocationUtils.DEFAULT_SEARCH_RADIUS) .setQuery(mQueryText) .build() .call(); } @Override public SearchResponse loadInBackground() { ArrayList<ObaElement> results = new ArrayList<ObaElement>(); ObaRoutesForLocationResponse routes = getRoutes(); ObaStopsForLocationResponse stops = getStops(); int routeCode = routes.getCode(); int stopCode = stops.getCode(); int code = ObaApi.OBA_OK; // if neither of them are OK, return one of them. if (routeCode != ObaApi.OBA_OK && stopCode != ObaApi.OBA_OK) { code = routeCode; } if (code == ObaApi.OBA_OK) { results.addAll(Arrays.asList(routes.getRoutesForLocation())); results.addAll(Arrays.asList(stops.getStops())); } return new SearchResponse(code, results); } } }