/*
* Copyright (C) 2011 Paul Watts (paulcwatts@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.ui;
import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.io.ObaAnalytics;
import org.onebusaway.android.io.ObaApi;
import org.onebusaway.android.io.elements.ObaStop;
import org.onebusaway.android.io.elements.ObaStopGroup;
import org.onebusaway.android.io.elements.ObaStopGrouping;
import org.onebusaway.android.io.request.ObaRouteRequest;
import org.onebusaway.android.io.request.ObaRouteResponse;
import org.onebusaway.android.io.request.ObaStopsForRouteRequest;
import org.onebusaway.android.io.request.ObaStopsForRouteResponse;
import org.onebusaway.android.provider.ObaContract;
import org.onebusaway.android.util.FragmentUtils;
import org.onebusaway.android.util.UIUtils;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RouteInfoListFragment extends ListFragment {
private static final String TAG = "RouteInfoListFragment";
private static final int ROUTE_INFO_LOADER = 0;
private static final int ROUTE_STOPS_LOADER = 1;
private String mRouteId;
private ObaRouteResponse mRouteInfo;
private StopsForRouteInfo mStopsForRoute;
private SimpleExpandableListAdapter mAdapter;
private final RouteLoaderCallback mRouteCallback = new RouteLoaderCallback();
private final StopsLoaderCallback mStopsCallback = new StopsLoaderCallback();
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
registerForContextMenu(getListView());
// Start out with a progress indicator.
setListShown(false);
// Initialize the expandable list
ExpandableListView list = (ExpandableListView) getListView();
list.setOnChildClickListener(mChildClick);
// Get the route ID from the "uri" argument
Uri uri = (Uri) getArguments().getParcelable(FragmentUtils.URI);
if (uri == null) {
Log.e(TAG, "No URI in arguments");
return;
}
mRouteId = uri.getLastPathSegment();
getLoaderManager().initLoader(ROUTE_INFO_LOADER, null, mRouteCallback);
getLoaderManager().initLoader(ROUTE_STOPS_LOADER, null, mStopsCallback);
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup root, Bundle savedInstanceState) {
if (root == null) {
// Currently in a layout without a container, so no
// reason to create our view.
return null;
}
return inflater.inflate(R.layout.route_info, null);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.route_info_options, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
boolean hasUrl = false;
if (mRouteInfo != null) {
hasUrl = !TextUtils.isEmpty(mRouteInfo.getUrl());
}
menu.findItem(R.id.goto_url).setEnabled(hasUrl).setVisible(hasUrl);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int id = item.getItemId();
if (id == R.id.show_on_map) {
HomeActivity.start(getActivity(), mRouteId);
return true;
} else if (id == R.id.goto_url) {
UIUtils.goToUrl(getActivity(), mRouteInfo.getUrl());
return true;
}
return false;
}
private final ExpandableListView.OnChildClickListener mChildClick =
new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent,
View v,
int groupPosition,
int childPosition,
long id) {
showArrivals(v);
return true;
}
};
private static final int CONTEXT_MENU_DEFAULT = 1;
private static final int CONTEXT_MENU_SHOWONMAP = 2;
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
ExpandableListView.ExpandableListContextMenuInfo info
= (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
if (ExpandableListView.getPackedPositionType(info.packedPosition)
!= ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
return;
}
final TextView text = (TextView) info.targetView.findViewById(R.id.name);
menu.setHeaderTitle(text.getText());
menu.add(0, CONTEXT_MENU_DEFAULT, 0, R.string.route_info_context_get_stop_info);
menu.add(0, CONTEXT_MENU_SHOWONMAP, 0, R.string.route_info_context_showonmap);
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
ExpandableListView.ExpandableListContextMenuInfo info
= (ExpandableListView.ExpandableListContextMenuInfo) item
.getMenuInfo();
switch (item.getItemId()) {
case CONTEXT_MENU_DEFAULT:
showArrivals(info.targetView);
return true;
case CONTEXT_MENU_SHOWONMAP:
showOnMap(info.targetView);
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public void onStart() {
super.onStart();
ObaAnalytics.reportFragmentStart(this);
}
private void showArrivals(View v) {
final TextView text = (TextView) v.findViewById(R.id.stop_id);
final String stopId = (String) text.getText();
ObaStop stop = null;
if (mStopsForRoute != null) {
stop = mStopsForRoute.getStopMap().get(stopId);
}
ArrivalsListActivity.Builder b = new ArrivalsListActivity.Builder(getActivity(), stopId);
if (stop != null) {
b.setStopName(stop.getName());
b.setStopDirection(stop.getDirection());
}
b.setUpMode(NavHelp.UP_MODE_BACK);
b.start();
}
private void showOnMap(View v) {
final TextView text = (TextView) v.findViewById(R.id.stop_id);
final String stopId = (String) text.getText();
// we need to find this route in the response because
// we need to know it's lat/lon
ObaStop stop = mStopsForRoute.getStopMap().get(stopId);
if (stop == null) {
return;
}
HomeActivity.start(getActivity(), stopId, stop.getLatitude(), stop.getLongitude());
}
//
// Loader callbacks
// This is simply because of our two loaders have different callback types,
// so we can't easily implement them directly in the fragment.
//
private final class RouteLoaderCallback
implements LoaderManager.LoaderCallbacks<ObaRouteResponse> {
@Override
public Loader<ObaRouteResponse> onCreateLoader(int id, Bundle args) {
return new RouteInfoLoader(getActivity(), mRouteId);
}
@Override
public void onLoadFinished(Loader<ObaRouteResponse> loader,
ObaRouteResponse data) {
setHeader(data, true);
}
@Override
public void onLoaderReset(Loader<ObaRouteResponse> loader) {
// Nothing to do right here...
}
}
private final class StopsLoaderCallback
implements LoaderManager.LoaderCallbacks<StopsForRouteInfo> {
@Override
public Loader<StopsForRouteInfo> onCreateLoader(int id, Bundle args) {
return new StopsForRouteLoader(getActivity(), mRouteId);
}
@Override
public void onLoadFinished(Loader<StopsForRouteInfo> loader,
StopsForRouteInfo data) {
setStopsForRoute(data);
}
@Override
public void onLoaderReset(Loader<StopsForRouteInfo> loader) {
// Nothing to do...
}
}
//
// Loader
//
private final static class RouteInfoLoader extends AsyncTaskLoader<ObaRouteResponse> {
private final String mRouteId;
RouteInfoLoader(Context context, String routeId) {
super(context);
mRouteId = routeId;
}
@Override
public void onStartLoading() {
forceLoad();
}
@Override
public ObaRouteResponse loadInBackground() {
return ObaRouteRequest.newRequest(getContext(), mRouteId).call();
}
}
private final static class StopsForRouteLoader extends AsyncTaskLoader<StopsForRouteInfo> {
private final String mRouteId;
StopsForRouteLoader(Context context, String routeId) {
super(context);
mRouteId = routeId;
}
@Override
public void onStartLoading() {
forceLoad();
}
@Override
public StopsForRouteInfo loadInBackground() {
final ObaStopsForRouteResponse response =
new ObaStopsForRouteRequest.Builder(getContext(), mRouteId)
.setIncludeShapes(false)
.build()
.call();
return new StopsForRouteInfo(getContext(), response);
}
}
private final static class StopsForRouteInfo {
private final int mResultCode;
private final ArrayList<HashMap<String, String>> mStopGroups;
private final ArrayList<ArrayList<HashMap<String, String>>> mStops;
private final HashMap<String, ObaStop> mStopMap;
public StopsForRouteInfo(Context cxt, ObaStopsForRouteResponse response) {
mStopGroups = new ArrayList<HashMap<String, String>>();
mStops = new ArrayList<ArrayList<HashMap<String, String>>>();
mStopMap = new HashMap<String, ObaStop>();
mResultCode = response.getCode();
initMaps(cxt, response);
}
private static Map<String, ObaStop> getStopMap(List<ObaStop> stops) {
final int len = stops.size();
HashMap<String, ObaStop> result = new HashMap<String, ObaStop>(len);
for (int i = 0; i < len; ++i) {
ObaStop stop = stops.get(i);
result.put(stop.getId(), stop);
}
return result;
}
private void initMaps(Context cxt, ObaStopsForRouteResponse response) {
// Convert to weird array type. From the documentation of
// SimpleExpandableListAdapter:
// StopGroupings: A List of Maps. Each entry in the List corresponds
// to one group in the list. The Maps contain the data for each
// group,
// and should include all the entries specified in "groupFrom".
//
// StopGroupings: A List of Maps. Each entry in the List corresponds
// to one group in the list. The Maps contain the data for each
// group,
// and should include all the entries specified in "groupFrom".
//
// Stops:
// A List of List of Maps. Each entry in the outer List corresponds
// to a group (index by group position), each entry in the inner
// List
// corresponds to a child within the group (index by child
// position),
// and the Map corresponds to the data for a child (index by values
// in the childFrom array). The Map contains the data for each
// child,
// and should include all the entries specified in "childFrom"
if (response.getCode() == ObaApi.OBA_OK) {
final List<ObaStop> stops = response.getStops();
final Map<String, ObaStop> stopMap = getStopMap(stops);
final ObaStopGrouping[] groupings = response.getStopGroupings();
final int groupingsLen = groupings.length;
for (int groupingIndex = 0; groupingIndex < groupingsLen; ++groupingIndex) {
final ObaStopGrouping grouping = groupings[groupingIndex];
final ObaStopGroup[] groups = grouping.getStopGroups();
final int groupsLen = groups.length;
for (int i = 0; i < groupsLen; ++i) {
final HashMap<String, String> groupMap = new HashMap<String, String>(1);
final ObaStopGroup group = groups[i];
// We can initialize the stop grouping values.
groupMap.put("name", UIUtils.formatDisplayText(group.getName()));
// Add this to the groupings map
// Create the sub list (the list of stops in the group)
final String[] stopIds = group.getStopIds();
final int stopIdLen = stopIds.length;
final ArrayList<HashMap<String, String>> childList
= new ArrayList<HashMap<String, String>>(
stopIdLen);
for (int j = 0; j < stopIdLen; ++j) {
final String stopId = stopIds[j];
final ObaStop stop = stopMap.get(stopId);
HashMap<String, String> groupStopMap = new HashMap<String, String>(2);
if (stop != null) {
groupStopMap.put("name", UIUtils.formatDisplayText(stop.getName()));
String dir = cxt.getString(UIUtils.getStopDirectionText(stop
.getDirection()));
groupStopMap.put("direction", dir);
groupStopMap.put("id", stopId);
mStopMap.put(stopId, stop);
} else {
groupStopMap.put("name", "");
groupStopMap.put("direction", "");
groupStopMap.put("id", stopId);
}
childList.add(groupStopMap);
}
mStopGroups.add(groupMap);
mStops.add(childList);
}
}
}
}
public int getResultCode() {
return mResultCode;
}
public ArrayList<HashMap<String, String>> getStopGroups() {
return mStopGroups;
}
public ArrayList<ArrayList<HashMap<String, String>>> getStops() {
return mStops;
}
public HashMap<String, ObaStop> getStopMap() {
return mStopMap;
}
}
//
// Helper functions
//
private void setHeader(ObaRouteResponse routeInfo, boolean addToDb) {
mRouteInfo = routeInfo;
View view = getView();
if (routeInfo.getCode() == ObaApi.OBA_OK) {
TextView shortNameText = (TextView) view.findViewById(R.id.short_name);
TextView longNameText = (TextView) view.findViewById(R.id.long_name);
TextView agencyText = (TextView) view.findViewById(R.id.agency);
String url = mRouteInfo.getUrl();
String shortName = routeInfo.getShortName();
String longName = routeInfo.getLongName();
if (TextUtils.isEmpty(shortName)) {
shortName = longName;
}
if (TextUtils.isEmpty(longName) || shortName.equals(longName)) {
longName = routeInfo.getDescription();
}
shortNameText.setText(UIUtils.formatDisplayText(shortName));
longNameText.setText(UIUtils.formatDisplayText(longName));
agencyText.setText(mRouteInfo.getAgency().getName());
if (addToDb) {
ContentValues values = new ContentValues();
values.put(ObaContract.Routes.SHORTNAME, shortName);
values.put(ObaContract.Routes.LONGNAME, longName);
values.put(ObaContract.Routes.URL, url);
if (Application.get().getCurrentRegion() != null) {
values.put(ObaContract.Routes.REGION_ID,
Application.get().getCurrentRegion().getId());
}
ObaContract.Routes.insertOrUpdate(getActivity(), mRouteInfo.getId(), values, true);
}
} else {
setEmptyText(UIUtils.getRouteErrorString(getActivity(), routeInfo.getCode()));
}
}
private void setStopsForRoute(StopsForRouteInfo result) {
mStopsForRoute = result;
final int code = mStopsForRoute.getResultCode();
if (code == ObaApi.OBA_OK) {
setEmptyText("");
} else {
setEmptyText(UIUtils.getRouteErrorString(getActivity(), code));
}
mAdapter = new SimpleExpandableListAdapter(
getActivity(),
result.getStopGroups(),
android.R.layout.simple_expandable_list_item_1,
new String[]{"name"},
new int[]{android.R.id.text1},
result.getStops(),
R.layout.route_info_listitem,
new String[]{"name", "direction", "id"},
new int[]{R.id.name, R.id.direction, R.id.stop_id}
);
setListAdapter(mAdapter);
}
public void setListAdapter(SimpleExpandableListAdapter adapter) {
ExpandableListView list = (ExpandableListView) getListView();
if (list != null) {
list.setAdapter(adapter);
setListShown(true);
}
}
}