Java tutorial
/* * Copyright (C) 2010-2013 Paul Watts (paulcwatts@gmail.com) * and individual contributors. * * 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 com.google.android.gms.common.api.GoogleApiClient; import org.onebusaway.android.R; import org.onebusaway.android.app.Application; import org.onebusaway.android.io.ObaApi; import org.onebusaway.android.io.elements.ObaRegion; import org.onebusaway.android.io.elements.ObaRoute; import org.onebusaway.android.io.elements.ObaSituation; import org.onebusaway.android.io.elements.ObaStop; import org.onebusaway.android.provider.ObaContract; import org.onebusaway.android.ui.HomeActivity; import org.onebusaway.android.view.RealtimeIndicatorView; import org.onebusaway.util.comparators.AlphanumComparator; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.TargetApi; import android.app.Activity; import android.app.SearchManager; import android.content.ActivityNotFoundException; import android.content.ContentQueryMap; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.location.Location; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Parcelable; import android.os.SystemClock; import android.provider.Settings; import android.support.v4.app.Fragment; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; import android.text.Spannable; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; /** * A class containing utility methods related to the user interface */ public final class UIUtils { private static final String TAG = "UIHelp"; public static void setupActionBar(AppCompatActivity activity) { ActionBar bar = activity.getSupportActionBar(); bar.setIcon(android.R.color.transparent); bar.setDisplayShowTitleEnabled(true); // HomeActivity is the root for all other activities if (!(activity instanceof HomeActivity)) { bar.setDisplayHomeAsUpEnabled(true); } } /** * Sets up the search view in the action bar */ public static void setupSearch(Activity activity, Menu menu) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE); final MenuItem searchMenu = menu.findItem(R.id.action_search); SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchMenu); searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName())); // Close the keyboard and SearchView at same time when the back button is pressed searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean queryTextFocused) { if (!queryTextFocused) { MenuItemCompat.collapseActionView(searchMenu); } } }); } } public static void showProgress(Fragment fragment, boolean visible) { AppCompatActivity act = (AppCompatActivity) fragment.getActivity(); if (act != null) { act.setSupportProgressBarIndeterminateVisibility(visible); } } public static void setClickableSpan(TextView v, ClickableSpan span) { Spannable text = (Spannable) v.getText(); text.setSpan(span, 0, text.length(), 0); v.setMovementMethod(LinkMovementMethod.getInstance()); } public static void removeAllClickableSpans(TextView v) { Spannable text = (Spannable) v.getText(); ClickableSpan[] spans = text.getSpans(0, text.length(), ClickableSpan.class); for (ClickableSpan cs : spans) { text.removeSpan(cs); } } public static final int getStopDirectionText(String direction) { if (direction.equals("N")) { return R.string.direction_n; } else if (direction.equals("NW")) { return R.string.direction_nw; } else if (direction.equals("W")) { return R.string.direction_w; } else if (direction.equals("SW")) { return R.string.direction_sw; } else if (direction.equals("S")) { return R.string.direction_s; } else if (direction.equals("SE")) { return R.string.direction_se; } else if (direction.equals("E")) { return R.string.direction_e; } else if (direction.equals("NE")) { return R.string.direction_ne; } else { return R.string.direction_none; } } public static final String getRouteDisplayName(ObaRoute route) { String result = route.getShortName(); if (!TextUtils.isEmpty(result)) { return result; } result = route.getLongName(); if (!TextUtils.isEmpty(result)) { return result; } // Just so we never return null. return ""; } public static final String getRouteDescription(ObaRoute route) { String shortName = route.getShortName(); String longName = route.getLongName(); if (TextUtils.isEmpty(shortName)) { shortName = longName; } if (TextUtils.isEmpty(longName) || shortName.equals(longName)) { longName = route.getDescription(); } return MyTextUtils.toTitleCase(longName); } // Shows or hides the view, depending on whether or not the direction is // available. public static final void setStopDirection(View v, String direction, boolean show) { final TextView text = (TextView) v; final int directionText = UIUtils.getStopDirectionText(direction); if ((directionText != R.string.direction_none) || show) { text.setText(directionText); text.setVisibility(View.VISIBLE); } else { text.setVisibility(View.GONE); } } // Common code to set a route list item view public static final void setRouteView(View view, ObaRoute route) { TextView shortNameText = (TextView) view.findViewById(R.id.short_name); TextView longNameText = (TextView) view.findViewById(R.id.long_name); String shortName = route.getShortName(); String longName = MyTextUtils.toTitleCase(route.getLongName()); if (TextUtils.isEmpty(shortName)) { shortName = longName; } if (TextUtils.isEmpty(longName) || shortName.equals(longName)) { longName = MyTextUtils.toTitleCase(route.getDescription()); } shortNameText.setText(shortName); longNameText.setText(longName); } private static final String[] STOP_USER_PROJECTION = { ObaContract.Stops._ID, ObaContract.Stops.FAVORITE, ObaContract.Stops.USER_NAME }; public static class StopUserInfoMap { private final ContentQueryMap mMap; public StopUserInfoMap(Context context) { ContentResolver cr = context.getContentResolver(); Cursor c = cr.query(ObaContract.Stops.CONTENT_URI, STOP_USER_PROJECTION, "(" + ObaContract.Stops.USER_NAME + " IS NOT NULL)" + "OR (" + ObaContract.Stops.FAVORITE + "=1)", null, null); mMap = new ContentQueryMap(c, ObaContract.Stops._ID, true, null); } public void close() { mMap.close(); } public void requery() { mMap.requery(); } public void setView(View stopRoot, String stopId, String stopName) { TextView nameView = (TextView) stopRoot.findViewById(R.id.stop_name); setView2(nameView, stopId, stopName, true); } /** * This should be used with compound drawables */ public void setView2(TextView nameView, String stopId, String stopName, boolean showIcon) { ContentValues values = mMap.getValues(stopId); int icon = 0; if (values != null) { Integer i = values.getAsInteger(ObaContract.Stops.FAVORITE); final boolean favorite = (i != null) && (i == 1); final String userName = values.getAsString(ObaContract.Stops.USER_NAME); nameView.setText(TextUtils.isEmpty(userName) ? MyTextUtils.toTitleCase(stopName) : userName); icon = favorite && showIcon ? R.drawable.ic_toggle_star : 0; } else { nameView.setText(MyTextUtils.toTitleCase(stopName)); } nameView.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); } } /** * Returns a comma-delimited list of route display names that serve a stop * <p/> * For example, if a stop was served by "14" and "54", this method will return "14,54" * * @param stop the stop for which the route display names should be serialized * @param routes a HashMap containing all routes that serve this stop, with the routeId as the * key. * Note that for efficiency this routes HashMap may contain routes that don't * serve this stop as well - * the routes for the stop are referenced via stop.getRouteDisplayNames() * @return comma-delimited list of route display names that serve a stop */ public static String serializeRouteDisplayNames(ObaStop stop, HashMap<String, ObaRoute> routes) { StringBuffer sb = new StringBuffer(); String[] routeIds = stop.getRouteIds(); for (int i = 0; i < routeIds.length; i++) { if (routes != null) { ObaRoute route = routes.get(routeIds[i]); sb.append(getRouteDisplayName(route)); } else { // We don't have route mappings - use routeIds sb.append(routeIds[i]); } if (i != routeIds.length - 1) { sb.append(","); } } return sb.toString(); } /** * Returns a list of route display names from a serialized list of route display names * <p/> * See {@link #serializeRouteDisplayNames(ObaStop, java.util.HashMap)} * * @param serializedRouteDisplayNames comma-separate list of routeIds from serializeRouteDisplayNames() * @return list of route display names */ public static List<String> deserializeRouteDisplayNames(String serializedRouteDisplayNames) { String routes[] = serializedRouteDisplayNames.split(","); return Arrays.asList(routes); } /** * Returns a formatted and sorted list of route display names for presentation in a single line * <p/> * For example, the following list: * <p/> * 11,1,15, 8b * <p/> * ...would be formatted as: * <p/> * 4, 8b, 11, 15 * * @param routeDisplayNames list of route display names * @param nextArrivalRouteShortNames the short route names of the next X arrivals at the stop * that are the same. These will be highlighted in the * results. * @return a formatted and sorted list of route display names for presentation in a single line */ public static String formatRouteDisplayNames(List<String> routeDisplayNames, List<String> nextArrivalRouteShortNames) { Collections.sort(routeDisplayNames, new AlphanumComparator()); StringBuffer sb = new StringBuffer(); for (int i = 0; i < routeDisplayNames.size(); i++) { boolean match = false; for (String nextArrivalRouteShortName : nextArrivalRouteShortNames) { if (routeDisplayNames.get(i).equalsIgnoreCase(nextArrivalRouteShortName)) { match = true; break; } } if (match) { // If this route name matches a route name for the next X arrivals that are the same, highlight this route in the text sb.append(routeDisplayNames.get(i) + "*"); } else { // Just append the normally-formatted route name sb.append(routeDisplayNames.get(i)); } if (i != routeDisplayNames.size() - 1) { sb.append(", "); } } return sb.toString(); } /** * Default implementation for creating a shortcut when in shortcut mode. * * @param name The name of the shortcut. * @param destIntent The destination intent. */ public static final Intent makeShortcut(Context context, String name, Intent destIntent) { // Set up the container intent Intent intent = new Intent(); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, destIntent); intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); Parcelable iconResource = Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher); intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); return intent; } public static void goToUrl(Context context, String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); try { context.startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(context, context.getString(R.string.browser_error), Toast.LENGTH_SHORT).show(); } } public static final String getRouteErrorString(Context context, int code) { if (!isConnected(context)) { if (isAirplaneMode(context)) { return context.getString(R.string.airplane_mode_error); } else { return context.getString(R.string.no_network_error); } } switch (code) { case ObaApi.OBA_INTERNAL_ERROR: return context.getString(R.string.internal_error); case ObaApi.OBA_NOT_FOUND: ObaRegion r = Application.get().getCurrentRegion(); if (r != null) { return context.getString(R.string.route_not_found_error_with_region_name, r.getName()); } else { return context.getString(R.string.route_not_found_error_no_region); } case ObaApi.OBA_BAD_GATEWAY: return context.getString(R.string.bad_gateway_error); case ObaApi.OBA_OUT_OF_MEMORY: return context.getString(R.string.out_of_memory_error); default: return context.getString(R.string.generic_comm_error); } } public static final String getStopErrorString(Context context, int code) { if (!isConnected(context)) { if (isAirplaneMode(context)) { return context.getString(R.string.airplane_mode_error); } else { return context.getString(R.string.no_network_error); } } switch (code) { case ObaApi.OBA_INTERNAL_ERROR: return context.getString(R.string.internal_error); case ObaApi.OBA_NOT_FOUND: ObaRegion r = Application.get().getCurrentRegion(); if (r != null) { return context.getString(R.string.stop_not_found_error_with_region_name, r.getName()); } else { return context.getString(R.string.stop_not_found_error_no_region); } case ObaApi.OBA_BAD_GATEWAY: return context.getString(R.string.bad_gateway_error); case ObaApi.OBA_OUT_OF_MEMORY: return context.getString(R.string.out_of_memory_error); default: return context.getString(R.string.generic_comm_error); } } public static final int getMapErrorString(Context context, int code) { if (!isConnected(context)) { if (isAirplaneMode(context)) { return R.string.airplane_mode_error; } else { return R.string.no_network_error; } } switch (code) { case ObaApi.OBA_INTERNAL_ERROR: return R.string.internal_error; case ObaApi.OBA_BAD_GATEWAY: return R.string.bad_gateway_error; case ObaApi.OBA_OUT_OF_MEMORY: return R.string.out_of_memory_error; default: return R.string.map_generic_error; } } public static boolean isAirplaneMode(Context context) { ContentResolver cr = context.getContentResolver(); return Settings.System.getInt(cr, Settings.System.AIRPLANE_MODE_ON, 0) != 0; } public static boolean isConnected(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return (activeNetwork != null) && activeNetwork.isConnectedOrConnecting(); } /** * Returns the first string for the query URI. */ public static String stringForQuery(Context context, Uri uri, String column) { ContentResolver cr = context.getContentResolver(); Cursor c = cr.query(uri, new String[] { column }, null, null, null); if (c != null) { try { if (c.moveToFirst()) { return c.getString(0); } } finally { c.close(); } } return ""; } public static Integer intForQuery(Context context, Uri uri, String column) { ContentResolver cr = context.getContentResolver(); Cursor c = cr.query(uri, new String[] { column }, null, null, null); if (c != null) { try { if (c.moveToFirst()) { return c.getInt(0); } } finally { c.close(); } } return null; } public static final int MINUTES_IN_HOUR = 60; /** * Takes the number of minutes, and returns a user-readable string * saying the number of minutes in which no arrivals are coming, * or the number of hours and minutes if minutes if minutes > 60 * * @param minutes number of minutes for which there are no upcoming arrivals * @param additionalArrivals true if the response should include the word additional, false if * it should not * @param shortFormat true if the format should be abbreviated, false if it should be * long * @return a user-readable string saying the number of minutes in which no arrivals are coming, * or the number of hours and minutes if minutes > 60 */ public static String getNoArrivalsMessage(Context context, int minutes, boolean additionalArrivals, boolean shortFormat) { if (minutes <= MINUTES_IN_HOUR) { // Return just minutes if (additionalArrivals) { if (shortFormat) { // Abbreviated version return context.getString(R.string.stop_info_no_additional_data_minutes_short_format, minutes); } else { // Long version return context.getString(R.string.stop_info_no_additional_data_minutes, minutes); } } else { if (shortFormat) { // Abbreviated version return context.getString(R.string.stop_info_nodata_minutes_short_format, minutes); } else { // Long version return context.getString(R.string.stop_info_nodata_minutes, minutes); } } } else { // Return hours and minutes if (additionalArrivals) { if (shortFormat) { // Abbreviated version return context.getResources().getQuantityString( R.plurals.stop_info_no_additional_data_hours_minutes_short_format, minutes / 60, minutes % 60, minutes / 60); } else { // Long version return context.getResources().getQuantityString( R.plurals.stop_info_no_additional_data_hours_minutes, minutes / 60, minutes % 60, minutes / 60); } } else { if (shortFormat) { // Abbreviated version return context.getResources().getQuantityString( R.plurals.stop_info_nodata_hours_minutes_short_format, minutes / 60, minutes % 60, minutes / 60); } else { // Long version return context.getResources().getQuantityString(R.plurals.stop_info_nodata_hours_minutes, minutes / 60, minutes % 60, minutes / 60); } } } } /** * Returns true if the activity is still active and dialogs can be managed (i.e., displayed * or dismissed), or false if it is * not * * @param activity Activity to check for displaying/dismissing a dialog * @return true if the activity is still active and dialogs can be managed, or false if it is * not */ public static boolean canManageDialog(Activity activity) { if (activity == null) { return false; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { return !activity.isFinishing() && !activity.isDestroyed(); } else { return !activity.isFinishing(); } } /** * Returns true if the context is an Activity and is still active and dialogs can be managed * (i.e., displayed or dismissed) OR the context is not an Activity, or false if the Activity * is * no longer active. * * NOTE: We really shouldn't display dialogs from a Service - a notification is a better way * to communicate with the user. * * @param context Context to check for displaying/dismissing a dialog * @return true if the context is an Activity and is still active and dialogs can be managed * (i.e., displayed or dismissed) OR the context is not an Activity, or false if the Activity * is * no longer active */ public static boolean canManageDialog(Context context) { if (context == null) { return false; } if (context instanceof Activity) { return canManageDialog((Activity) context); } else { // We really shouldn't be displaying dialogs from a Service, but if for some reason we // need to do this, we don't have any way of checking whether its possible return true; } } /** * Returns true if the API level supports animating Views using ViewPropertyAnimator, false if * it doesn't * * @return true if the API level supports animating Views using ViewPropertyAnimator, false if * it doesn't */ public static boolean canAnimateViewModern() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1; } /** * Returns true if the API level supports canceling existing animations via the * ViewPropertyAnimator, and false if it does not * * @return true if the API level supports canceling existing animations via the * ViewPropertyAnimator, and false if it does not */ public static boolean canCancelAnimation() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; } /** * Returns true if the API level supports our Arrival Info Style B (sort by route) views, false * if it does not. See #350 and #275. * * @return true if the API level supports our Arrival Info Style B (sort by route) views, false * if it does not */ public static boolean canSupportArrivalInfoStyleB() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; } /** * Shows a view, using animation if the platform supports it * * @param v View to show * @param animationDuration duration of animation */ @TargetApi(14) public static void showViewWithAnimation(final View v, int animationDuration) { // If we're on a legacy device, show the view without the animation if (!canAnimateViewModern()) { showViewWithoutAnimation(v); return; } if (v.getVisibility() == View.VISIBLE && v.getAlpha() == 1) { // View is already visible and not transparent, return without doing anything return; } v.clearAnimation(); if (canCancelAnimation()) { v.animate().cancel(); } if (v.getVisibility() != View.VISIBLE) { // Set the content view to 0% opacity but visible, so that it is visible // (but fully transparent) during the animation. v.setAlpha(0f); v.setVisibility(View.VISIBLE); } // Animate the content view to 100% opacity, and clear any animation listener set on the view. v.animate().alpha(1f).setDuration(animationDuration).setListener(null); } /** * Shows a view without using animation * * @param v View to show */ public static void showViewWithoutAnimation(final View v) { if (v.getVisibility() == View.VISIBLE) { // View is already visible, return without doing anything return; } v.setVisibility(View.VISIBLE); } /** * Hides a view, using animation if the platform supports it * * @param v View to hide * @param animationDuration duration of animation */ @TargetApi(14) public static void hideViewWithAnimation(final View v, int animationDuration) { // If we're on a legacy device, hide the view without the animation if (!canAnimateViewModern()) { hideViewWithoutAnimation(v); return; } if (v.getVisibility() == View.GONE) { // View is already gone, return without doing anything return; } v.clearAnimation(); if (canCancelAnimation()) { v.animate().cancel(); } // Animate the view to 0% opacity. After the animation ends, set its visibility to GONE as // an optimization step (it won't participate in layout passes, etc.) v.animate().alpha(0f).setDuration(animationDuration).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { v.setVisibility(View.GONE); } }); } /** * Hides a view without using animation * * @param v View to hide */ public static void hideViewWithoutAnimation(final View v) { if (v.getVisibility() == View.GONE) { // View is already gone, return without doing anything return; } // Hide the view without animation v.setVisibility(View.GONE); } /** * Prints View visibility information to the log for debugging purposes * * @param v View to log visibility information for */ @TargetApi(12) public static void logViewVisibility(View v) { if (v != null) { if (v.getVisibility() == View.VISIBLE) { Log.d(TAG, v.getContext().getResources().getResourceEntryName(v.getId()) + " is visible"); if (UIUtils.canAnimateViewModern()) { Log.d(TAG, v.getContext().getResources().getResourceEntryName(v.getId()) + " alpha - " + v.getAlpha()); } } else if (v.getVisibility() == View.INVISIBLE) { Log.d(TAG, v.getContext().getResources().getResourceEntryName(v.getId()) + " is INVISIBLE"); } else if (v.getVisibility() == View.GONE) { Log.d(TAG, v.getContext().getResources().getResourceEntryName(v.getId()) + " is GONE"); } else { Log.d(TAG, v.getContext().getResources().getResourceEntryName(v.getId()) + ".getVisibility() - " + v.getVisibility()); } } } /** * Converts screen dimension units from dp to pixels, based on algorithm defined in * http://developer.android.com/guide/practices/screens_support.html#dips-pels * * @param dp value in dp * @return value in pixels */ public static int dpToPixels(Context context, float dp) { // Get the screen's density scale final float scale = context.getResources().getDisplayMetrics().density; // Convert the dps to pixels, based on density scale return (int) (dp * scale + 0.5f); } /** * Sets the margins for a given view * * @param v View to set the margin for * @param l left margin, in pixels * @param t top margin, in pixels * @param r right margin, in pixels * @param b bottom margin, in pixels */ public static void setMargins(View v, int l, int t, int r, int b) { ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); p.setMargins(l, t, r, b); v.setLayoutParams(p); } /** * Formats a view so it is ignored for accessible access */ public static void setAccessibilityIgnore(View view) { view.setClickable(false); view.setFocusable(false); view.setContentDescription(""); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } } /** * Builds the list of Strings that should be shown for a given trip "Bus Options" menu, * provided the arguments for that trip * * @param c Context * @param isRouteFavorite true if this route is a user favorite, false if it is not * @param hasUrl true if the route provides a URL for schedule data, false if it does * not * @param isReminderVisible true if the reminder is currently visible for a trip, false if it * is * not * @return the list of Strings that should be shown for a given trip, provided the arguments for * that trip */ public static List<String> buildTripOptions(Context c, boolean isRouteFavorite, boolean hasUrl, boolean isReminderVisible) { ArrayList<String> list = new ArrayList<>(); if (!isRouteFavorite) { list.add(c.getString(R.string.bus_options_menu_add_star)); } else { list.add(c.getString(R.string.bus_options_menu_remove_star)); } list.add(c.getString(R.string.bus_options_menu_show_route_on_map)); list.add(c.getString(R.string.bus_options_menu_show_trip_details)); if (!isReminderVisible) { list.add(c.getString(R.string.bus_options_menu_set_reminder)); } else { list.add(c.getString(R.string.bus_options_menu_edit_reminder)); } list.add(c.getString(R.string.bus_options_menu_show_only_this_route)); if (hasUrl) { list.add(c.getString(R.string.bus_options_menu_show_route_schedule)); } list.add(c.getString(R.string.bus_options_menu_report_trip_problem)); return list; } /** * Builds the array of icons that should be shown for the trip "Bus Options" menu, given the * provided arguments for that trip * * @param isRouteFavorite true if this route is a user favorite, false if it is not * @param hasUrl true if the route provides a URL for schedule data, false if it does * not * @return the array of icons that should be shown for a given trip */ public static List<Integer> buildTripOptionsIcons(boolean isRouteFavorite, boolean hasUrl) { ArrayList<Integer> list = new ArrayList<>(); if (!isRouteFavorite) { list.add(R.drawable.focus_star_on); } else { list.add(R.drawable.focus_star_off); } list.add(R.drawable.ic_arrivals_styleb_action_map); list.add(R.drawable.ic_trip_details); list.add(R.drawable.ic_drawer_alarm); list.add(R.drawable.ic_content_filter_list); if (hasUrl) { list.add(R.drawable.ic_notification_event_note); } list.add(R.drawable.ic_alert_warning); return list; } /** * Sets the line and fill colors for real-time indicator circles contained in the provided * realtime_indicator.xml layout. There are several circles, so each needs to be set * individually. The resource code for the color to be used should be provided. * * @param vg realtime_indicator.xml layout * @param lineColor resource code color to be used as line color, or null to use the default * colors * @param fillColor resource code color to be used as fill color, or null to use the default * colors */ public static void setRealtimeIndicatorColorByResourceCode(ViewGroup vg, Integer lineColor, Integer fillColor) { Resources r = vg.getResources(); setRealtimeIndicatorColor(vg, r.getColor(lineColor), r.getColor(fillColor)); } /** * Sets the line and fill colors for real-time indicator circles contained in the provided * realtime_indicator.xml layout. There are several circles, so each needs to be set * individually. The integer representation of the color to be used should be provided. * * @param vg realtime_indicator.xml layout * @param lineColor color to be used as line color, or null to use the default colors * @param fillColor color to be used as fill color, or null to use the default colors */ public static void setRealtimeIndicatorColor(ViewGroup vg, Integer lineColor, Integer fillColor) { for (int i = 0; i < vg.getChildCount(); i++) { View v = vg.getChildAt(i); if (v instanceof RealtimeIndicatorView) { if (lineColor != null) { ((RealtimeIndicatorView) v).setLineColor(lineColor); } else { // Use default color ((RealtimeIndicatorView) v).setLineColor(R.color.realtime_indicator_line); } if (fillColor != null) { ((RealtimeIndicatorView) v).setFillColor(fillColor); } else { // Use default color ((RealtimeIndicatorView) v).setLineColor(R.color.realtime_indicator_fill); } } } } /** * Creates a new Bitmap, with the black color of the source image changed to the given color. * The source Bitmap isn't modified. * * @param source the source Bitmap with a black background * @param color the color to change the black color to * @return the resulting Bitmap that has the black changed to the color */ public static Bitmap colorBitmap(Bitmap source, int color) { int width = source.getWidth(); int height = source.getHeight(); int[] pixels = new int[width * height]; source.getPixels(pixels, 0, width, 0, 0, width, height); for (int x = 0; x < pixels.length; ++x) { pixels[x] = (pixels[x] == Color.BLACK) ? color : pixels[x]; } Bitmap out = Bitmap.createBitmap(width, height, source.getConfig()); out.setPixels(pixels, 0, width, 0, 0, width, height); return out; } /** * Returns true if the provided touch event was within the provided view * * @return true if the provided touch event was within the provided view */ public static boolean isTouchInView(View view, MotionEvent event) { Rect rect = new Rect(); view.getGlobalVisibleRect(rect); return rect.contains((int) event.getRawX(), (int) event.getRawY()); } /** * Opens a "Contact Us" email, based on the currently selected region * * @param googleApiClient The GoogleApiClient being used to obtain fused provider updates, or * null if one isn't available */ public static void sendContactEmail(Context c, GoogleApiClient googleApiClient) { PackageManager pm = c.getPackageManager(); PackageInfo appInfo; try { appInfo = pm.getPackageInfo(c.getPackageName(), PackageManager.GET_META_DATA); } catch (PackageManager.NameNotFoundException e) { // Do nothing, perhaps we'll get to show it again? Or never. return; } ObaRegion region = Application.get().getCurrentRegion(); if (region == null) { return; } Location loc = Application.getLastKnownLocation(c, googleApiClient); // appInfo.versionName // Build.MODEL // Build.VERSION.RELEASE // Build.VERSION.SDK // %s\nModel: %s\nOS Version: %s\nSDK Version: %s\ final String body = c.getString(R.string.bug_report_body, appInfo.versionName, Build.MODEL, Build.VERSION.RELEASE, Build.VERSION.SDK_INT, LocationUtils.printLocationDetails(loc)); Intent send = new Intent(Intent.ACTION_SEND); send.putExtra(Intent.EXTRA_EMAIL, new String[] { region.getContactEmail() }); send.putExtra(Intent.EXTRA_SUBJECT, c.getString(R.string.bug_report_subject)); send.putExtra(Intent.EXTRA_TEXT, body); send.setType("message/rfc822"); try { c.startActivity(Intent.createChooser(send, c.getString(R.string.bug_report_subject))); } catch (ActivityNotFoundException e) { Toast.makeText(c, R.string.bug_report_error, Toast.LENGTH_LONG).show(); } } /** * Returns the current time for comparison against another current time. For API levels >= * Jelly Bean MR1 the SystemClock.getElapsedRealtimeNanos() method is used, and for API levels * < * Jelly Bean MR1 System.currentTimeMillis() is used. * * @return the current time for comparison against another current time, in nanoseconds */ public static long getCurrentTimeForComparison() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { // Use elapsed real-time nanos, since its guaranteed monotonic return SystemClock.elapsedRealtimeNanos(); } else { return TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); } } /** * Open the soft keyboard */ public static void openKeyboard(Context context) { InputMethodManager inputMethodManager = (InputMethodManager) context .getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_NOT_ALWAYS); } /** * Closes the soft keyboard */ public static void closeKeyboard(Context context, View v) { InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } /** * Returns true if the provided currentTime falls within the situation's (i.e., alert's) active * windows or if the situation does not provide an active window, and false if the currentTime * falls outside of the situation's active windows * * @param currentTime the time to compare to the situation's windows * @return true if the provided currentTime falls within the situation's (i.e., alert's) active * windows or if the situation does not provide an active window, and false if the currentTime * falls outside of the situation's active windows */ public static boolean isActiveWindowForSituation(ObaSituation situation, long currentTime) { if (situation.getActiveWindows().length == 0) { // We assume a situation is active if it doesn't contain any active window information return true; } boolean isActiveWindowForSituation = false; for (ObaSituation.ActiveWindow activeWindow : situation.getActiveWindows()) { long from = activeWindow.getFrom(); long to = activeWindow.getTo(); if (from <= currentTime && currentTime <= to) { isActiveWindowForSituation = true; break; } } return isActiveWindowForSituation; } }