/*
* Copyright (C) 2016 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.util;
import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.io.elements.ObaArrivalInfo;
import org.onebusaway.android.ui.ArrivalInfo;
import android.content.Context;
import android.content.res.Resources;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class ArrivalInfoUtils {
final static class InfoComparator implements Comparator<ArrivalInfo> {
public int compare(ArrivalInfo lhs, ArrivalInfo rhs) {
return (int) (lhs.getEta() - rhs.getEta());
}
}
/**
* Converts the ObaArrivalInfo array received from the server to an ArrayList for the adapter
*
* @param context
* @param arrivalInfo
* @param filter routeIds to filter for
* @param ms current time in milliseconds
* @param includeArrivalDepartureInStatusLabel true if the arrival/departure label should be
* included in the status label, false if it should
* not
* @return ArrayList of arrival info to be used with the adapter
*/
public static final ArrayList<ArrivalInfo> convertObaArrivalInfo(Context context,
ObaArrivalInfo[] arrivalInfo,
ArrayList<String> filter, long ms,
boolean includeArrivalDepartureInStatusLabel) {
final int len = arrivalInfo.length;
ArrayList<ArrivalInfo> result = new ArrayList<ArrivalInfo>(len);
if (filter != null && filter.size() > 0) {
// Only add routes that haven't been filtered out
for (int i = 0; i < len; ++i) {
ObaArrivalInfo arrival = arrivalInfo[i];
if (filter.contains(arrival.getRouteId())) {
ArrivalInfo info = new ArrivalInfo(context, arrival, ms,
includeArrivalDepartureInStatusLabel);
if (shouldAddEta(info)) {
result.add(info);
}
}
}
} else {
// Add arrivals for all routes
for (int i = 0; i < len; ++i) {
ArrivalInfo info = new ArrivalInfo(context, arrivalInfo[i], ms,
includeArrivalDepartureInStatusLabel);
if (shouldAddEta(info)) {
result.add(info);
}
}
}
// Sort by ETA
Collections.sort(result, new InfoComparator());
return result;
}
/**
* Returns true if this ETA should be added based on the user preference for adding negative
* arrival times, and false if it should not
*
* @param info info that includes the ETA to be evaluated
* @return true if this ETA should be added based on the user preference for adding negative
* arrival times, and false if it should not
*/
private static boolean shouldAddEta(ArrivalInfo info) {
boolean showNegativeArrivals = Application.getPrefs()
.getBoolean(Application.get().getResources()
.getString(R.string.preference_key_show_negative_arrivals), true);
if (info.getEta() >= 0) {
// Always add positive ETAs
return true;
} else {
// Only add negative ETAs based on setting
if (showNegativeArrivals) {
return true;
}
}
return false;
}
/**
* Returns the index in the provided infoList for the first non-negative arrival ETA in the
* list, or -1 if no non-negative ETAs exist in the list
*
* @param infoList list to search for non-negative arrival times, ordered by relative ETA from
* negative infinity to positive infinity
* @return the index in the provided infoList for the first non-negative arrival ETA in the
* list, or -1 if no non-negative ETAs exist in the list
*/
public static int findFirstNonNegativeArrival(ArrayList<ArrivalInfo> infoList) {
for (int i = 0; i < infoList.size(); i++) {
ArrivalInfo info = infoList.get(i);
if (info.getEta() >= 0) {
return i;
}
}
// We didn't find any non-negative ETAs
return -1;
}
/**
* Returns the indexes in the provided infoList for the preferred route/headsign combinations
* to be prioritized for displayed in the header, or null if no non-negative ETAs exist in the
* list. If no route/headsign combinations are favorited, the indexes returned may simply be
* the indexes of the first (and second, if it exists) non-negative arrival times.
*
* @param infoList list to search for non-negative arrival times, ordered by relative ETA from
* negative infinity to positive infinity
* @return the indexes in the provided infoList for the preferred route/headsign combinations
* to be prioritized for displayed in the header, or null if no non-negative ETAs exist in the
* list
*/
public static ArrayList<Integer> findPreferredArrivalIndexes(ArrayList<ArrivalInfo> infoList) {
// Start by getting the index of the first non-negative arrival time
int firstIndex = findFirstNonNegativeArrival(infoList);
if (firstIndex == -1) {
return null;
}
// Find any favorites
ArrayList<Integer> preferredIndexes = new ArrayList<>();
for (int i = firstIndex; i < infoList.size(); i++) {
ArrivalInfo info = infoList.get(i);
if (info.isRouteAndHeadsignFavorite()) {
preferredIndexes.add(i);
}
}
// If we have at least two favorites, that's enough to fill the header - return them
if (preferredIndexes.size() >= 2) {
return preferredIndexes;
}
// If we have one favorite, and the index is different from the firstIndex, then add the firstIndex and return
if (preferredIndexes.size() == 1 && preferredIndexes.get(0) != firstIndex) {
preferredIndexes.add(firstIndex);
}
// If we have no preferred indexes (i.e., starred route/headsigns) at this point, then add the firstIndex
if (preferredIndexes.size() == 0) {
preferredIndexes.add(firstIndex);
// If there is another non-negative arrival time, then add it too
int secondIndex = firstIndex + 1;
if (secondIndex < infoList.size()) {
preferredIndexes.add(secondIndex);
}
}
return preferredIndexes;
}
/**
* Returns the status color to be used, depending on whether the vehicle is running early,
* late,
* ontime,
* or if we don't have real-time info (i.e., scheduled)
*
* @param scheduled the scheduled time, in minutes past unix epoch
* @param predicted the predicted time, in minutes past unix epoch
* @return the status color to be used, depending on whether the vehicle is running early, late,
* ontime,
* or if we don't have real-time info (i.e., scheduled)
*/
public static int computeColor(final long scheduled, final long predicted) {
if (predicted != 0) {
return computeColorFromDeviation(predicted - scheduled);
} else {
// Use scheduled color
return R.color.stop_info_scheduled_time;
}
}
/**
* Returns the status color to be used, depending on whether the vehicle is running early,
* late,
* ontime,
* or if we don't have real-time info (i.e., scheduled)
*
* @param delay the deviation from the scheduled time, in minutes - positive means bus is
* running late,
* negative means early
* @return the status color to be used, depending on whether the vehicle is running early, late,
* ontime,
* or if we don't have real-time info (i.e., scheduled)
*/
public static int computeColorFromDeviation(final long delay) {
// Bus is arriving
if (delay > 0) {
// Arriving delayed
return R.color.stop_info_delayed;
} else if (delay < 0) {
// Arriving early
return R.color.stop_info_early;
} else {
// Arriving on time
return R.color.stop_info_ontime;
}
}
/**
* Computes the arrival status label from the delay (i.e., schedule deviation), where positive
* means the bus is running late and negative means the bus is running ahead of schedule
*
* @param delay schedule deviation, in minutes, for this vehicle where positive
* means the bus is running late and negative means the bus is running ahead of
* schedule
* @return the arrival status label based on the deviation
*/
public static String computeArrivalLabelFromDelay(Resources res, long delay) {
if (delay > 0) {
// Arriving delayed
return res.getQuantityString(
R.plurals.stop_info_arrive_delayed, (int) delay,
delay);
} else if (delay < 0) {
// Arriving early
delay = -delay;
return res
.getQuantityString(
R.plurals.stop_info_arrive_early,
(int) delay, delay);
} else {
// Arriving on time
return res.getString(R.string.stop_info_ontime);
}
}
}