/* BostonBusMap Copyright (C) 2009 George Schneeloch This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package boston.Bus.Map.main; import java.io.IOException; import java.util.List; import java.util.Map; import com.google.android.gms.maps.model.LatLng; import com.google.common.base.Optional; import com.schneeloch.bostonbusmap_library.data.GroupKey; import com.schneeloch.bostonbusmap_library.data.Location; import com.schneeloch.bostonbusmap_library.data.Locations; import com.schneeloch.bostonbusmap_library.data.Path; import com.schneeloch.bostonbusmap_library.data.RouteConfig; import com.schneeloch.bostonbusmap_library.data.RouteTitles; import com.schneeloch.bostonbusmap_library.data.Selection; import boston.Bus.Map.data.UpdateArguments; import boston.Bus.Map.ui.MapManager; import boston.Bus.Map.ui.ProgressMessage; import com.schneeloch.bostonbusmap_library.util.Constants; import com.schneeloch.bostonbusmap_library.util.LogUtil; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import android.app.ProgressDialog; import android.content.OperationApplicationException; import android.os.AsyncTask; import android.os.Build; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.Toast; /** * Handles the heavy work of downloading and parsing the XML in a separate thread from the UI. * */ public abstract class UpdateAsyncTask extends AsyncTask<Object, Object, ImmutableList<ImmutableList<Location>>> { private final boolean doShowUnpredictable; private final int maxOverlays; private String progressDialogTitle; private String progressDialogMessage; private int progressDialogMax; private int progressDialogProgress; private boolean progressDialogIsShowing; private boolean progressIsShowing; protected final UpdateArguments arguments; protected final UpdateHandler handler; protected final Selection selection; private final Runnable afterUpdate; /** * The last read center of the map. */ protected final LatLng currentMapCenter; public UpdateAsyncTask(UpdateArguments arguments, boolean doShowUnpredictable, int maxOverlays, Selection selection, UpdateHandler handler, Runnable afterUpdate) { super(); this.arguments = arguments; //NOTE: these should only be used in one of the UI threads this.doShowUnpredictable = doShowUnpredictable; this.maxOverlays = maxOverlays; this.selection = selection; this.handler = handler; currentMapCenter = arguments.getMapView().getCameraPosition().target; this.afterUpdate = afterUpdate; } /** * A type safe wrapper around execute */ public void runUpdate() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // allow tasks to be run in parallel. This will allow the task which updates information // to avoid blocking the task which updates positions on the map executeOnExecutor(UpdateAsyncTask.THREAD_POOL_EXECUTOR); } else { execute(); } } @Override protected ImmutableList<ImmutableList<Location>> doInBackground(Object... args) { //number of bus pictures to draw. Too many will make things slow return updateBusLocations(); } @Override protected void onProgressUpdate(Object... values) { try { progressUpdate(values); } catch (Throwable t) { LogUtil.e(t); } } protected void progressUpdate(Object... objects) { if (objects.length == 0) { return; } final ProgressDialog progressDialog = arguments.getProgressDialog(); final ProgressBar progress = arguments.getProgress(); if (progressDialog == null || progress == null) { return; } Object obj = objects[0]; if (areUpdatesSilenced() == false) { if (obj instanceof Integer) { int value = (Integer)obj; progressDialog.setProgress(value); progressDialogProgress = value; } else if (obj instanceof ProgressMessage) { ProgressMessage message = (ProgressMessage)obj; switch (message.type) { case ProgressMessage.PROGRESS_OFF: if (progressDialog != null) { progressDialog.dismiss(); } progressDialogIsShowing = false; if (progress != null) { progress.setVisibility(View.INVISIBLE); } progressIsShowing = false; break; case ProgressMessage.PROGRESS_DIALOG_ON: if (progressDialog != null) { progressDialog.setTitle(message.title); progressDialog.setMessage(message.message); progressDialog.show(); } progressDialogTitle = message.title; progressDialogMessage = message.message; progressDialogIsShowing = true; break; case ProgressMessage.SET_MAX: if (progressDialog != null) { progressDialog.setMax(message.max); } progressDialogMax = message.max; break; case ProgressMessage.PROGRESS_SPINNER_ON: if (progress != null) { progress.setVisibility(View.VISIBLE); } progressIsShowing = true; break; case ProgressMessage.TOAST: Log.v("BostonBusMap", "Toast made: " + obj); Toast.makeText(arguments.getContext(), message.message, Toast.LENGTH_LONG).show(); break; } } } } /** * Do the time consuming stuff we need to do in the background thread * * Returns true for success, false for failure. Errors should handled in function * to provide a helpful error message * @throws OperationApplicationException * @throws RemoteException */ protected abstract boolean doUpdate() throws RemoteException, OperationApplicationException; protected abstract boolean areUpdatesSilenced(); protected ImmutableList<ImmutableList<Location>> updateBusLocations() { final Locations busLocations = arguments.getBusLocations(); try { if (doUpdate() == false) { return null; } } catch (Throwable e) { LogUtil.e(e); publish(new ProgressMessage(ProgressMessage.TOAST, null, "Unknown error occurred")); return null; } try { LatLng geoPoint = currentMapCenter; return busLocations.getLocations(maxOverlays, geoPoint.latitude, geoPoint.longitude, doShowUnpredictable, selection); } catch (IOException e) { publish(new ProgressMessage(ProgressMessage.TOAST, null, "Error getting route data from database")); LogUtil.e(e); return null; } } @Override protected void onPostExecute(final ImmutableList<ImmutableList<Location>> locations) { try { postExecute(locations); } catch (Throwable t) { LogUtil.e(t); } } protected void postExecute(final ImmutableList<ImmutableList<Location>> locationsNearCenter) { //get currently selected location id, or -1 if nothing is selected MapManager manager = arguments.getOverlayGroup(); //routeOverlay.setDrawCoarseLine(showCoarseRouteLine); //get a list of lat/lon pairs which describe the route final Optional<GroupKey> selectedBusId = manager.getSelectedBusId(); //draw the buses on the map LatLng newCenter = manager.getMap().getCameraPosition().target; // we need to do this here because addLocation creates PredictionViews, which needs // to happen after makeSnippetAndTitle and addToSnippetAndTitle manager.updateNewLocations(locationsNearCenter, selectedBusId, newCenter, forceNewMarker()); //busOverlay.refreshBalloons(); if (!newCenter.equals(currentMapCenter)) { handler.triggerUpdate(); } if (afterUpdate != null) { afterUpdate.run(); } } protected abstract boolean forceNewMarker(); /** * public method exposing protected publishProgress() * @param msg */ public void publish(ProgressMessage msg) { publishProgress(msg); } public void publish(int value) { publishProgress(value); } public void nullifyProgress() { arguments.setProgress(null); arguments.setProgressDialog(null); } /** * This must get run in the UI thread. Neither parameter can be null; use nullifyProgress for that * * @param progress * @param progressDialog */ public void setProgress(ProgressBar progress, ProgressDialog progressDialog) { arguments.setProgress(progress); arguments.setProgressDialog(progressDialog); progress.setVisibility(progressIsShowing ? View.VISIBLE : View.INVISIBLE); progressDialog.setTitle(progressDialogTitle); progressDialog.setMessage(progressDialogMessage); progressDialog.setMax(progressDialogMax); if (progressDialogIsShowing) { progressDialog.show(); } } }