/**
* Copyright (C) 2016 Cambridge Systematics, Inc.
*
* 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.sothree.slidinguppanel.ScrollableViewHelper;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.directions.tasks.TripRequest;
import org.onebusaway.android.directions.util.OTPConstants;
import org.onebusaway.android.directions.util.TripRequestBuilder;
import org.onebusaway.android.io.ObaAnalytics;
import org.onebusaway.android.util.LocationUtils;
import org.onebusaway.android.util.UIUtils;
import org.opentripplanner.api.model.Itinerary;
import org.opentripplanner.api.ws.Message;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class TripPlanActivity extends AppCompatActivity implements TripRequest.Callback,
TripResultsFragment.Listener, TripPlanFragment.Listener {
private static final String TAG = "TripPlanActivity";
TripRequestBuilder mBuilder;
TripRequest mTripRequest = null;
SlidingUpPanelLayout mPanel;
AlertDialog mFeedbackDialog;
private static final String PLAN_ERROR_CODE = "org.onebusaway.android.PLAN_ERROR_CODE";
private static final String PLAN_ERROR_URL = "org.onebusaway.android.PLAN_ERROR_URL";
private static final String PLAN_REQUEST_URL = "org.onebusaway.android.PLAN_REQUEST_URL";
private static final String PANEL_STATE_EXPANDED = "org.onebusaway.android.PANEL_STATE_EXPANDED";
private static final String SHOW_ERROR_DIALOG = "org.onebusaway.android.SHOW_ERROR_DIALOG";
private static final String REQUEST_LOADING = "org.onebusaway.android.REQUEST_LOADING";
TripResultsFragment mResultsFragment;
private ProgressDialog mProgressDialog;
boolean mRequestLoading = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trip_plan);
UIUtils.setupActionBar(this);
Bundle bundle = (savedInstanceState == null) ? new Bundle() : savedInstanceState;
mBuilder = new TripRequestBuilder(bundle);
}
@Override
protected void onResume() {
super.onResume();
Bundle bundle = mBuilder.getBundle();
boolean newItineraries = false;
// see if there is data from intent
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
OTPConstants.Source source = (OTPConstants.Source) intent
.getSerializableExtra(OTPConstants.INTENT_SOURCE);
if (source != null) {
// Copy planning params - necessary if this intent came from a notification.
if (source == OTPConstants.Source.NOTIFICATION) {
new TripRequestBuilder(intent.getExtras()).copyIntoBundle(bundle);
}
ArrayList<Itinerary> itineraries = (ArrayList<Itinerary>)
intent.getExtras().getSerializable(OTPConstants.ITINERARIES);
if (itineraries != null) {
bundle.putSerializable(OTPConstants.ITINERARIES, itineraries);
newItineraries = true;
}
if (intent.getIntExtra(PLAN_ERROR_CODE, -1) != -1) {
bundle.putSerializable(SHOW_ERROR_DIALOG, true);
bundle.putInt(PLAN_ERROR_CODE, intent.getIntExtra(PLAN_ERROR_CODE, 0));
bundle.putString(PLAN_ERROR_URL, intent.getStringExtra(PLAN_ERROR_URL));
}
setIntent(null);
bundle.putBoolean(REQUEST_LOADING, false);
mRequestLoading = false;
}
}
if (mRequestLoading || bundle.getBoolean(REQUEST_LOADING)) {
showProgressDialog();
}
// Check which fragment to create
boolean haveTripPlan = bundle.getSerializable(OTPConstants.ITINERARIES) != null;
TripPlanFragment fragment = (TripPlanFragment) getSupportFragmentManager()
.findFragmentById(R.id.trip_plan_fragment_container);
if (fragment == null) {
fragment = new TripPlanFragment();
fragment.setArguments(bundle);
fragment.setListener(this);
getSupportFragmentManager().beginTransaction()
.add(R.id.trip_plan_fragment_container, fragment).commit();
}
mPanel = (SlidingUpPanelLayout) findViewById(R.id.trip_plan_sliding_layout);
mPanel.setEnabled(haveTripPlan);
if (haveTripPlan) {
initResultsFragment();
if (bundle.getBoolean(PANEL_STATE_EXPANDED) || newItineraries) {
mPanel.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
}
}
// show error dialog if necessary
if (bundle.getBoolean(SHOW_ERROR_DIALOG)) {
int planErrorCode = intent.getIntExtra(PLAN_ERROR_CODE, 0);
String planErrorUrl = intent.getStringExtra(PLAN_ERROR_URL);
showFeedbackDialog(planErrorCode, planErrorUrl);
}
if (fragment != null && intent != null) {
fragment.setPlanErrorUrl(intent.getStringExtra(PLAN_ERROR_URL));
fragment.setPlanRequestUrl(intent.getStringExtra(PLAN_REQUEST_URL));
}
// Set the height of the panel after drawing occurs.
final ViewGroup layout = (ViewGroup) findViewById(R.id.trip_plan_fragment_container);
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int viewHeight = mPanel.getHeight();
int height = layout.getMeasuredHeight();
mPanel.setPanelHeight(viewHeight - height);
}
});
}
@Override
public void onNewIntent(Intent intent) {
setIntent(intent);
}
@Override
public void onSaveInstanceState(Bundle bundle) {
if (mBuilder != null) {
mBuilder.copyIntoBundle(bundle);
// We also saved the itineraries and the results state in this bundle
Bundle source = mBuilder.getBundle();
ArrayList<Itinerary> itineraries = (ArrayList<Itinerary>) source
.getSerializable(OTPConstants.ITINERARIES);
if (itineraries != null) {
bundle.putSerializable(OTPConstants.ITINERARIES, itineraries);
}
int rank = source.getInt(OTPConstants.SELECTED_ITINERARY);
bundle.putInt(OTPConstants.SELECTED_ITINERARY, rank);
boolean showMap = source.getBoolean(OTPConstants.SHOW_MAP);
bundle.putBoolean(OTPConstants.SHOW_MAP, showMap);
boolean panelStateExpanded = (mPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED);
bundle.putBoolean(PANEL_STATE_EXPANDED, panelStateExpanded);
boolean showError = source.getBoolean(SHOW_ERROR_DIALOG);
bundle.putBoolean(SHOW_ERROR_DIALOG, showError);
bundle.putBoolean(REQUEST_LOADING, mRequestLoading);
}
}
@Override
public void onPause() {
super.onPause();
// Close possible progress dialog.
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if (mFeedbackDialog != null) {
mFeedbackDialog.dismiss();
}
}
@Override
public void onBackPressed() {
if (mPanel != null && mPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
mPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
} else {
super.onBackPressed();
}
}
@Override
public void onTripRequestReady() {
// Remove results fragment if it exists
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.trip_results_fragment_container);
if (fragment != null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
mTripRequest = mBuilder.setListener(this).execute(this);
// clear out selected itinerary from bundle
Bundle bundle = mBuilder.getBundle();
bundle.remove(OTPConstants.ITINERARIES);
bundle.remove(OTPConstants.SELECTED_ITINERARY);
ObaAnalytics.reportEventWithCategory(ObaAnalytics.ObaEventCategory.SUBMIT.toString(),
getString(R.string.analytics_action_trip_plan),
getString(R.string.analytics_label_trip_plan));
showProgressDialog();
}
private void initResultsFragment() {
mResultsFragment = (TripResultsFragment) getSupportFragmentManager().findFragmentById(R.id.trip_results_fragment_container);
if (mResultsFragment != null) {
// bundle arguments already set
mResultsFragment.displayNewResults();
return;
}
mResultsFragment = new TripResultsFragment();
mResultsFragment.setListener(this);
mResultsFragment.setArguments(mBuilder.getBundle());
getSupportFragmentManager().beginTransaction()
.add(R.id.trip_results_fragment_container, mResultsFragment).commit();
getSupportFragmentManager().executePendingTransactions();
}
@Override
public void onTripRequestComplete(List<Itinerary> itineraries, String url) {
// Send intent to ourselves...
Intent intent = new Intent(this, TripPlanActivity.class)
.setAction(Intent.ACTION_MAIN)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra(OTPConstants.ITINERARIES, (ArrayList<Itinerary>) itineraries)
.putExtra(OTPConstants.INTENT_SOURCE, OTPConstants.Source.ACTIVITY)
.putExtra(PLAN_REQUEST_URL, url);
startActivity(intent);
}
@Override
public void onTripRequestFailure(int errorCode, String url) {
Intent intent = new Intent(this, TripPlanActivity.class)
.setAction(Intent.ACTION_MAIN)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra(PLAN_ERROR_CODE, errorCode)
.putExtra(PLAN_ERROR_URL, url)
.putExtra(OTPConstants.INTENT_SOURCE, OTPConstants.Source.ACTIVITY);
startActivity(intent);
}
private void showFeedbackDialog(int errorCode, final String url) {
String msg = getString(R.string.tripplanner_error_not_defined);
if (errorCode > 0 && errorCode != Message.PLAN_OK.getId()) {
msg = getErrorMessage(errorCode);
}
final Bundle bundle = mBuilder.getBundle();
AlertDialog.Builder feedback = new AlertDialog.Builder(this)
.setTitle(R.string.tripplanner_error_dialog_title)
.setMessage(msg);
feedback.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
bundle.putBoolean(SHOW_ERROR_DIALOG, false);
clearBundleErrors();
}
});
if (errorCode != Message.SYSTEM_ERROR.getId() && errorCode != Message.REQUEST_TIMEOUT
.getId()) {
// Only add the report button if we get a server response (see #747)
feedback.setNegativeButton(R.string.report_problem_report,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String email = Application.get().getCurrentRegion()
.getOtpContactEmail();
if (!TextUtils.isEmpty(email)) {
Location loc = Application
.getLastKnownLocation(getApplicationContext(), null);
String locString = null;
if (loc != null) {
locString = LocationUtils.printLocationDetails(loc);
}
UIUtils.sendEmail(TripPlanActivity.this, email, locString, url,
true);
ObaAnalytics.reportEventWithCategory(
ObaAnalytics.ObaEventCategory.UI_ACTION.toString(),
getString(R.string.analytics_action_problem),
getString(R.string.analytics_label_app_feedback_otp));
} else {
Toast.makeText(TripPlanActivity.this,
getString(R.string.tripplanner_no_contact),
Toast.LENGTH_SHORT).show();
}
bundle.putBoolean(SHOW_ERROR_DIALOG, false);
clearBundleErrors();
}
});
}
mFeedbackDialog = feedback.create();
mFeedbackDialog.show();
}
private void showProgressDialog() {
mRequestLoading = true;
if (mProgressDialog == null || !mProgressDialog.isShowing()) {
mProgressDialog = ProgressDialog.show(this, "",
getResources().getText(R.string.task_progress_tripplanner_progress), true);
}
}
private String getErrorMessage(int errorCode) {
if (errorCode == Message.SYSTEM_ERROR.getId()) {
return (getString(R.string.tripplanner_error_system));
} else if (errorCode == Message.OUTSIDE_BOUNDS.getId()) {
return (getString(R.string.tripplanner_error_outside_bounds));
} else if (errorCode == Message.PATH_NOT_FOUND.getId()) {
return (getString(R.string.tripplanner_error_path_not_found));
} else if (errorCode == Message.NO_TRANSIT_TIMES.getId()) {
return (getString(R.string.tripplanner_error_no_transit_times));
} else if (errorCode == Message.REQUEST_TIMEOUT.getId()) {
return (getString(R.string.tripplanner_error_request_timeout));
} else if (errorCode == Message.BOGUS_PARAMETER.getId()) {
return (getString(R.string.tripplanner_error_bogus_parameter));
} else if (errorCode == Message.GEOCODE_FROM_NOT_FOUND.getId()) {
return (getString(R.string.tripplanner_error_geocode_from_not_found));
} else if (errorCode == Message.GEOCODE_TO_NOT_FOUND.getId()) {
return (getString(R.string.tripplanner_error_geocode_to_not_found));
} else if (errorCode == Message.GEOCODE_FROM_TO_NOT_FOUND.getId()) {
return (getString(R.string.tripplanner_error_geocode_from_to_not_found));
} else if (errorCode == Message.TOO_CLOSE.getId()) {
return (getString(R.string.tripplanner_error_too_close));
} else if (errorCode == Message.LOCATION_NOT_ACCESSIBLE.getId()) {
return (getString(R.string.tripplanner_error_location_not_accessible));
} else if (errorCode == Message.GEOCODE_FROM_AMBIGUOUS.getId()) {
return (getString(R.string.tripplanner_error_geocode_from_ambiguous));
} else if (errorCode == Message.GEOCODE_TO_AMBIGUOUS.getId()) {
return (getString(R.string.tripplanner_error_geocode_to_ambiguous));
} else if (errorCode == Message.GEOCODE_FROM_TO_AMBIGUOUS.getId()) {
return (getString(R.string.tripplanner_error_geocode_from_to_ambiguous));
} else if (errorCode == Message.UNDERSPECIFIED_TRIANGLE.getId()
|| errorCode == Message.TRIANGLE_NOT_AFFINE.getId()
|| errorCode == Message.TRIANGLE_OPTIMIZE_TYPE_NOT_SET.getId()
|| errorCode == Message.TRIANGLE_VALUES_NOT_SET.getId()) {
return (getString(R.string.tripplanner_error_triangle));
} else if (errorCode == TripRequest.NO_SERVER_SELECTED) {
return getString(R.string.tripplanner_no_server_selected_error);
} else {
return null;
}
}
private void clearBundleErrors() {
mBuilder.getBundle().remove(PLAN_ERROR_CODE);
mBuilder.getBundle().remove(PLAN_ERROR_URL);
}
// Handle the sliding panel's interactions with the list view and the map view.
@Override
public void onResultViewCreated(View container, final ListView listView, View mapView) {
if (mPanel != null) {
mPanel.setScrollableViewHelper(new ScrollableViewHelper() {
@Override
public int getScrollableViewScrollPosition(View scrollableView, boolean isSlidingUp) {
if (mResultsFragment.isMapShowing()) {
return 1; // Map can scroll infinitely, so return a positive value
} else {
return super.getScrollableViewScrollPosition(listView, isSlidingUp);
}
}
});
mPanel.setScrollableView(container);
}
}
}