From 9e4d720793eb44da84824cabea9f403155c41a87 Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Wed, 8 Jun 2016 17:06:19 +0300 Subject: [PATCH] [android][locator] refactor: GPS locator refactored. --- .../maps/DownloadResourcesActivity.java | 15 +- .../com/mapswithme/maps/LocationState.java | 45 +- .../src/com/mapswithme/maps/MapFragment.java | 7 +- .../src/com/mapswithme/maps/MwmActivity.java | 276 ++----- .../maps/base/BaseActivityDelegate.java | 8 + .../maps/base/BaseMwmFragmentActivity.java | 8 + .../maps/bookmarks/BookmarkListAdapter.java | 9 +- .../ChooseBookmarkCategoryFragment.java | 6 +- .../maps/location/AndroidNativeProvider.java | 66 +- .../maps/location/BaseLocationProvider.java | 12 +- .../mapswithme/maps/location/CompassData.java | 42 ++ .../location/GoogleFusedLocationProvider.java | 51 +- .../maps/location/LocationHelper.java | 694 ++++++++++++------ .../maps/location/LocationListener.java | 22 + .../maps/location/LocationPredictor.java | 120 +-- .../maps/location/SensorHelper.java | 118 +++ .../maps/location/TrackRecorder.java | 8 +- .../maps/news/FirstStartFragment.java | 17 +- .../maps/search/SearchFragment.java | 11 +- .../mapswithme/maps/widget/menu/MainMenu.java | 2 +- .../maps/widget/menu/MyPositionButton.java | 53 +- .../widget/placepage/DirectionFragment.java | 8 +- .../maps/widget/placepage/PlacePageView.java | 7 +- .../com/mapswithme/util/LocationUtils.java | 35 +- .../com/mapswithme/util/ThemeSwitcher.java | 13 +- 25 files changed, 993 insertions(+), 660 deletions(-) create mode 100644 android/src/com/mapswithme/maps/location/CompassData.java create mode 100644 android/src/com/mapswithme/maps/location/LocationListener.java create mode 100644 android/src/com/mapswithme/maps/location/SensorHelper.java diff --git a/android/src/com/mapswithme/maps/DownloadResourcesActivity.java b/android/src/com/mapswithme/maps/DownloadResourcesActivity.java index e33a5a28ac..d12abec330 100644 --- a/android/src/com/mapswithme/maps/DownloadResourcesActivity.java +++ b/android/src/com/mapswithme/maps/DownloadResourcesActivity.java @@ -32,6 +32,7 @@ import com.mapswithme.maps.bookmarks.data.BookmarkManager; import com.mapswithme.maps.downloader.CountryItem; import com.mapswithme.maps.downloader.MapManager; import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.location.LocationListener; import com.mapswithme.maps.search.SearchEngine; import com.mapswithme.util.ConnectionState; import com.mapswithme.util.Constants; @@ -99,16 +100,16 @@ public class DownloadResourcesActivity extends BaseMwmFragmentActivity new KmzKmlProcessor() }; - private final LocationHelper.LocationListener mLocationListener = new LocationHelper.SimpleLocationListener() + private final LocationListener mLocationListener = new LocationListener.Simple() { @Override - public void onLocationUpdated(Location l) + public void onLocationUpdated(Location location) { if (mCurrentCountry != null) return; - final double lat = l.getLatitude(); - final double lon = l.getLongitude(); + final double lat = location.getLatitude(); + final double lon = location.getLongitude(); mCurrentCountry = MapManager.nativeFindCountry(lat, lon); if (TextUtils.isEmpty(mCurrentCountry)) { @@ -146,7 +147,7 @@ public class DownloadResourcesActivity extends BaseMwmFragmentActivity checkBox.setText(checkBoxText); } - LocationHelper.INSTANCE.removeLocationListener(this); + LocationHelper.INSTANCE.removeListener(this); } }; @@ -242,14 +243,14 @@ public class DownloadResourcesActivity extends BaseMwmFragmentActivity protected void onResume() { super.onResume(); - LocationHelper.INSTANCE.addLocationListener(mLocationListener, true); + LocationHelper.INSTANCE.addListener(mLocationListener, true); } @Override protected void onPause() { super.onPause(); - LocationHelper.INSTANCE.removeLocationListener(mLocationListener); + LocationHelper.INSTANCE.removeListener(mLocationListener); } private void suggestRemoveLiteOrSamsung() diff --git a/android/src/com/mapswithme/maps/LocationState.java b/android/src/com/mapswithme/maps/LocationState.java index a11e93973f..637b8e0154 100644 --- a/android/src/com/mapswithme/maps/LocationState.java +++ b/android/src/com/mapswithme/maps/LocationState.java @@ -1,13 +1,11 @@ package com.mapswithme.maps; -public enum LocationState +public final class LocationState { - INSTANCE; - public interface ModeChangeListener { @SuppressWarnings("unused") - void onMyPositionModeChangedCallback(final int newMode, final boolean routingActive); + void onMyPositionModeChanged(int newMode); } // These values should correspond to location::EMyPositionMode enum (from platform/location.hpp) @@ -17,21 +15,23 @@ public enum LocationState public static final int FOLLOW = 3; public static final int FOLLOW_AND_ROTATE = 4; - public native void nativeSwitchToNextMode(); - private native int nativeGetMode(); + public static native void nativeSwitchToNextMode(); + private static native int nativeGetMode(); - public native void nativeSetListener(ModeChangeListener listener); - public native void nativeRemoveListener(); + public static native void nativeSetListener(ModeChangeListener listener); + public static native void nativeRemoveListener(); + + private LocationState() {} /** * Checks if location state on the map is active (so its not turned off or pending). */ public static boolean isTurnedOn() { - return isTurnedOn(getMode()); + return hasLocation(getMode()); } - public static boolean isTurnedOn(int mode) + public static boolean hasLocation(int mode) { return (mode > NOT_FOLLOW_NO_POSITION); } @@ -39,7 +39,30 @@ public enum LocationState public static int getMode() { MwmApplication.get().initNativeCore(); - return INSTANCE.nativeGetMode(); + return nativeGetMode(); + } + public static String nameOf(int mode) + { + switch (mode) + { + case PENDING_POSITION: + return "PENDING_POSITION"; + + case NOT_FOLLOW_NO_POSITION: + return "NOT_FOLLOW_NO_POSITION"; + + case NOT_FOLLOW: + return "NOT_FOLLOW"; + + case FOLLOW: + return "FOLLOW"; + + case FOLLOW_AND_ROTATE: + return "FOLLOW_AND_ROTATE"; + + default: + return "Unknown: " + mode; + } } } diff --git a/android/src/com/mapswithme/maps/MapFragment.java b/android/src/com/mapswithme/maps/MapFragment.java index 951a4a117a..2e55d28230 100644 --- a/android/src/com/mapswithme/maps/MapFragment.java +++ b/android/src/com/mapswithme/maps/MapFragment.java @@ -270,12 +270,9 @@ public class MapFragment extends BaseMwmFragment boolean isFirstStart() { - return mFirstStart; - } - - void clearFirstStart() - { + boolean res = mFirstStart; mFirstStart = false; + return res; } static native void nativeCompassUpdated(double magneticNorth, double trueNorth, boolean forceRedraw); diff --git a/android/src/com/mapswithme/maps/MwmActivity.java b/android/src/com/mapswithme/maps/MwmActivity.java index 0fceb42ba8..7b6b882a69 100644 --- a/android/src/com/mapswithme/maps/MwmActivity.java +++ b/android/src/com/mapswithme/maps/MwmActivity.java @@ -3,12 +3,10 @@ package com.mapswithme.maps; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.location.Location; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; @@ -24,7 +22,6 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.LinearLayout; -import android.widget.Toast; import java.io.Serializable; import java.util.Stack; @@ -51,8 +48,8 @@ import com.mapswithme.maps.editor.EditorHostFragment; import com.mapswithme.maps.editor.FeatureCategoryActivity; import com.mapswithme.maps.editor.ReportFragment; import com.mapswithme.maps.editor.ViralFragment; +import com.mapswithme.maps.location.CompassData; import com.mapswithme.maps.location.LocationHelper; -import com.mapswithme.maps.location.LocationPredictor; import com.mapswithme.maps.news.FirstStartFragment; import com.mapswithme.maps.news.SinglePageNewsFragment; import com.mapswithme.maps.routing.NavigationController; @@ -77,7 +74,6 @@ import com.mapswithme.util.Animations; import com.mapswithme.util.BottomSheetHelper; import com.mapswithme.util.Config; import com.mapswithme.util.InputUtils; -import com.mapswithme.util.LocationUtils; import com.mapswithme.util.ThemeUtils; import com.mapswithme.util.UiUtils; import com.mapswithme.util.Utils; @@ -91,8 +87,7 @@ import ru.mail.android.mytarget.nativeads.NativeAppwallAd; import ru.mail.android.mytarget.nativeads.banners.NativeAppwallBanner; public class MwmActivity extends BaseMwmFragmentActivity - implements LocationHelper.LocationListener, - MapObjectListener, + implements MapObjectListener, View.OnTouchListener, BasePlacePageAnimationController.OnVisibilityChangedListener, OnClickListener, @@ -100,7 +95,7 @@ public class MwmActivity extends BaseMwmFragmentActivity CustomNavigateUpListener, ChooseBookmarkCategoryFragment.Listener, RoutingController.Container, - LocationState.ModeChangeListener + LocationHelper.UiCallback { public static final String EXTRA_TASK = "map_task"; private static final String EXTRA_CONSUMED = "mwm.extra.intent.processed"; @@ -116,8 +111,6 @@ public class MwmActivity extends BaseMwmFragmentActivity private static final String STATE_PP_OPENED = "PpOpened"; private static final String STATE_MAP_OBJECT = "MapObject"; - public static final int REQUEST_CHECK_SETTINGS = 101; - // Map tasks that we run AFTER rendering initialized private final Stack mTasks = new Stack<>(); private final StoragePathManager mPathManager = new StoragePathManager(); @@ -146,15 +139,10 @@ public class MwmActivity extends BaseMwmFragmentActivity private boolean mIsFullscreen; private boolean mIsFullscreenAnimating; - private LocationPredictor mLocationPredictor; private FloatingSearchToolbarController mSearchController; - private LastCompassData mLastCompassData; // The first launch of application ever - onboarding screen will be shown. private boolean mFirstStart; - // The first launch after process started. Used to skip "Not follow, no position" state and to run locator. - private static boolean sColdStart = true; - private static boolean sLocationStopped; public interface LeftAnimationTrackListener { @@ -165,20 +153,6 @@ public class MwmActivity extends BaseMwmFragmentActivity void onTrackLeftAnimation(float offset); } - private static class LastCompassData - { - double magneticNorth; - double trueNorth; - double north; - - void update(int rotation, double magneticNorth, double trueNorth) - { - this.magneticNorth = LocationUtils.correctCompassAngle(rotation, magneticNorth); - this.trueNorth = LocationUtils.correctCompassAngle(rotation, trueNorth); - north = (this.trueNorth >= 0.0) ? this.trueNorth : this.magneticNorth; - } - } - public static Intent createShowMapIntent(Context context, String countryId, boolean doAutoDownload) { return new Intent(context, DownloadResourcesActivity.class) @@ -198,6 +172,7 @@ public class MwmActivity extends BaseMwmFragmentActivity checkMeasurementSystem(); checkKitkatMigrationMove(); + LocationHelper.INSTANCE.attach(this); runTasks(); } @@ -351,7 +326,6 @@ public class MwmActivity extends BaseMwmFragmentActivity Framework.nativeSetMapObjectListener(this); mSearchController = new FloatingSearchToolbarController(this); - mLocationPredictor = new LocationPredictor(new Handler(), this); processIntent(getIntent()); SharingHelper.prepare(); } @@ -462,7 +436,7 @@ public class MwmActivity extends BaseMwmFragmentActivity private boolean closePlacePage() { - if (mPlacePage.getState() == State.HIDDEN) + if (mPlacePage.isHidden()) return false; mPlacePage.hide(); @@ -532,7 +506,7 @@ public class MwmActivity extends BaseMwmFragmentActivity private void initMenu() { - mMainMenu = new MainMenu((ViewGroup) findViewById(R.id.menu_frame), new MainMenu.Container() + mMainMenu = new MainMenu(this, (ViewGroup) findViewById(R.id.menu_frame), new MainMenu.Container() { @Override public Activity getActivity() @@ -684,14 +658,16 @@ public class MwmActivity extends BaseMwmFragmentActivity @Override protected void onSaveInstanceState(Bundle outState) { - if (mPlacePage.getState() != State.HIDDEN) + if (!mPlacePage.isHidden()) { outState.putBoolean(STATE_PP_OPENED, true); mPlacePage.saveBookmarkTitle(); outState.putParcelable(STATE_MAP_OBJECT, mPlacePage.getMapObject()); } + if (!mIsFragmentContainer && RoutingController.get().isPlanning()) mRoutingPlanInplaceController.onSaveState(outState); + RoutingController.get().onSaveState(); super.onSaveInstanceState(outState); } @@ -752,174 +728,11 @@ public class MwmActivity extends BaseMwmFragmentActivity runTasks(); } - @Override - public void onLocationError(int errorCode) - { - LocationHelper.nativeOnLocationError(errorCode); - - if (sLocationStopped) - return; - - if (errorCode == LocationHelper.ERROR_DENIED) - { - Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); - if (intent.resolveActivity(getPackageManager()) == null) - { - intent = new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS); - if (intent.resolveActivity(getPackageManager()) == null) - return; - } - - final Intent finIntent = intent; - new AlertDialog.Builder(this) - .setTitle(R.string.enable_location_service) - .setMessage(R.string.location_is_disabled_long_text) - .setPositiveButton(R.string.connection_settings, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - startActivity(finIntent); - } - }) - .setNegativeButton(R.string.close, null) - .show(); - } - else if (errorCode == LocationHelper.ERROR_GPS_OFF) - { - Toast.makeText(this, R.string.gps_is_disabled_long_text, Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onLocationUpdated(final Location location) - { - if (!location.getProvider().equals(LocationHelper.LOCATION_PREDICTOR_PROVIDER)) - mLocationPredictor.reset(location); - - LocationHelper.onLocationUpdated(location); - - if (mPlacePage.getState() != State.HIDDEN) - mPlacePage.refreshLocation(location); - - if (!RoutingController.get().isNavigating()) - return; - - RoutingInfo info = Framework.nativeGetRouteFollowingInfo(); - mNavigationController.update(info); - mMainMenu.updateRoutingInfo(info); - - TtsPlayer.INSTANCE.playTurnNotifications(); - } - - @Override - public void onCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy) - { - if (mLastCompassData == null) - mLastCompassData = new LastCompassData(); - - mLastCompassData.update(getWindowManager().getDefaultDisplay().getRotation(), magneticNorth, trueNorth); - MapFragment.nativeCompassUpdated(mLastCompassData.magneticNorth, mLastCompassData.trueNorth, false); - - mPlacePage.refreshAzimuth(mLastCompassData.north); - mNavigationController.updateNorth(mLastCompassData.north); - } - - public static void enableLocation() - { - sLocationStopped = false; - } - - @Override - public void onMyPositionModeChangedCallback(final int newMode, final boolean routingActive) - { - mLocationPredictor.myPositionModeChanged(newMode); - mMainMenu.getMyPositionButton().update(newMode); - - if (LocationState.isTurnedOn(newMode)) - sLocationStopped = false; - - switch (newMode) - { - case LocationState.PENDING_POSITION: - resumeLocation(); - LocationHelper.INSTANCE.restart(); // restart to check settings again - break; - - case LocationState.NOT_FOLLOW_NO_POSITION: - if (sColdStart) - { - LocationState.INSTANCE.nativeSwitchToNextMode(); - break; - } - - pauseLocation(); - - if (sLocationStopped) - break; - - sLocationStopped = true; - - if (mMapFragment != null && mMapFragment.isFirstStart()) - { - mMapFragment.clearFirstStart(); - break; - } - - if (LocationHelper.INSTANCE.shouldResolveErrors() && LocationUtils.areLocationServicesTurnedOn()) - { - LocationHelper.INSTANCE.setShouldResolveErrors(false); - - String message = String.format("%s\n\n%s", getString(R.string.current_location_unknown_message), - getString(R.string.current_location_unknown_title)); - new AlertDialog.Builder(this) - .setMessage(message) - .setNegativeButton(R.string.current_location_unknown_stop_button, null) - .setPositiveButton(R.string.current_location_unknown_continue_button, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - sLocationStopped = false; - LocationState.INSTANCE.nativeSwitchToNextMode(); - } - }).show(); - } - break; - - default: - LocationHelper.INSTANCE.restart(); - break; - } - - sColdStart = false; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - if (requestCode == REQUEST_CHECK_SETTINGS) - { - if (resultCode == RESULT_OK) - { - LocationHelper.INSTANCE.setShouldResolveErrors(true); - LocationHelper.INSTANCE.restart(); - return; - } - - LocationHelper.INSTANCE.setShouldResolveErrors(false); - } - - super.onActivityResult(requestCode, resultCode, data); - } - @Override protected void onResume() { super.onResume(); - LocationState.INSTANCE.nativeSetListener(this); - mMainMenu.getMyPositionButton().update(LocationState.getMode()); - resumeLocation(); + mSearchController.refreshToolbar(); mMainMenu.onResume(new Runnable() { @@ -936,14 +749,6 @@ public class MwmActivity extends BaseMwmFragmentActivity mOnmapDownloader.onResume(); } - private void resumeLocation() - { - LocationHelper.INSTANCE.addLocationListener(this, true); - // Do not turn off the screen while displaying position - Utils.keepScreenOn(true, getWindow()); - mLocationPredictor.resume(); - } - @Override public void recreate() { @@ -969,7 +774,7 @@ public class MwmActivity extends BaseMwmFragmentActivity public boolean run(MwmActivity target) { if (LocationState.isTurnedOn()) - LocationState.INSTANCE.nativeSwitchToNextMode(); + LocationState.nativeSwitchToNextMode(); return false; } }); @@ -1021,22 +826,12 @@ public class MwmActivity extends BaseMwmFragmentActivity @Override protected void onPause() { - LocationState.INSTANCE.nativeRemoveListener(); - pauseLocation(); TtsPlayer.INSTANCE.stop(); LikesManager.INSTANCE.cancelDialogs(); mOnmapDownloader.onPause(); super.onPause(); } - private void pauseLocation() - { - LocationHelper.INSTANCE.removeLocationListener(this); - // Enable automatic turning screen off while app is idle - Utils.keepScreenOn(false, getWindow()); - mLocationPredictor.pause(); - } - @Override protected void onStart() { @@ -1045,6 +840,9 @@ public class MwmActivity extends BaseMwmFragmentActivity RoutingController.get().attach(this); if (!mIsFragmentContainer) mRoutingPlanInplaceController.setStartButton(); + + if (MapFragment.nativeIsEngineCreated()) + LocationHelper.INSTANCE.attach(this); } private void initShowcase() @@ -1084,6 +882,7 @@ public class MwmActivity extends BaseMwmFragmentActivity protected void onStop() { super.onStop(); + LocationHelper.INSTANCE.detach(); mMytargetHelper.cancel(); RoutingController.get().detach(); } @@ -1383,18 +1182,19 @@ public class MwmActivity extends BaseMwmFragmentActivity } } - public void adjustCompass(int offsetX, int offsetY) + void adjustCompass(int offsetX, int offsetY) { if (mMapFragment == null || !mMapFragment.isAdded()) return; mMapFragment.setupCompass((mPanelAnimator != null && mPanelAnimator.isVisible()) ? offsetX : 0, offsetY, true); - if (mLastCompassData != null) - MapFragment.nativeCompassUpdated(mLastCompassData.magneticNorth, mLastCompassData.trueNorth, true); + CompassData compass = LocationHelper.INSTANCE.getCompassData(); + if (compass != null) + MapFragment.nativeCompassUpdated(compass.getMagneticNorth(), compass.getTrueNorth(), true); } - public void adjustRuler(int offsetX, int offsetY) + private void adjustRuler(int offsetX, int offsetY) { if (mMapFragment == null || !mMapFragment.isAdded()) return; @@ -1527,4 +1327,40 @@ public class MwmActivity extends BaseMwmFragmentActivity mFirstStart = false; return res; } + + @Override + public void onMyPositionModeChanged(int newMode) + { + mMainMenu.getMyPositionButton().update(newMode); + } + + @Override + public void onLocationUpdated(Location location) + { + if (!mPlacePage.isHidden()) + mPlacePage.refreshLocation(location); + + if (!RoutingController.get().isNavigating()) + return; + + RoutingInfo info = Framework.nativeGetRouteFollowingInfo(); + mNavigationController.update(info); + mMainMenu.updateRoutingInfo(info); + + TtsPlayer.INSTANCE.playTurnNotifications(); + } + + @Override + public void onCompassUpdated(CompassData compass) + { + MapFragment.nativeCompassUpdated(compass.getMagneticNorth(), compass.getTrueNorth(), false); + mPlacePage.refreshAzimuth(compass.getNorth()); + mNavigationController.updateNorth(compass.getNorth()); + } + + @Override + public boolean shouldNotifyLocationNotFound() + { + return (mMapFragment != null && !mMapFragment.isFirstStart()); + } } diff --git a/android/src/com/mapswithme/maps/base/BaseActivityDelegate.java b/android/src/com/mapswithme/maps/base/BaseActivityDelegate.java index 98a420855f..21e5dbc873 100644 --- a/android/src/com/mapswithme/maps/base/BaseActivityDelegate.java +++ b/android/src/com/mapswithme/maps/base/BaseActivityDelegate.java @@ -1,5 +1,8 @@ package com.mapswithme.maps.base; +import android.content.Intent; + +import com.mapswithme.maps.location.LocationHelper; import com.mapswithme.util.Config; import com.mapswithme.util.UiUtils; import com.mapswithme.util.ViewServer; @@ -67,4 +70,9 @@ public class BaseActivityDelegate } }); } + + static boolean onActivityResult(int requestCode, int resultCode, Intent data) + { + return LocationHelper.INSTANCE.onActivityResult(requestCode, resultCode); + } } diff --git a/android/src/com/mapswithme/maps/base/BaseMwmFragmentActivity.java b/android/src/com/mapswithme/maps/base/BaseMwmFragmentActivity.java index 3be6239735..6d354a7434 100644 --- a/android/src/com/mapswithme/maps/base/BaseMwmFragmentActivity.java +++ b/android/src/com/mapswithme/maps/base/BaseMwmFragmentActivity.java @@ -1,6 +1,7 @@ package com.mapswithme.maps.base; import android.app.Activity; +import android.content.Intent; import android.media.AudioManager; import android.os.Bundle; import android.support.annotation.NonNull; @@ -121,6 +122,13 @@ public class BaseMwmFragmentActivity extends AppCompatActivity mBaseDelegate.onPause(); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + if (!BaseActivityDelegate.onActivityResult(requestCode, resultCode, data)) + super.onActivityResult(requestCode, resultCode, data); + } + protected Toolbar getToolbar() { return (Toolbar) findViewById(R.id.toolbar); diff --git a/android/src/com/mapswithme/maps/bookmarks/BookmarkListAdapter.java b/android/src/com/mapswithme/maps/bookmarks/BookmarkListAdapter.java index 6294837d81..6cf232b820 100644 --- a/android/src/com/mapswithme/maps/bookmarks/BookmarkListAdapter.java +++ b/android/src/com/mapswithme/maps/bookmarks/BookmarkListAdapter.java @@ -19,6 +19,7 @@ import com.mapswithme.maps.bookmarks.data.BookmarkCategory; import com.mapswithme.maps.bookmarks.data.DistanceAndAzimut; import com.mapswithme.maps.bookmarks.data.Track; import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.location.LocationListener; import com.mapswithme.util.Graphics; @@ -35,10 +36,10 @@ public class BookmarkListAdapter extends BaseAdapter private static final int SECTION_TRACKS = 0; private static final int SECTION_BMKS = 1; - private final LocationHelper.LocationListener mLocationListener = new LocationHelper.SimpleLocationListener() + private final LocationListener mLocationListener = new LocationListener.Simple() { @Override - public void onLocationUpdated(Location l) + public void onLocationUpdated(Location location) { notifyDataSetChanged(); } @@ -52,12 +53,12 @@ public class BookmarkListAdapter extends BaseAdapter public void startLocationUpdate() { - LocationHelper.INSTANCE.addLocationListener(mLocationListener, true); + LocationHelper.INSTANCE.addListener(mLocationListener, true); } public void stopLocationUpdate() { - LocationHelper.INSTANCE.removeLocationListener(mLocationListener); + LocationHelper.INSTANCE.removeListener(mLocationListener); } @Override diff --git a/android/src/com/mapswithme/maps/bookmarks/ChooseBookmarkCategoryFragment.java b/android/src/com/mapswithme/maps/bookmarks/ChooseBookmarkCategoryFragment.java index 0a743dc7d6..8feb7ad077 100644 --- a/android/src/com/mapswithme/maps/bookmarks/ChooseBookmarkCategoryFragment.java +++ b/android/src/com/mapswithme/maps/bookmarks/ChooseBookmarkCategoryFragment.java @@ -16,9 +16,9 @@ import com.mapswithme.maps.bookmarks.data.BookmarkManager; import com.mapswithme.maps.dialog.EditTextDialogFragment; import com.mapswithme.util.statistics.Statistics; -import static com.mapswithme.maps.dialog.EditTextDialogFragment.OnTextSaveListener; - -public class ChooseBookmarkCategoryFragment extends BaseMwmDialogFragment implements OnTextSaveListener, ChooseBookmarkCategoryAdapter.CategoryListener +public class ChooseBookmarkCategoryFragment extends BaseMwmDialogFragment + implements EditTextDialogFragment.OnTextSaveListener, + ChooseBookmarkCategoryAdapter.CategoryListener { public static final String CATEGORY_ID = "ExtraCategoryId"; public static final String BOOKMARK_ID = "ExtraBookmarkId"; diff --git a/android/src/com/mapswithme/maps/location/AndroidNativeProvider.java b/android/src/com/mapswithme/maps/location/AndroidNativeProvider.java index 0233ba8d7d..b6035cace3 100644 --- a/android/src/com/mapswithme/maps/location/AndroidNativeProvider.java +++ b/android/src/com/mapswithme/maps/location/AndroidNativeProvider.java @@ -22,38 +22,36 @@ class AndroidNativeProvider extends BaseLocationProvider } @Override - protected void startUpdates() + protected boolean start() { if (mIsActive) - return; - - final List providers = getFilteredProviders(); + return true; + List providers = filterProviders(); if (providers.isEmpty()) - LocationHelper.INSTANCE.notifyLocationError(LocationHelper.ERROR_DENIED); - else + return false; + + mIsActive = true; + for (String provider : providers) + mLocationManager.requestLocationUpdates(provider, LocationHelper.INSTANCE.getInterval(), 0, this); + + LocationHelper.INSTANCE.startSensors(); + + Location location = findBestNotExpiredLocation(providers, LocationUtils.LOCATION_EXPIRATION_TIME_MILLIS_SHORT); + if (!isLocationBetterThanLast(location)) { - mIsActive = true; - for (final String provider : providers) - mLocationManager.requestLocationUpdates(provider, LocationHelper.INSTANCE.getInterval(), 0, this); - - LocationHelper.INSTANCE.registerSensorListeners(); - - final Location newLocation = findBestNotExpiredLocation(providers, LocationUtils.LOCATION_EXPIRATION_TIME_MILLIS_SHORT); - if (isLocationBetterThanLast(newLocation)) - LocationHelper.INSTANCE.saveLocation(newLocation); - else - { - final Location lastLocation = LocationHelper.INSTANCE.getSavedLocation(); - if (lastLocation != null && !LocationUtils.isExpired(lastLocation, LocationHelper.INSTANCE.getSavedLocationTime(), - LocationUtils.LOCATION_EXPIRATION_TIME_MILLIS_SHORT)) - LocationHelper.INSTANCE.saveLocation(lastLocation); - } + location = LocationHelper.INSTANCE.getSavedLocation(); + if (location == null || LocationUtils.isExpired(location, LocationHelper.INSTANCE.getSavedLocationTime(), + LocationUtils.LOCATION_EXPIRATION_TIME_MILLIS_SHORT)) + return true; } + + onLocationChanged(location); + return true; } @Override - protected void stopUpdates() + protected void stop() { mLocationManager.removeUpdates(this); mIsActive = false; @@ -75,26 +73,20 @@ class AndroidNativeProvider extends BaseLocationProvider return res; } - List getFilteredProviders() + List filterProviders() { - final List allProviders = mLocationManager.getProviders(false); - final List acceptedProviders = new ArrayList<>(allProviders.size()); + List allProviders = mLocationManager.getProviders(false); + List res = new ArrayList<>(allProviders.size()); - for (final String prov : allProviders) + for (final String provider : allProviders) { - if (LocationManager.PASSIVE_PROVIDER.equals(prov)) + if (LocationManager.PASSIVE_PROVIDER.equals(provider)) continue; - if (mLocationManager.isProviderEnabled(prov)) - { - acceptedProviders.add(prov); - continue; - } - - if (LocationManager.GPS_PROVIDER.equals(prov)) // warn about turned off gps - LocationHelper.INSTANCE.notifyLocationError(LocationHelper.ERROR_GPS_OFF); + if (mLocationManager.isProviderEnabled(provider)) + res.add(provider); } - return acceptedProviders; + return res; } } \ No newline at end of file diff --git a/android/src/com/mapswithme/maps/location/BaseLocationProvider.java b/android/src/com/mapswithme/maps/location/BaseLocationProvider.java index 4f1020943a..46e505e49d 100644 --- a/android/src/com/mapswithme/maps/location/BaseLocationProvider.java +++ b/android/src/com/mapswithme/maps/location/BaseLocationProvider.java @@ -14,13 +14,10 @@ abstract class BaseLocationProvider implements LocationListener static final Logger sLogger = SimpleLogger.get(BaseLocationProvider.class.getName()); private static final double DEFAULT_SPEED_MPS = 5; - protected abstract void startUpdates(); - protected abstract void stopUpdates(); - protected boolean isLocationBetterThanLast(Location newLocation, Location lastLocation) { double speed = Math.max(DEFAULT_SPEED_MPS, (newLocation.getSpeed() + lastLocation.getSpeed()) / 2.0); - double lastAccuracy = (lastLocation.getAccuracy() + speed * LocationUtils.getDiffWithLastLocation(lastLocation, newLocation)); + double lastAccuracy = (lastLocation.getAccuracy() + speed * LocationUtils.getDiff(lastLocation, newLocation)); return (newLocation.getAccuracy() < lastAccuracy); } @@ -42,8 +39,8 @@ abstract class BaseLocationProvider implements LocationListener if (isLocationBetterThanLast(location)) { - LocationHelper.INSTANCE.initMagneticField(location); - LocationHelper.INSTANCE.saveLocation(location); + LocationHelper.INSTANCE.resetMagneticField(location); + LocationHelper.INSTANCE.onLocationUpdated(location); } } @@ -64,4 +61,7 @@ abstract class BaseLocationProvider implements LocationListener { sLogger.d("Status changed for location provider: ", provider, status); } + + protected abstract boolean start(); + protected abstract void stop(); } diff --git a/android/src/com/mapswithme/maps/location/CompassData.java b/android/src/com/mapswithme/maps/location/CompassData.java new file mode 100644 index 0000000000..12dc40d01d --- /dev/null +++ b/android/src/com/mapswithme/maps/location/CompassData.java @@ -0,0 +1,42 @@ +package com.mapswithme.maps.location; + +import android.app.Activity; + +import com.mapswithme.maps.MwmApplication; +import com.mapswithme.util.LocationUtils; + + +public final class CompassData +{ + private double mMagneticNorth; + private double mTrueNorth; + private double mNorth; + + public void update(double magneticNorth, double trueNorth) + { + Activity top = MwmApplication.backgroundTracker().getTopActivity(); + if (top == null) + return; + + int rotation = top.getWindowManager().getDefaultDisplay().getRotation(); + mMagneticNorth = LocationUtils.correctCompassAngle(rotation, magneticNorth); + mTrueNorth = LocationUtils.correctCompassAngle(rotation, trueNorth); + mNorth = ((mTrueNorth >= 0.0) ? mTrueNorth : mMagneticNorth); + } + + public double getMagneticNorth() + { + return mMagneticNorth; + } + + public double getTrueNorth() + { + return mTrueNorth; + } + + public double getNorth() + { + return mNorth; + } +} + diff --git a/android/src/com/mapswithme/maps/location/GoogleFusedLocationProvider.java b/android/src/com/mapswithme/maps/location/GoogleFusedLocationProvider.java index d2e338c66a..437cfcb156 100644 --- a/android/src/com/mapswithme/maps/location/GoogleFusedLocationProvider.java +++ b/android/src/com/mapswithme/maps/location/GoogleFusedLocationProvider.java @@ -1,5 +1,7 @@ package com.mapswithme.maps.location; +import android.app.Activity; +import android.content.IntentSender; import android.location.Location; import android.os.Bundle; import android.support.annotation.NonNull; @@ -28,7 +30,7 @@ class GoogleFusedLocationProvider extends BaseLocationProvider private LocationRequest mLocationRequest; private PendingResult mLocationSettingsResult; - public GoogleFusedLocationProvider() + GoogleFusedLocationProvider() { mGoogleApiClient = new GoogleApiClient.Builder(MwmApplication.get()) .addApi(LocationServices.API) @@ -38,10 +40,10 @@ class GoogleFusedLocationProvider extends BaseLocationProvider } @Override - protected void startUpdates() + protected boolean start() { if (mGoogleApiClient == null || mGoogleApiClient.isConnected() || mGoogleApiClient.isConnecting()) - return; + return true; mLocationRequest = LocationRequest.create(); // mLocationRequest.setPriority(LocationHelper.INSTANCE.isHighAccuracy() ? LocationRequest.PRIORITY_HIGH_ACCURACY @@ -57,18 +59,21 @@ class GoogleFusedLocationProvider extends BaseLocationProvider mLocationRequest.setFastestInterval(interval / 2); mGoogleApiClient.connect(); + return true; } @Override - protected void stopUpdates() + protected void stop() { if (mGoogleApiClient == null) return; if (mGoogleApiClient.isConnected()) LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); + if (mLocationSettingsResult != null && !mLocationSettingsResult.isCanceled()) mLocationSettingsResult.cancel(); + mGoogleApiClient.disconnect(); } @@ -76,12 +81,12 @@ class GoogleFusedLocationProvider extends BaseLocationProvider protected boolean isLocationBetterThanLast(Location newLocation, Location lastLocation) { // We believe that google services always returns good locations. - return isFromFusedProvider(newLocation) || - !isFromFusedProvider(lastLocation) && super.isLocationBetterThanLast(newLocation, lastLocation); + return (isFromFusedProvider(newLocation) || + (!isFromFusedProvider(lastLocation) && super.isLocationBetterThanLast(newLocation, lastLocation))); } - private boolean isFromFusedProvider(Location location) + private static boolean isFromFusedProvider(Location location) { return GMS_LOCATION_PROVIDER.equalsIgnoreCase(location.getProvider()); } @@ -107,31 +112,49 @@ class GoogleFusedLocationProvider extends BaseLocationProvider switch (status.getStatusCode()) { case LocationSettingsStatusCodes.SUCCESS: - LocationHelper.INSTANCE.setShouldResolveErrors(true); break; + case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: // Location settings are not satisfied, but this can be fixed by showing the user a dialog. - LocationHelper.INSTANCE.resolveLocationError(status); - break; + resolveError(status); + return; + case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: // Location settings are not satisfied. However, we have no way to fix the settings so we won't show the dialog. break; } + requestLocationUpdates(); } }); } + private static void resolveError(Status status) + { + if (LocationHelper.INSTANCE.isLocationStopped()) + return; + + LocationHelper.INSTANCE.stop(false); + Activity activity = MwmApplication.backgroundTracker().getTopActivity(); + if (activity != null) + { + try + { + status.startResolutionForResult(activity, LocationHelper.REQUEST_RESOLVE_ERROR); + } catch (IntentSender.SendIntentException ignored) {} + } + } + private void requestLocationUpdates() { if (!mGoogleApiClient.isConnected()) return; LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); - LocationHelper.INSTANCE.registerSensorListeners(); - final Location last = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); + LocationHelper.INSTANCE.startSensors(); + Location last = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); if (last != null) - LocationHelper.INSTANCE.saveLocation(last); + onLocationChanged(last); } @Override @@ -145,6 +168,6 @@ class GoogleFusedLocationProvider extends BaseLocationProvider { sLogger.d("Fused onConnectionFailed. Fall back to native provider. ConnResult " + connectionResult); // TODO handle error in a smarter way - LocationHelper.INSTANCE.initLocationProvider(true); + LocationHelper.INSTANCE.initProvider(true); } } diff --git a/android/src/com/mapswithme/maps/location/LocationHelper.java b/android/src/com/mapswithme/maps/location/LocationHelper.java index 0832f71c12..ad573d5768 100644 --- a/android/src/com/mapswithme/maps/location/LocationHelper.java +++ b/android/src/com/mapswithme/maps/location/LocationHelper.java @@ -1,42 +1,35 @@ package com.mapswithme.maps.location; import android.app.Activity; -import android.content.Context; -import android.content.IntentSender; -import android.content.SharedPreferences; -import android.hardware.GeomagneticField; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; +import android.content.DialogInterface; +import android.content.Intent; import android.location.Location; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; +import java.lang.ref.WeakReference; import java.util.List; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; -import com.google.android.gms.common.api.Status; import com.mapswithme.maps.Framework; import com.mapswithme.maps.LocationState; -import com.mapswithme.maps.MwmActivity; import com.mapswithme.maps.MwmApplication; import com.mapswithme.maps.R; import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.routing.RoutingController; +import com.mapswithme.util.Config; import com.mapswithme.util.Listeners; import com.mapswithme.util.LocationUtils; +import com.mapswithme.util.Utils; import com.mapswithme.util.concurrency.UiThread; +import com.mapswithme.util.log.DebugLogger; import com.mapswithme.util.log.Logger; -import com.mapswithme.util.log.SimpleLogger; -public enum LocationHelper implements SensorEventListener +public enum LocationHelper { INSTANCE; - protected final Logger mLogger; // These constants should correspond to values defined in platform/location.hpp // Leave 0-value as no any error. public static final int ERROR_NOT_SUPPORTED = 1; @@ -49,88 +42,180 @@ public enum LocationHelper implements SensorEventListener private static final long INTERVAL_NOT_FOLLOW_MS = 10000; private static final long INTERVAL_NAVIGATION_VEHICLE_MS = 500; private static final long INTERVAL_NAVIGATION_PEDESTRIAN_MS = 3000; + // TODO (trashkalmar): Correct value + private static final long INTERVAL_NAVIGATION_BICYCLE_MS = 1000; - public static final String LOCATION_PREDICTOR_PROVIDER = "LocationPredictorProvider"; - private static final float DISTANCE_TO_RECREATE_MAGNETIC_FIELD_M = 1000; private static final long STOP_DELAY_MS = 5000; + static final int REQUEST_RESOLVE_ERROR = 101; - private static final String PREF_RESOLVE_ERRORS = "ResolveLocationErrors"; - - public interface LocationListener + public interface UiCallback { - void onLocationUpdated(final Location l); - void onCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy); - void onLocationError(int errorCode); + Activity getActivity(); + void onMyPositionModeChanged(int newMode); + void onLocationUpdated(Location location); + void onCompassUpdated(CompassData compass); + boolean shouldNotifyLocationNotFound(); } - public static class SimpleLocationListener implements LocationListener + private final LocationListener mLocationListener = new LocationListener() { @Override - public void onLocationUpdated(Location l) {} + public void onLocationUpdated(Location location) + { + mLogger.d("onLocationUpdated()"); + + mPredictor.onLocationUpdated(location); + + nativeLocationUpdated(location.getTime(), + location.getLatitude(), + location.getLongitude(), + location.getAccuracy(), + location.getAltitude(), + location.getSpeed(), + location.getBearing()); + } @Override - public void onCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy) {} + public void onCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy) + { + if (mCompassData == null) + mCompassData = new CompassData(); + + mCompassData.update(magneticNorth, trueNorth); + + if (mUiCallback != null) + mUiCallback.onCompassUpdated(mCompassData); + } @Override - public void onLocationError(int errorCode) {} - } + public void onLocationError(int errorCode) + { + if (mLocationStopped) + return; + nativeOnLocationError(errorCode); + + if (mUiCallback == null) + return; + + Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); + if (intent.resolveActivity(MwmApplication.get().getPackageManager()) == null) + { + intent = new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS); + if (intent.resolveActivity(MwmApplication.get().getPackageManager()) == null) + return; + } + + final Intent finIntent = intent; + new AlertDialog.Builder(mUiCallback.getActivity()) + .setTitle(R.string.enable_location_services) + .setMessage(R.string.location_is_disabled_long_text) + .setNegativeButton(R.string.close, null) + .setPositiveButton(R.string.connection_settings, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (mUiCallback != null) + mUiCallback.getActivity().startActivity(finIntent); + } + }).show(); + } + }; + + private final Logger mLogger = new DebugLogger(LocationHelper.class.getSimpleName()); private final Listeners mListeners = new Listeners<>(); private boolean mActive; + private boolean mLocationStopped; + private boolean mColdStart = true; private Location mSavedLocation; private MapObject mMyPosition; private long mLastLocationTime; - private final SensorManager mSensorManager; - private Sensor mAccelerometer; - private Sensor mMagnetometer; - private GeomagneticField mMagneticField; + private final SensorHelper mSensorHelper = new SensorHelper(); private BaseLocationProvider mLocationProvider; + private final LocationPredictor mPredictor = new LocationPredictor(mLocationListener); + private UiCallback mUiCallback; + private WeakReference mPrevUiCallback; private long mInterval; private boolean mHighAccuracy; + private CompassData mCompassData; - private float[] mGravity; - private float[] mGeomagnetic; - private final float[] mR = new float[9]; - private final float[] mI = new float[9]; - private final float[] mOrientation = new float[3]; + private boolean mPendingNoLocation; - private final Runnable mStopLocationTask = new Runnable() { + @SuppressWarnings("FieldCanBeLocal") + private final LocationState.ModeChangeListener mModeChangeListener = new LocationState.ModeChangeListener() + { + @Override + public void onMyPositionModeChanged(int newMode) + { + notifyMyPositionModeChanged(newMode); + + switch (newMode) + { + case LocationState.PENDING_POSITION: + addListener(mLocationListener, true); + break; + + case LocationState.NOT_FOLLOW_NO_POSITION: + if (mColdStart) + { + LocationState.nativeSwitchToNextMode(); + break; + } + + removeListener(mLocationListener); + + if (mLocationStopped) + break; + + if (!hasPendingNoLocation() && LocationUtils.areLocationServicesTurnedOn()) + { + mLocationStopped = true; + notifyLocationNotFound(); + } + break; + + default: + mLocationStopped = false; + restart(); + break; + } + + mColdStart = false; + } + }; + + private final Runnable mStopLocationTask = new Runnable() + { @Override public void run() { - if (!mActive) - return; + mLogger.d("mStopLocationTask.run(). Was active: " + mActive); - mActive = false; - mLocationProvider.stopUpdates(); - mMagneticField = null; - if (mSensorManager != null) - mSensorManager.unregisterListener(LocationHelper.this); + if (mActive) + stopInternal(); } }; LocationHelper() { - mLogger = SimpleLogger.get(LocationHelper.class.getName()); - initLocationProvider(false); - mSensorManager = (SensorManager) MwmApplication.get().getSystemService(Context.SENSOR_SERVICE); - if (mSensorManager != null) - { - mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); - } + mLogger.d("ctor()"); + + LocationState.nativeSetListener(mModeChangeListener); + calcParams(); + initProvider(false); } - public void initLocationProvider(boolean forceNativeProvider) + public void initProvider(boolean forceNative) { final MwmApplication application = MwmApplication.get(); final boolean containsGoogleServices = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(application) == ConnectionResult.SUCCESS; - final boolean googleServicesTurnedInSettings = prefs().getBoolean(application.getString(R.string.pref_play_services), false); - if (!forceNativeProvider && + final boolean googleServicesTurnedInSettings = Config.useGoogleServices(); + if (!forceNative && containsGoogleServices && googleServicesTurnedInSettings) { @@ -145,7 +230,15 @@ public enum LocationHelper implements SensorEventListener mActive = !mListeners.isEmpty(); if (mActive) - start(); + startInternal(); + } + + public void onLocationUpdated(Location location) + { + mSavedLocation = location; + mMyPosition = null; + mLastLocationTime = System.currentTimeMillis(); + notifyLocationUpdated(); } @Nullable @@ -167,169 +260,213 @@ public enum LocationHelper implements SensorEventListener } /** - *

Obtains last known saved location. It depends on "My position" button state and is cleared on "No position" one. + *

Obtains last known saved location. It depends on "My position" button mode and is erased on "No follow, no position" one. *

If you need the location regardless of the button's state, use {@link #getLastKnownLocation()}. - * @return {@code null} if no location is saved or "My position" button is in "No position" state. + * @return {@code null} if no location is saved or "My position" button is in "No follow, no position" mode. */ public Location getSavedLocation() { return mSavedLocation; } public long getSavedLocationTime() { return mLastLocationTime; } - public void saveLocation(@NonNull Location loc) - { - mSavedLocation = loc; - mMyPosition = null; - mLastLocationTime = System.currentTimeMillis(); - notifyLocationUpdated(); - } - - protected void notifyLocationUpdated() - { - if (mSavedLocation == null) - return; - - for (LocationListener listener : mListeners) - listener.onLocationUpdated(mSavedLocation); - mListeners.finishIterate(); - } - - protected void notifyLocationError(int errCode) - { - for (LocationListener listener : mListeners) - listener.onLocationError(errCode); - mListeners.finishIterate(); - } - - public boolean shouldResolveErrors() - { - return prefs().getBoolean(PREF_RESOLVE_ERRORS, true); - } - - public void setShouldResolveErrors(boolean shouldResolve) - { - prefs().edit().putBoolean(PREF_RESOLVE_ERRORS, shouldResolve).apply(); - } - - protected void resolveLocationError(Status status) - { - if (!shouldResolveErrors()) - return; - - for (LocationListener listener : mListeners) - { - if (listener instanceof Activity) - { - // Show the dialog and check the result in onActivityResult(). - try - { - status.startResolutionForResult((Activity) listener, MwmActivity.REQUEST_CHECK_SETTINGS); - } catch (IntentSender.SendIntentException ignored) {} - } - } - mListeners.finishIterate(); - } - - private void notifyCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy) + void notifyCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy) { for (LocationListener listener : mListeners) listener.onCompassUpdated(time, magneticNorth, trueNorth, accuracy); mListeners.finishIterate(); } - @android.support.annotation.UiThread - public void addLocationListener(LocationListener listener, boolean forceUpdate) + void notifyLocationUpdated() { + mLogger.d("notifyLocationUpdated()"); + + if (mSavedLocation == null) + { + mLogger.d("No saved location - skip"); + return; + } + + for (LocationListener listener : mListeners) + listener.onLocationUpdated(mSavedLocation); + mListeners.finishIterate(); + } + + private void notifyLocationUpdated(LocationListener listener) + { + mLogger.d("notifyLocationUpdated(), listener: " + listener); + + if (mSavedLocation == null) + { + mLogger.d("No saved location - skip"); + return; + } + + listener.onLocationUpdated(mSavedLocation); + } + + private void notifyLocationError(int errCode) + { + mLogger.d("notifyLocationError(): " + errCode); + + for (LocationListener listener : mListeners) + listener.onLocationError(errCode); + mListeners.finishIterate(); + } + + private void notifyMyPositionModeChanged(int newMode) + { + mLogger.d("notifyMyPositionModeChanged(): " + LocationState.nameOf(newMode)); + + if (mUiCallback != null) + mUiCallback.onMyPositionModeChanged(newMode); + + mPredictor.onMyPositionModeChanged(newMode); + } + + private void notifyLocationNotFound() + { + mLogger.d("notifyLocationNotFound()"); + + setHasPendingNoLocation(mUiCallback == null); + if (hasPendingNoLocation()) + { + mLogger.d("UI detached - schedule notification"); + return; + } + + if (!mUiCallback.shouldNotifyLocationNotFound()) + return; + + Activity activity = mUiCallback.getActivity(); + String message = String.format("%s\n\n%s", activity.getString(R.string.current_location_unknown_message), + activity.getString(R.string.current_location_unknown_title)); + new AlertDialog.Builder(activity) + .setMessage(message) + .setNegativeButton(R.string.current_location_unknown_stop_button, null) + .setPositiveButton(R.string.current_location_unknown_continue_button, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + mLocationStopped = false; + onMyPositionButtonClicked(); + } + }).setOnDismissListener(new DialogInterface.OnDismissListener() + { + @Override + public void onDismiss(DialogInterface dialog) + { + setHasPendingNoLocation(false); + } + }).show(); + } + + public boolean hasPendingNoLocation() + { + return mPendingNoLocation; + } + + public void setHasPendingNoLocation(boolean hasPending) + { + mLogger.d("setHasPendingNoLocation(), hasPending: " + hasPending); + mPendingNoLocation = hasPending; + } + + boolean isLocationStopped() + { + return mLocationStopped; + } + + void stop(boolean delayed) + { + mLogger.d("stop(), delayed: " + delayed); + mLocationStopped = true; + removeListener(mLocationListener, delayed); + } + + public boolean onActivityResult(int requestCode, int resultCode) + { + if (requestCode != REQUEST_RESOLVE_ERROR) + return false; + + mLocationStopped = (resultCode != Activity.RESULT_OK); + mLogger.d("onActivityResult(): success: " + !mLocationStopped); + + if (!mLocationStopped) + onMyPositionButtonClicked(); + + return true; + } + + /** + * Registers listener about location changes. Starts polling on the first listener registration. + * @param listener listener to register. + * @param forceUpdate instantly notify given listener about available location, if any. + */ + @android.support.annotation.UiThread + public void addListener(LocationListener listener, boolean forceUpdate) + { + mLogger.d("addListener(): " + listener + ", forceUpdate: " + forceUpdate); + mLogger.d(" - listener count was: " + mListeners.getSize()); + UiThread.cancelDelayedTasks(mStopLocationTask); - if (mListeners.isEmpty()) - start(); - + boolean wasEmpty = mListeners.isEmpty(); mListeners.register(listener); - if (forceUpdate) - notifyLocationUpdated(); + if (wasEmpty) + { + calcParams(); + startInternal(); + } + + if (mActive && forceUpdate) + notifyLocationUpdated(listener); } @android.support.annotation.UiThread - public void removeLocationListener(LocationListener listener) + private void removeListener(LocationListener listener, boolean delayed) { + mLogger.d("removeListener(), delayed: " + delayed + ", listener: " + listener); + mLogger.d(" - listener count was: " + mListeners.getSize()); + boolean wasEmpty = mListeners.isEmpty(); mListeners.unregister(listener); if (!wasEmpty && mListeners.isEmpty()) - // Make a delay with disconnection from location providers, so that orientation changes and short app sleeps - // doesn't take long time to connect again. - UiThread.runLater(mStopLocationTask, STOP_DELAY_MS); - } - - void registerSensorListeners() - { - if (mSensorManager != null) { - if (mAccelerometer != null) - mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI); - if (mMagnetometer != null) - mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_UI); - } - } + mLogger.d(" - was not empty"); - @Override - public void onSensorChanged(SensorEvent event) - { - if (!MwmApplication.get().isFrameworkInitialized()) - return; - - boolean hasOrientation = false; - - switch (event.sensor.getType()) - { - case Sensor.TYPE_ACCELEROMETER: - mGravity = nativeUpdateCompassSensor(0, event.values); - break; - case Sensor.TYPE_MAGNETIC_FIELD: - mGeomagnetic = nativeUpdateCompassSensor(1, event.values); - break; - } - - if (mGravity != null && mGeomagnetic != null) - { - if (SensorManager.getRotationMatrix(mR, mI, mGravity, mGeomagnetic)) + if (delayed) { - hasOrientation = true; - SensorManager.getOrientation(mR, mOrientation); + mLogger.d(" - schedule stop"); + stopDelayed(); } - } - - if (hasOrientation) - { - final double magneticHeading = LocationUtils.correctAngle(mOrientation[0], 0.0); - - if (mMagneticField == null) - notifyCompassUpdated(event.timestamp, magneticHeading, -1.0, -1.0); // -1.0 - as default parameters else { - // positive 'offset' means the magnetic field is rotated east that match from true north - final double offset = Math.toRadians(mMagneticField.getDeclination()); - final double trueHeading = LocationUtils.correctAngle(magneticHeading, offset); - - notifyCompassUpdated(event.timestamp, magneticHeading, trueHeading, offset); + mLogger.d(" - stop now"); + stopInternal(); } } } - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) {} - - public void initMagneticField(Location newLocation) + /** + * Removes given location listener. Stops polling if there are no listeners left. + * @param listener listener to unregister. + */ + @android.support.annotation.UiThread + public void removeListener(LocationListener listener) { - if (mSensorManager != null) - { - // Recreate magneticField if location has changed significantly - if (mMagneticField == null || mSavedLocation == null || - newLocation.distanceTo(mSavedLocation) > DISTANCE_TO_RECREATE_MAGNETIC_FIELD_M) - mMagneticField = new GeomagneticField((float) newLocation.getLatitude(), (float) newLocation.getLongitude(), - (float) newLocation.getAltitude(), newLocation.getTime()); - } + removeListener(listener, false); + } + + void startSensors() + { + mSensorHelper.start(); + } + + void resetMagneticField(Location location) + { + mSensorHelper.resetMagneticField(mSavedLocation, location); } private void calcParams() @@ -338,13 +475,23 @@ public enum LocationHelper implements SensorEventListener if (RoutingController.get().isNavigating()) { final @Framework.RouterType int router = Framework.nativeGetRouter(); - if (router == Framework.ROUTER_TYPE_PEDESTRIAN) + switch (router) + { + case Framework.ROUTER_TYPE_PEDESTRIAN: mInterval = INTERVAL_NAVIGATION_PEDESTRIAN_MS; - else if (router == Framework.ROUTER_TYPE_VEHICLE) - mInterval = INTERVAL_NAVIGATION_VEHICLE_MS; - else - // TODO yunikkk determine correct interval for bicycle + break; + + case Framework.ROUTER_TYPE_VEHICLE: mInterval = INTERVAL_NAVIGATION_VEHICLE_MS; + break; + + case Framework.ROUTER_TYPE_BICYCLE: + mInterval = INTERVAL_NAVIGATION_BICYCLE_MS; + break; + + default: + throw new IllegalArgumentException("Unsupported router type: " + router); + } return; } @@ -373,39 +520,151 @@ public enum LocationHelper implements SensorEventListener return mInterval; } + // TODO (trashkalmar): Usage of this method was temporarily commented out from GoogleFusedLocationProvider.start(). See comments there. boolean isHighAccuracy() { return mHighAccuracy; } + /** + * Recalculates location parameters and restarts locator if it was in a progress before. + *

Does nothing if there were no subscribers. + */ public void restart() { - mLogger.d("restart"); - mLocationProvider.stopUpdates(); - mActive &= !mListeners.isEmpty(); - if (mActive) - start(); - } + if (!mActive) + { + stopInternal(); + return; + } + + boolean oldHighAccuracy = mHighAccuracy; + long oldInterval = mInterval; + mLogger.d("restart. Old params: " + oldInterval + " / " + (oldHighAccuracy ? "high" : "normal")); - private void start() - { - mActive = true; calcParams(); + mLogger.d("New params: " + mInterval + " / " + (mHighAccuracy ? "high" : "normal")); - mLogger.d("start. Params: " + mInterval + ", high: " + mHighAccuracy); - mLocationProvider.startUpdates(); + if (mHighAccuracy != oldHighAccuracy || mInterval != oldInterval) + { + boolean active = mActive; + stopInternal(); + + if (active) + startInternal(); + } } - public static void onLocationUpdated(@NonNull Location location) + /** + * Actually starts location polling. + */ + private void startInternal() { - nativeLocationUpdated(location.getTime(), - location.getLatitude(), - location.getLongitude(), - location.getAccuracy(), - location.getAltitude(), - location.getSpeed(), - location.getBearing()); + mLogger.d("startInternal()"); + + mActive = mLocationProvider.start(); + mLogger.d(mActive ? "SUCCESS" : "FAILURE"); + + if (mActive) + mPredictor.resume(); + else + notifyLocationError(LocationHelper.ERROR_DENIED); + } + + /** + * Actually stops location polling. + */ + private void stopInternal() + { + mLogger.d("stopInternal()"); + + mActive = false; + mLocationProvider.stop(); + mSensorHelper.stop(); + mPredictor.pause(); + notifyMyPositionModeChanged(LocationState.getMode()); + } + + /** + * Schedules poll termination after {@link #STOP_DELAY_MS}. + */ + private void stopDelayed() + { + mLogger.d("stopDelayed()"); + UiThread.runLater(mStopLocationTask, STOP_DELAY_MS); + } + + /** + * Attach UI to helper. + */ + public void attach(UiCallback callback) + { + mLogger.d("attach()"); + + if (mUiCallback != null) + { + mLogger.d(" - already attached. Skip."); + return; + } + + mUiCallback = callback; + if (mPrevUiCallback != null) + { + UiCallback prev = mPrevUiCallback.get(); + if (prev != null && prev != mUiCallback) + { + // Another UI instance attached, cancel pending "No location" dialog. + setHasPendingNoLocation(false); + } + } + + Utils.keepScreenOn(mActive, mUiCallback.getActivity().getWindow()); + + int mode = LocationState.getMode(); + mUiCallback.onMyPositionModeChanged(mode); + if (mCompassData != null) + mUiCallback.onCompassUpdated(mCompassData); + + mPrevUiCallback = new WeakReference<>(mUiCallback); + + if (hasPendingNoLocation()) + notifyLocationNotFound(); + + if (!mLocationStopped) + addListener(mLocationListener, true); + } + + /** + * Detach UI from helper. + */ + public void detach() + { + mLogger.d("detach()"); + + if (mUiCallback == null) + { + mLogger.d(" - already detached. Skip."); + return; + } + + mUiCallback = null; + stop(true); + } + + public void onMyPositionButtonClicked() + { + int mode = LocationState.getMode(); + mLogger.d("onMyPositionButtonClicked(). Mode was: " + LocationState.nameOf(mode)); + + if (mode == LocationState.PENDING_POSITION) + { + mLogger.d("Pending. Skip"); + return; + } + + mLocationStopped = false; + LocationState.nativeSwitchToNextMode(); } /** @@ -417,9 +676,16 @@ public enum LocationHelper implements SensorEventListener if (mSavedLocation != null) return mSavedLocation; - AndroidNativeProvider provider = new AndroidNativeProvider(); - List providers = provider.getFilteredProviders(); + AndroidNativeProvider provider = new AndroidNativeProvider() + { + @Override + public void onLocationChanged(Location location) + { + // Block ancestor + } + }; + List providers = provider.filterProviders(); if (providers.isEmpty()) return null; @@ -431,12 +697,12 @@ public enum LocationHelper implements SensorEventListener return getLastKnownLocation(LocationUtils.LOCATION_EXPIRATION_TIME_MILLIS_LONG); } - private SharedPreferences prefs() + public CompassData getCompassData() { - return PreferenceManager.getDefaultSharedPreferences(MwmApplication.get()); + return mCompassData; } - public static native void nativeOnLocationError(int errorCode); + private static native void nativeOnLocationError(int errorCode); private static native void nativeLocationUpdated(long time, double lat, double lon, float accuracy, double altitude, float speed, float bearing); - private static native float[] nativeUpdateCompassSensor(int ind, float[] arr); + static native float[] nativeUpdateCompassSensor(int ind, float[] arr); } diff --git a/android/src/com/mapswithme/maps/location/LocationListener.java b/android/src/com/mapswithme/maps/location/LocationListener.java new file mode 100644 index 0000000000..996ac5e0e1 --- /dev/null +++ b/android/src/com/mapswithme/maps/location/LocationListener.java @@ -0,0 +1,22 @@ +package com.mapswithme.maps.location; + +import android.location.Location; + +public interface LocationListener +{ + class Simple implements LocationListener + { + @Override + public void onLocationUpdated(Location location) {} + + @Override + public void onCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy) {} + + @Override + public void onLocationError(int errorCode) {} + } + + void onLocationUpdated(Location location); + void onCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy); + void onLocationError(int errorCode); +} diff --git a/android/src/com/mapswithme/maps/location/LocationPredictor.java b/android/src/com/mapswithme/maps/location/LocationPredictor.java index 0c955af62f..5830ae054c 100644 --- a/android/src/com/mapswithme/maps/location/LocationPredictor.java +++ b/android/src/com/mapswithme/maps/location/LocationPredictor.java @@ -1,114 +1,122 @@ package com.mapswithme.maps.location; import android.location.Location; -import android.os.Handler; import com.mapswithme.maps.Framework; import com.mapswithme.maps.LocationState; +import com.mapswithme.util.concurrency.UiThread; -public class LocationPredictor +class LocationPredictor { + private static final String PREDICTOR_PROVIDER = "LocationPredictorProvider"; private static final long PREDICTION_INTERVAL = 200; private static final long MAX_PREDICTION_COUNT = 20; - private final Runnable mRunnable; - private final Handler mHandler; - - private final LocationHelper.LocationListener mListener; + private final LocationListener mListener; private Location mLastLocation; private boolean mGeneratePredictions; private int mPredictionCount; - public LocationPredictor(Handler handler, LocationHelper.LocationListener listener) + private final Runnable mRunnable = new Runnable() { - mHandler = handler; - mListener = listener; - - mRunnable = new Runnable() + @Override + public void run() { - @Override - public void run() - { - if (generatePrediction()) - mHandler.postDelayed(mRunnable, PREDICTION_INTERVAL); - } - }; - } + if (generatePrediction()) + schedule(); + } + }; - public void resume() + private void schedule() { - myPositionModeChanged(LocationState.getMode()); + UiThread.runLater(mRunnable, PREDICTION_INTERVAL); } - public void pause() + LocationPredictor(LocationListener listener) + { + mListener = listener; + } + + void resume() + { + onMyPositionModeChanged(LocationState.getMode()); + } + + void pause() { mGeneratePredictions = false; mLastLocation = null; - resetHandler(); + stop(); } - public void myPositionModeChanged(final int mode) + void onMyPositionModeChanged(int mode) { - if (!LocationState.isTurnedOn(mode)) + if (!LocationState.hasLocation(mode)) mLastLocation = null; mGeneratePredictions = (mode == LocationState.FOLLOW_AND_ROTATE); - resetHandler(); + + stop(); + start(); } - public void reset(Location location) + void onLocationUpdated(Location location) { + if (location.getProvider().equals(PREDICTOR_PROVIDER)) + return; + if (location.hasBearing() && location.hasSpeed()) { mLastLocation = new Location(location); mLastLocation.setTime(System.currentTimeMillis()); - mLastLocation.setProvider(LocationHelper.LOCATION_PREDICTOR_PROVIDER); + mLastLocation.setProvider(PREDICTOR_PROVIDER); + start(); } else + { mLastLocation = null; - - resetHandler(); + stop(); + } } - private boolean isPredict() + private boolean shouldStart() { - return mLastLocation != null && mGeneratePredictions; + return (mLastLocation != null && mGeneratePredictions); } - private void resetHandler() + private void start() + { + if (shouldStart()) + schedule(); + } + + private void stop() { mPredictionCount = 0; - - mHandler.removeCallbacks(mRunnable); - - if (isPredict()) - mHandler.postDelayed(mRunnable, PREDICTION_INTERVAL); + UiThread.cancelDelayedTasks(mRunnable); } private boolean generatePrediction() { - if (!isPredict()) + if (!shouldStart() || mPredictionCount >= MAX_PREDICTION_COUNT) return false; - if (mPredictionCount < MAX_PREDICTION_COUNT) - { - ++mPredictionCount; + mPredictionCount++; - Location info = new Location(mLastLocation); - info.setTime(System.currentTimeMillis()); + Location info = new Location(mLastLocation); + info.setTime(System.currentTimeMillis()); - final long elapsedMillis = info.getTime() - mLastLocation.getTime(); + double elapsed = (info.getTime() - mLastLocation.getTime()) / 1000.0; + double[] newLatLon = Framework.nativePredictLocation(info.getLatitude(), + info.getLongitude(), + info.getAccuracy(), + info.getBearing(), + info.getSpeed(), + elapsed); + info.setLatitude(newLatLon[0]); + info.setLongitude(newLatLon[1]); - double[] newLatLon = Framework.nativePredictLocation(info.getLatitude(), info.getLongitude(), - info.getAccuracy(), info.getBearing(), - info.getSpeed(), elapsedMillis / 1000.0); - info.setLatitude(newLatLon[0]); - info.setLongitude(newLatLon[1]); - - mListener.onLocationUpdated(info); - return true; - } - - return false; + mListener.onLocationUpdated(info); + return true; } } diff --git a/android/src/com/mapswithme/maps/location/SensorHelper.java b/android/src/com/mapswithme/maps/location/SensorHelper.java new file mode 100644 index 0000000000..9e93dcd0b1 --- /dev/null +++ b/android/src/com/mapswithme/maps/location/SensorHelper.java @@ -0,0 +1,118 @@ +package com.mapswithme.maps.location; + +import android.content.Context; +import android.hardware.GeomagneticField; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.location.Location; + +import com.mapswithme.maps.MwmApplication; +import com.mapswithme.util.LocationUtils; + +class SensorHelper implements SensorEventListener +{ + private static final float DISTANCE_TO_RECREATE_MAGNETIC_FIELD_M = 1000; + + private final SensorManager mSensorManager; + private Sensor mAccelerometer; + private Sensor mMagnetometer; + private GeomagneticField mMagneticField; + + private float[] mGravity; + private float[] mGeomagnetic; + private final float[] mR = new float[9]; + private final float[] mI = new float[9]; + private final float[] mOrientation = new float[3]; + + @Override + public void onSensorChanged(SensorEvent event) + { + if (!MwmApplication.get().isFrameworkInitialized()) + return; + + boolean hasOrientation = false; + + switch (event.sensor.getType()) + { + case Sensor.TYPE_ACCELEROMETER: + mGravity = LocationHelper.nativeUpdateCompassSensor(0, event.values); + break; + + case Sensor.TYPE_MAGNETIC_FIELD: + mGeomagnetic = LocationHelper.nativeUpdateCompassSensor(1, event.values); + break; + } + + if (mGravity != null && mGeomagnetic != null) + { + if (SensorManager.getRotationMatrix(mR, mI, mGravity, mGeomagnetic)) + { + hasOrientation = true; + SensorManager.getOrientation(mR, mOrientation); + } + } + + if (hasOrientation) + { + double trueHeading = -1.0; + double offset = -1.0; + double magneticHeading = LocationUtils.correctAngle(mOrientation[0], 0.0); + + if (mMagneticField != null) + { + // Positive 'offset' means the magnetic field is rotated east that match from true north + offset = Math.toRadians(mMagneticField.getDeclination()); + trueHeading = LocationUtils.correctAngle(magneticHeading, offset); + } + + LocationHelper.INSTANCE.notifyCompassUpdated(event.timestamp, magneticHeading, trueHeading, offset); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} + + SensorHelper() + { + mSensorManager = (SensorManager) MwmApplication.get().getSystemService(Context.SENSOR_SERVICE); + if (mSensorManager != null) + { + mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); + } + } + + void start() + { + if (mAccelerometer != null) + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI); + + if (mMagnetometer != null) + mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_UI); + } + + void stop() + { + mMagneticField = null; + if (mSensorManager != null) + mSensorManager.unregisterListener(this); + } + + void resetMagneticField(Location oldLocation, Location newLocation) + { + if (mSensorManager == null) + return; + + // Recreate magneticField if location has changed significantly + if (mMagneticField == null || oldLocation == null || + newLocation.distanceTo(oldLocation) > DISTANCE_TO_RECREATE_MAGNETIC_FIELD_M) + { + mMagneticField = new GeomagneticField((float) newLocation.getLatitude(), + (float) newLocation.getLongitude(), + (float) newLocation.getAltitude(), + newLocation.getTime()); + } + } +} diff --git a/android/src/com/mapswithme/maps/location/TrackRecorder.java b/android/src/com/mapswithme/maps/location/TrackRecorder.java index dc827a67a8..d5898d7ca1 100644 --- a/android/src/com/mapswithme/maps/location/TrackRecorder.java +++ b/android/src/com/mapswithme/maps/location/TrackRecorder.java @@ -38,14 +38,14 @@ public final class TrackRecorder private static Boolean sEnableLogging; private static Logger sLogger; - private static final LocationHelper.LocationListener sLocationListener = new LocationHelper.SimpleLocationListener() + private static final LocationListener sLocationListener = new LocationListener.Simple() { @Override public void onLocationUpdated(Location location) { log("onLocationUpdated()"); setAwaitTimeout(LOCATION_TIMEOUT_MIN_MS); - LocationHelper.onLocationUpdated(location); + LocationHelper.INSTANCE.onLocationUpdated(location); TrackRecorderWakeService.stop(); } @@ -180,7 +180,7 @@ public final class TrackRecorder public void run() { TrackRecorder.log("onServiceStarted(): actually runs here"); - LocationHelper.INSTANCE.addLocationListener(sLocationListener, false); + LocationHelper.INSTANCE.addListener(sLocationListener, false); } }); } @@ -195,7 +195,7 @@ public final class TrackRecorder public void run() { TrackRecorder.log("onServiceStopped(): actually runs here"); - LocationHelper.INSTANCE.removeLocationListener(sLocationListener); + LocationHelper.INSTANCE.removeListener(sLocationListener); if (!MwmApplication.backgroundTracker().isForeground()) restartAlarmIfEnabled(); diff --git a/android/src/com/mapswithme/maps/news/FirstStartFragment.java b/android/src/com/mapswithme/maps/news/FirstStartFragment.java index 1bfb355a1a..e45a2e714c 100644 --- a/android/src/com/mapswithme/maps/news/FirstStartFragment.java +++ b/android/src/com/mapswithme/maps/news/FirstStartFragment.java @@ -12,6 +12,7 @@ import com.mapswithme.maps.BuildConfig; import com.mapswithme.maps.Framework; import com.mapswithme.maps.R; import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.location.LocationListener; import com.mapswithme.util.Config; import com.mapswithme.util.statistics.Statistics; @@ -20,12 +21,12 @@ public class FirstStartFragment extends BaseNewsFragment private static Location sLocation; private static int sError = LocationHelper.ERROR_UNKNOWN; - private final LocationHelper.LocationListener mLocationListener = new LocationHelper.SimpleLocationListener() + private final LocationListener mLocationListener = new LocationListener.Simple() { @Override - public void onLocationUpdated(Location loc) + public void onLocationUpdated(Location location) { - sLocation = loc; + sLocation = location; sError = LocationHelper.ERROR_UNKNOWN; } @@ -86,7 +87,7 @@ public class FirstStartFragment extends BaseNewsFragment @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - LocationHelper.INSTANCE.addLocationListener(mLocationListener, true); + LocationHelper.INSTANCE.addListener(mLocationListener, true); return super.onCreateDialog(savedInstanceState); } @@ -100,12 +101,8 @@ public class FirstStartFragment extends BaseNewsFragment String reason; switch (sError) { - case LocationHelper.ERROR_GPS_OFF: - reason = "unavailable"; - break; - case LocationHelper.ERROR_DENIED: - reason = "not_permitted"; + reason = "unavailable"; break; default: @@ -120,7 +117,7 @@ public class FirstStartFragment extends BaseNewsFragment Framework.nativeZoomToPoint(sLocation.getLatitude(), sLocation.getLongitude(), 14, true); sLocation = null; - LocationHelper.INSTANCE.removeLocationListener(mLocationListener); + LocationHelper.INSTANCE.removeListener(mLocationListener); } public static boolean showOn(FragmentActivity activity) diff --git a/android/src/com/mapswithme/maps/search/SearchFragment.java b/android/src/com/mapswithme/maps/search/SearchFragment.java index ea9e82ee26..dbeaa8760b 100644 --- a/android/src/com/mapswithme/maps/search/SearchFragment.java +++ b/android/src/com/mapswithme/maps/search/SearchFragment.java @@ -27,6 +27,7 @@ import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.downloader.CountrySuggestFragment; import com.mapswithme.maps.downloader.MapManager; import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.location.LocationListener; import com.mapswithme.maps.routing.RoutingController; import com.mapswithme.maps.widget.SearchToolbarController; import com.mapswithme.util.UiUtils; @@ -141,12 +142,12 @@ public class SearchFragment extends BaseMwmFragment private String mInitialQuery; private boolean mFromRoutePlan; - private final LocationHelper.LocationListener mLocationListener = new LocationHelper.SimpleLocationListener() + private final LocationListener mLocationListener = new LocationListener.Simple() { @Override - public void onLocationUpdated(Location l) + public void onLocationUpdated(Location location) { - mLastPosition.set(l.getLatitude(), l.getLongitude()); + mLastPosition.set(location.getLatitude(), location.getLongitude()); if (!TextUtils.isEmpty(getQuery())) mSearchAdapter.notifyDataSetChanged(); @@ -276,13 +277,13 @@ public class SearchFragment extends BaseMwmFragment public void onResume() { super.onResume(); - LocationHelper.INSTANCE.addLocationListener(mLocationListener, true); + LocationHelper.INSTANCE.addListener(mLocationListener, true); } @Override public void onPause() { - LocationHelper.INSTANCE.removeLocationListener(mLocationListener); + LocationHelper.INSTANCE.removeListener(mLocationListener); super.onPause(); } diff --git a/android/src/com/mapswithme/maps/widget/menu/MainMenu.java b/android/src/com/mapswithme/maps/widget/menu/MainMenu.java index 679a3eb73a..152742a454 100644 --- a/android/src/com/mapswithme/maps/widget/menu/MainMenu.java +++ b/android/src/com/mapswithme/maps/widget/menu/MainMenu.java @@ -366,7 +366,7 @@ public class MainMenu setState(State.MENU, false); } - public MainMenu(ViewGroup frame, Container container) + public MainMenu(MwmActivity activity, ViewGroup frame, Container container) { mContainer = container; mFrame = frame; diff --git a/android/src/com/mapswithme/maps/widget/menu/MyPositionButton.java b/android/src/com/mapswithme/maps/widget/menu/MyPositionButton.java index 3a87bf62d3..3afd29d834 100644 --- a/android/src/com/mapswithme/maps/widget/menu/MyPositionButton.java +++ b/android/src/com/mapswithme/maps/widget/menu/MyPositionButton.java @@ -2,11 +2,11 @@ package com.mapswithme.maps.widget.menu; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; +import android.util.SparseArray; import android.view.View; import android.widget.ImageView; import com.mapswithme.maps.LocationState; -import com.mapswithme.maps.MwmActivity; import com.mapswithme.maps.R; import com.mapswithme.maps.location.LocationHelper; import com.mapswithme.util.Graphics; @@ -17,6 +17,7 @@ import com.mapswithme.util.statistics.Statistics; public class MyPositionButton { private final ImageView mButton; + private final SparseArray mIcons = new SparseArray<>(); // Location mode -> Button icon MyPositionButton(View button) { @@ -26,44 +27,44 @@ public class MyPositionButton @Override public void onClick(View v) { + LocationHelper.INSTANCE.onMyPositionButtonClicked(); + Statistics.INSTANCE.trackEvent(Statistics.EventName.TOOLBAR_MY_POSITION); AlohaHelper.logClick(AlohaHelper.TOOLBAR_MY_POSITION); - if (!LocationState.isTurnedOn()) - { - MwmActivity.enableLocation(); - LocationHelper.INSTANCE.setShouldResolveErrors(true); - LocationHelper.INSTANCE.restart(); // restart to check location settings again. - } - LocationState.INSTANCE.nativeSwitchToNextMode(); } }); } @SuppressWarnings("deprecation") - public void update(int state) + public void update(int mode) { - Drawable image; - switch (state) + Drawable image = mIcons.get(mode); + if (image == null) { - case LocationState.PENDING_POSITION: - image = mButton.getResources().getDrawable(ThemeUtils.getResource(mButton.getContext(), R.attr.myPositionButtonAnimation)); - break; + switch (mode) + { + case LocationState.PENDING_POSITION: + image = mButton.getResources().getDrawable(ThemeUtils.getResource(mButton.getContext(), R.attr.myPositionButtonAnimation)); + break; - case LocationState.NOT_FOLLOW_NO_POSITION: - case LocationState.NOT_FOLLOW: - image = Graphics.tint(mButton.getContext(), R.drawable.ic_not_follow); - break; + case LocationState.NOT_FOLLOW_NO_POSITION: + case LocationState.NOT_FOLLOW: + image = Graphics.tint(mButton.getContext(), R.drawable.ic_not_follow); + break; - case LocationState.FOLLOW: - image = Graphics.tint(mButton.getContext(), R.drawable.ic_follow, R.attr.colorAccent); - break; + case LocationState.FOLLOW: + image = Graphics.tint(mButton.getContext(), R.drawable.ic_follow, R.attr.colorAccent); + break; - case LocationState.FOLLOW_AND_ROTATE: - image = Graphics.tint(mButton.getContext(), R.drawable.ic_follow_and_rotate, R.attr.colorAccent); - break; + case LocationState.FOLLOW_AND_ROTATE: + image = Graphics.tint(mButton.getContext(), R.drawable.ic_follow_and_rotate, R.attr.colorAccent); + break; - default: - throw new IllegalArgumentException("Invalid button state: " + state); + default: + throw new IllegalArgumentException("Invalid button mode: " + mode); + } + + mIcons.put(mode, image); } mButton.setImageDrawable(image); diff --git a/android/src/com/mapswithme/maps/widget/placepage/DirectionFragment.java b/android/src/com/mapswithme/maps/widget/placepage/DirectionFragment.java index 39ce5ba9bd..5e97e1981a 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/DirectionFragment.java +++ b/android/src/com/mapswithme/maps/widget/placepage/DirectionFragment.java @@ -16,13 +16,15 @@ import com.mapswithme.maps.base.BaseMwmDialogFragment; import com.mapswithme.maps.bookmarks.data.DistanceAndAzimut; import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.location.LocationListener; import com.mapswithme.maps.widget.ArrowView; import com.mapswithme.util.LocationUtils; import com.mapswithme.util.UiUtils; import com.mapswithme.util.statistics.AlohaHelper; import com.mapswithme.util.statistics.Statistics; -public class DirectionFragment extends BaseMwmDialogFragment implements LocationHelper.LocationListener +public class DirectionFragment extends BaseMwmDialogFragment + implements LocationListener { private static final String EXTRA_MAP_OBJECT = "MapObject"; @@ -106,7 +108,7 @@ public class DirectionFragment extends BaseMwmDialogFragment implements Location public void onResume() { super.onResume(); - LocationHelper.INSTANCE.addLocationListener(this, true); + LocationHelper.INSTANCE.addListener(this, true); refreshViews(); } @@ -114,7 +116,7 @@ public class DirectionFragment extends BaseMwmDialogFragment implements Location public void onPause() { super.onPause(); - LocationHelper.INSTANCE.removeLocationListener(this); + LocationHelper.INSTANCE.removeListener(this); } @Override diff --git a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java index 0ba8861ad0..67734c26c5 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java +++ b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java @@ -877,7 +877,7 @@ public class PlacePageView extends RelativeLayout implements View.OnClickListene public void refreshAzimuth(double northAzimuth) { - if (getState() == State.HIDDEN || + if (isHidden() || mMapObject == null || MapObject.isOfType(MapObject.MY_POSITION, mMapObject)) return; @@ -1193,6 +1193,11 @@ public class PlacePageView extends RelativeLayout implements View.OnClickListene setState(State.HIDDEN); } + public boolean isHidden() + { + return (getState() == State.HIDDEN); + } + @SuppressWarnings("SimplifiableIfStatement") public boolean hideOnTouch() { diff --git a/android/src/com/mapswithme/util/LocationUtils.java b/android/src/com/mapswithme/util/LocationUtils.java index cf780a5243..aad7117254 100644 --- a/android/src/com/mapswithme/util/LocationUtils.java +++ b/android/src/com/mapswithme/util/LocationUtils.java @@ -10,7 +10,6 @@ import android.text.TextUtils; import android.view.Surface; import com.mapswithme.maps.MwmApplication; -import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.location.LocationHelper; public class LocationUtils @@ -20,8 +19,6 @@ public class LocationUtils public static final long LOCATION_EXPIRATION_TIME_MILLIS_SHORT = 60 * 1000; // 1 minute public static final long LOCATION_EXPIRATION_TIME_MILLIS_LONG = 6 * 60 * 60 * 1000; // 6 hours - public static final double LAT_LON_EPSILON = 1E-6; - /** * Correct compass angles due to display orientation. */ @@ -50,21 +47,16 @@ public class LocationUtils public static double correctAngle(double angle, double correction) { - angle += correction; + double res = angle + correction; final double twoPI = 2.0 * Math.PI; - angle = angle % twoPI; + res %= twoPI; // normalize angle into [0, 2PI] - if (angle < 0.0) - angle += twoPI; + if (res < 0.0) + res += twoPI; - return angle; - } - - public static double bearingToHeading(double bearing) - { - return correctAngle(0.0, Math.toRadians(bearing)); + return res; } public static boolean isExpired(Location l, long millis, long expirationMillis) @@ -77,7 +69,7 @@ public class LocationUtils return (timeDiff > expirationMillis); } - public static double getDiffWithLastLocation(Location lastLocation, Location newLocation) + public static double getDiff(Location lastLocation, Location newLocation) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return (newLocation.getElapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos()) * 1.0E-9; @@ -97,20 +89,9 @@ public class LocationUtils } } - public static boolean isSameLocationProvider(String p1, String p2) + private static boolean isSameLocationProvider(String p1, String p2) { - return !(p1 == null || p2 == null) && p1.equals(p2); - } - - /** - * Detects whether object are close enough to each other, so that we can assume they represent the same poi. - * @param first object - * @param second object - */ - public static boolean areLatLonEqual(MapObject first, MapObject second) - { - return Math.abs(first.getLat() - second.getLat()) < LocationUtils.LAT_LON_EPSILON && - Math.abs(first.getLon() - second.getLon()) < LocationUtils.LAT_LON_EPSILON; + return (p1 != null && p1.equals(p2)); } @SuppressLint("InlinedApi") diff --git a/android/src/com/mapswithme/util/ThemeSwitcher.java b/android/src/com/mapswithme/util/ThemeSwitcher.java index 2db08cb6de..334951847d 100644 --- a/android/src/com/mapswithme/util/ThemeSwitcher.java +++ b/android/src/com/mapswithme/util/ThemeSwitcher.java @@ -7,6 +7,7 @@ import com.mapswithme.maps.Framework; import com.mapswithme.maps.MwmApplication; import com.mapswithme.maps.downloader.DownloaderStatusIcon; import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.location.LocationListener; import com.mapswithme.maps.routing.RoutingController; import com.mapswithme.util.concurrency.UiThread; @@ -16,19 +17,19 @@ public final class ThemeSwitcher private static final Runnable sCheckProc = new Runnable() { - private final LocationHelper.LocationListener mLocationListener = new LocationHelper.SimpleLocationListener() + private final LocationListener mLocationListener = new LocationListener.Simple() { @Override - public void onLocationUpdated(Location l) + public void onLocationUpdated(Location location) { - LocationHelper.INSTANCE.removeLocationListener(this); + LocationHelper.INSTANCE.removeListener(this); run(); } @Override public void onLocationError(int errorCode) { - LocationHelper.INSTANCE.removeLocationListener(this); + LocationHelper.INSTANCE.removeListener(this); } }; @@ -42,12 +43,12 @@ public final class ThemeSwitcher Location last = LocationHelper.INSTANCE.getSavedLocation(); if (last == null) { - LocationHelper.INSTANCE.addLocationListener(mLocationListener, true); + LocationHelper.INSTANCE.addListener(mLocationListener, true); theme = Config.getCurrentUiTheme(); } else { - LocationHelper.INSTANCE.removeLocationListener(mLocationListener); + LocationHelper.INSTANCE.removeListener(mLocationListener); boolean day = Framework.nativeIsDayTime(System.currentTimeMillis() / 1000, last.getLatitude(), last.getLongitude()); theme = (day ? ThemeUtils.THEME_DEFAULT : ThemeUtils.THEME_NIGHT);