/*
* Copyright 2011 Marcy Gordon
*
* 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.directions.tasks;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.directions.util.JacksonConfig;
import org.opentripplanner.api.model.Itinerary;
import org.opentripplanner.api.ws.Message;
import org.opentripplanner.api.ws.Request;
import org.opentripplanner.api.ws.Response;
import org.opentripplanner.routing.core.TraverseMode;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* AsyncTask that invokes a trip planning request to the OTP Server
*
* @author Khoa Tran
* @author Sean Barbeau (conversion to Jackson)
* @author Simon Jacobs (integration for onebusaway-android)
*/
public class TripRequest extends AsyncTask<Request, Integer, Long> {
public interface Callback {
void onTripRequestComplete(List<Itinerary> itineraries, String url);
void onTripRequestFailure(int errorCode, String url);
}
public static int NO_SERVER_SELECTED = 1000;
// Constants that are defined in OTPApp in CUTR OTP Android app
private static final String TAG = "TripRequest";
private static final String FOLDER_STRUCTURE_PREFIX_NEW = "/routers/default";
public static final String OTP_RENTAL_QUALIFIER = "_RENT";
public static final String PLAN_LOCATION = "/plan";
public static final int HTTP_CONNECTION_TIMEOUT = 15000;
public static final int HTTP_SOCKET_TIMEOUT = 15000;
private Response mResponse;
private String mBaseUrl;
private String mRequestUrl;
private Callback mCallback;
// change Server object to baseUrl string.
public TripRequest(String baseUrl, Callback callback) {
mBaseUrl = baseUrl;
mCallback = callback;
}
/**
* Show the progress dialog for this request. Called when request starts, or by caller activity
* (i.e., in onCreate() after a rotation)
* @param reqs
* @return
*/
protected Long doInBackground(Request... reqs) {
long totalSize = 0;
if (mBaseUrl == null) {
mCallback.onTripRequestFailure(NO_SERVER_SELECTED, null);
return null;
} else {
String prefix = FOLDER_STRUCTURE_PREFIX_NEW;
boolean useOldUrlVersion = Application.get().getUseOldOtpApiUrlVersion();
for (Request req : reqs) {
mResponse = requestPlan(req, prefix, mBaseUrl, useOldUrlVersion);
}
}
return totalSize;
}
protected void onCancelled(Long result) {
mCallback.onTripRequestFailure(Message.REQUEST_TIMEOUT.getId(), mRequestUrl);
}
protected void onPostExecute(Long result) {
if (result == null) {
return;
}
if (mResponse != null && mResponse.getPlan() != null
&& mResponse.getPlan().getItinerary().get(0) != null) {
mCallback.onTripRequestComplete(mResponse.getPlan().getItinerary(), mRequestUrl);
} else {
Log.e(TAG, "Error retrieving routing from OTP server: " + mResponse);
int errorCode = -1;
if (mResponse != null && mResponse.getError() != null) {
errorCode = mResponse.getError().getId();
}
mCallback.onTripRequestFailure(errorCode, mRequestUrl);
}
}
protected Response requestPlan(Request requestParams, String prefix, String baseURL,
boolean useOldUrlStructure) {
HashMap<String, String> tmp = requestParams.getParameters();
Collection c = tmp.entrySet();
Iterator itr = c.iterator();
String params = "";
boolean first = true;
while (itr.hasNext()) {
if (first) {
params += "?" + itr.next();
first = false;
} else {
params += "&" + itr.next();
}
}
if (requestParams.getBikeRental()) {
String updatedString;
if (prefix.equals(FOLDER_STRUCTURE_PREFIX_NEW)) {
updatedString = params.replace(TraverseMode.BICYCLE.toString(),
TraverseMode.BICYCLE.toString() + OTP_RENTAL_QUALIFIER);
} else {
updatedString = params.replace(TraverseMode.BICYCLE.toString(),
TraverseMode.BICYCLE.toString() + ", " + TraverseMode.WALK.toString());
}
params = updatedString;
}
String u;
if (!useOldUrlStructure) {
u = baseURL + prefix + PLAN_LOCATION + params;
} else {
u = baseURL + PLAN_LOCATION + params;
}
// Save url for error reporting purposes
mRequestUrl = u;
Log.d(TAG, "URL: " + u);
HttpURLConnection urlConnection = null;
URL url;
Response plan = null;
try {
url = new URL(u);
disableConnectionReuseIfNecessary(); // For bugs in HttpURLConnection pre-Froyo
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(HTTP_CONNECTION_TIMEOUT);
urlConnection.setReadTimeout(HTTP_SOCKET_TIMEOUT);
plan = JacksonConfig.getObjectReaderInstance()
.readValue(urlConnection.getInputStream());
if (useOldUrlStructure) {
// If the old url structure is successful then cache it
Application.get().setUseOldOtpApiUrlVersion(true);
}
} catch (java.net.SocketTimeoutException e) {
Log.e(TAG, "Timeout fetching JSON or XML: " + e);
e.printStackTrace();
cancel(true);
} catch (FileNotFoundException e) {
if (!useOldUrlStructure) {
Log.v(TAG, "The OTP url might be old, trying old url structure");
if (urlConnection != null) {
urlConnection.disconnect();
}
return requestPlan(requestParams, prefix, baseURL, true);
} else {
Log.e(TAG, "Error fetching JSON or XML: " + e);
e.printStackTrace();
cancel(true);
}
} catch (IOException e) {
Log.e(TAG, "Error fetching JSON or XML: " + e);
e.printStackTrace();
cancel(true);
// Reset timestamps to show there was an error
// requestStartTime = 0;
// requestEndTime = 0;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return plan;
}
/**
* Disable HTTP connection reuse which was buggy pre-froyo
*/
private void disableConnectionReuseIfNecessary() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
}