diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index 60ae1ed8c4..16bee7408a 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -16,6 +16,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.TextView; +import android.widget.Toast; import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; @@ -63,6 +64,8 @@ import app.organicmaps.intent.MapTask; import app.organicmaps.location.LocationHelper; import app.organicmaps.location.LocationListener; import app.organicmaps.location.LocationState; +import app.organicmaps.location.SensorHelper; +import app.organicmaps.location.SensorListener; import app.organicmaps.maplayer.MapButtonsController; import app.organicmaps.maplayer.MapButtonsViewModel; import app.organicmaps.maplayer.Mode; @@ -121,6 +124,7 @@ public class MwmActivity extends BaseMwmFragmentActivity CustomNavigateUpListener, RoutingController.Container, LocationListener, + SensorListener, LocationState.ModeChangeListener, RoutingPlanInplaceController.RoutingPlanListener, RoutingBottomMenuListener, @@ -1011,6 +1015,7 @@ public class MwmActivity extends BaseMwmFragmentActivity refreshLightStatusBar(); LocationState.nativeSetLocationPendingTimeoutListener(this::onLocationPendingTimeout); + SensorHelper.from(this).addListener(this); } @Override @@ -1038,6 +1043,7 @@ public class MwmActivity extends BaseMwmFragmentActivity mOnmapDownloader.onPause(); mNavigationController.onActivityPaused(this); LocationState.nativeRemoveLocationPendingTimeoutListener(); + SensorHelper.from(this).removeListener(this); dismissLocationErrorDialog(); dismissAlertDialog(); super.onPause(); @@ -1229,7 +1235,7 @@ public class MwmActivity extends BaseMwmFragmentActivity mMapFragment.updateCompassOffset(offsetX, offsetY); - final double north = LocationHelper.INSTANCE.getSavedNorth(); + final double north = SensorHelper.from(this).getSavedNorth(); if (!Double.isNaN(north)) Map.onCompassUpdated(north, true); } @@ -1739,6 +1745,22 @@ public class MwmActivity extends BaseMwmFragmentActivity mNavigationController.updateNorth(); } + @Override + @UiThread + public void onCompassCalibrationRecommended() + { + Toast.makeText(this, getString(R.string.compass_calibration_recommended), + Toast.LENGTH_LONG).show(); + } + + @Override + @UiThread + public void onCompassCalibrationRequired() + { + Toast.makeText(this, getString(R.string.compass_calibration_required), + Toast.LENGTH_LONG).show(); + } + /** * Start location services when the user presses a button or starts routing. */ diff --git a/android/app/src/main/java/app/organicmaps/MwmApplication.java b/android/app/src/main/java/app/organicmaps/MwmApplication.java index d88f002d2d..aff6656ba2 100644 --- a/android/app/src/main/java/app/organicmaps/MwmApplication.java +++ b/android/app/src/main/java/app/organicmaps/MwmApplication.java @@ -23,6 +23,7 @@ import app.organicmaps.bookmarks.data.BookmarkManager; import app.organicmaps.downloader.CountryItem; import app.organicmaps.downloader.MapManager; import app.organicmaps.location.LocationHelper; +import app.organicmaps.location.SensorHelper; import app.organicmaps.maplayer.isolines.IsolinesManager; import app.organicmaps.maplayer.subway.SubwayManager; import app.organicmaps.maplayer.traffic.TrafficManager; @@ -59,6 +60,10 @@ public class MwmApplication extends Application implements Application.ActivityL @NonNull private IsolinesManager mIsolinesManager; + @SuppressWarnings("NotNullFieldNotInitialized") + @NonNull + private SensorHelper mSensorHelper; + private volatile boolean mFrameworkInitialized; private volatile boolean mPlatformInitialized; @@ -90,6 +95,12 @@ public class MwmApplication extends Application implements Application.ActivityL return mIsolinesManager; } + @NonNull + public SensorHelper getSensorHelper() + { + return mSensorHelper; + } + public MwmApplication() { super(); @@ -131,6 +142,7 @@ public class MwmApplication extends Application implements Application.ActivityL registerActivityLifecycleCallbacks(this); mSubwayManager = new SubwayManager(this); mIsolinesManager = new IsolinesManager(this); + mSensorHelper = new SensorHelper(this); mPlayer = new MediaPlayerWrapper(this); } @@ -294,7 +306,7 @@ public class MwmApplication extends Application implements Application.ActivityL { Logger.d(TAG, "activity = " + activity); Utils.showOnLockScreen(Config.isShowOnLockScreenEnabled(), activity); - LocationHelper.INSTANCE.setRotation(activity.getWindowManager().getDefaultDisplay().getRotation()); + mSensorHelper.setRotation(activity.getWindowManager().getDefaultDisplay().getRotation()); mTopActivity = new WeakReference<>(activity); } diff --git a/android/app/src/main/java/app/organicmaps/location/LocationHelper.java b/android/app/src/main/java/app/organicmaps/location/LocationHelper.java index 513657404b..db07af7354 100644 --- a/android/app/src/main/java/app/organicmaps/location/LocationHelper.java +++ b/android/app/src/main/java/app/organicmaps/location/LocationHelper.java @@ -3,7 +3,6 @@ package app.organicmaps.location; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static app.organicmaps.location.LocationState.LOCATION_TAG; import android.app.PendingIntent; import android.content.Context; @@ -14,7 +13,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresPermission; import androidx.annotation.UiThread; -import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import app.organicmaps.Framework; @@ -54,25 +52,18 @@ public enum LocationHelper implements Initializable, BaseLocationProvid private final Set mListeners = new LinkedHashSet<>(); @Nullable private Location mSavedLocation; - private double mSavedNorth = Double.NaN; private MapObject mMyPosition; @SuppressWarnings("NotNullFieldNotInitialized") @NonNull - private SensorHelper mSensorHelper; - @SuppressWarnings("NotNullFieldNotInitialized") - @NonNull private BaseLocationProvider mLocationProvider; private long mInterval; private boolean mInFirstRun; private boolean mActive; - private int mRotation = 0; - @Override public void initialize(@NonNull Context context) { mContext = context; - mSensorHelper = new SensorHelper(context); mLocationProvider = LocationProviderFactory.getProvider(mContext, this); } @@ -119,19 +110,6 @@ public enum LocationHelper implements Initializable, BaseLocationProvid return mActive; } - public void setRotation(int rotation) - { - Logger.i(TAG, "rotation = " + rotation); - mRotation = rotation; - } - - void notifyCompassUpdated(double north) - { - mSavedNorth = LocationUtils.correctCompassAngle(mRotation, north); - for (LocationListener listener : mListeners) - listener.onCompassUpdated(mSavedNorth); - } - private void notifyLocationUpdated() { if (mSavedLocation == null) @@ -243,8 +221,6 @@ public enum LocationHelper implements Initializable, BaseLocationProvid mListeners.add(listener); if (mSavedLocation != null) listener.onLocationUpdated(mSavedLocation); - if (!Double.isNaN(mSavedNorth)) - listener.onCompassUpdated(mSavedNorth); } /** @@ -345,7 +321,9 @@ public enum LocationHelper implements Initializable, BaseLocationProvid Logger.i(TAG); checkForAgpsUpdates(); - mSensorHelper.start(); + if (ContextCompat.checkSelfPermission(mContext, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) + SensorHelper.from(mContext).start(); + final long oldInterval = mInterval; calcLocationUpdatesInterval(); Logger.i(TAG, "provider = " + mLocationProvider.getClass().getSimpleName() + @@ -367,7 +345,7 @@ public enum LocationHelper implements Initializable, BaseLocationProvid Logger.i(TAG); mLocationProvider.stop(); - mSensorHelper.stop(); + SensorHelper.from(mContext).stop(); mActive = false; } @@ -444,9 +422,4 @@ public enum LocationHelper implements Initializable, BaseLocationProvid Framework.nativeRunFirstLaunchAnimation(); } } - - public double getSavedNorth() - { - return mSavedNorth; - } } diff --git a/android/app/src/main/java/app/organicmaps/location/LocationListener.java b/android/app/src/main/java/app/organicmaps/location/LocationListener.java index a6d0c3eadb..17143bf449 100644 --- a/android/app/src/main/java/app/organicmaps/location/LocationListener.java +++ b/android/app/src/main/java/app/organicmaps/location/LocationListener.java @@ -9,11 +9,6 @@ public interface LocationListener { void onLocationUpdated(@NonNull Location location); - default void onCompassUpdated(double north) - { - // No op. - } - default void onLocationDisabled() { // No op. diff --git a/android/app/src/main/java/app/organicmaps/location/SensorData.java b/android/app/src/main/java/app/organicmaps/location/SensorData.java deleted file mode 100644 index f772719ad4..0000000000 --- a/android/app/src/main/java/app/organicmaps/location/SensorData.java +++ /dev/null @@ -1,38 +0,0 @@ -package app.organicmaps.location; - -import androidx.annotation.Nullable; - -class SensorData -{ - @Nullable - private float[] mGravity; - @Nullable - private float[] mGeomagnetic; - - @Nullable - public float[] getGravity() - { - return mGravity; - } - - public void setGravity(@Nullable float[] gravity) - { - mGravity = gravity; - } - - @Nullable - public float[] getGeomagnetic() - { - return mGeomagnetic; - } - - public void setGeomagnetic(@Nullable float[] geomagnetic) - { - mGeomagnetic = geomagnetic; - } - - public boolean isAbsent() - { - return mGravity == null || mGeomagnetic == null; - } -} diff --git a/android/app/src/main/java/app/organicmaps/location/SensorHelper.java b/android/app/src/main/java/app/organicmaps/location/SensorHelper.java index ee3f2f17bb..57d11c8ed5 100644 --- a/android/app/src/main/java/app/organicmaps/location/SensorHelper.java +++ b/android/app/src/main/java/app/organicmaps/location/SensorHelper.java @@ -6,27 +6,42 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.util.Log; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import app.organicmaps.MwmApplication; -import app.organicmaps.R; +import app.organicmaps.util.LocationUtils; +import app.organicmaps.util.log.Logger; -class SensorHelper implements SensorEventListener +import java.util.LinkedHashSet; +import java.util.Set; + +public class SensorHelper implements SensorEventListener { + private static final String TAG = SensorHelper.class.getSimpleName(); + @NonNull private final SensorManager mSensorManager; @Nullable - private final Sensor mRotationVectorSensor; - @NonNull - private final MwmApplication mMwmApplication; + private Sensor mRotationVectorSensor; private final float[] mRotationMatrix = new float[9]; private final float[] mRotationValues = new float[3]; // Initialized with purposely invalid value. private int mLastAccuracy = -42; + private double mSavedNorth = Double.NaN; + private int mRotation = 0; + + @NonNull + private final Set mListeners = new LinkedHashSet<>(); + + @NonNull + public static SensorHelper from(@NonNull Context context) + { + return MwmApplication.from(context).getSensorHelper(); + } @Override public void onSensorChanged(SensorEvent event) @@ -43,16 +58,14 @@ class SensorHelper implements SensorEventListener case SensorManager.SENSOR_STATUS_ACCURACY_HIGH: break; case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM: - Toast.makeText(mMwmApplication, - mMwmApplication.getString(R.string.compass_calibration_recommended), - Toast.LENGTH_LONG).show(); + for (SensorListener listener : mListeners) + listener.onCompassCalibrationRecommended(); break; case SensorManager.SENSOR_STATUS_ACCURACY_LOW: case SensorManager.SENSOR_STATUS_UNRELIABLE: default: - Toast.makeText(mMwmApplication, - mMwmApplication.getString(R.string.compass_calibration_required), - Toast.LENGTH_LONG).show(); + for (SensorListener listener : mListeners) + listener.onCompassCalibrationRequired(); } } @@ -63,7 +76,9 @@ class SensorHelper implements SensorEventListener SensorManager.getOrientation(mRotationMatrix, mRotationValues); // mRotationValues indexes: 0 - yaw (azimuth), 1 - pitch, 2 - roll. - LocationHelper.INSTANCE.notifyCompassUpdated(mRotationValues[0]); + mSavedNorth = LocationUtils.correctCompassAngle(mRotation, mRotationValues[0]); + for (SensorListener listener : mListeners) + listener.onCompassUpdated(mSavedNorth); } @Override @@ -75,31 +90,79 @@ class SensorHelper implements SensorEventListener // Looks like modern Androids can send this event after starting the sensor. } - SensorHelper(@NonNull Context context) + public SensorHelper(@NonNull Context context) { - mMwmApplication = MwmApplication.from(context); - mSensorManager = (SensorManager) mMwmApplication.getSystemService(Context.SENSOR_SERVICE); - Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); - if (sensor == null) + mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + } + + public double getSavedNorth() + { + return mSavedNorth; + } + + public void setRotation(int rotation) + { + Logger.i(TAG, "rotation = " + rotation); + mRotation = rotation; + } + + /** + * Registers listener to obtain compass updates. + * @param listener listener to be registered. + */ + @UiThread + public void addListener(@NonNull SensorListener listener) + { + Logger.d(TAG, "listener: " + listener + " count was: " + mListeners.size()); + + mListeners.add(listener); + if (!Double.isNaN(mSavedNorth)) + listener.onCompassUpdated(mSavedNorth); + } + + /** + * Removes given compass listener. + * @param listener listener to unregister. + */ + @UiThread + public void removeListener(@NonNull SensorListener listener) + { + Logger.d(TAG, "listener: " + listener + " count was: " + mListeners.size()); + mListeners.remove(listener); + } + + public void start() + { + if (mRotationVectorSensor != null) { - Log.w("SensorHelper", "WARNING: There is no ROTATION_VECTOR sensor, requesting GEOMAGNETIC_ROTATION_VECTOR"); - sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR); - if (sensor == null) - Log.w("SensorHelper", "WARNING: There is no GEOMAGNETIC_ROTATION_VECTOR sensor, device orientation can not be calculated"); + Logger.d(TAG, "Already started"); + return; } - // Can be null in rare cases on devices without magnetic sensors. - mRotationVectorSensor = sensor; + + mRotationVectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + if (mRotationVectorSensor == null) + { + Logger.w(TAG, "There is no ROTATION_VECTOR sensor, requesting GEOMAGNETIC_ROTATION_VECTOR"); + mRotationVectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR); + if (mRotationVectorSensor == null) + { + // Can be null in rare cases on devices without magnetic sensors. + Logger.w(TAG, "There is no GEOMAGNETIC_ROTATION_VECTOR sensor, device orientation can not be calculated"); + return; + } + } + + Logger.d(TAG); + mSensorManager.registerListener(this, mRotationVectorSensor, SensorManager.SENSOR_DELAY_UI); } - void start() + public void stop() { - if (mRotationVectorSensor != null) - mSensorManager.registerListener(this, mRotationVectorSensor, SensorManager.SENSOR_DELAY_UI); - } + if (mRotationVectorSensor == null) + return; + Logger.d(TAG); - void stop() - { - if (mRotationVectorSensor != null) - mSensorManager.unregisterListener(this); + mSensorManager.unregisterListener(this); + mRotationVectorSensor = null; } } diff --git a/android/app/src/main/java/app/organicmaps/location/SensorListener.java b/android/app/src/main/java/app/organicmaps/location/SensorListener.java new file mode 100644 index 0000000000..92350acd36 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/location/SensorListener.java @@ -0,0 +1,16 @@ +package app.organicmaps.location; + +public interface SensorListener +{ + void onCompassUpdated(double north); + + default void onCompassCalibrationRecommended() + { + // No op. + } + + default void onCompassCalibrationRequired() + { + // No op. + } +} diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java index ddc994d6e9..6df564ab2f 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/DirectionFragment.java @@ -18,12 +18,14 @@ import app.organicmaps.bookmarks.data.DistanceAndAzimut; import app.organicmaps.bookmarks.data.MapObject; import app.organicmaps.location.LocationHelper; import app.organicmaps.location.LocationListener; +import app.organicmaps.location.SensorHelper; +import app.organicmaps.location.SensorListener; import app.organicmaps.widget.ArrowView; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; public class DirectionFragment extends BaseMwmDialogFragment - implements LocationListener + implements LocationListener, SensorListener { private static final String EXTRA_MAP_OBJECT = "MapObject"; @@ -101,6 +103,7 @@ public class DirectionFragment extends BaseMwmDialogFragment { super.onResume(); LocationHelper.INSTANCE.addListener(this); + SensorHelper.from(requireContext()).addListener(this); refreshViews(); } @@ -109,6 +112,7 @@ public class DirectionFragment extends BaseMwmDialogFragment { super.onPause(); LocationHelper.INSTANCE.removeListener(this); + SensorHelper.from(requireContext()).removeListener(this); } @Override diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java index bcba9e3815..fa511acccf 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java @@ -36,6 +36,8 @@ import app.organicmaps.downloader.MapManager; import app.organicmaps.editor.Editor; import app.organicmaps.location.LocationHelper; import app.organicmaps.location.LocationListener; +import app.organicmaps.location.SensorHelper; +import app.organicmaps.location.SensorListener; import app.organicmaps.routing.RoutingController; import app.organicmaps.util.SharingUtils; import app.organicmaps.util.StringUtils; @@ -52,7 +54,6 @@ import com.google.android.material.button.MaterialButton; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Objects; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -60,6 +61,7 @@ import static android.view.View.VISIBLE; public class PlacePageView extends Fragment implements View.OnClickListener, View.OnLongClickListener, LocationListener, + SensorListener, Observer { @@ -257,6 +259,7 @@ public class PlacePageView extends Fragment implements View.OnClickListener, super.onStart(); mViewModel.getMapObject().observe(requireActivity(), this); LocationHelper.INSTANCE.addListener(this); + SensorHelper.from(requireContext()).addListener(this); } @Override @@ -265,6 +268,7 @@ public class PlacePageView extends Fragment implements View.OnClickListener, super.onStop(); mViewModel.getMapObject().removeObserver(this); LocationHelper.INSTANCE.removeListener(this); + SensorHelper.from(requireContext()).removeListener(this); detachCountry(); }