/**
  * Returns the currently selected stop sort order as the index in R.array.sort_stops
  *
  * @return the currently selected stop sort order as the index in R.array.sort_stops
  */
 public static int getStopSortOrderFromPreferences() {
   Resources r = Application.get().getResources();
   SharedPreferences settings = Application.getPrefs();
   String[] sortOptions = r.getStringArray(R.array.sort_stops);
   String sortPref =
       settings.getString(r.getString(R.string.preference_key_default_stop_sort), sortOptions[0]);
   if (sortPref.equalsIgnoreCase(sortOptions[0])) {
     return 0;
   } else if (sortPref.equalsIgnoreCase(sortOptions[1])) {
     return 1;
   }
   return 0; // Default to the first option
 }
    /**
     * Returns an icon for the vehicle that should be shown on the map
     *
     * @param isRealtime true if the marker shown indicate real-time info, false if it should
     *     indicate schedule
     * @param status the vehicles status to add to the map
     * @param response the response which contained the provided status
     * @return an icon for the vehicle that should be shown on the map
     */
    private BitmapDescriptor getVehicleIcon(
        boolean isRealtime, ObaTripStatus status, ObaTripsForRouteResponse response) {
      int colorResource;
      Resources r = Application.get().getResources();

      if (isRealtime) {
        long deviationMin = TimeUnit.SECONDS.toMinutes(status.getScheduleDeviation());
        colorResource = ArrivalInfo.computeColorFromDeviation(deviationMin);
      } else {
        colorResource = R.color.stop_info_scheduled_time;
      }
      int color = r.getColor(colorResource);
      double direction = MathUtils.toDirection(status.getOrientation());
      int halfWind = MathUtils.getHalfWindIndex((float) direction, NUM_DIRECTIONS - 1);
      // Log.d(TAG, "VehicleId=" + status.getVehicleId() + ", orientation= " +
      // status.getOrientation() + ", direction=" + direction + ", halfWind= " + halfWind + ",
      // deviation=" + status.getScheduleDeviation());

      String key = createBitmapCacheKey(halfWind, colorResource);
      Bitmap b = getBitmapFromCache(key);
      if (b == null) {
        // Cache miss - create Bitmap and add to cache
        b = UIUtils.colorBitmap(vehicle_icons[halfWind], color);
        addBitmapToCache(key, b);
      }
      return BitmapDescriptorFactory.fromBitmap(b);
    }
  /** Cache the core black template Bitmaps used for vehicle icons */
  private static final void loadIcons() {
    // Initialize variables used for all marker icons
    Resources r = Application.get().getResources();

    // Black vehicle icons
    if (vehicle_icons[0] == null) {
      vehicle_icons[0] = createVehicleIcon(NORTH);
      vehicle_icons[1] = createVehicleIcon(NORTH_EAST);
      vehicle_icons[2] = createVehicleIcon(EAST);
      vehicle_icons[3] = createVehicleIcon(SOUTH_EAST);
      vehicle_icons[4] = createVehicleIcon(SOUTH);
      vehicle_icons[5] = createVehicleIcon(SOUTH_WEST);
      vehicle_icons[6] = createVehicleIcon(WEST);
      vehicle_icons[7] = createVehicleIcon(NORTH_WEST);
      vehicle_icons[8] = createVehicleIcon(NO_DIRECTION);
    }

    /**
     * Cache for colored versions of the vehicle icons. Total possible number of entries is 9
     * directions * 4 color types (early, ontime, delayed, scheduled) = 36. In a test, the
     * RouteMapController used around 15 bitmaps over a 30 min period for 4 vehicles on the map at
     * 10 sec refresh rate. This can be more depending on the route configuration (if the route has
     * lots of curves) and number of vehicles. To conserve memory, we'll set the max cache size at
     * 15.
     */
    final int MAX_CACHE_SIZE = 15;
    if (mVehicleColoredIconCache == null) {
      mVehicleColoredIconCache = new LruCache<>(MAX_CACHE_SIZE);
    }
  }
  /** Cache the BitmapDescriptors that hold the images used for icons */
  private static final void loadIcons() {
    // Initialize variables used for all marker icons
    Resources r = Application.get().getResources();
    mPx = r.getDimensionPixelSize(R.dimen.map_stop_shadow_size_6);
    mArrowWidthPx = mPx / 2f; // half the stop icon size
    mArrowHeightPx = mPx / 3f; // 1/3 the stop icon size
    float arrowSpacingReductionPx = mPx / 10f;
    mBuffer = mArrowHeightPx - arrowSpacingReductionPx;

    // Set offset used to position the image for markers (see getX/YPercentOffsetForDirection())
    // This allows the current selection marker to land on the middle of the stop marker circle
    mPercentOffset = (mBuffer / (mPx + mBuffer)) * 0.5f;

    mArrowPaintStroke = new Paint();
    mArrowPaintStroke.setColor(Color.WHITE);
    mArrowPaintStroke.setStyle(Paint.Style.STROKE);
    mArrowPaintStroke.setStrokeWidth(1.0f);
    mArrowPaintStroke.setAntiAlias(true);

    bus_stop_icons[0] = createBusStopIcon(NORTH);
    bus_stop_icons[1] = createBusStopIcon(NORTH_WEST);
    bus_stop_icons[2] = createBusStopIcon(WEST);
    bus_stop_icons[3] = createBusStopIcon(SOUTH_WEST);
    bus_stop_icons[4] = createBusStopIcon(SOUTH);
    bus_stop_icons[5] = createBusStopIcon(SOUTH_EAST);
    bus_stop_icons[6] = createBusStopIcon(EAST);
    bus_stop_icons[7] = createBusStopIcon(NORTH_EAST);
    bus_stop_icons[8] = createBusStopIcon(NO_DIRECTION);
  }
 protected void initCheckedState() {
   setChecked(
       Application.getPrefs()
           .getBoolean(
               getContext().getString(R.string.preference_key_experimental_regions),
               DEFAULT_VALUE));
 }
  /**
   * Creates a vehicle icon with the given direction arrow, or without a direction arrow if the
   * direction is NO_DIRECTION. Color is black so they can be tinted later.
   *
   * @param direction vehicle direction, obtained from status.orientation, translated to direction,
   *     and defined in constants in this class, or NO_DIRECTION if the vehicle icon shouldn't have
   *     a direction arrow
   * @return a vehicle icon bitmap with the arrow pointing the given direction, or with no arrow if
   *     direction is NO_DIRECTION
   */
  private static Bitmap createVehicleIcon(int direction) throws NullPointerException {
    if (direction < 0 || direction >= NUM_DIRECTIONS) {
      throw new IllegalArgumentException("Invalid direction - " + direction);
    }

    Resources r = Application.get().getResources();
    Bitmap b;

    switch (direction) {
      case NORTH:
        b = BitmapFactory.decodeResource(r, R.drawable.ic_marker_with_bus_smaller_north_inside);
        break;
      case NORTH_EAST:
        b =
            BitmapFactory.decodeResource(
                r, R.drawable.ic_marker_with_bus_smaller_north_east_inside);
        break;
      case EAST:
        b = BitmapFactory.decodeResource(r, R.drawable.ic_marker_with_bus_smaller_east_inside);
        break;
      case SOUTH_EAST:
        b =
            BitmapFactory.decodeResource(
                r, R.drawable.ic_marker_with_bus_smaller_south_east_inside);
        break;
      case SOUTH:
        b = BitmapFactory.decodeResource(r, R.drawable.ic_marker_with_bus_smaller_south_inside);
        break;
      case SOUTH_WEST:
        b =
            BitmapFactory.decodeResource(
                r, R.drawable.ic_marker_with_bus_smaller_south_west_inside);
        break;
      case WEST:
        b = BitmapFactory.decodeResource(r, R.drawable.ic_marker_with_bus_smaller_west_inside);
        break;
      case NORTH_WEST:
        b =
            BitmapFactory.decodeResource(
                r, R.drawable.ic_marker_with_bus_smaller_north_west_inside);
        break;
      case NO_DIRECTION:
      default:
        b = BitmapFactory.decodeResource(r, R.drawable.ic_marker_with_bus_smaller_none_inside);
    }
    return b;
  }
  @Override
  public boolean onMarkerClick(Marker marker) {
    long startTime = Long.MAX_VALUE, endTime = Long.MAX_VALUE;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      startTime = SystemClock.elapsedRealtimeNanos();
    }

    ObaStop stop = mMarkerData.getStopFromMarker(marker);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      endTime = SystemClock.elapsedRealtimeNanos();
      Log.d(
          TAG,
          "Stop HashMap read time: "
              + TimeUnit.MILLISECONDS.convert(endTime - startTime, TimeUnit.NANOSECONDS)
              + "ms");
    }

    if (stop == null) {
      // The marker isn't a stop that is contained in this StopOverlay - return unhandled
      return false;
    }

    if (BuildConfig.DEBUG) {
      // Show the stop_id in a toast for debug purposes
      Toast.makeText(mActivity, stop.getId(), Toast.LENGTH_SHORT).show();
    }

    doFocusChange(stop);

    // Report Stop distance metric
    Location stopLocation = stop.getLocation();
    Location myLocation = Application.getLastKnownLocation(mActivity, null);
    // Track the users distance to bus stop
    ObaAnalytics.trackBusStopDistance(stop.getId(), myLocation, stopLocation);
    return true;
  }
 public static void saveBoolean(String key, boolean value) {
   saveBoolean(Application.getPrefs(), key, value);
 }
 public static void saveLong(String key, long value) {
   saveLong(Application.getPrefs(), key, value);
 }
 public static void saveInt(String key, int value) {
   saveInt(Application.getPrefs(), key, value);
 }
  /**
   * Creates a bus stop icon with the given direction arrow, or without a direction arrow if the
   * direction is NO_DIRECTION
   *
   * @param direction Bus stop direction, obtained from ObaStop.getDirection() and defined in
   *     constants in this class, or NO_DIRECTION if the stop icon shouldn't have a direction arrow
   * @return a bus stop icon bitmap with the arrow pointing the given direction, or with no arrow if
   *     direction is NO_DIRECTION
   */
  private static Bitmap createBusStopIcon(String direction) throws NullPointerException {
    if (direction == null) {
      throw new IllegalArgumentException(direction);
    }

    Resources r = Application.get().getResources();
    Context context = Application.get();

    Float directionAngle = null; // 0-360 degrees
    Bitmap bm;
    Canvas c;
    Drawable shape;
    Float rotationX = null, rotationY = null; // Point around which to rotate the arrow

    Paint arrowPaintFill = new Paint();
    arrowPaintFill.setStyle(Paint.Style.FILL);
    arrowPaintFill.setAntiAlias(true);

    if (direction.equals(NO_DIRECTION)) {
      // Don't draw the arrow
      bm = Bitmap.createBitmap(mPx, mPx, Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds(0, 0, bm.getWidth(), bm.getHeight());
    } else if (direction.equals(NORTH)) {
      directionAngle = 0f;
      bm = Bitmap.createBitmap(mPx, (int) (mPx + mBuffer), Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds(0, (int) mBuffer, mPx, bm.getHeight());
      // Shade with darkest color at tip of arrow
      arrowPaintFill.setShader(
          new LinearGradient(
              bm.getWidth() / 2,
              0,
              bm.getWidth() / 2,
              mArrowHeightPx,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      // For NORTH, no rotation occurs - use center of image anyway so we have some value
      rotationX = bm.getWidth() / 2f;
      rotationY = bm.getHeight() / 2f;
    } else if (direction.equals(NORTH_WEST)) {
      directionAngle = 315f; // Arrow is drawn N, rotate 315 degrees
      bm =
          Bitmap.createBitmap(
              (int) (mPx + mBuffer), (int) (mPx + mBuffer), Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds((int) mBuffer, (int) mBuffer, bm.getWidth(), bm.getHeight());
      // Shade with darkest color at tip of arrow
      arrowPaintFill.setShader(
          new LinearGradient(
              0,
              0,
              mBuffer,
              mBuffer,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      // Rotate around below coordinates (trial and error)
      rotationX = mPx / 2f + mBuffer / 2f;
      rotationY = bm.getHeight() / 2f - mBuffer / 2f;
    } else if (direction.equals(WEST)) {
      directionAngle = 0f; // Arrow is drawn pointing West, so no rotation
      bm = Bitmap.createBitmap((int) (mPx + mBuffer), mPx, Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds((int) mBuffer, 0, bm.getWidth(), bm.getHeight());
      arrowPaintFill.setShader(
          new LinearGradient(
              0,
              bm.getHeight() / 2,
              mArrowHeightPx,
              bm.getHeight() / 2,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      // For WEST
      rotationX = bm.getHeight() / 2f;
      rotationY = bm.getHeight() / 2f;
    } else if (direction.equals(SOUTH_WEST)) {
      directionAngle = 225f; // Arrow is drawn N, rotate 225 degrees
      bm =
          Bitmap.createBitmap(
              (int) (mPx + mBuffer), (int) (mPx + mBuffer), Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds((int) mBuffer, 0, bm.getWidth(), mPx);
      arrowPaintFill.setShader(
          new LinearGradient(
              0,
              bm.getHeight(),
              mBuffer,
              bm.getHeight() - mBuffer,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      // Rotate around below coordinates (trial and error)
      rotationX = bm.getWidth() / 2f - mBuffer / 4f;
      rotationY = mPx / 2f + mBuffer / 4f;
    } else if (direction.equals(SOUTH)) {
      directionAngle = 180f; // Arrow is drawn N, rotate 180 degrees
      bm = Bitmap.createBitmap(mPx, (int) (mPx + mBuffer), Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds(0, 0, bm.getWidth(), (int) (bm.getHeight() - mBuffer));
      arrowPaintFill.setShader(
          new LinearGradient(
              bm.getWidth() / 2,
              bm.getHeight(),
              bm.getWidth() / 2,
              bm.getHeight() - mArrowHeightPx,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      rotationX = bm.getWidth() / 2f;
      rotationY = bm.getHeight() / 2f;
    } else if (direction.equals(SOUTH_EAST)) {
      directionAngle = 135f; // Arrow is drawn N, rotate 135 degrees
      bm =
          Bitmap.createBitmap(
              (int) (mPx + mBuffer), (int) (mPx + mBuffer), Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds(0, 0, mPx, mPx);
      arrowPaintFill.setShader(
          new LinearGradient(
              bm.getWidth(),
              bm.getHeight(),
              bm.getWidth() - mBuffer,
              bm.getHeight() - mBuffer,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      // Rotate around below coordinates (trial and error)
      rotationX = (mPx + mBuffer / 2) / 2f;
      rotationY = bm.getHeight() / 2f;
    } else if (direction.equals(EAST)) {
      directionAngle = 180f; // Arrow is drawn pointing West, so rotate 180
      bm = Bitmap.createBitmap((int) (mPx + mBuffer), mPx, Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds(0, 0, mPx, bm.getHeight());
      arrowPaintFill.setShader(
          new LinearGradient(
              bm.getWidth(),
              bm.getHeight() / 2,
              bm.getWidth() - mArrowHeightPx,
              bm.getHeight() / 2,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      rotationX = bm.getWidth() / 2f;
      rotationY = bm.getHeight() / 2f;
    } else if (direction.equals(NORTH_EAST)) {
      directionAngle = 45f; // Arrow is drawn pointing N, so rotate 45 degrees
      bm =
          Bitmap.createBitmap(
              (int) (mPx + mBuffer), (int) (mPx + mBuffer), Bitmap.Config.ARGB_8888);
      c = new Canvas(bm);
      shape = ContextCompat.getDrawable(context, R.drawable.map_stop_icon);
      shape.setBounds(0, (int) mBuffer, mPx, bm.getHeight());
      // Shade with darkest color at tip of arrow
      arrowPaintFill.setShader(
          new LinearGradient(
              bm.getWidth(),
              0,
              bm.getWidth() - mBuffer,
              mBuffer,
              r.getColor(R.color.theme_primary),
              r.getColor(R.color.theme_accent),
              Shader.TileMode.MIRROR));
      // Rotate around middle of circle
      rotationX = (float) mPx / 2;
      rotationY = bm.getHeight() - (float) mPx / 2;
    } else {
      throw new IllegalArgumentException(direction);
    }

    shape.draw(c);

    if (direction.equals(NO_DIRECTION)) {
      // Everything after this point is for drawing the arrow image, so return the bitmap as-is for
      // no arrow
      return bm;
    }

    /**
     * Draw the arrow - all dimensions should be relative to px so the arrow is drawn the same size
     * for all orientations
     */
    // Height of the cutout in the bottom of the triangle that makes it an arrow (0=triangle)
    final float CUTOUT_HEIGHT = mPx / 12;
    Path path = new Path();
    float x1 = 0, y1 = 0; // Tip of arrow
    float x2 = 0, y2 = 0; // lower left
    float x3 = 0, y3 = 0; // cutout in arrow bottom
    float x4 = 0, y4 = 0; // lower right

    if (direction.equals(NORTH)
        || direction.equals(SOUTH)
        || direction.equals(NORTH_EAST)
        || direction.equals(SOUTH_EAST)
        || direction.equals(NORTH_WEST)
        || direction.equals(SOUTH_WEST)) {
      // Arrow is drawn pointing NORTH
      // Tip of arrow
      x1 = mPx / 2;
      y1 = 0;

      // lower left
      x2 = (mPx / 2) - (mArrowWidthPx / 2);
      y2 = mArrowHeightPx;

      // cutout in arrow bottom
      x3 = mPx / 2;
      y3 = mArrowHeightPx - CUTOUT_HEIGHT;

      // lower right
      x4 = (mPx / 2) + (mArrowWidthPx / 2);
      y4 = mArrowHeightPx;
    } else if (direction.equals(EAST) || direction.equals(WEST)) {
      // Arrow is drawn pointing WEST
      // Tip of arrow
      x1 = 0;
      y1 = mPx / 2;

      // lower left
      x2 = mArrowHeightPx;
      y2 = (mPx / 2) - (mArrowWidthPx / 2);

      // cutout in arrow bottom
      x3 = mArrowHeightPx - CUTOUT_HEIGHT;
      y3 = mPx / 2;

      // lower right
      x4 = mArrowHeightPx;
      y4 = (mPx / 2) + (mArrowWidthPx / 2);
    }

    path.setFillType(Path.FillType.EVEN_ODD);
    path.moveTo(x1, y1);
    path.lineTo(x2, y2);
    path.lineTo(x3, y3);
    path.lineTo(x4, y4);
    path.lineTo(x1, y1);
    path.close();

    // Rotate arrow around (rotationX, rotationY) point
    Matrix matrix = new Matrix();
    matrix.postRotate(directionAngle, rotationX, rotationY);
    path.transform(matrix);

    c.drawPath(path, arrowPaintFill);
    c.drawPath(path, mArrowPaintStroke);

    return bm;
  }
 @Override
 protected void onClick() {
   if (!isChecked()) {
     /*
     Warn the user before enabling, since experimental regions
     may not have real-time info or may be unavailable.
     */
     AlertDialog dialog =
         new AlertDialog.Builder(getContext())
             .setMessage(R.string.preferences_experimental_regions_enable_warning)
             .setPositiveButton(
                 android.R.string.ok,
                 new DialogInterface.OnClickListener() {
                   @Override
                   public void onClick(DialogInterface dialog, int which) {
                     dialog.dismiss();
                     setValue(true);
                   }
                 })
             .setNegativeButton(
                 android.R.string.cancel,
                 new DialogInterface.OnClickListener() {
                   @Override
                   public void onClick(DialogInterface dialog, int which) {
                     dialog.dismiss();
                   }
                 })
             .create();
     dialog.show();
   } else {
     if (Application.get().getCurrentRegion() != null
         && Application.get().getCurrentRegion().getExperimental()) {
       // If the user is currently using an experimental region, warn that it won't be available
       AlertDialog dialog =
           new AlertDialog.Builder(getContext())
               .setMessage(R.string.preferences_experimental_regions_disable_warning)
               .setPositiveButton(
                   android.R.string.ok,
                   new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                       dialog.dismiss();
                       // Set the region info to null, so we no longer use the current experimental
                       // region
                       Application.get().setCurrentRegion(null);
                       setValue(false);
                     }
                   })
               .setNegativeButton(
                   android.R.string.cancel,
                   new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                       dialog.dismiss();
                     }
                   })
               .create();
       dialog.show();
     } else {
       setValue(false);
     }
   }
 }