-
Notifications
You must be signed in to change notification settings - Fork 320
Add dynamic offline routing to NavigationView #1829
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package com.mapbox.services.android.navigation.ui.v5; | ||
|
|
||
| import android.annotation.SuppressLint; | ||
| import android.content.Context; | ||
| import android.net.ConnectivityManager; | ||
| import android.net.NetworkInfo; | ||
| import android.net.wifi.WifiInfo; | ||
| import android.net.wifi.WifiManager; | ||
|
|
||
| import java.util.HashMap; | ||
|
|
||
| class ConnectivityStatusProvider { | ||
|
|
||
| private final Context context; | ||
| private final WifiNetworkChecker wifiNetworkChecker; | ||
| private final MobileNetworkChecker mobileNetworkChecker; | ||
|
|
||
| ConnectivityStatusProvider(Context applicationContext) { | ||
| this.context = applicationContext; | ||
| this.wifiNetworkChecker = new WifiNetworkChecker(new HashMap<Integer, Boolean>()); | ||
| this.mobileNetworkChecker = new MobileNetworkChecker(new HashMap<Integer, Boolean>()); | ||
| } | ||
|
|
||
| boolean isConnected() { | ||
| NetworkInfo info = getNetworkInfo(context); | ||
| return (info != null && info.isConnected()); | ||
| } | ||
|
|
||
| boolean isConnectedFast() { | ||
| NetworkInfo info = getNetworkInfo(context); | ||
| int wifiLevel = getWifiLevel(context); | ||
| return (info != null | ||
| && info.isConnected() | ||
| && isConnectionFast(info.getType(), info.getSubtype(), wifiLevel)); | ||
| } | ||
|
|
||
| @SuppressLint("MissingPermission") | ||
| private NetworkInfo getNetworkInfo(Context context) { | ||
| ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); | ||
| return cm.getActiveNetworkInfo(); | ||
| } | ||
|
|
||
| @SuppressLint({"MissingPermission", "WifiManagerPotentialLeak"}) | ||
| private int getWifiLevel(Context context) { | ||
| WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); | ||
| int numberOfLevels = 5; | ||
| WifiInfo wifiInfo = wifiManager.getConnectionInfo(); | ||
| return WifiManager.calculateSignalLevel(wifiInfo.getRssi(), numberOfLevels); | ||
| } | ||
|
|
||
| private boolean isConnectionFast(int type, int networkType, int wifiLevel) { | ||
| if (type == ConnectivityManager.TYPE_WIFI) { | ||
| return wifiNetworkChecker.isFast(wifiLevel); | ||
| } else if (type == ConnectivityManager.TYPE_MOBILE) { | ||
| return mobileNetworkChecker.isFast(networkType); | ||
| } else { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.mapbox.services.android.navigation.ui.v5; | ||
|
|
||
| import android.support.annotation.NonNull; | ||
| import android.telephony.TelephonyManager; | ||
|
|
||
| import java.util.HashMap; | ||
|
|
||
| class MobileNetworkChecker { | ||
|
|
||
| private final HashMap<Integer, Boolean> statusMap; | ||
|
|
||
| MobileNetworkChecker(HashMap<Integer, Boolean> statusMap) { | ||
| this.statusMap = statusMap; | ||
| initialize(statusMap); | ||
| } | ||
|
|
||
| @NonNull | ||
| Boolean isFast(Integer networkType) { | ||
| Boolean isConnectionFast = statusMap.get(networkType); | ||
| if (isConnectionFast == null) { | ||
| isConnectionFast = false; | ||
| } | ||
| return isConnectionFast; | ||
| } | ||
|
|
||
| private void initialize(HashMap<Integer, Boolean> statusMap) { | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_1xRTT, false); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_GPRS, false); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_CDMA, false); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_EDGE, false); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_IDEN, false); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, false); | ||
|
|
||
| statusMap.put(TelephonyManager.NETWORK_TYPE_EVDO_0, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_EVDO_A, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_HSDPA, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_HSPA, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_HSUPA, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_UMTS, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_EHRPD, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_EVDO_B, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_HSPAP, true); | ||
| statusMap.put(TelephonyManager.NETWORK_TYPE_LTE, true); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,6 @@ | |
| import android.arch.lifecycle.MutableLiveData; | ||
| import android.content.Context; | ||
| import android.location.Location; | ||
| import android.net.ConnectivityManager; | ||
| import android.net.NetworkInfo; | ||
| import android.support.annotation.NonNull; | ||
| import android.support.annotation.Nullable; | ||
| import android.text.TextUtils; | ||
|
|
@@ -21,9 +19,6 @@ | |
| import com.mapbox.services.android.navigation.ui.v5.feedback.FeedbackItem; | ||
| import com.mapbox.services.android.navigation.ui.v5.instruction.BannerInstructionModel; | ||
| import com.mapbox.services.android.navigation.ui.v5.instruction.InstructionModel; | ||
| import com.mapbox.services.android.navigation.ui.v5.route.OffRouteEvent; | ||
| import com.mapbox.services.android.navigation.ui.v5.route.ViewRouteFetcher; | ||
| import com.mapbox.services.android.navigation.ui.v5.route.ViewRouteListener; | ||
| import com.mapbox.services.android.navigation.ui.v5.summary.SummaryModel; | ||
| import com.mapbox.services.android.navigation.ui.v5.voice.NavigationSpeechPlayer; | ||
| import com.mapbox.services.android.navigation.ui.v5.voice.SpeechAnnouncement; | ||
|
|
@@ -42,6 +37,7 @@ | |
| import com.mapbox.services.android.navigation.v5.navigation.metrics.FeedbackEvent; | ||
| import com.mapbox.services.android.navigation.v5.offroute.OffRouteListener; | ||
| import com.mapbox.services.android.navigation.v5.route.FasterRouteListener; | ||
| import com.mapbox.services.android.navigation.v5.route.RouteFetcher; | ||
| 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.utils.DistanceFormatter; | ||
|
|
@@ -69,14 +65,13 @@ public class NavigationViewModel extends AndroidViewModel { | |
| private final MutableLiveData<Point> destination = new MutableLiveData<>(); | ||
|
|
||
| private MapboxNavigation navigation; | ||
| private ViewRouteFetcher routeFetcher; | ||
| private NavigationViewRouter router; | ||
| private LocationEngineConductor locationEngineConductor; | ||
| private NavigationViewEventDispatcher navigationViewEventDispatcher; | ||
| private SpeechPlayer speechPlayer; | ||
| private VoiceInstructionLoader voiceInstructionLoader; | ||
| private VoiceInstructionCache voiceInstructionCache; | ||
| private int voiceInstructionsToAnnounce = 0; | ||
| private ConnectivityManager connectivityManager; | ||
| private RouteProgress routeProgress; | ||
| private String feedbackId; | ||
| private String screenshot; | ||
|
|
@@ -93,9 +88,8 @@ public class NavigationViewModel extends AndroidViewModel { | |
| public NavigationViewModel(Application application) { | ||
| super(application); | ||
| this.accessToken = Mapbox.getAccessToken(); | ||
| initializeConnectivityManager(application); | ||
| initializeNavigationRouteEngine(); | ||
| initializeNavigationLocationEngine(); | ||
| initializeLocationEngine(); | ||
| initializeRouter(); | ||
| routeUtils = new RouteUtils(); | ||
| localeUtils = new LocaleUtils(); | ||
| } | ||
|
|
@@ -121,7 +115,7 @@ public NavigationViewModel(Application application) { | |
| public void onDestroy(boolean isChangingConfigurations) { | ||
| this.isChangingConfigurations = isChangingConfigurations; | ||
| if (!isChangingConfigurations) { | ||
| routeFetcher.onDestroy(); | ||
| router.onDestroy(); | ||
| endNavigation(); | ||
| deactivateInstructionPlayer(); | ||
| isRunning = false; | ||
|
|
@@ -208,7 +202,7 @@ void initialize(NavigationViewOptions options) { | |
| initializeVoiceInstructionCache(); | ||
| initializeNavigationSpeechPlayer(options); | ||
| } | ||
| routeFetcher.extractRouteOptions(options); | ||
| router.extractRouteOptions(options); | ||
| } | ||
|
|
||
| void updateFeedbackScreenshot(String screenshot) { | ||
|
|
@@ -264,15 +258,14 @@ MutableLiveData<Point> retrieveDestination() { | |
| return destination; | ||
| } | ||
|
|
||
| private void initializeConnectivityManager(Application application) { | ||
| connectivityManager = (ConnectivityManager) application.getSystemService(Context.CONNECTIVITY_SERVICE); | ||
| private void initializeRouter() { | ||
| RouteFetcher onlineRouter = new RouteFetcher(getApplication(), accessToken); | ||
devotaaabel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Context applicationContext = getApplication().getApplicationContext(); | ||
| ConnectivityStatusProvider connectivityStatus = new ConnectivityStatusProvider(applicationContext); | ||
| router = new NavigationViewRouter(onlineRouter, connectivityStatus, routeEngineListener); | ||
| } | ||
|
|
||
| private void initializeNavigationRouteEngine() { | ||
| routeFetcher = new ViewRouteFetcher(getApplication(), accessToken, routeEngineListener); | ||
| } | ||
|
|
||
| private void initializeNavigationLocationEngine() { | ||
| private void initializeLocationEngine() { | ||
| locationEngineConductor = new LocationEngineConductor(); | ||
| } | ||
|
|
||
|
|
@@ -365,7 +358,7 @@ private void addMilestones(NavigationViewOptions options) { | |
| @Override | ||
| public void onProgressChange(Location location, RouteProgress routeProgress) { | ||
| NavigationViewModel.this.routeProgress = routeProgress; | ||
| routeFetcher.updateLocation(location); | ||
| router.updateLocation(location); | ||
| instructionModel.setValue(new InstructionModel(distanceFormatter, routeProgress)); | ||
| summaryModel.setValue(new SummaryModel(getApplication(), distanceFormatter, routeProgress, timeFormatType)); | ||
| navigationLocation.setValue(location); | ||
|
|
@@ -376,11 +369,9 @@ public void onProgressChange(Location location, RouteProgress routeProgress) { | |
| private OffRouteListener offRouteListener = new OffRouteListener() { | ||
| @Override | ||
| public void userOffRoute(Location location) { | ||
| if (hasNetworkConnection()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Connectivity checks are now done in |
||
| speechPlayer.onOffRoute(); | ||
| Point newOrigin = Point.fromLngLat(location.getLongitude(), location.getLatitude()); | ||
| handleOffRouteEvent(newOrigin); | ||
| } | ||
| speechPlayer.onOffRoute(); | ||
| Point newOrigin = Point.fromLngLat(location.getLongitude(), location.getLatitude()); | ||
| handleOffRouteEvent(newOrigin); | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -469,15 +460,6 @@ private void updateBannerInstruction(RouteProgress routeProgress, Milestone mile | |
| } | ||
| } | ||
|
|
||
| @SuppressWarnings( {"MissingPermission"}) | ||
| private boolean hasNetworkConnection() { | ||
| if (connectivityManager == null) { | ||
| return false; | ||
| } | ||
| NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); | ||
| return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); | ||
| } | ||
|
|
||
| private void sendEventFeedback(FeedbackItem feedbackItem) { | ||
| if (navigationViewEventDispatcher != null) { | ||
| navigationViewEventDispatcher.onFeedbackSent(feedbackItem); | ||
|
|
@@ -493,8 +475,7 @@ private void sendEventArrival(RouteProgress routeProgress) { | |
| private void handleOffRouteEvent(Point newOrigin) { | ||
| if (navigationViewEventDispatcher != null && navigationViewEventDispatcher.allowRerouteFrom(newOrigin)) { | ||
| navigationViewEventDispatcher.onOffRoute(newOrigin); | ||
| OffRouteEvent event = new OffRouteEvent(newOrigin, routeProgress); | ||
| routeFetcher.fetchRouteFromOffRouteEvent(event); | ||
| router.findRouteFrom(routeProgress); | ||
| isOffRoute.setValue(true); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.mapbox.services.android.navigation.ui.v5; | ||
|
|
||
| import com.mapbox.services.android.navigation.v5.navigation.MapboxOfflineRouter; | ||
| import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute; | ||
| import com.mapbox.services.android.navigation.v5.navigation.OfflineRoute; | ||
|
|
||
| import timber.log.Timber; | ||
|
|
||
| class NavigationViewOfflineRouter { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a lot of objects called
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I completely follow regarding sole responsibility of configuration,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I understand what you are saying. Can we call this a |
||
|
|
||
| private final MapboxOfflineRouter offlineRouter; | ||
| private final NavigationViewRouter router; | ||
| private boolean isConfigured; | ||
| private String tileVersion; | ||
|
|
||
| NavigationViewOfflineRouter(MapboxOfflineRouter offlineRouter, NavigationViewRouter router) { | ||
| this.offlineRouter = offlineRouter; | ||
| this.router = router; | ||
| } | ||
|
|
||
| void configure(String tileVersion) { | ||
| if (!isConfigured || isNew(tileVersion)) { | ||
| offlineRouter.configure(tileVersion, new OfflineRouterConfiguredCallback(this)); | ||
| } | ||
| this.tileVersion = tileVersion; | ||
| } | ||
|
|
||
| void setIsConfigured(boolean isConfigured) { | ||
| this.isConfigured = isConfigured; | ||
| } | ||
|
|
||
| boolean isConfigured() { | ||
| return isConfigured; | ||
| } | ||
|
|
||
| void findRouteWith(NavigationRoute.Builder builder) { | ||
| if (!isConfigured) { | ||
| Timber.e("Cannot find route - offline router is not configured"); | ||
| return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we give an error if not configured?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, good catch
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we throw an exception here? As it is, this would be a somewhat silent failure.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We actually won't call this method if this class isn't configured https://github.com/mapbox/mapbox-navigation-android/pull/1829/files#diff-29009b5750e50557cd5e8a7fd79a46f7R80. So we may not need this check at all?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, let's remove it altogether then. |
||
| } | ||
|
|
||
| OfflineRoute offlineRoute = OfflineRoute.builder(builder).build(); | ||
| offlineRouter.findRoute(offlineRoute, new OfflineRouteFoundCallback(router)); | ||
| } | ||
|
|
||
| private boolean isNew(String tileVersion) { | ||
| return !this.tileVersion.equals(tileVersion); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be careful here because you could be connected to a Wi-Fi but that doesn't mean to have connectivity e.g. you could be connected to a hotspot and don't have connection until you log in.
I believe we should implement something similar as with mobile connectivity. Maybe 👀 Wi-Fi strength?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good to think about as an improvement but can possibly be deferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a wifi strength "checker" to address this