/* * Copyright (C) 2012-2015 Paul Watts (paulcwatts@gmail.com), University of South Florida * * 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.elements.ObaRegion; import org.onebusaway.android.region.ObaRegionsLoader; import org.onebusaway.android.util.ArrayAdapter; import org.onebusaway.android.util.LocationUtils; import org.onebusaway.android.util.PreferenceUtils; import org.onebusaway.android.util.RegionUtils; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.location.Location; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.Locale; public class RegionsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<ArrayList<ObaRegion>> { private static final String TAG = "RegionsFragment"; private static final String RELOAD = ".reload"; private ArrayAdapter<ObaRegion> mAdapter; private Location mLocation; Locale mLocale; SharedPreferences mSettings = Application.getPrefs(); private static String IMPERIAL; private static String METRIC; private static String AUTOMATIC; // Current region private ObaRegion mCurrentRegion; /** * 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(); } mLocale = Locale.getDefault(); IMPERIAL = getString(R.string.preferences_preferred_units_option_imperial); METRIC = getString(R.string.preferences_preferred_units_option_metric); AUTOMATIC = getString(R.string.preferences_preferred_units_option_automatic); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); mLocation = Application.getLastKnownLocation(getActivity(), mGoogleApiClient); mCurrentRegion = Application.get().getCurrentRegion(); Bundle args = new Bundle(); args.putBoolean(RELOAD, false); getLoaderManager().initLoader(0, args, this); } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Get the region and set this as the default region. ObaRegion region = mAdapter.getItem(position); Application.get().setCurrentRegion(region); //If we're currently auto-selecting regions, disable this so it doesn't override the manual setting if (Application.getPrefs() .getBoolean(getString(R.string.preference_key_auto_select_region), true)) { PreferenceUtils .saveBoolean(getString(R.string.preference_key_auto_select_region), false); Toast.makeText(this.getActivity(), R.string.region_disabled_auto_selection, Toast.LENGTH_LONG) .show(); } Log.d(TAG, "User manually set region to '" + region.getName() + "'."); //Analytics ObaAnalytics.reportEventWithCategory(ObaAnalytics.ObaEventCategory.UI_ACTION.toString(), getString(R.string.analytics_action_set_region), getString(R.string.analytics_label_set_region)+ region.getName()); NavHelp.goHome(getActivity(), false); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.regions_list, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { final int id = item.getItemId(); if (id == R.id.refresh) { refresh(); return true; } return false; } @Override public void onStart() { super.onStart(); // 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 refresh() { setListShown(false); setListAdapter(null); mAdapter = null; Bundle args = new Bundle(); args.putBoolean(RELOAD, true); getLoaderManager().restartLoader(0, args, this); } @Override public Loader<ArrayList<ObaRegion>> onCreateLoader(int id, Bundle args) { boolean refresh = args.getBoolean(RELOAD); return new ObaRegionsLoader(getActivity(), refresh); } @Override public void onLoadFinished(Loader<ArrayList<ObaRegion>> loader, ArrayList<ObaRegion> results) { // Create our generic adapter mAdapter = new Adapter(getActivity()); setListAdapter(mAdapter); //Remove any regions that aren't usable before setting the adapter. //Loop using an Iterator, since per Oracle Iterator.remove() is the only safe way //to remove an item from a Collection (including ArrayList) during iteration: //http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html try { Iterator<ObaRegion> iter = results.iterator(); while (iter.hasNext()) { ObaRegion r = iter.next(); if (!RegionUtils.isRegionUsable(r)) { iter.remove(); Log.d(TAG, "Removed region '" + r.getName() + "' from adapter."); } } } catch (UnsupportedOperationException e) { Log.w(TAG, "Problem removing region from list using iterator: " + e); //The platform apparently didn't like the "efficient" way to do this, so we'll just //loop through a copy and remove what we don't want from the original ArrayList<ObaRegion> copy = new ArrayList<ObaRegion>(results); for (ObaRegion r : copy) { if (!RegionUtils.isRegionUsable(r)) { results.remove(r); Log.d(TAG, "Removed region '" + r.getName() + "' from adapter."); } } } mAdapter.setData(results); if (mLocation != null) { mAdapter.sort(mClosest); } } @Override public void onLoaderReset(Loader<ArrayList<ObaRegion>> arg0) { setListAdapter(null); mAdapter = null; } private Comparator<ObaRegion> mClosest = new Comparator<ObaRegion>() { @Override public int compare(ObaRegion r1, ObaRegion r2) { Float r1distance = RegionUtils.getDistanceAway(r1, mLocation); Float r2distance = RegionUtils.getDistanceAway(r2, mLocation); if (r1distance == null) { r1distance = Float.MAX_VALUE; } if (r2distance == null) { r2distance = Float.MAX_VALUE; } return r1distance.compareTo(r2distance); } }; private class Adapter extends ArrayAdapter<ObaRegion> { Adapter(Context context) { super(context, R.layout.simple_list_item_2_checked); } @Override protected void initView(View view, ObaRegion region) { TextView text1 = (TextView) view.findViewById(android.R.id.text1); TextView text2 = (TextView) view.findViewById(android.R.id.text2); ImageView image = (ImageView) view.findViewById(android.R.id.selectedIcon); text1.setText(region.getName()); Float distance = null; int regionVis = View.INVISIBLE; if (mCurrentRegion != null && region.getId() == mCurrentRegion.getId()) { regionVis = View.VISIBLE; } image.setVisibility(regionVis); if (mLocation != null) { distance = RegionUtils.getDistanceAway(region, mLocation); } if (distance != null) { double miles = distance * 0.000621371; distance /= 1000; // Convert meters to kilometers String preferredUnits = mSettings .getString(getString(R.string.preference_key_preferred_units), AUTOMATIC); if (preferredUnits.equalsIgnoreCase(AUTOMATIC)) { Log.d(TAG, "Setting units automatically"); // If the country is set to USA, assume imperial, otherwise metric // TODO - Method of guessing metric/imperial can definitely be improved if (mLocale.getISO3Country().equalsIgnoreCase(Locale.US.getISO3Country())) { // Assume imperial setDistanceTextView(text2, miles, IMPERIAL); } else { // Assume metric setDistanceTextView(text2, distance, METRIC); } } else if (preferredUnits.equalsIgnoreCase(IMPERIAL)) { setDistanceTextView(text2, miles, IMPERIAL); } else if (preferredUnits.equalsIgnoreCase(METRIC)) { setDistanceTextView(text2, distance, METRIC); } } else { view.setEnabled(false); text2.setText(R.string.region_unavailable); } } } /** * Sets the text view that contains distance with units based on input parameters * * @param text the TextView to be set * @param distance the distance to be used, in miles (for imperial) or kilometers (for metric) * @param units the units to be used from strings.xml, either preferences_preferred_units_option_metric * or preferences_preferred_units_option_imperial */ private void setDistanceTextView(TextView text, double distance, String units) { Resources r = getResources(); NumberFormat fmt = NumberFormat.getInstance(); if (fmt instanceof DecimalFormat) { fmt.setMaximumFractionDigits(1); } if (units.equalsIgnoreCase( getString(R.string.preferences_preferred_units_option_imperial))) { text.setText(r.getQuantityString(R.plurals.distance_miles, (int) distance, fmt.format(distance))); } else if (units.equalsIgnoreCase( getString(R.string.preferences_preferred_units_option_metric))) { text.setText(r.getQuantityString(R.plurals.distance_kilometers, (int) distance, fmt.format(distance))); } } }