/* * The CroudTrip! application aims at revolutionizing the car-ride-sharing market with its easy, * user-friendly and highly automated way of organizing shared Trips. Copyright (C) 2015 Nazeeh Ammari, * Philipp Eichhorn, Ricarda Hohn, Vanessa Lange, Alexander Popp, Frederik Simon, Michael Weber * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * Affero General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package org.croudtrip.location; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.places.AutocompleteFilter; import com.google.android.gms.location.places.AutocompletePrediction; import com.google.android.gms.location.places.AutocompletePredictionBuffer; import com.google.android.gms.location.places.Places; import com.google.android.gms.maps.model.LatLngBounds; import android.content.Context; import android.util.Log; import android.widget.ArrayAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.Toast; import java.util.ArrayList; import java.util.Iterator; import java.util.concurrent.TimeUnit; /** * Adapter that handles Autocomplete requests from the Places Geo Data API. * Results are encoded as PlaceAutocomplete objects * that contain both the Place ID and the text description from the autocomplete query. * <p> * Note that this adapter requires a valid {@link com.google.android.gms.common.api.GoogleApiClient}. * The API client must be maintained in the encapsulating Activity, including all lifecycle and * connection states. When the API client is not available (for example because it has been * disconnected or suspended), call {@link #setGoogleApiClient(com.google.android.gms.common.api.GoogleApiClient)} * with * a <code>null</code> parameter to disable this adapter. It will stop returning results until a * valid Api Client is set again. The API client must be connected with the {@link * Places#GEO_DATA_API} API. */ public class PlaceAutocompleteAdapter extends ArrayAdapter<PlaceAutocompleteAdapter.PlaceAutocomplete> implements Filterable { /** * Current results returned by this adapter. */ private ArrayList<PlaceAutocomplete> mResultList; private ArrayList<PlaceAutocomplete> history; /** * Handles autocomplete requests. */ private GoogleApiClient mGoogleApiClient; /** * The bounds used for Places Geo Data autocomplete API requests. */ private LatLngBounds mBounds; /** * The autocomplete filter used to restrict queries to a specific set of place types. */ private AutocompleteFilter mPlaceFilter; /** * Initializes with a resource for text rows and autocomplete query bounds. * * @see android.widget.ArrayAdapter#ArrayAdapter(android.content.Context, int) */ public PlaceAutocompleteAdapter(Context context, int resource, LatLngBounds bounds, AutocompleteFilter filter) { super(context, resource); mBounds = bounds; mPlaceFilter = filter; } public void setHistory(ArrayList<PlaceAutocomplete> history) { this.history = history; } /** * Sets the GoogleApiClient to use for autocomplete queries. * Autocomplete queries are suspended when the client is set to null. * Ensure that the client has successfully connected, containsPassenger the {@link Places#GEO_DATA_API} * API and is available for queries, otherwise API access will be disabled when it is set here. */ public void setGoogleApiClient(GoogleApiClient googleApiClient) { if (googleApiClient == null || !googleApiClient.isConnected()) { mGoogleApiClient = null; } else { mGoogleApiClient = googleApiClient; } } /** * Sets the bounds for all subsequent queries. */ public void setBounds(LatLngBounds bounds) { mBounds = bounds; } /** * Returns the number of results received in the last autocomplete query. */ @Override public int getCount() { if (mResultList != null) { return mResultList.size(); } if (history != null) { return history.size(); } return 0; } /** * Returns an item from the last autocomplete query. */ @Override public PlaceAutocomplete getItem(int position) { if (mResultList != null) { return mResultList.get(position); } if (history != null) { return history.get(position); } return null; } /** * Returns the filter for the current set of autocomplete results. */ @Override public Filter getFilter() { Filter filter = new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint == null || constraint.length() < 2) { results.values = history; results.count = history.size(); mResultList = history; return results; } // Skip the autocomplete query if no constraints are given. if (constraint != null) { // Query the autocomplete API for the (constraint) search string. mResultList = getAutocomplete(constraint); if (mResultList != null) { // The API successfully returned results. results.values = mResultList; results.count = mResultList.size(); } } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { if (results != null && results.count > 0) { // The API returned at least one result, update the data. notifyDataSetChanged(); } else { // The API did not return any results, invalidate the data set. notifyDataSetInvalidated(); } } }; return filter; } /** * Submits an autocomplete query to the Places Geo Data Autocomplete API. * Results are returned as PlaceAutocomplete objects to store the Place ID and description that the API returns. * Returns an empty list if no results were found. * Returns null if the API client is not available or the query did not complete * successfully. * This method MUST be called off the main UI thread, as it will block until data is returned * from the API, which may include a network request. * * @param constraint Autocomplete query string * @return Results from the autocomplete API or null if the query was not successful. * @see Places#GEO_DATA_API#getAutocomplete(CharSequence) */ private ArrayList<PlaceAutocomplete> getAutocomplete(CharSequence constraint) { if (mGoogleApiClient != null) { // Submit the query to the autocomplete API and retrieve a PendingResult that will // contain the results when the query completes. PendingResult<AutocompletePredictionBuffer> results = Places.GeoDataApi .getAutocompletePredictions(mGoogleApiClient, constraint.toString(), mBounds, mPlaceFilter); // This method should have been called off the main UI thread. Block and wait for at most 60s // for a result from the API. AutocompletePredictionBuffer autocompletePredictions = results .await(60, TimeUnit.SECONDS); // Confirm that the query completed successfully, otherwise return null final Status status = autocompletePredictions.getStatus(); if (!status.isSuccess()) { Toast.makeText(getContext(), "Error contacting API: " + status.toString(), Toast.LENGTH_SHORT).show(); autocompletePredictions.release(); return null; } // Copy the results into our own data structure, because we can't hold onto the buffer. // AutocompletePrediction objects encapsulate the API response (place ID and description). Iterator<AutocompletePrediction> iterator = autocompletePredictions.iterator(); ArrayList resultList = new ArrayList<>(autocompletePredictions.getCount()); while (iterator.hasNext()) { AutocompletePrediction prediction = iterator.next(); // Get the details of this prediction and copy it into a new PlaceAutocomplete object. resultList.add(new PlaceAutocomplete(prediction.getPlaceId(), prediction.getDescription())); } // Release the buffer now that all data has been copied. autocompletePredictions.release(); return resultList; } return null; } /** * Holder for Places Geo Data Autocomplete API results. */ public class PlaceAutocomplete { public CharSequence placeId; public CharSequence description; public PlaceAutocomplete(CharSequence placeId, CharSequence description) { this.placeId = placeId; this.description = description; } @Override public String toString() { return description.toString(); } } }