У меня проблемы с настройкой прослушивателя событий Milestone с помощью пользовательского Milestone.Я следовал инструкциям в документации, но код внутри события Milestone, похоже, не срабатывает вообще.Моя цель - получить BannerInstruction и запустить инструкцию по умолчанию за 10 метров до следующего шага.Тест инструкции баннера (например, «повернуть налево») будет затем отправлен через BLE (я использую библиотеку RxAndroidBle) на другое устройство.
До сих пор мне удавалось отправлять инструкции баннера через Bluetooth, вызывая willDisplayслушатель (который активируется каждый раз, когда происходит изменение в инструкциях, но проблема в том, что я не контролирую время отображения инструкции.
Я новичок в Java, поэтому я неполностью уверен, что то, что я сделал, было правильно. Пожалуйста, не возражайте против плохо написанного кода, я все еще учусь!
Это MainActivity.java:
package com.bpnavi.backpacknavigator;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.mapbox.api.directions.v5.DirectionsCriteria;
import com.mapbox.api.directions.v5.MapboxDirections;
import com.mapbox.api.directions.v5.models.BannerInstructions;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapView;
// classes needed to add the location component
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import android.location.Location;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.Toast;
import com.mapbox.mapboxsdk.geometry.LatLng;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.services.android.navigation.ui.v5.NavigationLauncherOptions;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.android.core.permissions.PermissionsManager;
// classes to calculate a route
import com.mapbox.services.android.navigation.ui.v5.NavigationView;
import com.mapbox.services.android.navigation.ui.v5.NavigationViewOptions;
import com.mapbox.services.android.navigation.ui.v5.OnNavigationReadyCallback;
import com.mapbox.services.android.navigation.ui.v5.listeners.BannerInstructionsListener;
import com.mapbox.services.android.navigation.ui.v5.route.NavigationMapRoute;
import com.mapbox.services.android.navigation.v5.instruction.Instruction;
import com.mapbox.services.android.navigation.v5.milestone.Milestone;
import com.mapbox.services.android.navigation.v5.milestone.MilestoneEventListener;
import com.mapbox.services.android.navigation.v5.milestone.RouteMilestone;
import com.mapbox.services.android.navigation.v5.milestone.StepMilestone;
import com.mapbox.services.android.navigation.v5.milestone.Trigger;
import com.mapbox.services.android.navigation.v5.milestone.TriggerProperty;
import com.mapbox.services.android.navigation.v5.navigation.NavigationEventListener;
import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute;
import com.mapbox.api.directions.v5.models.DirectionsResponse;
import com.mapbox.api.directions.v5.models.DirectionsRoute;
import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation;
import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigationOptions;
import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgressState;
import com.mapbox.services.android.navigation.v5.utils.RouteUtils;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;
// classes needed to launch navigation UI
import android.view.View;
import android.widget.Button;
import com.mapbox.services.android.navigation.ui.v5.NavigationLauncher;
import com.mapbox.services.android.navigation.v5.offroute.OffRouteListener;
import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress;
import com.polidea.rxandroidble2.RxBleClient;
import com.polidea.rxandroidble2.RxBleConnection;
import com.polidea.rxandroidble2.RxBleDevice;
import com.polidea.rxandroidble2.internal.RxBleLog;
import com.polidea.rxandroidble2.scan.ScanSettings;
import android.util.Log;
import java.util.List;
import java.util.UUID;
public class MainActivity extends AppCompatActivity implements PermissionsListener, MapboxMap.OnMapClickListener, OnNavigationReadyCallback, BannerInstructionsListener,
MilestoneEventListener, OffRouteListener, ProgressChangeListener {
private MapView mapView;
private NavigationView navigationView;
private MapboxMap mapboxMap;
private PermissionsManager permissionsManager;
private Location originLocation;
// variables for adding a marker
private Marker destinationMarker;
private LatLng originCoord;
private LatLng destinationCoord;
// variables for calculating and drawing a route
private Point originPosition;
private Point destinationPosition;
private DirectionsRoute currentRoute;
private static final String TAG = "DirectionsActivity";
private NavigationMapRoute navigationMapRoute;
private MapboxDirections client;
private Button startButton;
public static final String BACKPACK_DEVICE_ADDRESS = "123";
public static final String TEST_DEVICE_ADDRESS = "123";
private static final UUID WRITE_CHARACTERISTIC = UUID.fromString("abc123");
private Disposable disposable;
private RxBleDevice device;
private BannerInstructions BannerInstructionMilestone;
private MapboxNavigation navigation;
private static final int INSTR_MILESTONE = 1001;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RxBleClient rxBleClient = RxBleClient.create(this);
Disposable scanSubscription = rxBleClient.scanBleDevices(
new ScanSettings.Builder()
// .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // change if needed
// .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // change if needed
.build()
// add filters if needed
)
.subscribe(
scanResult -> {
// Process scan result here.
},
throwable -> {
// Handle an error here.
}
);
// When done, just dispose.
scanSubscription.dispose();
Disposable flowDisposable = rxBleClient.observeStateChanges()
.switchMap(state -> { // switchMap makes sure that if the state will change the rxBleClient.scanBleDevices() will dispose and thus end the scan
switch (state) {
case READY:
// everything should work
return rxBleClient.scanBleDevices();
case BLUETOOTH_NOT_AVAILABLE:
// basically no functionality will work here
case LOCATION_PERMISSION_NOT_GRANTED:
// scanning and connecting will not work
case BLUETOOTH_NOT_ENABLED:
// scanning and connecting will not work
case LOCATION_SERVICES_NOT_ENABLED:
// scanning will not work
default:
return Observable.empty();
}
})
.subscribe(
rxBleScanResult -> {
// Process scan result here.
},
throwable -> {
// Handle an error here.
}
);
// When done, just dispose.
flowDisposable.dispose();
device = rxBleClient.getBleDevice(TEST_DEVICE_ADDRESS);
Disposable disposable = device.establishConnection(true) // <-- autoConnect flag
.subscribe(
rxBleConnection -> {
// All GATT operations are done through the rxBleConnection.
},
throwable -> {
// Handle an error here.
}
);
// disposable.dispose();
Mapbox.getInstance(this, getString(R.string.access_token));
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.mapView);
startButton = findViewById(R.id.startButton);
startButton.setOnClickListener(v -> {
setTheme(R.style.Theme_AppCompat_NoActionBar);
setContentView(R.layout.activity_navigation);
navigationView = findViewById(R.id.navigationView);
navigationView.onCreate(savedInstanceState);
navigationView.initialize(this);
// boolean simulateRoute = true;
// NavigationLauncherOptions options = NavigationLauncherOptions.builder()
// .directionsRoute(currentRoute)
// .shouldSimulateRoute(simulateRoute)
// .build();
// // Call this method with Context from within an Activity
// NavigationLauncher.startNavigation(MainActivity.this, options);
});
MapboxNavigationOptions navOptions = MapboxNavigationOptions.builder().isDebugLoggingEnabled(true).build();
navigation = new MapboxNavigation(getApplicationContext(), getString(R.string.access_token), navOptions);
navigation.addMilestoneEventListener(this);
navigation.addMilestone(new StepMilestone.Builder()
.setIdentifier(INSTR_MILESTONE)
.setInstruction(this.myInstruction)
.setTrigger(
Trigger.all(
Trigger.lt(TriggerProperty.STEP_DISTANCE_REMAINING_METERS, 10)))
.build()
);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(mapboxMap -> {
MainActivity.this.mapboxMap = mapboxMap;
enableLocationComponent();
originCoord = new LatLng(originLocation.getLatitude(), originLocation.getLongitude());
mapboxMap.addOnMapClickListener(this);
//Set stuff here
mapboxMap.addMarker(new MarkerOptions()
.position(new LatLng(45.506833, 9.163333))
.title("Politecnico di Milano")
.snippet("Dipartimento di Design")
);
});
}
@SuppressWarnings( {"MissingPermission"})
private void enableLocationComponent() {
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(this)) {
// Activate the MapboxMap LocationComponent to show user location
// Adding in LocationComponentOptions is also an optional parameter
LocationComponent locationComponent = mapboxMap.getLocationComponent();
locationComponent.activateLocationComponent(this);
locationComponent.setLocationComponentEnabled(true);
// Set the component's camera mode
locationComponent.setCameraMode(CameraMode.TRACKING_GPS);
locationComponent.setRenderMode(RenderMode.GPS);
originLocation = locationComponent.getLastKnownLocation();
} else {
permissionsManager = new PermissionsManager(this);
permissionsManager.requestLocationPermissions(this);
}
}
@Override
public void onMapClick(@NonNull LatLng point){
if (destinationMarker != null) {
mapboxMap.removeMarker(destinationMarker);
}
destinationCoord = point;
destinationMarker = mapboxMap.addMarker(new MarkerOptions()
.position(destinationCoord)
);
destinationPosition = Point.fromLngLat(destinationCoord.getLongitude(), destinationCoord.getLatitude());
originPosition = Point.fromLngLat(originCoord.getLongitude(), originCoord.getLatitude());
getRoute(originPosition, destinationPosition);
}
private void getRoute(Point origin, Point destination) {
Toast.makeText(this, "Calculating route...", Toast.LENGTH_SHORT).show();
MapboxDirections.builder()
.origin(origin)
.destination(destination)
.accessToken(getString(R.string.access_token))
.profile(DirectionsCriteria.PROFILE_CYCLING)
.bannerInstructions(true)
.steps(true)
.roundaboutExits(true)
.build();
NavigationRoute.builder(this)
.accessToken(getString(R.string.access_token))
.origin(origin)
.destination(destination)
.profile(DirectionsCriteria.PROFILE_CYCLING)
.voiceUnits("metric")
.build()
.getRoute(new Callback<DirectionsResponse>() {
@Override
public void onResponse(Call<DirectionsResponse> call, Response<DirectionsResponse> response) {
// You can get the generic HTTP info about the response
Log.d(TAG, "Response code: " + response.code());
if (response.body() == null) {
Log.e(TAG, "No routes found, make sure you set the right user and access token.");
return;
} else if (response.body().routes().size() < 1) {
Log.e(TAG, "No routes found");
return;
}
currentRoute = response.body().routes().get(0);
// Draw the route on the map
if (navigationMapRoute != null) {
navigationMapRoute.removeRoute();
} else {
navigationMapRoute = new NavigationMapRoute(null, mapView, mapboxMap, R.style.NavigationMapRoute);
startButton.setVisibility(View.VISIBLE);
startButton.setBackgroundResource(R.color.colorPrimary);
}
navigationMapRoute.addRoute(currentRoute);
}
@Override
public void onFailure(Call<DirectionsResponse> call, Throwable throwable) {
Log.e(TAG, "Error: " + throwable.getMessage());
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
}
@Override
public void onPermissionResult(boolean granted) {
if (granted) {
enableLocationComponent();
} else {
Toast.makeText(this, R.string.user_location_permission_not_granted, Toast.LENGTH_LONG).show();
finish();
}
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
int REQUEST_ENABLE_BT = 1;
this.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
if (disposable != null) {
disposable.dispose();
disposable = null;
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
@Override
public void onNavigationReady(boolean isRunning) {
NavigationViewOptions options = NavigationViewOptions.builder()
.directionsRoute(currentRoute)
.shouldSimulateRoute(true)
.bannerInstructionsListener(this)
.milestoneEventListener(MainActivity.this)
.build();
navigationView.startNavigation(options);
}
@Override
public void onProgressChange(Location location, RouteProgress routeProgress) {
Toast.makeText(this, "change", Toast.LENGTH_LONG).show();
}
@Override
public void userOffRoute(Location location) {
Toast.makeText(this, "offRoute", Toast.LENGTH_LONG).show();
}
@Override
public void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) {
Context context = getApplicationContext();
String instr = myInstruction.buildInstruction(routeProgress);
int duration = Toast.LENGTH_SHORT;
Toast.makeText(context, instr, duration).show();
}
Instruction myInstruction = new Instruction() {
@Override
public String buildInstruction(RouteProgress routeProgress) {
return routeProgress.currentLegProgress().upComingStep().maneuver().type()+" "+routeProgress.currentLegProgress().upComingStep().maneuver().modifier();
}
};
@Override
public BannerInstructions willDisplay(BannerInstructions instructions) {
RxBleClient rxBleClient = RxBleClient.create(this);
Context context = getApplicationContext();
String instr = instructions.primary().type()+" "+instructions.primary().modifier();
int duration = Toast.LENGTH_SHORT;
// Toast.makeText(context, instr, duration).show();
device = rxBleClient.getBleDevice(TEST_DEVICE_ADDRESS);
Disposable disposable = device.establishConnection(true) // <-- autoConnect flag
.flatMapSingle(rxBleConnection -> rxBleConnection.writeCharacteristic(WRITE_CHARACTERISTIC, instr.getBytes()))
.subscribe(
characteristicValue -> {
// Characteristic value confirmed.
},
throwable -> {
// Handle an error here.
}
);
// When done... dispose and forget about connection teardown :)
// disposable.dispose();
return instructions;
}
}
Что яне так?
ОБНОВЛЕНИЕ: в отсутствие лучшего решения я нашел обходной путь: используя onProgressChange
слушатель, я использую объект routeProgress
, чтобы получить все данные, которые мне нужны, чтобы показать инструкцию в нужное время итолько один раз за шаг.
@Override
public void onProgressChange(Location location, RouteProgress routeProgress) {
if (routeProgress.currentLegProgress().currentStepProgress().distanceRemaining()>20) {
noRe = false;
}
Context context = getApplicationContext();
String instr = myInstruction.buildInstruction(routeProgress).replaceAll(" ", "");
int duration = Toast.LENGTH_SHORT;
if (instr.length() < 20 &&
!noRe &&
routeProgress.currentLegProgress().currentStepProgress().distanceRemaining() < activationDistance
) {
noRe = true;
Toast.makeText(context, instr, duration).show();
}
}