public class AppSettings {
    public enum SettType {Default, Navi};
    private Activity activity;
    private ViewGroup appSettingsVP, trackingAnalyticsVP, trackingLayoutVP, naviLayoutVP, changeMapItemVP;
    private TextView tvspeed, tvdistance, tvdisunit;

    /**
     * init and set
     *
     * @param activity
     * @param calledFromVP
     */
    public AppSettings (Activity activity) {
        this.activity = activity;
        appSettingsVP = (ViewGroup) activity.findViewById(R.id.app_settings_layout);
        trackingAnalyticsVP = (ViewGroup) activity.findViewById(R.id.app_settings_tracking_analytics);
        trackingLayoutVP = (ViewGroup) activity.findViewById(R.id.app_settings_tracking_layout);
        naviLayoutVP = (ViewGroup) activity.findViewById(R.id.app_settings_navigation_layout);
        changeMapItemVP = (ViewGroup) activity.findViewById(R.id.app_settings_change_map);
    }
    
    public void showAppSettings(final ViewGroup calledFromVP, SettType settType)
    {
        initClearBtn(appSettingsVP, calledFromVP);
        if (settType == SettType.Default)
        {
          changeMapItemVP.setVisibility(View.VISIBLE);
          trackingLayoutVP.setVisibility(View.VISIBLE);
          naviLayoutVP.setVisibility(View.GONE);
          chooseMapBtn(appSettingsVP);
          trackingBtn(appSettingsVP);
        }
        else
        {
          naviLayoutVP.setVisibility(View.VISIBLE);
          changeMapItemVP.setVisibility(View.GONE);
          trackingLayoutVP.setVisibility(View.GONE);
          alternateRoute();
          naviDirections();
        }
        appSettingsVP.setVisibility(View.VISIBLE);
        calledFromVP.setVisibility(View.INVISIBLE);
        if (getTracking().isTracking()) resetAnalyticsItem();
    }


    public ViewGroup getAppSettingsVP() {
        return appSettingsVP;
    }
    
    private Tracking getTracking()
    {
      return Tracking.getTracking(activity.getApplicationContext());
    }

