org.onebusaway.android.report.ui.InfrastructureIssueActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.onebusaway.android.report.ui.InfrastructureIssueActivity.java

Source

/*
* Copyright (C) 2014-2015 University of South Florida (sjbarbeau@gmail.com)
*
* 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.report.ui;

import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.io.ObaAnalytics;
import org.onebusaway.android.io.elements.ObaArrivalInfo;
import org.onebusaway.android.io.elements.ObaRoute;
import org.onebusaway.android.io.elements.ObaStop;
import org.onebusaway.android.io.elements.ObaStopElement;
import org.onebusaway.android.map.MapParams;
import org.onebusaway.android.map.googlemapsv2.BaseMapFragment;
import org.onebusaway.android.report.connection.GeocoderTask;
import org.onebusaway.android.report.connection.ServiceListTask;
import org.onebusaway.android.report.constants.ReportConstants;
import org.onebusaway.android.report.ui.adapter.EntrySpinnerAdapter;
import org.onebusaway.android.report.ui.adapter.SectionItem;
import org.onebusaway.android.report.ui.adapter.ServiceSpinnerItem;
import org.onebusaway.android.report.ui.adapter.SpinnerItem;
import org.onebusaway.android.report.ui.dialog.ReportSuccessDialog;
import org.onebusaway.android.report.ui.util.IssueLocationHelper;
import org.onebusaway.android.report.ui.util.ServiceUtils;
import org.onebusaway.android.util.LocationUtils;
import org.onebusaway.android.util.PreferenceUtils;
import org.onebusaway.android.util.ShowcaseViewUtils;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import edu.usf.cutr.open311client.Open311;
import edu.usf.cutr.open311client.Open311Manager;
import edu.usf.cutr.open311client.models.Service;
import edu.usf.cutr.open311client.models.ServiceListRequest;
import edu.usf.cutr.open311client.models.ServiceListResponse;

public class InfrastructureIssueActivity extends BaseReportActivity
        implements BaseMapFragment.OnFocusChangedListener, ServiceListTask.Callback, ReportProblemFragmentCallback,
        IssueLocationHelper.Callback, SimpleArrivalListFragment.Callback, GeocoderTask.Callback {

    private static final int REQUEST_CODE = 0;

    private static final String SHOW_STOP_MARKER = ".showMarker";

    private static final String SELECTED_SERVICE = ".selectedService";

    private static final String SELECTED_SERVICE_TYPE = ".selectedServiceType";

    private static final String RESTORED_SERVICE = ".restoredService";

    private static final String SHOW_CATEGORIES = ".showCategories";

    private static final String SHOW_INFO = ".showInfo";

    private static final String HEURISTIC_MATCH = ".isHeuristicMatch";

    private static final String ARRIVAL_LIST = ".arrivalList";

    private static final String TRIP_INFO = ".tripInfo";

    private static final String AGENCY_NAME = ".agencyName";

    private static final String ACTION_BAR_TITLE = ".actionBarTitle";

    private static final String TUTORIAL_COUNTER = "tutorial_counter";

    /**
     * UI Elements
     */
    private EditText mAddressEditText;

    private Spinner mServicesSpinner;

    private RelativeLayout mBusStopHeader;

    //Map Fragment
    private BaseMapFragment mMapFragment;

    // Services spinner container
    private FrameLayout mServicesSpinnerFrameLayout;

    //Location helper for tracking the issue location
    private IssueLocationHelper mIssueLocationHelper;

    /**
     * Open311 client
     */
    private Open311 mOpen311;

    /**
     * Store the transit service type if there is no bus stop selected
     * Then, when a user selects a bus stop, find the transit service by type and show
     */
    private String mTransitServiceIssueTypeWithoutStop;

    /**
     * Selected transit service object
     */
    private Service mSelectedTransitService;

    /**
     * Select this issue type when the activity starts
     * If a user selects "report stop or trip problem" from the main activity, set the default
     * value to this variable
     */
    private String mDefaultIssueType;

    /**
     * True if the transit services were matched heuristically, and false if they were not
     */
    private boolean mIsAllTransitHeuristicMatch;

    /**
     * Restore this issue on rotation
     */
    private String mRestoredServiceName;

    /**
     * Selected arrival information for trip problem reporting
     */
    private ObaArrivalInfo mArrivalInfo;

    /**
     * Agency name for trip problem reporting
     */
    private String mAgencyName;

    /**
     * For rotation changes:
     * Save instance state vars
     * Restores old selected categories and marker position
     */
    private boolean mShowCategories = false;

    private boolean mShowStopMarker = false;

    private boolean mShowArrivalListFragment = false;

    /**
     * Starts the InfrastructureIssueActivity.
     *
     * startActivityForResult was used to close the calling activity. This used in BaseReportActivity
     * to close it when a user submits an issue.
     *
     * @param activity The parent activity.
     * @param intent   The Intent containing focusId, lat, lon of the map
     */
    public static void start(Activity activity, Intent intent) {
        activity.startActivityForResult(makeIntent(activity, intent), REQUEST_CODE);
    }

    /**
     * Starts the InfrastructureIssueActivity with a given open311 service category selected
     *
     * startActivityForResult was used to close the calling activity. This used in BaseReportActivity
     * to close it when a user submits an issue.
     *
     * @param activity The parent activity.
     * @param intent   The Intent containing focusId, lat, lon of the map
     */
    public static void startWithService(Activity activity, Intent intent, String serviceKeyword) {
        intent = makeIntent(activity, intent);
        intent.putExtra(SELECTED_SERVICE, serviceKeyword);
        activity.startActivityForResult(intent, REQUEST_CODE);
    }

    /**
     * Starts the InfrastructureIssueActivity with a given open311 service category selected
     *
     * @param context The context of the parent activity.
     * @param intent   The Intent containing focusId, lat, lon of the map
     */
    public static void startWithService(Context context, Intent intent, String serviceKeyword) {
        intent = makeIntent(context, intent);
        intent.putExtra(SELECTED_SERVICE, serviceKeyword);
        context.startActivity(intent);
    }

    /**
     * Starts the InfrastructureIssueActivity with a given open311 service category selected
     *
     * startActivityForResult was used to close the calling activity. This used in BaseReportActivity
     * to close it when a user submits an issue.
     *
     * @param activity       The parent activity.
     * @param intent         The Intent containing focusId, lat, lon of the map
     * @param obaArrivalInfo Arrival info for trip problems
     */
    public static void startWithService(Activity activity, Intent intent, String serviceKeyword,
            ObaArrivalInfo obaArrivalInfo, String agencyName) {
        intent = makeIntent(activity, intent);
        // Put trip issue specific data
        intent.putExtra(SELECTED_SERVICE, serviceKeyword);
        intent.putExtra(TRIP_INFO, obaArrivalInfo);
        intent.putExtra(AGENCY_NAME, agencyName);

        activity.startActivityForResult(intent, REQUEST_CODE);
    }

    /**
     * Returns an intent that will start the MapActivity with a particular stop
     * focused with the center of the map at a particular point.
     *
     * @param context The context of the activity.
     * @param intent  The Intent containing focusId, lat, lon of the map
     */
    public static Intent makeIntent(Context context, Intent intent) {
        Intent myIntent = new Intent(context, InfrastructureIssueActivity.class);
        myIntent.putExtra(MapParams.STOP_ID, intent.getStringExtra(MapParams.STOP_ID));
        myIntent.putExtra(MapParams.STOP_NAME, intent.getStringExtra(MapParams.STOP_NAME));
        myIntent.putExtra(MapParams.STOP_CODE, intent.getStringExtra(MapParams.STOP_CODE));
        myIntent.putExtra(MapParams.CENTER_LAT, intent.getDoubleExtra(MapParams.CENTER_LAT, 0));
        myIntent.putExtra(MapParams.CENTER_LON, intent.getDoubleExtra(MapParams.CENTER_LON, 0));
        return myIntent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

        super.onCreate(savedInstanceState);

        setContentView(R.layout.infrastructure_issue);

        setUpOpen311();

        setUpProgressBar();

        setupMapFragment(savedInstanceState);

        setupLocationHelper(savedInstanceState);

        setupViews();

        setupIntentData(savedInstanceState);

        setupIconColors();

        initLocation();

        setActionBarTitle(savedInstanceState);
    }

    /**
     * Set the intent parameters when the activity starts
     * @param savedInstanceState
     */
    private void setupIntentData(Bundle savedInstanceState) {
        if (savedInstanceState == null) {
            double lat = getIntent().getDoubleExtra(MapParams.CENTER_LAT, 0);
            double lon = getIntent().getDoubleExtra(MapParams.CENTER_LON, 0);
            // If a bus stop selected in the main screen
            // Set the focus to the current stop
            String stopId = getIntent().getStringExtra(MapParams.STOP_ID);
            String stopName = getIntent().getStringExtra(MapParams.STOP_NAME);
            String stopCode = getIntent().getStringExtra(MapParams.STOP_CODE);
            if (stopId != null) {
                mIssueLocationHelper.setObaStop(new ObaStopElement(stopId, lat, lon, stopName, stopCode));
                showBusStopHeader(stopName);
                mShowStopMarker = true;
            }
            mArrivalInfo = (ObaArrivalInfo) getIntent().getSerializableExtra(TRIP_INFO);
            mAgencyName = getIntent().getStringExtra(AGENCY_NAME);
            mDefaultIssueType = getIntent().getStringExtra(SELECTED_SERVICE);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        ObaAnalytics.reportActivityStart(this);
    }

    @Override
    protected void onPause() {
        ShowcaseViewUtils.hideShowcaseView();
        super.onPause();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putFloat(MapParams.ZOOM, mMapFragment.getMapView().getZoomLevelAsFloat());
        ObaStop obaStop = mIssueLocationHelper.getObaStop();
        if (obaStop != null) {
            String stopId = obaStop.getId();
            getIntent().putExtra(MapParams.STOP_ID, stopId);
            outState.putString(MapParams.STOP_ID, stopId);
            outState.putString(MapParams.STOP_NAME, obaStop.getName());
            outState.putString(MapParams.STOP_CODE, obaStop.getStopCode());
            outState.putBoolean(SHOW_STOP_MARKER, true);
        }

        outState.putDouble(MapParams.CENTER_LAT, mIssueLocationHelper.getIssueLocation().getLatitude());
        outState.putDouble(MapParams.CENTER_LON, mIssueLocationHelper.getIssueLocation().getLongitude());

        SpinnerItem spinnerItem = (SpinnerItem) mServicesSpinner.getSelectedItem();
        if (spinnerItem != null && (!spinnerItem.isHint() && !spinnerItem.isSection()
                || (spinnerItem.isHint() && mServicesSpinnerFrameLayout.getVisibility() == View.VISIBLE))) {
            Service service = ((ServiceSpinnerItem) spinnerItem).getService();
            outState.putString(RESTORED_SERVICE, service.getService_name());
            outState.putBoolean(SHOW_CATEGORIES, true);
        }

        if (isInfoVisible()) {
            String infoText = ((TextView) mInfoHeader.findViewById(R.id.ri_info_text)).getText().toString();
            outState.putString(SHOW_INFO, infoText);
        }

        outState.putBoolean(ARRIVAL_LIST, mShowArrivalListFragment);
        outState.putBoolean(HEURISTIC_MATCH, mIsAllTransitHeuristicMatch);
        outState.putString(AGENCY_NAME, mAgencyName);
        outState.putString(SELECTED_SERVICE_TYPE, mTransitServiceIssueTypeWithoutStop);
        outState.putString(ACTION_BAR_TITLE, getTitle().toString());
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) {
            mShowCategories = savedInstanceState.getBoolean(SHOW_CATEGORIES, false);
            mShowStopMarker = savedInstanceState.getBoolean(SHOW_STOP_MARKER, false);
            mShowArrivalListFragment = savedInstanceState.getBoolean(ARRIVAL_LIST, false);
            mRestoredServiceName = savedInstanceState.getString(RESTORED_SERVICE);
            mAgencyName = savedInstanceState.getString(AGENCY_NAME);
            mTransitServiceIssueTypeWithoutStop = savedInstanceState.getString(SELECTED_SERVICE_TYPE);
            mIsAllTransitHeuristicMatch = savedInstanceState.getBoolean(HEURISTIC_MATCH);

            String bundleStopId = savedInstanceState.getString(MapParams.STOP_ID);
            String stopName = savedInstanceState.getString(MapParams.STOP_NAME);
            String stopCode = savedInstanceState.getString(MapParams.STOP_CODE);

            if (bundleStopId != null) {
                double lat = savedInstanceState.getDouble(MapParams.CENTER_LAT, 0);
                double lon = savedInstanceState.getDouble(MapParams.CENTER_LON, 0);
                Location location = LocationUtils.makeLocation(lat, lon);
                mIssueLocationHelper.updateMarkerPosition(location,
                        new ObaStopElement(bundleStopId, lat, lon, stopName, stopCode));
            }
            String infoText = savedInstanceState.getString(SHOW_INFO);
            if (infoText != null) {
                addInfoText(infoText);
            }

            if (mShowArrivalListFragment) {
                removeTripProblemFragment();
                showArrivalListFragment(mIssueLocationHelper.getObaStop());
            }
        }
    }

    /**
     * Set default open311 client
     */
    private void setUpOpen311() {
        mOpen311 = Open311Manager.getDefaultOpen311();
    }

    /**
     * Setting up the BaseMapFragment
     * BaseMapFragment was used to implement a map.
     */
    private void setupMapFragment(Bundle bundle) {
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentByTag(BaseMapFragment.TAG);
        if (fragment != null) {
            mMapFragment = (BaseMapFragment) fragment;
            mMapFragment.setOnFocusChangeListener(this);
        }
        if (mMapFragment == null) {

            mMapFragment = new BaseMapFragment();
            mMapFragment.setArguments(bundle);
            // Register listener for map focus callbacks
            mMapFragment.setOnFocusChangeListener(this);

            fm.beginTransaction().add(R.id.ri_frame_map_view, mMapFragment, BaseMapFragment.TAG).commit();
        }
        fm.beginTransaction().show(mMapFragment).commit();
    }

    /**
     * Setting up the location helper
     * IssueLocationHelper helps tracking the issue location and issue stop
     */
    private void setupLocationHelper(Bundle savedInstanceState) {

        double lat;
        double lon;
        if (savedInstanceState == null) {
            lat = getIntent().getDoubleExtra(MapParams.CENTER_LAT, 0);
            lon = getIntent().getDoubleExtra(MapParams.CENTER_LON, 0);
        } else {
            lat = savedInstanceState.getDouble(MapParams.CENTER_LAT, 0);
            lon = savedInstanceState.getDouble(MapParams.CENTER_LON, 0);
        }

        Location mapCenterLocation = LocationUtils.makeLocation(lat, lon);
        mIssueLocationHelper = new IssueLocationHelper(mapCenterLocation, this);

        // Set map center location
        mMapFragment.setMapCenter(mapCenterLocation, true, false);
    }

    /**
     * Initialize UI components
     */
    private void setupViews() {
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }

        mServicesSpinnerFrameLayout = (FrameLayout) findViewById(R.id.ri_spinnerView);

        mAddressEditText = (EditText) findViewById(R.id.ri_address_editText);
        mAddressEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    searchAddress();
                    return true;
                }
                return false;
            }
        });

        CustomScrollView mainScrollView = (CustomScrollView) findViewById(R.id.ri_scrollView);
        mainScrollView.addInterceptScrollView(findViewById(R.id.ri_frame_map_view));

        mServicesSpinner = (Spinner) findViewById(R.id.ri_spinnerServices);
        mServicesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {

                if (mShowCategories) {
                    mShowCategories = false;
                    return;
                }

                SpinnerItem spinnerItem = (SpinnerItem) mServicesSpinner.getSelectedItem();
                if (!spinnerItem.isHint() && !spinnerItem.isSection()) {

                    Service service = ((ServiceSpinnerItem) spinnerItem).getService();
                    onSpinnerItemSelected(service);
                } else if (spinnerItem.isHint()) {
                    clearReportingFragments(false);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parentView) {
            }
        });

        mBusStopHeader = (RelativeLayout) findViewById(R.id.bus_stop_header);
    }

    private void setActionBarTitle(Bundle savedInstanceState) {
        if (savedInstanceState == null) {
            setActionBarTitle(mDefaultIssueType);
        } else {
            setTitle(savedInstanceState.getString(ACTION_BAR_TITLE));
        }
    }

    /**
     * Set action bar title by issue type
     *
     * @param issueType could be stop, trip, dynamic_stop or dynamic_trip
     */
    private void setActionBarTitle(String issueType) {
        if (ServiceUtils.isTransitStopServiceByType(issueType)) {
            setTitle(getString(R.string.rt_stop_problem_title));
        } else if (ServiceUtils.isTransitTripServiceByType(issueType)) {
            setTitle(getString(R.string.rt_arrival_problem_title));
        } else {
            setTitle(getString(R.string.rt_infrastructure_problem_title));
        }
    }

    private void setupIconColors() {
        ((ImageView) findViewById(R.id.ri_ic_location))
                .setColorFilter(getResources().getColor(R.color.material_gray));
        ((ImageView) findViewById(R.id.ri_ic_category))
                .setColorFilter(getResources().getColor(R.color.material_gray));
        ((ImageView) findViewById(R.id.ri_header_location))
                .setColorFilter(getResources().getColor(android.R.color.white));
    }

    /**
     * Show the current location on the map
     */
    private void initLocation() {
        syncAddress(mIssueLocationHelper.getIssueLocation());
    }

    private void onSpinnerItemSelected(Service service) {
        // Set static issue type to null
        mTransitServiceIssueTypeWithoutStop = null;
        mSelectedTransitService = null;

        if (service.getService_code() != null
                && !(service.getType() != null && service.getType().contains(ReportConstants.DYNAMIC_SERVICE))) {
            showOpen311Reporting(service);
        } else if (!ReportConstants.DEFAULT_SERVICE.equalsIgnoreCase(service.getType())) {
            mSelectedTransitService = service;
            //Remove the info text for select category
            removeInfoText();
            showTransitService(service.getType());
        }

        // Set action bar title based on the selected issue
        setActionBarTitle(service.getType());
    }

    /**
     * Places a marker into the map from given Location
     *
     * @param location position for marker
     */
    private void updateMarkerPosition(Location location) {
        int markerId = mMapFragment.addMarker(location, null);
        mIssueLocationHelper.handleMarkerUpdate(markerId);
    }

    /**
     * Called when the issue location changes
     * Retrieves Open311 services from current address
     *
     * @param location current issue location
     */
    private void syncAddress(Location location) {
        syncAddressString(location);

        showProgress(Boolean.TRUE);

        ServiceListRequest slr = new ServiceListRequest(location.getLatitude(), location.getLongitude());

        List<Open311> open311List = Open311Manager.getAllOpen311();
        ServiceListTask serviceListTask = new ServiceListTask(slr, open311List, this);
        serviceListTask.execute();
    }

    /**
     * Search address string from mAddressEditText
     */
    private void searchAddress() {
        showProgress(Boolean.TRUE);
        String addressString = mAddressEditText.getText().toString();
        Location location = getLocationByAddress(addressString);
        if (location != null) {
            mMapFragment.setMapCenter(location, true, true);
            syncAddress(location);
        } else {
            String message = getResources().getString(R.string.ri_address_not_found);
            createToastMessage(message);
        }
        showProgress(Boolean.FALSE);
    }

    /**
     * Calculates the address of a given location
     *
     * @param location takes location object
     */
    private void syncAddressString(Location location) {
        GeocoderTask gct = new GeocoderTask(this, location, this);
        gct.execute();
    }

    /**
     * Converts plain address string to Location object
     *
     * @param addressString takes address string
     * @return Location of given address String
     */
    public Location getLocationByAddress(String addressString) {
        Geocoder coder = new Geocoder(this);
        List<Address> addressList;
        Location location;

        try {
            addressList = coder.getFromLocationName(addressString, 3);
            if (addressList == null || addressList.size() == 0) {
                return null;
            }
            Address address = addressList.get(0);

            location = new Location("");
            location.setLatitude(address.getLatitude());
            location.setLongitude(address.getLongitude());

            return location;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public void onClearMarker(int markerId) {
        mMapFragment.removeMarker(markerId);
    }

    /**
     * Called by the BaseMapFragment when a stop obtains focus, or no stops have focus
     *
     * @param stop   the ObaStop that obtained focus, or null if no stop is in focus
     * @param routes a HashMap of all route display names that serve this stop - key is routeId
     */
    @Override
    public void onFocusChanged(ObaStop stop, HashMap<String, ObaRoute> routes, Location location) {
        mIssueLocationHelper.updateMarkerPosition(location, stop);

        // Don't call syncAddress if you are restoring the instance
        if (mShowStopMarker) {
            if (stop != null) {
                // Show bus stop name on the header
                showBusStopHeader(stop.getName());
            }
            mShowStopMarker = false;
            return;
        }

        // Clear all reporting fragments
        clearReportingFragments();

        if (stop != null) {
            // Clear manually added markers
            mIssueLocationHelper.clearMarkers();

            // Show bus stop name on the header
            showBusStopHeader(stop.getName());

            showServicesSpinner();

            if (mTransitServiceIssueTypeWithoutStop != null) {
                showTransitService(mTransitServiceIssueTypeWithoutStop);
                syncAddressString(location);
            } else {
                syncAddress(stop.getLocation());
            }
            // Clear static issue type
            mTransitServiceIssueTypeWithoutStop = null;
        } else if (location != null) {
            hideBusStopHeader();

            syncAddress(location);
        }

        // Set action bar title based on the selected issue
        setActionBarTitle("");
    }

    @Override
    public void onSendReport() {
        (new ReportSuccessDialog()).show(getSupportFragmentManager(), ReportSuccessDialog.TAG);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        // If a fragment closes via back button, show 'Choose a Problem' category
        mServicesSpinner.setSelection(0);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            finishActivityWithResult();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Called by the ServicesTask when Open311 endpoint returns ServiceList
     *
     * @param services ServiceListResponse
     * @param open311  returns active open311
     */
    @Override
    public void onServicesTaskCompleted(ServiceListResponse services, Open311 open311) {
        // Close progress
        showProgress(Boolean.FALSE);

        // Set main open311
        this.mOpen311 = open311;

        prepareServiceList(services);
    }

    /**
     * Called when geocoder converts the location to an address string
     *
     * @param address the address string from given location
     */
    @Override
    public void onGeocoderTaskCompleted(String address) {
        mAddressEditText.setText(address);
    }

    /**
     * Prepares the service lists and shows as categories in the screen
     * Adds static service categories (stop and trip problems) if they are
     * not specified by open311 endpoint
     */
    private void prepareServiceList(ServiceListResponse services) {
        // Create the service list
        List<Service> serviceList = new ArrayList<>();

        // Add services to list if service response is successful
        if (services != null && services.isSuccess()
                && Open311Manager.isAreaManagedByOpen311(services.getServiceList())) {
            for (Service s : services.getServiceList()) {
                if (s.getService_name() != null && s.getService_code() != null) {
                    serviceList.add(s);
                }
            }
        }

        if (mShowStopMarker) {
            mMapFragment.setFocusStop(mIssueLocationHelper.getObaStop(), new ArrayList<ObaRoute>());
            showServicesSpinner();
        } else if (Open311Manager.isAreaManagedByOpen311(serviceList)) {
            // Set marker on the map if there are open311 services
            updateMarkerPosition(mIssueLocationHelper.getIssueLocation());
            showServicesSpinner();
        } else {
            // If there is no open311 services return and there is no stop selected
            // then hide the categories
            if (mIssueLocationHelper.getObaStop() == null) {
                hideServicesSpinner();
                // Show information to the user if there is no error on location
                addInfoText(getString(R.string.report_dialog_stop_header));
            }
        }

        // Mark the services that are transit-related
        mIsAllTransitHeuristicMatch = ServiceUtils.markTransitServices(getApplicationContext(), serviceList);

        /**
         * Map the group names with service list
         */
        Map<String, List<Service>> serviceListMap = new TreeMap<>();

        for (Service s : serviceList) {
            String groupName = s.getGroup() == null ? getString(R.string.ri_others) : s.getGroup();
            List<Service> mappedList = serviceListMap.get(groupName);
            if (mappedList != null) {
                mappedList.add(s);
            } else {
                mappedList = new ArrayList<>();
                mappedList.add(s);
                serviceListMap.put(groupName, mappedList);
            }
        }

        /**
         * Create Ordered Spinner items
         */
        ArrayList<SpinnerItem> spinnerItems = new ArrayList<>();
        ServiceSpinnerItem hintServiceSpinnerItem = new ServiceSpinnerItem(
                new Service(getString(R.string.ri_service_default), ReportConstants.DEFAULT_SERVICE));
        hintServiceSpinnerItem.setHint(true);
        spinnerItems.add(hintServiceSpinnerItem);

        // Create Transit categories first
        if (serviceListMap.get(ReportConstants.ISSUE_GROUP_TRANSIT) != null) {
            spinnerItems.add(new SectionItem(ReportConstants.ISSUE_GROUP_TRANSIT));

            for (Service s : serviceListMap.get(ReportConstants.ISSUE_GROUP_TRANSIT)) {
                spinnerItems.add(new ServiceSpinnerItem(s));
            }
        }

        // Create the rest of the categories
        for (String key : serviceListMap.keySet()) {
            // Skip if it is transit category
            if (ReportConstants.ISSUE_GROUP_TRANSIT.equals(key)) {
                continue;
            }
            spinnerItems.add(new SectionItem(key));
            for (Service s : serviceListMap.get(key)) {
                spinnerItems.add(new ServiceSpinnerItem(s));
            }
        }

        EntrySpinnerAdapter adapter = new EntrySpinnerAdapter(this, spinnerItems);
        mServicesSpinner.setAdapter(adapter);

        if (mDefaultIssueType != null) {
            // Select an default issue category programmatically
            selectDefaultTransitCategory(spinnerItems);
        } else if (mRestoredServiceName != null) {
            restoreSelection(spinnerItems);
        }
    }

    /**
     * Programmatically select a issue category on rotation
     *
     * @param spinnerItems the issue services loaded into the services spinner
     */
    private void restoreSelection(ArrayList<SpinnerItem> spinnerItems) {
        for (int i = 0; i < spinnerItems.size(); i++) {
            SpinnerItem item = spinnerItems.get(i);
            if (item instanceof ServiceSpinnerItem) {
                Service service = ((ServiceSpinnerItem) item).getService();
                if (service.getService_name().equals(mRestoredServiceName)) {
                    mServicesSpinner.setSelection(i, true);
                    mRestoredServiceName = null;
                    if (ServiceUtils.isTransitServiceByType(service.getType())) {
                        mSelectedTransitService = service;
                    }
                    break;
                }
            }
        }
    }

    /**
     * Programmatically select a default issue category from the spinner
     *
     * @param spinnerItems the issue services loaded into the services spinner
     */
    private void selectDefaultTransitCategory(ArrayList<SpinnerItem> spinnerItems) {
        int selectPosition = -1;
        Service selectedService = null;
        for (int i = 0; i < spinnerItems.size(); i++) {
            SpinnerItem item = spinnerItems.get(i);
            if (item instanceof ServiceSpinnerItem) {

                Service service = ((ServiceSpinnerItem) item).getService();

                boolean transitServiceFound = false;

                if (getString(R.string.ri_selected_service_stop).equals(mDefaultIssueType)
                        && ServiceUtils.isTransitStopServiceByType(service.getType())
                        && !mIsAllTransitHeuristicMatch) {
                    // We have an explicit (not heuristic-based) match for stop problem
                    transitServiceFound = true;
                } else if (getString(R.string.ri_selected_service_trip).equals(mDefaultIssueType)
                        && ServiceUtils.isTransitTripServiceByType(service.getType())) {
                    transitServiceFound = true;
                }

                if (transitServiceFound) {
                    selectPosition = i;
                    selectedService = service;
                    // If transit service selected and no bus stop selected remove markers
                    if (ServiceUtils.isTransitServiceByType(service.getType())
                            && mIssueLocationHelper.getObaStop() == null) {
                        mIssueLocationHelper.clearMarkers();
                    }
                    break;
                }
            }
        }

        // If is a stop problem type, an all transit heuristic-based match, and no bus stop selected remove markers
        if (getString(R.string.ri_selected_service_stop).equals(mDefaultIssueType) && mIsAllTransitHeuristicMatch
                && mIssueLocationHelper.getObaStop() == null) {
            mIssueLocationHelper.clearMarkers();
        }

        // Set mDefaultIssueType = null to prevent auto selections
        mDefaultIssueType = null;

        // Give the user a break from tutorials - allow at least one interaction without a tutorial
        int counter = Application.getPrefs().getInt(TUTORIAL_COUNTER, 0);
        counter++;
        PreferenceUtils.saveInt(TUTORIAL_COUNTER, counter);

        // Set selected category if it is in the spinner items list
        if (selectPosition != -1) {
            showServicesSpinner();
            mServicesSpinner.setSelection(selectPosition, true);
            // Show tutorial if this geographic area supports Open311 services
            if (counter % 2 == 0 && selectedService != null
                    && ServiceUtils.isTransitOpen311ServiceByType(selectedService.getType())) {
                ShowcaseViewUtils.showTutorial(ShowcaseViewUtils.TUTORIAL_SEND_FEEDBACK_OPEN311_CATEGORIES, this,
                        null);
            }
        }
    }

    private void hideServicesSpinner() {
        mServicesSpinnerFrameLayout.setVisibility(View.GONE);
    }

    private void showServicesSpinner() {
        mServicesSpinnerFrameLayout.setVisibility(View.VISIBLE);
    }

    /**
     * Disables open311 reporting if there is no category for region
     * Show static services
     */
    public void showTransitService(String issueType) {

        clearReportingFragments();

        ObaStop obaStop = mIssueLocationHelper.getObaStop();
        if (obaStop != null) {
            if (ReportConstants.STATIC_TRANSIT_SERVICE_STOP.equals(issueType)) {
                showStopProblemFragment(obaStop);
            } else if (ReportConstants.STATIC_TRANSIT_SERVICE_TRIP.equals(issueType)) {
                showTripProblemFragment(obaStop);
            } else if (ReportConstants.DYNAMIC_TRANSIT_SERVICE_STOP.equals(issueType)) {
                showOpen311Reporting(mSelectedTransitService);
            } else if (ReportConstants.DYNAMIC_TRANSIT_SERVICE_TRIP.equals(issueType)) {
                if (mArrivalInfo != null) {
                    showOpen311Reporting(mSelectedTransitService, mArrivalInfo);
                    mArrivalInfo = null;
                } else {
                    showTripProblemFragment(obaStop);
                }
            }
        } else {
            addInfoText(getString(R.string.report_dialog_stop_header));
            mTransitServiceIssueTypeWithoutStop = issueType;

            mIssueLocationHelper.clearMarkers();
        }
    }

    private void showOpen311Reporting(Service service) {

        clearReportingFragments();

        showOpen311ProblemFragment(service);

        updateMarkerPosition(mIssueLocationHelper.getIssueLocation());
    }

    private void showOpen311Reporting(Service service, ObaArrivalInfo obaArrivalInfo) {

        clearReportingFragments();

        showOpen311ProblemFragment(service, obaArrivalInfo);

        updateMarkerPosition(mIssueLocationHelper.getIssueLocation());
    }

    private void clearReportingFragments() {
        clearReportingFragments(true);
    }

    private void clearReportingFragments(boolean removeInfo) {
        if (removeInfo) {
            removeInfoText();
        }
        removeOpen311ProblemFragment();

        removeStopProblemFragment();

        removeTripProblemFragment();
    }

    private void showStopProblemFragment(ObaStop obaStop) {
        ReportStopProblemFragment.show(this, obaStop, R.id.ri_report_stop_problem);
    }

    private void showTripProblemFragment(ObaStop obaStop) {
        if (mArrivalInfo == null) {
            showArrivalListFragment(obaStop);
        } else {
            // Show default trip problem issue reporting
            ReportTripProblemFragment.show(this, mArrivalInfo, R.id.ri_report_stop_problem);
            mArrivalInfo = null;
        }
    }

    private void showArrivalListFragment(ObaStop obaStop) {
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        View v = layoutInflater.inflate(R.layout.arrivals_list_header, null);
        v.setVisibility(View.GONE);

        mShowArrivalListFragment = true;

        SimpleArrivalListFragment.show(this, R.id.ri_report_stop_problem, obaStop, this);
    }

    @Override
    public void onArrivalItemClicked(ObaArrivalInfo obaArrivalInfo, String agencyName) {
        mShowArrivalListFragment = false;

        mAgencyName = agencyName;

        removeFragmentByTag(SimpleArrivalListFragment.TAG);

        if (mSelectedTransitService != null
                && ReportConstants.DYNAMIC_TRANSIT_SERVICE_TRIP.equals(mSelectedTransitService.getType())) {
            // Show open311 defined issue reporting service
            showOpen311ProblemFragment(mSelectedTransitService, obaArrivalInfo);
        } else {
            // Show default trip problem issue reporting
            ReportTripProblemFragment.show(this, obaArrivalInfo, R.id.ri_report_stop_problem);
        }
    }

    private void showBusStopHeader(String text) {
        // First remove info text
        removeInfoText();

        ((TextView) findViewById(R.id.ri_bus_stop_text)).setText(text);
        if (mBusStopHeader.getVisibility() != View.VISIBLE) {
            mBusStopHeader.setVisibility(View.VISIBLE);
        }
    }

    public void hideBusStopHeader() {
        findViewById(R.id.bus_stop_header).setVisibility(View.GONE);
    }

    private void showOpen311ProblemFragment(Service service) {
        Open311ProblemFragment.show(this, R.id.ri_report_stop_problem, mOpen311, service);
    }

    private void showOpen311ProblemFragment(Service service, ObaArrivalInfo obaArrivalInfo) {
        Open311ProblemFragment.show(this, R.id.ri_report_stop_problem, mOpen311, service, obaArrivalInfo,
                mAgencyName);
    }

    public void removeOpen311ProblemFragment() {
        removeFragmentByTag(Open311ProblemFragment.TAG);
    }

    private void removeStopProblemFragment() {
        removeFragmentByTag(ReportStopProblemFragment.TAG);
    }

    private void removeTripProblemFragment() {
        mShowArrivalListFragment = false;

        removeFragmentByTag(ReportTripProblemFragment.TAG);

        removeFragmentByTag(SimpleArrivalListFragment.TAG);

        ((LinearLayout) findViewById(R.id.ri_report_stop_problem)).removeAllViews();
    }

    public IssueLocationHelper getIssueLocationHelper() {
        return mIssueLocationHelper;
    }

    public String getCurrentAddress() {
        return mAddressEditText.getText().toString();
    }

    public void finishActivityWithResult() {
        Intent returnIntent = new Intent();
        returnIntent.putExtra(BaseReportActivity.CLOSE_REQUEST, true);
        setResult(RESULT_OK, returnIntent);
        finish();
    }
}