    /**
     * init and implement directions checkbox
     */
    private void naviDirections() {
        CheckBox cb = (CheckBox) activity.findViewById(R.id.app_settings_directions_cb);
        final CheckBox cb_voice = (CheckBox) activity.findViewById(R.id.app_settings_voice);
        final CheckBox cb_light = (CheckBox) activity.findViewById(R.id.app_settings_light);
        final CheckBox cb_smooth = (CheckBox) activity.findViewById(R.id.app_settings_smooth);
        final CheckBox cb_showspeed = (CheckBox) activity.findViewById(R.id.app_settings_showspeed);
        final CheckBox cb_speakspeed = (CheckBox) activity.findViewById(R.id.app_settings_speakspeed);
        final TextView txt_voice = (TextView) activity.findViewById(R.id.txt_voice);
        final TextView txt_light = (TextView) activity.findViewById(R.id.txt_light);
        final TextView txt_smooth = (TextView) activity.findViewById(R.id.txt_smooth);
        final TextView txt_showspeed = (TextView) activity.findViewById(R.id.txt_showspeed);
        final TextView txt_speakspeed = (TextView) activity.findViewById(R.id.txt_speakspeed);
        cb.setChecked(Variable.getVariable().isDirectionsON());
        cb_voice.setChecked(Variable.getVariable().isVoiceON());
        cb_light.setChecked(Variable.getVariable().isLightSensorON());
        cb_smooth.setChecked(Variable.getVariable().isSmoothON());
        cb_showspeed.setChecked(Variable.getVariable().isShowingSpeedLimits());
        cb_speakspeed.setChecked(Variable.getVariable().isSpeakingSpeedLimits());
        cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Variable.getVariable().setDirectionsON(isChecked);
                cb_voice.setEnabled(isChecked);
                cb_light.setEnabled(isChecked);
                cb_smooth.setEnabled(isChecked);
                cb_showspeed.setEnabled(isChecked);
                cb_speakspeed.setEnabled(isChecked);
                txt_voice.setEnabled(isChecked);
                txt_light.setEnabled(isChecked);
                txt_smooth.setEnabled(isChecked);
                txt_showspeed.setEnabled(isChecked);
                txt_speakspeed.setEnabled(isChecked);
            }
        });
        cb_voice.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
          @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
              Variable.getVariable().setVoiceON(isChecked);
          }
        });
        cb_light.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
          @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
              Variable.getVariable().setLightSensorON(isChecked);
          }
        });
        cb_smooth.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Variable.getVariable().setSmoothON(isChecked);
                ((MapActivity)activity).ensureLocationListener(false);
            }
        });
        cb_showspeed.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Variable.getVariable().setShowSpeedLimits(isChecked);
            }
        });
        cb_speakspeed.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Variable.getVariable().setSpeakSpeedLimits(isChecked);
            }
        });
        if (!Variable.getVariable().isDirectionsON())
        {
          cb_voice.setEnabled(false);
          cb_light.setEnabled(false);
          cb_smooth.setEnabled(false);
          cb_showspeed.setEnabled(false);
          cb_speakspeed.setEnabled(false);
          txt_voice.setEnabled(false);
          txt_light.setEnabled(false);
          txt_smooth.setEnabled(false);
          txt_showspeed.setEnabled(false);
          txt_speakspeed.setEnabled(false);
        }
    }
    
    /**
     * init and set alternate route radio button option
     */
    private void alternateRoute() {
        RadioGroup rg = (RadioGroup) activity.findViewById(R.id.app_settings_weighting_rbtngroup);
        rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override public void onCheckedChanged(RadioGroup group, int checkedId) {
                switch (checkedId) {
                    case R.id.app_settings_fastest_rbtn:
                        Variable.getVariable().setWeighting("fastest");
                        break;
                    case R.id.app_settings_shortest_rbtn:
                        Variable.getVariable().setWeighting("shortest");
                        break;
                }
            }
        });
        RadioButton rbf, rbs;
        rbf = (RadioButton) activity.findViewById(R.id.app_settings_fastest_rbtn);
        rbs = (RadioButton) activity.findViewById(R.id.app_settings_shortest_rbtn);
        if (Variable.getVariable().getWeighting().equalsIgnoreCase("fastest")) {
            rbf.setChecked(true);
        } else {
            rbs.setChecked(true);
        }
    }

    /**
     * tracking item btn handler
     *
     * @param appSettingsVP
     */
    private void trackingBtn(final ViewGroup appSettingsVP) {
        //        final ImageView iv = (ImageView) activity.findViewById(R.id.app_settings_tracking_iv);
        //        final TextView tv = (TextView) activity.findViewById(R.id.app_settings_tracking_tv);
        trackingBtnClicked();
        final TextView tbtn = (TextView) activity.findViewById(R.id.app_settings_tracking_tv_switch);
        tbtn.setOnTouchListener(new View.OnTouchListener() {
            @Override public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        tbtn.setBackgroundColor(activity.getResources().getColor(R.color.my_primary_light));
                        return true;
                    case MotionEvent.ACTION_UP:
                        tbtn.setBackgroundColor(activity.getResources().getColor(R.color.my_icons));
                        if (getTracking().isTracking()) {
                            confirmWindow();
                        } else {
                            getTracking().startTracking(activity);
                        }
                        trackingBtnClicked();
                        return true;
                }
                return false;
            }
        });
        final TextView tbtnLoad = (TextView) activity.findViewById(R.id.app_settings_trackload_tv_switch);
        tbtnLoad.setTextColor(activity.getResources().getColor(R.color.my_primary));
        tbtnLoad.setOnTouchListener(new View.OnTouchListener() {
            @Override public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        tbtnLoad.setBackgroundColor(activity.getResources().getColor(R.color.my_primary_light));
                        return true;
                    case MotionEvent.ACTION_UP:
                        tbtnLoad.setBackgroundColor(activity.getResources().getColor(R.color.my_icons));
                        showLoadDialog(v.getContext());
                        return true;
                }
                return false;
            }
        });
    }
    
    private void showLoadDialog(Context context)
    {
      android.app.AlertDialog.Builder builder1 = new android.app.AlertDialog.Builder(context);
      builder1.setTitle(R.string.tracking_load);
      builder1.setCancelable(true);
      final String items[] = Variable.getVariable().getTrackingFolder().list();
      OnClickListener listener = new OnClickListener()
      {
        @Override
        public void onClick(DialogInterface dialog, int buttonNr)
        {
          String selection = items[buttonNr];
          File gpxFile = new File(Variable.getVariable().getTrackingFolder(), selection);
          getTracking().loadData(activity, gpxFile, AppSettings.this);
        }
      };
      builder1.setItems(items, listener);
      android.app.AlertDialog alert11 = builder1.create();
      alert11.show();
    }

    private void confirmWindow() {
        // 1. Instantiate an AlertDialog.Builder with its constructor
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);

        final EditText edittext = new EditText(activity);
        builder.setTitle(activity.getResources().getString(R.string.dialog_stop_save_tracking));
        builder.setMessage("path: " + Variable.getVariable().getTrackingFolder().getAbsolutePath() + "/");
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
        String formattedDate = df.format(System.currentTimeMillis());
        edittext.setText(formattedDate);
        builder.setView(edittext);

        // Inflate and set the layout for the dialog
        // Pass null as the parent view because its going in the dialog layout
        //        builder.setView(inflater.inflate(R.layout.dialog_tracking_exit, null));
        // Add action buttons
        builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override public void onClick(DialogInterface dialog, int id) {
                // save file
                getTracking().saveAsGPX(edittext.getText().toString());
                getTracking().stopTracking(AppSettings.this);
                trackingBtnClicked();
            }
        }).setNeutralButton(R.string.stop, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                getTracking().stopTracking(AppSettings.this);
                trackingBtnClicked();
            }
        }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                dialog.dismiss();
            }
        });
        // 3. Get the AlertDialog from create()
        AlertDialog dialog = builder.create();
        //        ((EditText) ((AlertDialog) dialog).findViewById(R.id.dialog_tracking_exit_et)).setText(formattedDate);
        dialog.show();
    }

    /**
     * dynamic show start or stop tracking
     */
    public void trackingBtnClicked() {
        final ImageView iv = (ImageView) activity.findViewById(R.id.app_settings_tracking_iv);
        final TextView tv = (TextView) activity.findViewById(R.id.app_settings_tracking_tv_switch);
        if (getTracking().isTracking()) {
            iv.setImageResource(R.drawable.ic_stop_orange_24dp);
            tv.setTextColor(activity.getResources().getColor(R.color.my_accent));
            tv.setText(R.string.tracking_stop);
            resetAnalyticsItem();
        } else {
            iv.setImageResource(R.drawable.ic_play_arrow_light_green_a700_24dp);
            tv.setTextColor(activity.getResources().getColor(R.color.my_primary));
            tv.setText(R.string.tracking_start);
            trackingAnalyticsVP.setVisibility(View.GONE);
            changeMapItemVP.setVisibility(View.VISIBLE);
        }
    }

    /**
     * init and reset analytics items to visible (when tracking start)
     */
    private void resetAnalyticsItem() {
        changeMapItemVP.setVisibility(View.GONE);
        trackingAnalyticsVP.setVisibility(View.VISIBLE);
        trackingAnalyticsBtn();
        tvspeed = (TextView) activity.findViewById(R.id.app_settings_tracking_tv_tracking_speed);
        tvdistance = (TextView) activity.findViewById(R.id.app_settings_tracking_tv_tracking_distance);
        tvdisunit = (TextView) activity.findViewById(R.id.app_settings_tracking_tv_tracking_distance_unit);
        updateAnalytics(getTracking().getAvgSpeed(), getTracking().getDistance());
    }

    /**
     * actions to preform when tracking analytics item (btn) is clicked
     */
    private void trackingAnalyticsBtn() {
        trackingAnalyticsVP.setOnTouchListener(new View.OnTouchListener() {
            @Override public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        trackingAnalyticsVP
                                .setBackgroundColor(activity.getResources().getColor(R.color.my_primary_light));
                        return true;
                    case MotionEvent.ACTION_UP:
                        trackingAnalyticsVP.setBackgroundColor(activity.getResources().getColor(R.color.my_icons));
                        openAnalyticsActivity(true);
                        return true;
                }
                return false;
            }
        });
    }

    /**
     * update speed and distance at analytics item
     *
     * @param speed
     * @param distance
     */
    public void updateAnalytics(double speed, double distance) {
        if (tvdistance==null || tvdisunit==null || tvspeed==null) { return; }
        if (distance < 1000) {
            tvdistance.setText(String.valueOf(Math.round(distance)));
            tvdisunit.setText(UnitCalculator.getUnit(false));
        } else {
            tvdistance.setText(String.format("%.1f", distance / 1000));
            tvdisunit.setText(UnitCalculator.getUnit(true));
        }
        tvspeed.setText(String.format("%.1f", speed));
    }


    /**
     * move to select and load map view
     *
     * @param appSettingsVP
     */
    private void chooseMapBtn(final ViewGroup appSettingsVP) {
        changeMapItemVP.setOnTouchListener(new View.OnTouchListener() {
            @Override public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        changeMapItemVP.setBackgroundColor(activity.getResources().getColor(R.color.my_primary_light));
                        return true;
                    case MotionEvent.ACTION_UP:
                        changeMapItemVP.setBackgroundColor(activity.getResources().getColor(R.color.my_icons));
                        // Variable.getVariable().setAutoLoad(false); // close auto load from
                        // main activity
                        MapActivity.isMapAlive_preFinish();
                        activity.finish();
                        startMainActivity();
                        return true;
                }
                return false;
            }
        });
    }

    /**
     * init clear btn
     */
    private void initClearBtn(final ViewGroup appSettingsVP, final ViewGroup calledFromVP) {
        ImageButton appsettingsClearBtn = (ImageButton) activity.findViewById(R.id.app_settings_clear_btn);
        appsettingsClearBtn.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                appSettingsVP.setVisibility(View.INVISIBLE);
                calledFromVP.setVisibility(View.VISIBLE);
            }
        });
    }

    /**
     * move to main activity
     */
    private void startMainActivity() {
        //        if (Tracking.getTracking().isTracking()) {
        //            Toast.makeText(activity, "You need to stop your tracking first!", Toast.LENGTH_LONG).show();
        //        } else {
        Intent intent = new Intent(activity, MainActivity.class);
        intent.putExtra("com.junjunguo.pocketmaps.activities.MapActivity.SELECTNEWMAP", true);
        activity.startActivity(intent);
        //        activity.finish();
        //        }
    }

    /**
     * open analytics activity
     */

    public void openAnalyticsActivity(boolean startTimer) {
        Analytics.startTimer = startTimer;
        Intent intent = new Intent(activity, Analytics.class);
        activity.startActivity(intent);
        //        activity.finish();
    }

    /**
     * send message to logcat
     *
     * @param str
     */
    private void log(String str) {
        Log.i(this.getClass().getName(), str);
    }

    /**
     * send message to logcat and Toast it on screen
     *
     * @param str: message
     */
    private void logToast(String str) {
        log(str);
        Toast.makeText(activity, str, Toast.LENGTH_SHORT).show();
    }
} public enum SettType {Default, Navi}; public class Destination {
    private GeoPoint startPoint, endPoint;
    private String startPointName, endPointName;
    private static Destination destination = new Destination();

    private Destination() {}

    public static Destination getDestination() {
        return destination;
    }

    /**
     * @return a string of la: latitude, lo: longitude
     */
    public String getStartPointToString() {
        if (startPoint != null) {
            //            String la = "Lat:" + String.valueOf(startPoint.latitude);
            //            la = la.length() > 12 ? la.substring(0, 12) : la;
            //            String lo = "Lon:" + String.valueOf(startPoint.longitude);
            //            lo = lo.length() > 12 ? lo.substring(0, 12) : lo;
            String la = String.valueOf(startPoint.getLatitude());
            String lo = String.valueOf(startPoint.getLongitude());
            return la + "," + lo;
        }
        return null;
    }

    /**
     * @return a string of la: latitude, lo: longitude
     */
    public String getEndPointToString() {
        if (endPoint != null) {
            //            String la = "Lat:" + String.valueOf(endPoint.latitude);
            //            la = la.length() > 12 ? la.substring(0, 12) : la;
            //            String lo = "Lon:" + String.valueOf(endPoint.longitude);
            //            lo = lo.length() > 12 ? lo.substring(0, 12) : lo;
            String la = String.valueOf(endPoint.getLatitude());
            String lo = String.valueOf(endPoint.getLongitude());
            return la + "," + lo;
        }
        return null;
    }

    public GeoPoint getStartPoint() {
        return startPoint;
    }
    
    public String getStartPointName() {
      return startPointName;
    }

    public void setStartPoint(GeoPoint startPoint, String startPointName) {
        this.startPoint = startPoint;
        this.startPointName = startPointName;
    }

    public GeoPoint getEndPoint() {
        return endPoint;
    }
    
    public String getEndPointName() {
      return endPointName;
    }

    public void setEndPoint(GeoPoint endPoint, String endPointName) {
        this.endPoint = endPoint;
        this.endPointName = endPointName;
    }
} public class SportCategory {
    private String text;
    private Integer imageId;
    private Calorie.Type sportMET;

    public SportCategory(String text, Integer imageId, Calorie.Type activityMET) {
        this.text = text;
        this.imageId = imageId;
        this.sportMET = activityMET;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Integer getImageId() {
        return imageId;
    }

    public void setImageId(Integer imageId) {
        this.imageId = imageId;
    }

    public Calorie.Type getSportMET() {
        return sportMET;
    }
} public interface MapHandlerListener {
    /**
     * when use press on the screen to get a location form map
     *
     * @param latLong
     */
    void onPressLocation(GeoPoint latLong);

    /**
     * calculate path calculating (running) true NOT running or finished false
     *
     * @param shortestPathRunning
     */
    void pathCalculating(boolean shortestPathRunning);
} public interface NavigatorListener {
    /**
     * the change on navigator: navigation is used or not
     *
     * @param on
     */
    void onStatusChanged(boolean on);
    
    void onNaviStart(boolean on);
} public interface OnClickAddressListener {
    /**
     * tell Activity what to do when address is clicked
     *
     * @param view
     */
    void onClick(Address addr);
} public class NaviEngine
{
  /** The DEBUG_SIMULATOR will simulate first generated route on naviStart and trackingStart. **/
  private static final double MAX_WAY_TOLERANCE = GeoMath.DEGREE_PER_METER * 30.0;
  private static final double MAX_WAY_TOLERANCE_METER = 30.0;

  private static final int BEST_NAVI_ZOOM = 18;
  enum UiJob { Nothing, RecalcPath, UpdateInstruction, Finished };
  private UiJob uiJob = UiJob.Nothing;
  private boolean directTargetDir = false;
  NaviVoice naviVoice;
  LightSensor lightSensor;
  SpeedUtil speedUtil = new SpeedUtil();
  boolean naviVoiceSpoken = false;
  private GeoPoint recalcFrom, recalcTo;
  private static NaviEngine instance;
  private boolean mapUpdatesAllowed = true;
  private Location pos;
  final PointPosData nearestP = new PointPosData(); 
  private boolean active = false;
  private InstructionList instructions;
  private ImageView navtop_image;
  private TextView navtop_curloc;
  private TextView navtop_nextloc;
  private TextView navtop_when;
  private TextView navtop_time;
  private TextView navspeed_text;
  private float tiltMult = 1.0f;
  private float tiltMultPos = 1.0f;
  GHAsyncTask<GeoPoint, NaviInstruction, NaviInstruction> naviEngineTask;
  private double partDistanceScaler = 1.0;

  public static NaviEngine getNaviEngine()
  {
    if (instance == null) { instance = new NaviEngine(); }
    return instance;
  }
  
  public boolean isNavigating()
  {
    return active;
  }
  
  /** Ensures, that voice is initialized. Use forceReset for switching engine **/
  public void naviVoiceInit(Context appContext, boolean forceReset)
  {
    if (naviVoice == null)
    {
      naviVoice = new NaviVoice(appContext);
    }
    else if (forceReset)
    {
      naviVoice.shutdownTts();
      naviVoice = new NaviVoice(appContext);
    }
  }
  
  public ArrayList<String> naviVoiceList(Context appContext)
  {
      naviVoiceInit(appContext, false);
      return naviVoice.getVoiceListCompat();
  }
  
  public ArrayList<String> naviVoiceEngineList(Context appContext)
  {
      naviVoiceInit(appContext, false);
      return naviVoice.getEngineList();
  }
  
  public void naviVoiceSpeak(Context appContext, String fallbackTxt, String txt, boolean forceResetVoice)
  {
      naviVoiceInit(appContext, false);
      if (forceResetVoice) { naviVoice.curVoice = null; }
      naviVoice.speak(fallbackTxt, txt);
  }
  
  public boolean naviVoiceIsTtsReady(Context appContext)
  {
      naviVoiceInit(appContext, false);
      return naviVoice.isTtsReady();
  }
  
  public void setNavigating(Activity activity, boolean active)
  {
    this.active = active;
    if (active && lightSensor==null)
    {
      lightSensor = new LightSensor(activity);
    }
    else if ((!active) && lightSensor!=null)
    {
      lightSensor.cleanup(activity);
      lightSensor = null;
    }
    speedUtil.setEnabled(active && Variable.getVariable().isShowingSpeedLimits());
    naviVoiceInit(activity, false);
    if (active == false)
    {
      MapHandler.getMapHandler().showNaviCenterBtn(false);
      MapHandler.getMapHandler().setCustomPointIcon(activity, R.drawable.ic_my_location_dark_24dp);
      if (pos != null)
      {
        GeoPoint curPos = new GeoPoint(pos.getLatitude(), pos.getLongitude());
        MapHandler.getMapHandler().centerPointOnMap(curPos, BEST_NAVI_ZOOM, 0, 0);
      }
      NaviDebugSimulator.getSimu().setSimuRun(false);
      return;
    }
    mapUpdatesAllowed = true;
    MapHandler.getMapHandler().setCustomPointIcon(activity, R.drawable.ic_navigation_black_24dp);
    naviVoiceSpoken = false;
    uiJob = UiJob.Nothing;
    initFields(activity);
    instructions = Navigator.getNavigator().getGhResponse().getInstructions();
    speedUtil.updateList(Navigator.getNavigator().getGhResponse().getPathDetails());
    resetNewInstruction();
    if (instructions.size() > 0)
    {
      startDebugSimulator(activity, false);
    }
  }
  
  /** This allows to update pathLayer, when instructions is just a line from target to curLoc.
   *  @param directTargetDir True to update pathLayer, join to curPos. **/
  public void setDirectTargetDir(boolean directTargetDir)
  {
    this.directTargetDir = directTargetDir;
  }
  
  public void onUpdateInstructions(InstructionList instructions, Map<String, List<PathDetail>> pathDetails)
  {
    if (uiJob != UiJob.RecalcPath) { throw new IllegalStateException("Getting instructions but state is not RecalcPath!"); }
    if (!directTargetDir)
    {
      nearestP.checkDirectionOk(pos, instructions.get(0), naviVoice);
    }
    this.instructions = instructions;
    this.speedUtil.updateList(pathDetails);
    getNewInstruction();
    uiJob = UiJob.UpdateInstruction;
  }
  
  public void startDebugSimulator(Activity activity, boolean fromTracking)
  {
    NaviDebugSimulator.getSimu().startDebugSimulator(activity, instructions, fromTracking);
  }

  private GeoPoint findClosestStreet(GeoPoint fromPos)
  {
    if (MapHandler.getMapHandler().getHopper() == null) { return fromPos; } // Not loaded yet!
    QueryResult pos = MapHandler.getMapHandler().getHopper().getLocationIndex().findClosest(fromPos.getLatitude(), fromPos.getLongitude(), EdgeFilter.ALL_EDGES);
    int n = pos.getClosestEdge().getBaseNode();
    NodeAccess nodeAccess = MapHandler.getMapHandler().getHopper().getGraphHopperStorage().getNodeAccess();
    GeoPoint gp = new GeoPoint(nodeAccess.getLat(n), nodeAccess.getLon(n));
    return gp;
  }
  
  private void initFields(Activity activity)
  {
    navtop_image = activity.findViewById(R.id.navtop_image);
    navtop_curloc = activity.findViewById(R.id.navtop_curloc);
    navtop_nextloc = activity.findViewById(R.id.navtop_nextloc);
    navtop_when = activity.findViewById(R.id.navtop_when);
    navtop_time = activity.findViewById(R.id.navtop_time);
    navspeed_text = activity.findViewById(R.id.speed_sign_text);
    speedUtil.initTextView(navspeed_text);
  }
  
  public void setMapUpdatesAllowed(Context appContext, boolean allowed)
  {
      if (this.mapUpdatesAllowed != allowed)
      {
        this.mapUpdatesAllowed = allowed;
        MapHandler.getMapHandler().showNaviCenterBtn(!allowed);
        if (allowed)
        {
          MapHandler.getMapHandler().centerPointOnMap(new GeoPoint(pos.getLatitude(), pos.getLongitude()), BEST_NAVI_ZOOM, 0, 0);
          MapHandler.getMapHandler().setCustomPointIcon(appContext, R.drawable.ic_navigation_black_24dp);
        }
        else
        {
          MapHandler.getMapHandler().resetTilt(0);
          MapHandler.getMapHandler().setCustomPointIcon(appContext, R.drawable.ic_my_location_dark_24dp);
        }
      }
  }
  
  @UiThread
  public void updatePosition(Activity activity, Location pos)
  {
    if (active == false) { return; }
    if (uiJob == UiJob.RecalcPath) { return; }
    if (this.pos == null) { this.pos = new Location((String)null); }
    this.pos.set(pos);
    GeoPoint curPos = new GeoPoint(pos.getLatitude(), pos.getLongitude());
    GeoPoint newCenter = curPos.destinationPoint(70.0 * tiltMultPos, pos.getBearing());
    if (mapUpdatesAllowed)
    {
      MapHandler.getMapHandler().centerPointOnMap(newCenter, BEST_NAVI_ZOOM, 360.0f - pos.getBearing(), 45.0f * tiltMult);
    }
    
    calculatePositionAsync(activity, curPos);
  }

  @UiThread
  private void calculatePositionAsync(Activity activity, GeoPoint curPos)
  {
    if (naviEngineTask == null) { createNaviEngineTask(activity); }
    updateDirectTargetDir(curPos);
    if (naviEngineTask.getStatus() == Status.RUNNING)
    {
      log("Error, NaviEngineTask is still running! Drop job ...");
    }
    else if (naviEngineTask.hasError())
    {
      naviEngineTask.getError().printStackTrace();
    }
    else
    {
      createNaviEngineTask(activity); //TODO: Recreation of Asynctask seems necessary?!
      naviEngineTask.execute(curPos);
    }
  }
  
  private void updateDirectTargetDir(GeoPoint curPos)
  {
    if (!directTargetDir) { return; }
    MapHandler.getMapHandler().joinPathLayerToPos(curPos.getLatitude(), curPos.getLongitude());
  }

  @UiThread
  private void createNaviEngineTask(final Activity activity)
  {
    naviEngineTask = new GHAsyncTask<GeoPoint, NaviInstruction, NaviInstruction>()
    {
      @Override
      protected NaviInstruction saveDoInBackground(GeoPoint... params) throws Exception
      {
        return calculatePosition(params[0]);
      }
      
      @Override
      protected void onPostExecute(NaviInstruction in)
      {
        if (in == null)
        {
          if (NaviEngine.this.uiJob == UiJob.RecalcPath)
          {
            if (instructions != null)
            {
              instructions = null;
              MapHandler.getMapHandler().calcPath(recalcFrom.getLatitude(), recalcFrom.getLongitude(), recalcTo.getLatitude(), recalcTo.getLongitude(), activity);
              log("Recalculating of path!!!");
            }
          }
          else if (NaviEngine.this.uiJob == UiJob.Finished)
          {
            active = false;
          }
        }
        else if (NaviEngine.this.uiJob == UiJob.UpdateInstruction)
        {
          showInstruction(in);
        }
      }
    };
  }

  @UiThread
  private void showInstruction(NaviInstruction in)
  {
    if (in==null)
    {
      navtop_when.setText("0 " + UnitCalculator.getUnit(false));
      navtop_time.setText("--------");
      navtop_curloc.setText(R.string.search_location);
      navtop_nextloc.setText("==================");
      navtop_image.setImageResource(R.drawable.ic_2x_continue_on_street);
      setTiltMult(1);
    }
    else if(nearestP.isDirectionOk())
    {
      navtop_when.setText(in.getNextDistanceString());
      navtop_time.setText(in.getFullTimeString());
      navtop_curloc.setText(in.getCurStreet());
      navtop_nextloc.setText(in.getNextInstruction());
      navtop_image.setImageResource(in.getNextSignResource());
      speedUtil.updateTextView(nearestP.arrPos);
      setTiltMult(in.getNextDistance());
    }
    else
    {
      navtop_when.setText("0 " + UnitCalculator.getUnit(false));
      navtop_time.setText(in.getFullTimeString());
      navtop_curloc.setText(R.string.wrong_direction);
      navtop_nextloc.setText("==================");
      navtop_image.setImageResource(R.drawable.ic_2x_roundabout);
      setTiltMult(1);
    }
  }
  
  /** When next instruction has a distance of 400m then rotate tilt to see 400m far.
   *  Alternatively raise tilt on fast speed. **/
  private void setTiltMult(double nextDist)
  {
    double speedXtra = 0;
    if (pos!=null) { speedXtra = pos.getSpeed(); }
    if (speedXtra > 30.0) { speedXtra = 2; }
    else if (speedXtra < 8.0) { speedXtra = 0; }
    else
    {
      speedXtra = speedXtra - 8.0; // 0 - 22
      speedXtra = speedXtra / 22.0; // 0 - 1
      speedXtra = speedXtra * 2.0; // 0 - 2
    }
    if (nextDist > 400) { nextDist = 0; }
    else if (nextDist < 100) { nextDist = 0; }
    else
    {
      nextDist = nextDist - 100.0; // 0 - 300
      nextDist = nextDist / 300.0; // 0 - 1
      nextDist = nextDist * 2.0; // 0 - 2
    }
    if (speedXtra > nextDist) { nextDist = speedXtra; }
    tiltMultPos = (float)(1.0 + (nextDist * 0.5));
    tiltMult = (float)(1.0 + nextDist);
  }
  
  @WorkerThread
  private NaviInstruction calculatePosition(GeoPoint curPos)
  {
    if (uiJob == UiJob.RecalcPath) { return null; }
    if (uiJob == UiJob.Finished) { return null; }
    nearestP.setBaseData(getNearestPoint(instructions.get(0), nearestP.arrPos, curPos));
    
    if (nearestP.arrPos > 0)
    { // Check dist to line (backward)
      double lat1 = instructions.get(0).getPoints().getLatitude(nearestP.arrPos);
      double lon1 = instructions.get(0).getPoints().getLongitude(nearestP.arrPos);
      double lat2 = instructions.get(0).getPoints().getLatitude(nearestP.arrPos-1);
      double lon2 = instructions.get(0).getPoints().getLongitude(nearestP.arrPos-1);
      double lDist = GeoMath.distToLineSegment(curPos.getLatitude(), curPos.getLongitude(), lat1, lon1, lat2, lon2);
      if (lDist < nearestP.distance)
      {
        nearestP.distance = lDist;
        nearestP.status = PointPosData.Status.CurPosIsBackward;
      }
    }
    if (nearestP.arrPos < instructions.get(0).getPoints().size()-1)
    { // Check dist to line (forward)
      double lat1 = instructions.get(0).getPoints().getLatitude(nearestP.arrPos);
      double lon1 = instructions.get(0).getPoints().getLongitude(nearestP.arrPos);
      double lat2 = instructions.get(0).getPoints().getLatitude(nearestP.arrPos+1);
      double lon2 = instructions.get(0).getPoints().getLongitude(nearestP.arrPos+1);
      double lDist = GeoMath.distToLineSegment(curPos.getLatitude(), curPos.getLongitude(), lat1, lon1, lat2, lon2);
      if (lDist < nearestP.distance)
      {
        nearestP.distance = lDist;
        nearestP.status = PointPosData.Status.CurPosIsForward;
      }
    }
    else if (nearestP.arrPos == instructions.get(0).getPoints().size()-1 &&
             instructions.size()>1)
    {
      if (instructions.get(1).getPoints().size() > 0)
      { // Check dist to line (forward to next instruction)
        double lat1 = instructions.get(0).getPoints().getLatitude(nearestP.arrPos);
        double lon1 = instructions.get(0).getPoints().getLongitude(nearestP.arrPos);
        double lat2 = instructions.get(1).getPoints().getLatitude(0);
        double lon2 = instructions.get(1).getPoints().getLongitude(0);
        double lDist = GeoMath.distToLineSegment(curPos.getLatitude(), curPos.getLongitude(), lat1, lon1, lat2, lon2);
        if (lDist < nearestP.distance)
        {
          nearestP.distance = lDist;
          nearestP.status = PointPosData.Status.CurPosIsForward;
        }
      }
      if (instructions.get(1).getPoints().size() > 1)
      { // Check dist to line (forward next instruction p1+p2)
        double lat1 = instructions.get(1).getPoints().getLatitude(0);
        double lon1 = instructions.get(1).getPoints().getLongitude(0);
        double lat2 = instructions.get(1).getPoints().getLatitude(1);
        double lon2 = instructions.get(1).getPoints().getLongitude(1);
        double lDist = GeoMath.distToLineSegment(curPos.getLatitude(), curPos.getLongitude(), lat1, lon1, lat2, lon2);
        if (lDist < nearestP.distance)
        {
          nearestP.distance = lDist;
          nearestP.status = PointPosData.Status.CurPosIsForwardNext;
        }
      }
    }
    if (nearestP.isForward())
    {
      // Reset bearing with calculatePosition()
      nearestP.resetDirectionOk();
    }
    if (!nearestP.isDistanceOk())
    {
      double maxWayTolMeters = MAX_WAY_TOLERANCE_METER;
      if (directTargetDir) { maxWayTolMeters = maxWayTolMeters * 10.0; }
      Instruction nearestNext = instructions.find(curPos.getLatitude(), curPos.getLongitude(), maxWayTolMeters);
      if (nearestNext == null)
      {
        GeoPoint closestP = findClosestStreet(curPos);
        nearestNext = instructions.find(closestP.getLatitude(), closestP.getLongitude(), maxWayTolMeters);
      }
      if (nearestNext == null)
      {
        uiJob = UiJob.RecalcPath;
        recalcFrom = curPos;
        Instruction lastInstruction = instructions.get(instructions.size()-1);
        int lastPoint = lastInstruction.getPoints().size()-1;
        double lastPointLat = lastInstruction.getPoints().getLat(lastPoint);
        double lastPointLon = lastInstruction.getPoints().getLon(lastPoint);
        recalcTo = new GeoPoint(lastPointLat, lastPointLon);
log("NaviTask Start recalc !!!!!!");
        return null;
      }
      else
      { // Forward to nearest instruction.
        int deleteCounter = 0;
        Instruction lastDeleted = null;
        
        while (instructions.size()>0 && !instructions.get(0).equals(nearestNext))
        {
          deleteCounter++;
          lastDeleted = instructions.remove(0);
        }
        if (lastDeleted != null)
        { // Because we need the current, and not the next Instruction
          instructions.add(0, lastDeleted);
          deleteCounter --;
        }
        if (deleteCounter == 0)
        {
          PointPosData newNearestP = getNearestPoint(instructions.get(0), 0, curPos);
          //TODO: Continue-Instruction with DirectionInfo: getContinueInstruction() ?
log("NaviTask Start update far !!!!!!");
          return getUpdatedInstruction(curPos, newNearestP);
        }
log("NaviTask Start update skip-mult-" + deleteCounter + " !!!!!!");
        return getNewInstruction();
      }
    }
    else if (nearestP.isForwardNext())
    {
      speedUtil.updateInstructionDone(instructions.get(0).getPoints().size());
      instructions.remove(0);
log("NaviTask Start skip-next !!!!!!");
      return getNewInstruction();
    }
    else
    {
      // NaviTask Start update!
      return getUpdatedInstruction(curPos, nearestP);
    }
  }

  private static PointPosData getNearestPoint(Instruction instruction, int curPointPos, GeoPoint curPos)
  {
    int nextPointPos = curPointPos;
    int nearestPointPos = curPointPos;
    double nearestDist = Double.MAX_VALUE;
    while (instruction.getPoints().size() > nextPointPos)
    {
      double lat = instruction.getPoints().getLatitude(nextPointPos);
      double lon = instruction.getPoints().getLongitude(nextPointPos);
      double dist = GeoMath.fastDistance(curPos.getLatitude(), curPos.getLongitude(), lat, lon);
      if (dist < nearestDist)
      {
        nearestDist = dist;
        nearestPointPos = nextPointPos;
      }
      nextPointPos++;
    }
    PointPosData p = new PointPosData();
    p.arrPos = nearestPointPos;
    p.distance = nearestDist;
    return p;
  }

  private NaviInstruction getNewInstruction()
  {
    nearestP.arrPos = 0;
    nearestP.distance = Double.MAX_VALUE;
    uiJob = UiJob.UpdateInstruction;
    if (instructions.size() > 0)
    {
      Instruction in = instructions.get(0);
      long fullTime = countFullTime(in.getTime());
      GeoPoint curPos = new GeoPoint(in.getPoints().getLat(0), in.getPoints().getLon(0));
      double partDistance = countPartDistance(curPos, in, 0);
      if (partDistance == 0) { partDistanceScaler = 1; }
      else
      {
        partDistanceScaler = in.getDistance() / partDistance;
      }
      Instruction nextIn = null;
      if (instructions.size() > 1) { nextIn = instructions.get(1); }
      NaviInstruction nIn = new NaviInstruction(in, nextIn, fullTime);
      if (speakDistanceCheck(in.getDistance()) && nearestP.isDirectionOk())
      {
        naviVoice.speak(nIn.getVoiceTextFallback(), nIn.getVoiceText());
        naviVoiceSpoken = true;
      }
      else
      {
        naviVoiceSpoken = false;
      }
      return nIn;
    }
    uiJob = UiJob.Finished;
    return null;
  }
  
  private NaviInstruction getUpdatedInstruction(GeoPoint curPos, PointPosData nearestP)
  {
    uiJob = UiJob.UpdateInstruction;
    if (instructions.size() > 0)
    {
      Instruction in = instructions.get(0);
      long partTime = 0;
      double partDistance = countPartDistance(curPos, in, nearestP.arrPos);
      partDistance = partDistance * partDistanceScaler;
      if (in.getDistance() <= partDistance)
      {
        partDistance = in.getDistance();
        partTime = in.getTime();
      }
      else
      {
        double partValue = partDistance / in.getDistance();
        partTime = (long)(in.getTime() * partValue);
      }
      long fullTime = countFullTime(partTime);
      Instruction nextIn = null;
      if (instructions.size() > 1) { nextIn = instructions.get(1); }
      NaviInstruction newIn = new NaviInstruction(in, nextIn, fullTime);
      newIn.updateDist(partDistance);
      if (!naviVoiceSpoken && nearestP.isDirectionOk() && speakDistanceCheck(partDistance))
      {
        naviVoice.speak(newIn.getVoiceTextFallback(), newIn.getVoiceText());
        naviVoiceSpoken = true;
      }
      return newIn;
    }
    return null;
  }
  
  public void setNaviVoiceMute(boolean mute)
  {
    if (naviVoice!= null)
    {
      naviVoice.setTtsMute(mute);
    }
  }
  
  @UiThread
  private void resetNewInstruction()
  {
    nearestP.arrPos = 0;
    nearestP.distance = Double.MAX_VALUE;
    uiJob = UiJob.UpdateInstruction;
    showInstruction(null);
  }
  
  private boolean speakDistanceCheck(double dist)
  {
    if (dist < 200) { return true; }
    if (pos.getSpeed() > 150 * GeoMath.KMH_TO_MSEC)
    {
      if (dist < 1500) { return true; }
    }
    else if (pos.getSpeed() > 100 * GeoMath.KMH_TO_MSEC)
    {
      if (dist < 900) { return true; }
    }
    else if (pos.getSpeed() > 70 * GeoMath.KMH_TO_MSEC)
    {
      if (dist < 500) { return true; }
    }
    else if (pos.getSpeed() > 30 * GeoMath.KMH_TO_MSEC)
    {
      if (dist < 350) { return true; }
    }
    return false;
  }

  /** Count all time of Instructions.
   * @param partTime Time of current Instruction. **/
  private long countFullTime(long partTime)
  {
    long fullTime = partTime;
    for (int i=1; i<instructions.size(); i++)
    {
      fullTime += instructions.get(i).getTime();
    }
    return fullTime;
  }
  
  /** Counts the estimated rest-distance to next instruction. **/
  private double countPartDistance(GeoPoint curPos, Instruction in, int nearestPointPos)
  {
    double partDistance = 0;
    double lastLat = curPos.getLatitude();
    double lastLon = curPos.getLongitude();
    for (int i=nearestPointPos+1; i<in.getPoints().size(); i++)
    {
      double nextLat = in.getPoints().getLat(i);
      double nextLon = in.getPoints().getLon(i);
      partDistance += GeoMath.fastDistance(lastLat, lastLon, nextLat, nextLon);
      lastLat = nextLat;
      lastLon = nextLon;
    }
    partDistance = partDistance * GeoMath.METER_PER_DEGREE;
    return partDistance;
  }

  private static void log(String str)
  {
    Log.i(NaviEngine.class.getName(), str);
  }
  
  static class PointPosData
  {
    public enum Status { CurPosIsExactly, CurPosIsBackward, CurPosIsForward, CurPosIsForwardNext };
    public int arrPos;
    public double distance;
    public Status status = Status.CurPosIsExactly;
    private boolean wrongDir = false;
    private boolean wrongDirHint = false;
    public boolean isDistanceOk()
    {
      return (distance < MAX_WAY_TOLERANCE);
    }
    public boolean isBackward() { return (status == Status.CurPosIsBackward); }
    public boolean isForward() { return (status == Status.CurPosIsForward); }
    public boolean isForwardNext() { return (status == Status.CurPosIsForwardNext); }
    public boolean isDirectionOk() { return (!wrongDir); }
    public void resetDirectionOk()
    {
      if (!isDistanceOk()) { return; }
      wrongDirHint = false;
      wrongDir = false;
    }
    public void checkDirectionOk(Location pos, Instruction in, NaviVoice v)
    {
      calculateWrongDir(pos, in);
      if (wrongDir)
      {
        if (wrongDirHint) { return; }
        wrongDirHint = true;
        v.speak("Wrong direction", NaviText.sWrongDir);
      }
    }
    
    private void calculateWrongDir(Location pos, Instruction in)
    {
      if (in.getPoints().size()<2) { return; }
      if (!wrongDir)
      {
        GeoPoint pathP1 = new GeoPoint(in.getPoints().getLat(0), in.getPoints().getLon(0));
        GeoPoint pathP2 = new GeoPoint(in.getPoints().getLat(1), in.getPoints().getLon(1));
        double bearingOk = pathP1.bearingTo(pathP2);
        double bearingCur = pos.getBearing();
        double bearingDiff = bearingOk - bearingCur;
        if (bearingDiff < 0) { bearingDiff += 360.0; } //Normalize
        if (bearingDiff > 180) { bearingDiff = 360.0 - bearingDiff; } //Normalize
        wrongDir = (bearingDiff > 100);
log("Compare bearing cur=" + bearingCur + " way=" + bearingOk + " wrong=" + wrongDir);
      }
    }
    
    public void setBaseData(PointPosData p)
    {
      this.arrPos = p.arrPos;
      this.distance = p.distance;
      this.status = p.status;
      if (arrPos > 0)
      {
        resetDirectionOk();
      }
    }
  }
} public class MapHandler
{
  private static MapHandler mapHandler;
  private volatile boolean prepareInProgress = false;
  private volatile boolean calcPathActive = false;
  MapPosition tmpPos = new MapPosition();
  private GeoPoint startMarker;
  private GeoPoint endMarker;
  private boolean needLocation = false;
  private MapView mapView;
  private ItemizedLayer<MarkerItem> itemizedLayer;
  private ItemizedLayer<MarkerItem> customLayer;
  private PathLayer pathLayer;
  private PathLayer polylineTrack;
  private GraphHopper hopper;
  private MapHandlerListener mapHandlerListener;
  private String currentArea;
  File mapsFolder;
  FloatingActionButton naviCenterBtn;
  PointList trackingPointList = new PointList();
  private int customIcon = R.drawable.ic_my_location_dark_24dp;
  private MapFileTileSource tileSource;
  /**
   * need to know if path calculating status change; this will trigger MapActions function
   */
  private boolean needPathCal;
  
  public static MapHandler getMapHandler()
  {
    if (mapHandler == null)
    {
      reset();
    }
    return mapHandler;
  }

   /**
    * reset class, build a new instance
    */
  public static void reset()
  {
    mapHandler = new MapHandler();
  }

  private MapHandler()
  {
    setCalculatePath(false,false);
    startMarker = null;
    endMarker = null;
    needLocation = false;
    needPathCal = false;
  }

  public void init(MapView mapView, String currentArea, File mapsFolder)
  {
    this.mapView = mapView;
    this.currentArea = currentArea;
    this.mapsFolder = mapsFolder; // path/to/map/area-gh/
  }
  
  public MapFileTileSource getTileSource() { return tileSource; }

  /**
   * load map to mapView
   *
   * @param areaFolder
   */
  public void loadMap(File areaFolder, MapActivity activity)
  {
    logUser(activity, "loading map");

    // Map events receiver
    mapView.map().layers().add(new MapEventsReceiver(mapView.map()));

    // Map file source
    tileSource = new MapFileTileSource();
    tileSource.setMapFile(new File(areaFolder, currentArea + ".map").getAbsolutePath());
    VectorTileLayer l = mapView.map().setBaseMap(tileSource);
    mapView.map().setTheme(VtmThemes.DEFAULT);
    mapView.map().layers().add(new BuildingLayer(mapView.map(), l));
    mapView.map().layers().add(new LabelLayer(mapView.map(), l));
    
    // Markers layer
    itemizedLayer = new ItemizedLayer<>(mapView.map(), (MarkerSymbol) null);
    mapView.map().layers().add(itemizedLayer);
    customLayer = new ItemizedLayer<>(mapView.map(), (MarkerSymbol) null);
    mapView.map().layers().add(customLayer);

    // Map position
    GeoPoint mapCenter = tileSource.getMapInfo().boundingBox.getCenterPoint();
    mapView.map().setMapPosition(mapCenter.getLatitude(), mapCenter.getLongitude(), 1 << 12);

    ViewGroup.LayoutParams params =
        new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    activity.addContentView(mapView, params);
    
    loadGraphStorage(activity);
  }
  
  void loadGraphStorage(final MapActivity activity) {
      logUser(activity, "loading graph (" + Constants.VERSION + ") ... ");
      new GHAsyncTask<Void, Void, Path>() {
          protected Path saveDoInBackground(Void... v) throws Exception {
              GraphHopper tmpHopp = new GraphHopper().forMobile();
              // Why is "shortest" missing in default config? Add!
              tmpHopp.getCHFactoryDecorator().addCHProfileAsString("shortest");
              tmpHopp.load(new File(mapsFolder, currentArea).getAbsolutePath() + "-gh");
              log("found graph " + tmpHopp.getGraphHopperStorage().toString() + ", nodes:" + tmpHopp.getGraphHopperStorage().getNodes());
              hopper = tmpHopp;
              return null;
          }

          protected void onPostExecute(Path o) {
              if (hasError()) {
                  logUser(activity, "An error happened while creating graph:"
                          + getErrorMessage());
              } else {
                  logUser(activity, "Finished loading graph.");
              }

              GeoPoint g = ShowLocationActivity.locationGeoPoint;
              String lss = ShowLocationActivity.locationSearchString;
              if (g != null)
              {
                activity.getMapActions().onPressLocationEndPoint(g);
                ShowLocationActivity.locationGeoPoint = null;
              }
              else if (lss != null)
              {
                activity.getMapActions().startGeocodeActivity(null, null, false, false);
              }
              prepareInProgress = false;
          }
      }.execute();
  }
  
  public AllEdgesIterator getAllEdges()
  {
    if (hopper==null) { return null; }
    if (hopper.getGraphHopperStorage()==null) { return null; }
    return hopper.getGraphHopperStorage().getAllEdges();
  }

  /**
   * center the LatLong point in the map and zoom map to zoomLevel
   *
   * @param latLong
   * @param zoomLevel (if 0 use current zoomlevel)
   */
  public void centerPointOnMap(GeoPoint latLong, int zoomLevel, float bearing, float tilt)
  {
    if (zoomLevel == 0)
    {
      zoomLevel = mapView.map().getMapPosition().zoomLevel;
    }
    double scale = 1 << zoomLevel;
    tmpPos.setPosition(latLong);
    tmpPos.setScale(scale);
    tmpPos.setBearing(bearing);
    tmpPos.setTilt(tilt);
    mapView.map().animator().animateTo(300, tmpPos);
  }
  
  public void resetTilt(float tilt)
  {
      mapView.map().setMapPosition(mapView.map().getMapPosition().setTilt(tilt));
  }

  /**
   * @return
   */
  public boolean isNeedLocation()
  {
    return needLocation;
  }

  /**
   * set in need a location from screen point (touch)
   *
   * @param needLocation
   */
  public void setNeedLocation(boolean needLocation)
  {
    this.needLocation = needLocation;
  }

  /** Set start or end Point-Marker.
   *  @param p The Point to set, or null.
   *  @param isStart True for startpoint false for endpoint.
   *  @param recalculate True to calculate path, when booth points are set.
   *  @return Whether the path will be recalculated. **/
  public boolean setStartEndPoint(Activity activity, GeoPoint p, boolean isStart, boolean recalculate)
  {
    boolean result = false;
    boolean refreshBoth = false;
    if (startMarker!=null && endMarker!=null && p!=null) { refreshBoth = true; }
      
    if (isStart)
    {
      startMarker = p;
    }
    else { endMarker = p; }

    // remove routing layers
    if ((startMarker==null || endMarker==null) || refreshBoth)
    {
      if (pathLayer!=null) { pathLayer.clearPath(); }
      itemizedLayer.removeAllItems();
    }
    if (startMarker!=null)
    {
      itemizedLayer.addItem(createMarkerItem(activity, startMarker, R.drawable.ic_location_start_24dp, 0.5f, 1.0f));
    }
    if (endMarker!=null)
    {
      itemizedLayer.addItem(createMarkerItem(activity, endMarker, R.drawable.ic_location_end_24dp, 0.5f, 1.0f));
    }
    if (startMarker!=null && endMarker!=null && recalculate)
    {
      recalcPath(activity);
      result = true;
    }
    mapView.map().updateMap(true);
    return result;
  }
  
  public void recalcPath(Activity activity)
  {
    setCalculatePath(true, true);
    calcPath(startMarker.getLatitude(), startMarker.getLongitude(), endMarker.getLatitude(), endMarker.getLongitude(), activity);
  }

  /** Set the custom Point for current location, or null to delete.
   *  Sets the offset to center. **/
  public void setCustomPoint(Activity activity, GeoPoint p)
  {
    if (customLayer==null) { return; } // Not loaded yet.
    customLayer.removeAllItems();
    if (p!=null)
    {
      customLayer.addItem(createMarkerItem(activity, p,customIcon, 0.5f, 0.5f));
      mapView.map().updateMap(true);
    }
  }
  
  public void setCustomPointIcon(Context appContext, int customIcon)
  {
    this.customIcon = customIcon;
    if (customLayer.getItemList().size() > 0)
    { // RefreshIcon
      MarkerItem curSymbol = customLayer.getItemList().get(0);
      MarkerSymbol marker = createMarkerItem(appContext, new GeoPoint(0,0), customIcon, 0.5f, 0.5f).getMarker();
      curSymbol.setMarker(marker);
    }
  }

  private MarkerItem createMarkerItem(Context appContext, GeoPoint p, int resource, float offsetX, float offsetY) {
//      Drawable drawable = activity.getDrawable(resource); // Since API21
      Drawable drawable = ContextCompat.getDrawable(appContext, resource);
      Bitmap bitmap = AndroidGraphics.drawableToBitmap(drawable);
      MarkerSymbol markerSymbol = new MarkerSymbol(bitmap, offsetX, offsetY);
      MarkerItem markerItem = new MarkerItem("", "", p);
      markerItem.setMarker(markerSymbol);
      return markerItem;
  }

    /**
     * @return true if already loaded
     */
    boolean isReady() {
      return !prepareInProgress;
    }

    /**
     * start tracking : reset polylineTrack & trackingPointList & remove polylineTrack if exist
     */
    public void startTrack(Activity activity) {
        if (polylineTrack != null) {
            removeLayer(mapView.map().layers(), polylineTrack);
        }
        polylineTrack = null;
        trackingPointList.clear();
        if (polylineTrack != null) { polylineTrack.clearPath(); }
        polylineTrack = updatePathLayer(activity, polylineTrack, trackingPointList, 0x99003399, 4);
        NaviEngine.getNaviEngine().startDebugSimulator(activity, true);
    }

    /**
     * add a tracking point
     *
     * @param point
     */
    public void addTrackPoint(Activity activity, GeoPoint point) {
      trackingPointList.add(point.getLatitude(), point.getLongitude());
      updatePathLayer(activity, polylineTrack, trackingPointList, 0x9900cc33, 4);
      mapView.map().updateMap(true);
    }
    
  /**
   * remove a layer from map layers
   *
   * @param layers
   * @param layer
   */
  public static void removeLayer(Layers layers, Layer layer)
  {
    if (layers != null && layer != null && layers.contains(layer))
    {
      layers.remove(layer);
    }
  }

    public boolean isCalculatingPath() {
        return calcPathActive;
    }

    private void setCalculatePath(boolean calcPathActive, boolean callListener) {
        this.calcPathActive = calcPathActive;
        if (mapHandlerListener != null && needPathCal && callListener) mapHandlerListener.pathCalculating(calcPathActive);
    }

    public void setNeedPathCal(boolean needPathCal) {
        this.needPathCal = needPathCal;
    }

    /**
     * Get the hopper object, that may be null while loading map.
     * @return GraphHopper object
     */
    public GraphHopper getHopper() {
        return hopper;
    }

    /**
     * assign a new GraphHopper
     *
     * @param hopper
     */
    public void setHopper(GraphHopper hopper) {
        this.hopper = hopper;
    }

    /**
     * only tell on object
     *
     * @param mapHandlerListener
     */
    public void setMapHandlerListener(MapHandlerListener mapHandlerListener) {
        this.mapHandlerListener = mapHandlerListener;
    }

    public void calcPath(final double fromLat, final double fromLon,
                         final double toLat, final double toLon, final Activity activity) {
        setCalculatePath(true, false);
        log("calculating path ...");
        new AsyncTask<Void, Void, GHResponse>() {
            float time;

            @Override
            protected GHResponse doInBackground(Void... v) {
              StopWatch sw = new StopWatch().start();
              GHRequest req = new GHRequest(fromLat, fromLon, toLat, toLon).
                      setAlgorithm(Algorithms.DIJKSTRA_BI);
              req.getHints().put(Routing.INSTRUCTIONS, Variable.getVariable().getDirectionsON());
              req.setVehicle(Variable.getVariable().getTravelMode().toString().toLowerCase());
              req.setWeighting(Variable.getVariable().getWeighting());
              if (Variable.getVariable().isShowingSpeedLimits() || Variable.getVariable().isSpeakingSpeedLimits())
              {
                  req.getPathDetails().add(com.graphhopper.routing.profiles.MaxSpeed.KEY);
                  req.getPathDetails().add(com.graphhopper.util.Parameters.Details.AVERAGE_SPEED);
              }
              GHResponse resp = null;
              if (hopper != null)
              {
                resp = hopper.route(req);
              }
              if (resp==null || resp.hasErrors())
              {
                NaviEngine.getNaviEngine().setDirectTargetDir(true);
                Throwable error;
                if (resp != null) { error = resp.getErrors().get(0); }
                else { error = new NullPointerException("Hopper is null!!!"); }
                log("Multible errors, first: " + error);
                resp = TargetDirComputer.getInstance().createTargetdirResponse(fromLat, fromLon, toLat, toLon);
              }
              else
              {
                NaviEngine.getNaviEngine().setDirectTargetDir(false);
              }
              time = sw.stop().getSeconds();
              return resp;
            }


            @Override
            protected void onPostExecute(GHResponse ghResp) {
                if (!ghResp.hasErrors()) {
                    PathWrapper resp = ghResp.getBest();
                    log("from:" + fromLat + "," + fromLon + " to:" + toLat + ","
                            + toLon + " found path with distance:" + resp.getDistance()
                            / 1000f + ", nodes:" + resp.getPoints().getSize() + ", time:"
                            + time + " " + resp.getDebugInfo());
                    logUser(activity, "the route is " + (int) (resp.getDistance() / 100) / 10f
                            + "km long, time:" + resp.getTime() / 60000f + "min.");

                    int sWidth = 4;
                    pathLayer = updatePathLayer(activity, pathLayer, resp.getPoints(), 0x9900cc33, sWidth);
                    mapView.map().updateMap(true);
                    if (Variable.getVariable().isDirectionsON()) {
                      Navigator.getNavigator().setGhResponse(resp);
                    }
                } else {
                    logUser(activity, "Multible errors: " + ghResp.getErrors().size());
                    log("Multible errors, first: " + ghResp.getErrors().get(0));
                }
                if (NaviEngine.getNaviEngine().isNavigating())
                {
                  setCalculatePath(false, false);
                }
                else
                {
                  setCalculatePath(false, true);
                  try
                  {
                    activity.findViewById(R.id.map_nav_settings_path_finding).setVisibility(View.GONE);
                    activity.findViewById(R.id.nav_settings_layout).setVisibility(View.VISIBLE);
                  }
                  catch (Exception e) { e.printStackTrace(); }
                }
            }
        }.execute();
    }
    
  private PathLayer updatePathLayer(Activity activity, PathLayer ref, PointList pointList, int color, int strokeWidth) {
      if (ref==null) {
          ref = createPathLayer(activity, color, strokeWidth);
          mapView.map().layers().add(ref);
      }
      List<GeoPoint> geoPoints = new ArrayList<>();
      //TODO: Search for a more efficient way
      for (int i = 0; i < pointList.getSize(); i++)
          geoPoints.add(new GeoPoint(pointList.getLatitude(i), pointList.getLongitude(i)));
      ref.setPoints(geoPoints);
      return ref;
  }
  
  public void joinPathLayerToPos(double lat, double lon)
  {
    try
    {
      List<GeoPoint> geoPoints = new ArrayList<>();
      geoPoints.add(new GeoPoint(lat,lon));
      geoPoints.add(pathLayer.getPoints().get(1));
      pathLayer.setPoints(geoPoints);
    }
    catch (Exception e) { log("Error: " + e); }
  }
    
  private PathLayer createPathLayer(Activity activity, int color, int strokeWidth)
  {
      Style style = Style.builder()
        .fixed(true)
        .generalization(Style.GENERALIZATION_SMALL)
        .strokeColor(color)
        .strokeWidth(strokeWidth * activity.getResources().getDisplayMetrics().density)
        .build();
      PathLayer newPathLayer = new PathLayer(mapView.map(), style);
      return newPathLayer;
  }

  public void showNaviCenterBtn(boolean visible)
  {
    if (visible)
    {
      naviCenterBtn.setVisibility(View.VISIBLE);
    }
    else
    {
      naviCenterBtn.setVisibility(View.INVISIBLE);
    }
  }

  public void setNaviCenterBtn(final FloatingActionButton naviCenterBtn)
  {
    this.naviCenterBtn = naviCenterBtn;
  }
    
  class MapEventsReceiver extends Layer implements GestureListener
  {

      MapEventsReceiver(org.oscim.map.Map map) {
          super(map);
      }

      @Override
      public boolean onGesture(Gesture g, MotionEvent e) {
          if (g instanceof Gesture.Tap) {
              GeoPoint p = mMap.viewport().fromScreenPoint(e.getX(), e.getY());
              if (mapHandlerListener!=null && needLocation)
              {
                mapHandlerListener.onPressLocation(p);
              }
          }
          return false;
      }
  }
  
  private void logUser(Activity activity, String str)
  {
    log(str);
    try
    {
      Toast.makeText(activity, str, Toast.LENGTH_LONG).show();
    }
    catch (Exception e) { e.printStackTrace(); }
  }
  
  private void log(String str)
  {
    Log.i(MapHandler.class.getName(), str);
  }
} public class Navigator {
    /**
     * get from MapHandler calculate path
     */
    private PathWrapper ghResponse;
    /**
     * navigator is on or off
     */
    private boolean on;
    private List<NavigatorListener> listeners;
    private static Navigator navigator = null;


    private Navigator() {
        this.ghResponse = null;
        this.on = false;
        this.listeners = new ArrayList<>();
    }

    /**
     * @return Navigator object
     */
    public static Navigator getNavigator() {
        if (navigator == null) {
            navigator = new Navigator();
        }
        return navigator;
    }

    public PathWrapper getGhResponse() {
        return ghResponse;
    }

    public void setGhResponse(PathWrapper ghResponse) {
        this.ghResponse = ghResponse;
        if (NaviEngine.getNaviEngine().isNavigating())
        {
          NaviEngine.getNaviEngine().onUpdateInstructions(ghResponse.getInstructions(), ghResponse.getPathDetails());
        }
        else
        {
          setOn(ghResponse != null);
        }
    }

    /**
     * @param distance (<li>Instruction: return instructions distance </li>
     * @return a string  0.0 km (Exact one decimal place)
     */
    public String getDistance(Instruction distance) {
        if (distance.getSign() == Instruction.FINISH) return "";
        double d = distance.getDistance();
        return UnitCalculator.getString(d);
    }

    /**
     * @return distance of the whole journey
     */
    public String getDistance() {
        if (getGhResponse() == null) return UnitCalculator.getString(0);
        double d = getGhResponse().getDistance();
        return UnitCalculator.getString(d);
    }

    /**
     * @return a string time of the journey H:MM
     */
    public String getTime() {
        if (getGhResponse() == null) return " ";
        return getTimeString(getGhResponse().getTime());
    }
    
    public String getTimeString(long time)
    {
      int t = Math.round(time / 60000);
      if (t < 60) return t + " min";
      return t / 60 + " h: " + t % 60 + " m";
    }

    /**
     * @return a string time of the instruction min
     */
    public String getTime(Instruction time) {
        return Math.round(getGhResponse().getTime() / 60000) + " min";
    }


    /**
     * @return true is navigator is on
     */
    public boolean isOn() {
        return on;
    }

    /**
     * set navigator on or off
     *
     * @param on
     */
    public void setOn(boolean on) {
        this.on = on;
        broadcast();
    }
    
    public void setNaviStart(Activity activity, boolean on) {
        NaviEngine.getNaviEngine().setNavigating(activity, on);
        for (NavigatorListener listener : listeners) {
            listener.onNaviStart(on);
        }
    }

    /**
     * broadcast changes to listeners
     */
    protected void broadcast() {
        for (NavigatorListener listener : listeners) {
            listener.onStatusChanged(isOn());
        }
    }

    /**
     * add listener to listener list
     *
     * @param listener
     */
    public void addListener(NavigatorListener listener) {
        listeners.add(listener);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (ghResponse.getInstructions() != null) {
            for (Instruction i : ghResponse.getInstructions()) {
              sb.append("------>\ntime <long>: " + i.getTime());
              sb.append("\n");
              sb.append("name: street name" + i.getName());
              sb.append("\n");
              sb.append("annotation <InstructionAnnotation>");
              sb.append(i.getAnnotation().toString());
              sb.append("\n");
              sb.append("distance");
              sb.append(i.getDistance() + "\n");
              sb.append("sign <int>:" + i.getSign());
              sb.append("\n");
              sb.append("Points <PointsList>: " + i.getPoints());
              sb.append("\n");
            }
        }
        return sb.toString();
    }


    /**
     * this method can only used when Variable class is ready!
     *
     * @param dark (ask for dark icon resId ?)
     * @return int resId
     */
    public int getTravelModeResId(boolean dark) {
        if (dark)
        {
          if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Foot)
          {
            return R.drawable.ic_directions_walk_orange_24dp;
          }
          else if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Bike)
          {
            return R.drawable.ic_directions_bike_orange_24dp;
          }
          else if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Car)
          {
            return R.drawable.ic_directions_car_orange_24dp;
          }
        }
        else
        {
          if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Foot)
          {
            return R.drawable.ic_directions_walk_white_24dp;
          }
          else if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Bike)
          {
            return R.drawable.ic_directions_bike_white_24dp;
          }
          else if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Car)
          {
            return R.drawable.ic_directions_car_white_24dp;
          }
        }
        throw new NullPointerException("this method can only used when Variable class is ready!");
    }
    
    public int getTravelModeArrayIndex()
    {
      if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Foot)
      {
        return 0;
      }
      else if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Bike)
      {
        return 1;
      }
      else if (Variable.getVariable().getTravelMode() == Variable.TravelMode.Car)
      {
        return 2;
      }
      throw new NullPointerException("this method can only used when Variable class is ready!");
    }
    
    public boolean setTravelModeArrayIndex(int index) {
      Variable.TravelMode selected;
      switch (index) {
          case 0:
              selected = Variable.TravelMode.Foot;
              break;
          case 1:
              selected = Variable.TravelMode.Bike;
              break;
          default:
              selected = Variable.TravelMode.Car;
      }
      Variable.getVariable().setTravelMode(selected);
      return Variable.getVariable().saveVariables(Variable.VarType.Base);
    }

    /**
     * @param itemData
     * @return int resId to instruction direction's sign icon
     */
    public int getDirectionSign(Instruction itemData) {
        switch (itemData.getSign()) {
            case Instruction.LEAVE_ROUNDABOUT:
                return R.drawable.ic_roundabout;
            case Instruction.TURN_SHARP_LEFT:
                return R.drawable.ic_turn_sharp_left;
            case Instruction.TURN_LEFT:
                return R.drawable.ic_turn_left;
            case Instruction.TURN_SLIGHT_LEFT:
                return R.drawable.ic_turn_slight_left;
            case Instruction.CONTINUE_ON_STREET:
                return R.drawable.ic_continue_on_street;
            case Instruction.TURN_SLIGHT_RIGHT:
                return R.drawable.ic_turn_slight_right;
            case Instruction.TURN_RIGHT:
                return R.drawable.ic_turn_right;
            case Instruction.TURN_SHARP_RIGHT:
                return R.drawable.ic_turn_sharp_right;
            case Instruction.FINISH:
                return R.drawable.ic_finish_flag;
            case Instruction.REACHED_VIA:
                return R.drawable.ic_reached_via;
            case Instruction.USE_ROUNDABOUT:
                return R.drawable.ic_roundabout;
            case Instruction.KEEP_RIGHT:
                return R.drawable.ic_keep_right;
            case Instruction.KEEP_LEFT:
                return R.drawable.ic_keep_left;
        }
        return 0;
    }
    
    /**
     * @param itemData
     * @return int resId to instruction direction's sign icon
     */
    public int getDirectionSignHuge(Instruction itemData) {
        switch (itemData.getSign()) {
            case Instruction.LEAVE_ROUNDABOUT:
                return R.drawable.ic_2x_roundabout;
            case Instruction.TURN_SHARP_LEFT:
                return R.drawable.ic_2x_turn_sharp_left;
            case Instruction.TURN_LEFT:
                return R.drawable.ic_2x_turn_left;
            case Instruction.TURN_SLIGHT_LEFT:
                return R.drawable.ic_2x_turn_slight_left;
            case Instruction.CONTINUE_ON_STREET:
                return R.drawable.ic_2x_continue_on_street;
            case Instruction.TURN_SLIGHT_RIGHT:
                return R.drawable.ic_2x_turn_slight_right;
            case Instruction.TURN_RIGHT:
                return R.drawable.ic_2x_turn_right;
            case Instruction.TURN_SHARP_RIGHT:
                return R.drawable.ic_2x_turn_sharp_right;
            case Instruction.FINISH:
                return R.drawable.ic_2x_finish_flag;
            case Instruction.REACHED_VIA:
                return R.drawable.ic_2x_reached_via;
            case Instruction.USE_ROUNDABOUT:
                return R.drawable.ic_2x_roundabout;
            case Instruction.KEEP_RIGHT:
                return R.drawable.ic_2x_keep_right;
            case Instruction.KEEP_LEFT:
              return R.drawable.ic_2x_keep_left;
        }
        return 0;
    }

    /**
     * @param instruction
     * @return direction
     */
    public String getDirectionDescription(Instruction instruction, boolean longText) {
        if (instruction.getSign() == 4) return NaviText.sNavEnd;
        String str;
        String streetName = instruction.getName();
        int sign = instruction.getSign();
        String dir = "";
        String dirTo = NaviText.sOnto;
        switch (sign) {
            case Instruction.CONTINUE_ON_STREET:
                dir = (NaviText.sContinue);
                dirTo = NaviText.sOn;
                break;
            case Instruction.LEAVE_ROUNDABOUT:
                dir = (NaviText.sLeaveRound);
                break;
            case Instruction.TURN_SHARP_LEFT:
                dir = (NaviText.sTurnXXX.replace("xxx", NaviText.sSharpL));
                break;
            case Instruction.TURN_LEFT:
                dir = (NaviText.sTurnXXX.replace("xxx", NaviText.sLeft));
                break;
            case Instruction.TURN_SLIGHT_LEFT:
                dir = (NaviText.sTurnXXX.replace("xxx", NaviText.sSlightL));
                break;
            case Instruction.TURN_SLIGHT_RIGHT:
                dir = (NaviText.sTurnXXX.replace("xxx", NaviText.sSlightR));
                break;
            case Instruction.TURN_RIGHT:
                dir = (NaviText.sTurnXXX.replace("xxx", NaviText.sRight));
                break;
            case Instruction.TURN_SHARP_RIGHT:
                dir = (NaviText.sTurnXXX.replace("xxx", NaviText.sSharpR));
                break;
            case Instruction.REACHED_VIA:
                dir = ("Reached via");
                break;
            case Instruction.USE_ROUNDABOUT:
                dir = (NaviText.sUseRound);
                break;
            case Instruction.KEEP_LEFT:
              dir = (NaviText.sKeepXXX.replace("xxx", NaviText.sLeft));
              break;
            case Instruction.KEEP_RIGHT:
              dir = (NaviText.sKeepXXX.replace("xxx", NaviText.sRight));
              break;
        }
        if (!longText) { return dir; }
        str = Helper.isEmpty(streetName) ? dir : (dir + " " + dirTo + " " + streetName);
        return str;
    }
    
    /**
     * @param instruction
     * @return direction
     */
    public String getDirectionDescriptionFallback(Instruction instruction, boolean longText) {
        if (instruction.getSign() == 4) return "Navigation End";
        String str;
        String streetName = instruction.getName();
        int sign = instruction.getSign();
        String dir = "";
        String dirTo = "onto";
        switch (sign) {
            case Instruction.CONTINUE_ON_STREET:
                dir = ("Continue");
                dirTo = "on";
                break;
            case Instruction.LEAVE_ROUNDABOUT:
                dir = ("Leave roundabout");
                break;
            case Instruction.TURN_SHARP_LEFT:
                dir = ("Turn sharp left");
                break;
            case Instruction.TURN_LEFT:
                dir = ("Turn left");
                break;
            case Instruction.TURN_SLIGHT_LEFT:
                dir = ("Turn slight left");
                break;
            case Instruction.TURN_SLIGHT_RIGHT:
                dir = ("Turn slight right");
                break;
            case Instruction.TURN_RIGHT:
                dir = ("Turn right");
                break;
            case Instruction.TURN_SHARP_RIGHT:
                dir = ("Turn sharp right");
                break;
            case Instruction.REACHED_VIA:
                dir = ("Reached via");
                break;
            case Instruction.USE_ROUNDABOUT:
                dir = ("Use roundabout");
                break;
            case Instruction.KEEP_LEFT:
              dir = ("Keep left");
              break;
            case Instruction.KEEP_RIGHT:
              dir = ("Keep right");
              break;
        }
        if (!longText) { return dir; }
        str = Helper.isEmpty(streetName) ? dir : (dir + " " + dirTo + " " + streetName);
        return str;
    }

} public class InstructionAdapter extends RecyclerView.Adapter<InstructionAdapter.ViewHolder> {
    private InstructionList instructions;


    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView icon;
        public TextView description, distance, time;

        public ViewHolder(View itemView) {
            super(itemView);
            this.icon = (ImageView) itemView.findViewById(R.id.nav_instruction_item_sign_iv);
            this.description = (TextView) itemView.findViewById(R.id.nav_instruction_item_description_tv);
            this.distance = (TextView) itemView.findViewById(R.id.nav_instruction_item_distance_tv);
//            this.time = (TextView) itemView.findViewById(R.id.nav_instruction_item_time_tv);
        }

        public void setItemData(Instruction itemData) {
                        icon.setImageResource(Navigator.getNavigator().getDirectionSign(itemData));
            description.setText(Navigator.getNavigator().getDirectionDescription(itemData, true));
            distance.setText(Navigator.getNavigator().getDistance(itemData));
//            time.setText(String.valueOf(Navigator.getNavigator().getTime(itemData)));
        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public InstructionAdapter(InstructionList instructions) {
        this.instructions = instructions;
        notifyDataSetChanged();
    }

    // Create new views (invoked by the layout manager)
    @Override public InstructionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.map_nav_instruction_item, parent, false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override public void onBindViewHolder(ViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        holder.setItemData(instructions.get(position));
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override public int getItemCount() {
        return instructions.size();
    }


} public class SpinnerAdapter extends ArrayAdapter<SportCategory> {
    private int resource;
    private ArrayList<SportCategory> list;
    LayoutInflater inflater;

    /**
     * @param context
     * @param resource single item layout to be inflated
     * @param list
     */
    public SpinnerAdapter(Context context, int resource, ArrayList<SportCategory> list) {
        super(context, resource, list);
        this.list = list;
        this.resource = resource;
        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    /**
     * @param position
     * @param convertView
     * @param parent
     * @return an item view with text + img
     */
    public View getView(int position, View convertView, ViewGroup parent) {
        View itemView = inflater.inflate(resource, parent, false);
        ImageView imageView = (ImageView) itemView.findViewById(R.id.analytics_activity_type_img);
        imageView.setImageResource(list.get(position).getImageId());
        TextView textView = (TextView) itemView.findViewById(R.id.analytics_activity_type_txt);
        textView.setText(list.get(position).getText());
        return itemView;
    }

    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getView(position, convertView, parent);
    }
} public class Calorie {
    /*
     * sport category, which defines the MET value
     */
  
    /** Slow with bike 10mph or 16kmh **/
    public final static double BIKE_SLOW = 4.0;
    /** Normal with bike 13mph or 20kmh **/
    public final static double BIKE_MID = 8.0;
    /** Fast with bike 15mph or 24kmh **/
    public final static double BIKE_FAST = 10.0;
    /** Slow walk 3mph or 5kmh **/
    public final static double WALK_SLOW = 3.0;
    /** Slow running 5mph or 8kmh **/
    public final static double RUN_SLOW = 8.0;
    /** Normal running 8mph or 12kmh **/
    public final static double RUN_MID = 13.5;
    /** Fast running 10mph or 16kmh **/
    public final static double RUN_FAST = 16.0;
    /** General driving with car **/
    public final static double CAR_DRIVE = 2.0;
    
    public enum Type {Bike, Car, Run};
    
    /**
     * default body weight by kg if not defined by user
     */
    public final static double weightKg = 77.0;

    public static double getMET(double speedKmh, Type type)
    {
      if (type == Type.Run)
      {
        if (speedKmh<6.5) { return WALK_SLOW; }
        if (speedKmh<10) { return RUN_SLOW; }
        if (speedKmh<14) { return RUN_MID; }
        return RUN_FAST;
      }
      else if (type == Type.Bike)
      {
        if (speedKmh<18) { return BIKE_SLOW; }
        if (speedKmh<22) { return BIKE_MID; }
        return BIKE_FAST;
      }
      return CAR_DRIVE;
    }
    
    
    /**
     * weightKg = 77.0
     *
     * @param activity: bicycling, running, walking
     * @param timeHour: hours
     * @return calorie burned (activity * weightKg * timeHour)
     */
    public static double calorieBurned(double activity, double timeHour) {
        return calorieBurned(activity, weightKg, timeHour);
    }

    /**
     * @param activity: bicycling, running, walking
     * @param weightKg: in kg
     * @param timeHour: hours
     * @return calorie burned (activity * weightKg * timeHour)
     */
    public static double calorieBurned(double activity, double weightKg, double timeHour) {
        return activity * weightKg * timeHour;
    }

    /**
     * use The Harris–Benedict equations revised by Roza and Shizgal in 1984. BMR
     *
     * @param activity: bicycling, running, walking
     * @param weightKg: in kg
     * @param timeHour: hours
     * @param heightCm: height in cm
     * @param age:      age in years
     * @param men:      true -> men ; false -> women
     * @return calorie burned (BMR * activity * timeHour)
     */
    public static double calorieBurned(double activity, double weightKg, double timeHour, double heightCm, double age,
            boolean men) {
        if (men) {
            return (88.362 + (13.397 * weightKg) + (4.799 * heightCm) - (5.677 * age)) * activity * timeHour;
        }
        return (447.593 + (9.247 * weightKg) + (3.098 * heightCm) - (4.330 * age)) * activity * timeHour;
    }
} public class Variable {
    public enum TravelMode{Foot, Bike, Car};
    public enum VarType{Base, Geocode};
    private TravelMode travelMode;
    private HashMap<VarType, Boolean> loadStatus = new HashMap<VarType, Boolean>();
    /**
     * fastest, shortest (route)
     */
    private String weighting;
    /**
     * Bidirectional Dijkstra:      DIJKSTRA_BI             = "dijkstrabi"
     * <p/>
     * Unidirectional Dijkstra:     DIJKSTRA                = "dijkstra"
     * <p/>
     * one to many Dijkstra:        DIJKSTRA_ONE_TO_MANY    = "dijkstraOneToMany"
     * <p/>
     * Unidirectional A* :          ASTAR                   = "astar"
     * <p/>
     * Bidirectional A* :           ASTAR_BI                = "astarbi"
     */
    private String routingAlgorithms;
    /**
     * instructions on or off; default true (on)
     */
    private boolean directionsON;
    /**
     * Show a sign while navigating, that indicates max speed
     */
    private boolean showSpeedLimits;
    /**
     * Warn the user if current speed is more than max allowed speed
     */
    private boolean speakSpeedLimits;
    /**
     * users current / last used zoom level
     */
    private int lastZoomLevel;

    /**
     * users last browsed screen center location
     */
    private GeoPoint lastLocation;
    /**
     * map directory name: pocketmaps/maps/
     */
    private String mapDirectory;
    /**
     * download directory name: pocketmaps/downloads/
     */
    private String dlDirectory;
    /**
     * map directory name: pocketmaps/tracking/
     */
    private String trackingDirectory;

    /**
     * The default map to startup.
     */
    private boolean autoSelectMap;
    
    /**
     * The selected speech engine, null for AndroidTTS.
     */
    private String ttsEngine;
    private String ttsWantedVoice;

    /**
     * area or country name (need to be loaded)
     * <p/>
     * example: /storage/emulated/0/Download/(mapDirectory)/(country)-gh
     */
    private String country;
    /**
     * a File where all Areas or counties are in
     * <p/>
     * example:
     * <p/>
     * <li>mapsFolder.getAbsolutePath() = /storage/emulated/0/Download/pocketmaps/maps </li>
     * <p/>
     * <li> mapsFolder   =   new File("/storage/emulated/0/Download/pocketmaps/maps")</li>
     */
    private File mapsFolder;

    /**
     * Server for download JSON list of maps
     */
    private String mapUrlJSON;
    /**
     * prepare to load the map
     */
    private volatile boolean prepareInProgress;
    
    private boolean bImperalUnit = false;

    /**
     * list of downloaded maps in local storage; check and init when app started; used to avoid recheck local files
     */
    private List<MyMap> localMaps;
    /**
     * temporary memorialize recent downloaded maps from DownloadMapActivity
     */
    private List<MyMap> recentDownloadedMaps;

    /**
     * temporary memorialize download list of cloud maps from DownloadMapActivity
     */
    private List<MyMap> cloudMaps;
    
    /**
     * sport category spinner index at {@link Analytics#spinner}
     */
    private int sportCategoryIndex;

    private boolean lightSensorON;
    private boolean voiceON;
    private boolean smoothON;
    
    private int offlineSearchBits = GeocoderLocal.BIT_CITY + GeocoderLocal.BIT_STREET;
    private int geocodeSearchEngine = 0;
    private String geocodeSearchTextList = "";
    
    public static final int DEF_ZOOM = 8;
    public static final String DEF_WIGHTING = "fastest";
    public static final String DEF_ALG = "astarbi";

    /**
     * application context
     */
    private Context context;

    private static Variable variable;

    private Variable() {
        this.travelMode = TravelMode.Foot;
        this.weighting = DEF_WIGHTING;
        this.routingAlgorithms = DEF_ALG;
        this.autoSelectMap = false;
        this.ttsEngine = null;
        this.ttsWantedVoice = null;
        this.lastZoomLevel = DEF_ZOOM;
        this.lastLocation = null;
        this.country = null;
        this.mapsFolder = null;
        this.context = null;
        this.directionsON = true;
        this.showSpeedLimits = false;
        this.speakSpeedLimits = false;
        this.voiceON = true;
        this.lightSensorON = true;
        this.smoothON = false;
        this.mapDirectory = "pocketmaps/maps/";
        this.dlDirectory = "pocketmaps/downloads/";
        this.trackingDirectory = "pocketmaps/tracking/";
        this.mapUrlJSON = "http://vsrv15044.customer.xenway.de/maps";
        this.localMaps = new ArrayList<>();
        this.recentDownloadedMaps = new ArrayList<>();
        this.cloudMaps = new ArrayList<>();
        this.sportCategoryIndex = 0;
    }

    public static Variable getVariable() {
        if (variable == null) {
            variable = new Variable();
        }
        return variable;
    }
    
    public boolean isLoaded(VarType type)
    {
      if (loadStatus.containsKey(type)) { return true; }
      return false;
    }

    public String getMapUrlJSON() {
        return mapUrlJSON;
    }

    public TravelMode getTravelMode() {
        return travelMode;
    }

    public void setTravelMode(TravelMode travelMode) {
        this.travelMode = travelMode;
    }
    
    public boolean getAutoSelectMap()
    {
        return autoSelectMap;
    }
    
    public void setAutoSelectMap(boolean autoSelectMap)
    {
        this.autoSelectMap = autoSelectMap;
    }
    
    public String getTtsEngine()
    {
        return ttsEngine;
    }
    
    public void setTtsEngine(String ttsEngine)
    {
        this.ttsEngine = ttsEngine;
    }
    
    public String getTtsWantedVoice()
    {
        return ttsWantedVoice;
    }
    
    public void setTtsWantedVoice(String ttsWantedVoice)
    {
        this.ttsWantedVoice = ttsWantedVoice;
    }
    
    public int getGeocodeSearchEngine()
    {
      return geocodeSearchEngine;
    }
    
    public boolean setGeocodeSearchEngine(int geocodeSearchEngine)
    {
      if (this.geocodeSearchEngine == geocodeSearchEngine) { return false; }
      this.geocodeSearchEngine = geocodeSearchEngine;
      return true;
    }
    
    public int getOfflineSearchBits()
    {
      return offlineSearchBits;
    }
    
    public void setOfflineSearchBits(int offlineSearchBits)
    {
      this.offlineSearchBits = offlineSearchBits;
    }
    
    public String[] getGeocodeSearchTextList()
    {
      if (geocodeSearchTextList.isEmpty()) { return new String[0]; }
      return geocodeSearchTextList.trim().split("\n");
    }
    
    public boolean addGeocodeSearchText(String text)
    {
      if (text.isEmpty()) { return false; }
      text = text.toLowerCase();
      for (String curTxt : getGeocodeSearchTextList())
      {
        if (curTxt.contains(text)) { return false; }
      }
      geocodeSearchTextList = geocodeSearchTextList + "\n" + text;
      return true;
    }

    public String getWeighting() {
        return weighting;
    }

    public void setWeighting(String weighting) {
        this.weighting = weighting;
    }

    public String getRoutingAlgorithms() {
        return routingAlgorithms;
    }

    public void setRoutingAlgorithms(String routingAlgorithms) {
        this.routingAlgorithms = routingAlgorithms;
    }

    public boolean isDirectionsON() {
        return directionsON;
    }

    public void setDirectionsON(boolean directionsON) {
        this.directionsON = directionsON;
    }

    public boolean isShowingSpeedLimits() {
        return showSpeedLimits;
    }

    public void setShowSpeedLimits(boolean showSpeedLimits) {
        this.showSpeedLimits = showSpeedLimits;
    }

    public boolean isSpeakingSpeedLimits() {
        return speakSpeedLimits;
    }

    public void setSpeakSpeedLimits(boolean speakSpeedLimits) {
        this.speakSpeedLimits = speakSpeedLimits;
    }
    
    public boolean isVoiceON()
    {
      return voiceON;
    }
    
    public void setVoiceON(boolean voiceON)
    {
      this.voiceON = voiceON;
    }
    
    public boolean isLightSensorON()
    {
      return lightSensorON;
    }
    
    public void setLightSensorON(boolean lightSensorON)
    {
      this.lightSensorON = lightSensorON;
    }

    public boolean isSmoothON()
    {
        return smoothON;
    }

    public void setSmoothON(boolean smoothON)
    {
        this.smoothON = smoothON;
    }

    /**
     * @return is DirectionsON as string : "true or false"
     */
    public String getDirectionsON() {
        return isDirectionsON() ? "true" : "false";
    }

    public int getLastZoomLevel() {
        return lastZoomLevel;
    }

    public void setLastZoomLevel(int lastZoomLevel) {
        this.lastZoomLevel = lastZoomLevel;
    }
    
    public boolean isImperalUnit() {
      return bImperalUnit;
    }
    
    public void setImperalUnit(boolean bImperalUnit) {
        this.bImperalUnit = bImperalUnit;
    }

    public GeoPoint getLastLocation() {
        return lastLocation;
    }

    public void setLastLocation(GeoPoint lastLocation) {
        this.lastLocation = lastLocation;
    }

    /** Returns the last selected country, or an empty String. **/
    public String getCountry() {
        if (country==null) { return ""; }
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public File getMapsFolder() {
        return mapsFolder;
    }
    
    public File getDownloadsFolder() {
      File dlFolder = new File(mapsFolder.getParentFile().getParent(), dlDirectory);
      if (!dlFolder.exists()) { dlFolder.mkdirs(); }
      return IO.getDownloadDirectory(dlFolder, context);
    }

    public void setBaseFolder(String baseFolder) {
        this.mapsFolder = new File(baseFolder, mapDirectory);
    }

    public File getTrackingFolder() {
      return new File(mapsFolder.getParentFile().getParent(), trackingDirectory);
    }

    public Context getContext() {
        return context;
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public boolean isPrepareInProgress() {
        return prepareInProgress;
    }

    public void setPrepareInProgress(boolean prepareInProgress) {
        this.prepareInProgress = prepareInProgress;
    }

    public List<MyMap> getLocalMaps() {
        return localMaps;
    }

    /**
     * add a list of maps to localMaps
     *
     * @param localMaps list of maps
     */
    public void addLocalMaps(List<MyMap> localMaps) {
        this.localMaps.addAll(localMaps);
    }

    /**
     * add a map to local map list
     *
     * @param localMap MyMap
     */
    public void addLocalMap(MyMap localMap) {
        if (!getLocalMapNameList().contains(localMap.getMapName())) {
            this.localMaps.add(localMap);
        }
    }

    public void removeLocalMap(MyMap localMap) {
        this.localMaps.remove(localMap);
    }

    public void setLocalMaps(List<MyMap> localMaps) {
        this.localMaps = localMaps;
    }

    /**
     * @return a string list of local map names (continent_country)
     */
    public List<String> getLocalMapNameList() {
        ArrayList<String> al = new ArrayList<String>();
        for (MyMap mm : getLocalMaps()) {
            al.add(mm.getMapName());
        }
        return al;
    }

    public List<MyMap> getRecentDownloadedMaps() {
        return recentDownloadedMaps;
    }

    public List<MyMap> getCloudMaps() {
        return cloudMaps;
    }

    public void updateCloudMaps(List<MyMap> cloudMaps) {
      ArrayList<MyMap> newList = new ArrayList<MyMap>();
      for (MyMap oldMap : this.cloudMaps)
      {
        for (MyMap newMap : cloudMaps)
        {
          if (newMap.getUrl().equals(oldMap.getUrl()))
          {
            newMap.setStatus(oldMap.getStatus());
            break;
          }
        }
      }
      // Find same Map from CloudMaps
      for (MyMap newMap : cloudMaps)
      {
        int myIndex = Variable.getVariable().getCloudMaps().indexOf(newMap);
        if (myIndex < 0) { newList.add(newMap); continue; }
        MyMap sameMap = Variable.getVariable().getCloudMaps().get(myIndex);
        sameMap.set(newMap);
        newList.add(sameMap);
      }
      this.cloudMaps = newList;
    }

    /** Similar as getTravelMode() but used for spinner. **/
    public int getSportCategoryIndex() {
        return sportCategoryIndex;
    }

    /** Similar as setTravelMode() but used for spinner. **/
    public void setSportCategoryIndex(int sportCategoryIndex) {
        this.sportCategoryIndex = sportCategoryIndex;
    }

    /**
     * run when app open at run time
     * <p/>
     * load variables from saved file
     *
     * @return true if load succeed, false if nothing to load or load fail
     */
    public boolean loadVariables(VarType varType)
    {
      String content;
      if (varType == VarType.Base)
      {
        content = readFile("pocketmapssavedfile.txt");
        loadStatus.put(VarType.Base, Boolean.TRUE);
      }
      else
      {
        content = readFile("pocketmapssavedfile_" + varType + ".txt");
        loadStatus.put(VarType.Geocode, Boolean.TRUE);
      }
      if (content == null)
      {
        return false;
      }
      JsonWrapper jo;
      try
      {
        jo = new JsonWrapper(content);
        if (varType == VarType.Base)
        {
            setTravelMode(TravelMode.valueOf(toUpperFirst(jo.getStr("travelMode", TravelMode.Foot.toString()))));
            setWeighting(jo.getStr("weighting", DEF_WIGHTING));
            setRoutingAlgorithms(jo.getStr("routingAlgorithms", DEF_ALG));
            setDirectionsON(jo.getBool("directionsON", true));
            setSpeakSpeedLimits(jo.getBool("speakSpeedLimits", false));
            setShowSpeedLimits(jo.getBool("showSpeedLimits", false));
            setVoiceON(jo.getBool("voiceON", true));
            setLightSensorON(jo.getBool("lightSensorON", true));
            setAutoSelectMap(jo.getBool("autoSelectMap", false));
            setTtsEngine(jo.getStr("ttsEngine", null));
            setTtsWantedVoice(jo.getStr("ttsWantedVoice", null));
            setLastZoomLevel(jo.getInt("lastZoomLevel", DEF_ZOOM));
            setImperalUnit(jo.getBool("isImperalUnit", false));
            setSmoothON(jo.getBool("smoothON", false));
            double la = jo.getDouble("latitude", 0);
            double lo = jo.getDouble("longitude", 0);
            if (la != 0 && lo != 0) {
                setLastLocation(new GeoPoint(la, lo));
            }
            String coun = jo.getStr("country", "");
            if (!coun.isEmpty()) {
                setCountry(coun);
            }
            String mapsFolderAbsStr = jo.getStr("mapsFolderAbsPath", "/x/y/z");
            File mapsFolderAbsPath = new File(mapsFolderAbsStr);
            if (mapsFolderAbsPath.exists() && !mapsFolderAbsStr.equals("/x/y/z"))
            {
              setBaseFolder(mapsFolderAbsPath.getParentFile().getParent());
            }
            setSportCategoryIndex(jo.getInt("sportCategoryIndex", 0));
        }
        else if (varType == VarType.Geocode)
        {
          setGeocodeSearchEngine(jo.getInt("geocodeSearchEngine", 0));
          geocodeSearchTextList = jo.getStr("geocodeSearchTextList", "");
          offlineSearchBits = jo.getInt("offlineSearchBits", GeocoderLocal.BIT_CITY + GeocoderLocal.BIT_STREET);
        }
      }
      catch (JSONException e)
      {
          e.printStackTrace();
          return false;
      }
      return true;
    }

    private static String toUpperFirst(String string)
    {
      // TODO This is just a workaround, because of incompatiblity from older versions.
      // This workaround will ensure to override the fault data.
      // Can be deleted later, maybe in year 2022
      if (string == null) { return "Car"; }
      String first = string.substring(0,1).toUpperCase();
      String rest = string.substring(1);
      return first + rest;
    }

    /**
     * run before app destroyed at run time
     * <p/>
     * save variables to local file (json)   @return true is succeed, false otherwise
     */
    public boolean saveVariables(VarType varType) {
        JsonWrapper jo = new JsonWrapper();
        try
        {
          if (varType == VarType.Base)
          {
            jo.put("travelMode", getTravelMode().toString());
            jo.put("weighting", getWeighting());
            jo.put("routingAlgorithms", getRoutingAlgorithms());
            jo.put("directionsON", isDirectionsON());
            jo.put("showSpeedLimits", isShowingSpeedLimits());
            jo.put("speakSpeedLimits", isSpeakingSpeedLimits());
            jo.put("voiceON", isVoiceON());
            jo.put("lightSensorON", isLightSensorON());
            jo.put("autoSelectMap", autoSelectMap);
            jo.put("ttsEngine", ttsEngine);
            jo.put("ttsWantedVoice", ttsWantedVoice);
            jo.put("lastZoomLevel", getLastZoomLevel());
            if (getLastLocation() != null) {
                jo.put("latitude", getLastLocation().getLatitude());
                jo.put("longitude", getLastLocation().getLongitude());
            } else {
                jo.put("latitude", 0);
                jo.put("longitude", 0);
            }
            jo.put("isImperalUnit", bImperalUnit);
            jo.put("country", getCountry());
            jo.put("smoothON", smoothON);
            jo.put("mapsFolderAbsPath", getMapsFolder().getAbsolutePath());
            jo.put("sportCategoryIndex", getSportCategoryIndex());
          }
          else if (varType == VarType.Geocode)
          {
            jo.put("geocodeSearchEngine", geocodeSearchEngine);
            jo.put("geocodeSearchTextList", geocodeSearchTextList);
            jo.put("offlineSearchBits", offlineSearchBits);
          }
        } catch (JSONException e) {
            e.printStackTrace();
            return false;
        }
        if (varType == VarType.Base)
        {
          return saveStringToFile("pocketmapssavedfile.txt", jo.toString());
        }
        else
        {
          return saveStringToFile("pocketmapssavedfile_" + varType + ".txt", jo.toString());
        }
    }
    
    /** Return existing files, where settings are saved. **/
    public ArrayList<String> getSavingFiles()
    {
      ArrayList<String> list = new ArrayList<String>();
      for (String f : context.fileList())
      {
        if (isSavingFile(f)) { list.add(f); }
      }
      return list;
    }
    
    public static boolean isSavingFile(String f)
    {
      return f.startsWith("pocketmapssavedfile") && f.endsWith(".txt");
    }

    /**
     * @return read saved file and return it as a string
     */
    private String readFile(String file) {
        try(FileInputStream fis = context.openFileInput(file);
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader bufferedReader = new BufferedReader(isr))
        {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (IOException e) {
            log("Cant load savingfile, maybe the first time since app installed.");
            e.printStackTrace();
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @param file a string need to be saved
     * @return
     */
    private boolean saveStringToFile(String file, String content) {
        try(FileOutputStream fos = context.openFileOutput(file, Context.MODE_PRIVATE))
        {
            fos.write(content.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    
    private void log(String s) {
      Log.i(Variable.class.getName(), s);
    }

}
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.location.Address;
import android.location.Location;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.OvershootInterpolator;
import android.view.animation.ScaleAnimation;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.junjunguo.pocketmaps.R;
import com.junjunguo.pocketmaps.fragments.AppSettings;
import com.junjunguo.pocketmaps.fragments.AppSettings.SettType;
import com.junjunguo.pocketmaps.map.Destination;
import com.junjunguo.pocketmaps.model.SportCategory;
import com.junjunguo.pocketmaps.model.listeners.MapHandlerListener;
import com.junjunguo.pocketmaps.model.listeners.NavigatorListener;
import com.junjunguo.pocketmaps.model.listeners.OnClickAddressListener;
import com.junjunguo.pocketmaps.navigator.NaviEngine;
import com.junjunguo.pocketmaps.map.MapHandler;
import com.junjunguo.pocketmaps.map.Navigator;
import com.junjunguo.pocketmaps.fragments.InstructionAdapter;
import com.junjunguo.pocketmaps.fragments.SpinnerAdapter;
import com.junjunguo.pocketmaps.util.Calorie;
import com.junjunguo.pocketmaps.util.Variable;
import java.util.ArrayList;
import org.oscim.android.MapView;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.event.Event;
import org.oscim.map.Map.UpdateListener;

    /**
     * active directions, and directions view
     */
    private void activateDirections() {
        RecyclerView instructionsRV;
        RecyclerView.Adapter<?> instructionsAdapter;
        RecyclerView.LayoutManager instructionsLayoutManager;

        instructionsRV = (RecyclerView) activity.findViewById(R.id.nav_instruction_recycler_view);

        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        instructionsRV.setHasFixedSize(true);

        // use a linear layout manager
        instructionsLayoutManager = new LinearLayoutManager(activity);
        instructionsRV.setLayoutManager(instructionsLayoutManager);

        // specify an adapter (see also next example)
        instructionsAdapter = new InstructionAdapter(Navigator.getNavigator().getGhResponse().getInstructions());
        instructionsRV.setAdapter(instructionsAdapter);
        initNavListView();
    }

    /**
     * navigation list view
     * <p>
     * make nav list view control button ready to use
     */
    private void initNavListView() {
        fillNavListSummaryValues();
        navSettingsVP.setVisibility(View.INVISIBLE);
        navInstructionListVP.setVisibility(View.VISIBLE);
        ImageButton clearBtn, stopBtn, startNavBtn, stopNavBtn;
        stopBtn = (ImageButton) activity.findViewById(R.id.nav_instruction_list_stop_btn);
        clearBtn = (ImageButton) activity.findViewById(R.id.nav_instruction_list_clear_btn);
        startNavBtn = (ImageButton) activity.findViewById(R.id.nav_instruction_list_start_btn);
        stopNavBtn = (ImageButton) activity.findViewById(R.id.navtop_stop);
        stopBtn.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {

                // 1. Instantiate an AlertDialog.Builder with its constructor
                AlertDialog.Builder builder = new AlertDialog.Builder(activity);

                // 2. Chain together various setter methods to set the dialog characteristics
                builder.setMessage(R.string.stop_navigation_msg).setTitle(R.string.stop_navigation)
                        .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int id) {
                                // stop!
                                Navigator.getNavigator().setOn(false);
                                navInstructionListVP.setVisibility(View.INVISIBLE);
                                navSettingsVP.setVisibility(View.VISIBLE);
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        // User cancelled the dialog
                        dialog.dismiss();
                    }
                });
                // Create the AlertDialog object and return it

                // 3. Get the AlertDialog from create()
                AlertDialog dialog = builder.create();
                dialog.show();
            }
        });

        clearBtn.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                navInstructionListVP.setVisibility(View.INVISIBLE);
                navSettingsVP.setVisibility(View.INVISIBLE);
                sideBarVP.setVisibility(View.VISIBLE);
            }
        });

        startNavBtn.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                Navigator.getNavigator().setNaviStart(activity, true);
                Location curLoc = MapActivity.getmCurrentLocation();
                if (curLoc!=null)
                {
                  NaviEngine.getNaviEngine().updatePosition(activity, curLoc);
                }
            }
        });

        stopNavBtn.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                Navigator.getNavigator().setNaviStart(activity, false);
            }
        });
    }

    /**
     * fill up values for nav list summary
     */
    private void fillNavListSummaryValues() {
        initSpinner();
        TextView from, to, distance, time;
        from = (TextView) activity.findViewById(R.id.nav_instruction_list_summary_from_tv);
        to = (TextView) activity.findViewById(R.id.nav_instruction_list_summary_to_tv);
        distance = (TextView) activity.findViewById(R.id.nav_instruction_list_summary_distance_tv);
        time = (TextView) activity.findViewById(R.id.nav_instruction_list_summary_time_tv);

        from.setText(Destination.getDestination().getStartPointToString());
        to.setText(Destination.getDestination().getEndPointToString());
        distance.setText(Navigator.getNavigator().getDistance());
        time.setText(Navigator.getNavigator().getTime());
    }
    
    private void initSpinner() {
      Spinner spinner = (Spinner) activity.findViewById(R.id.nav_instruction_list_travel_mode_sp);

      ArrayList<SportCategory> spinnerList = new ArrayList<>();
      spinnerList.add(new SportCategory("walk", R.drawable.ic_directions_walk_orange_24dp, Calorie.Type.Run));
      spinnerList.add(new SportCategory("bike", R.drawable.ic_directions_bike_orange_24dp, Calorie.Type.Bike));
      spinnerList.add(new SportCategory("car", R.drawable.ic_directions_car_orange_24dp, Calorie.Type.Car));

      SpinnerAdapter adapter = new SpinnerAdapter(activity, R.layout.analytics_activity_type, spinnerList);