diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index 690e6adc57..6ce8120e12 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -90,7 +90,6 @@ import app.organicmaps.settings.DrivingOptionsActivity; import app.organicmaps.settings.RoadType; import app.organicmaps.settings.SettingsActivity; import app.organicmaps.settings.UnitLocale; -import app.organicmaps.sound.TtsPlayer; import app.organicmaps.util.Config; import app.organicmaps.util.Counters; import app.organicmaps.util.LocationUtils; @@ -1078,7 +1077,6 @@ public class MwmActivity extends BaseMwmFragmentActivity protected void onSafeDestroy() { super.onSafeDestroy(); - mNavigationController.destroy(); mLocationPermissionRequest.unregister(); mLocationPermissionRequest = null; mLocationResolutionRequest.unregister(); @@ -1721,15 +1719,6 @@ public class MwmActivity extends BaseMwmFragmentActivity return; mNavigationController.update(Framework.nativeGetRouteFollowingInfo()); - - TtsPlayer.INSTANCE.playTurnNotifications(getApplicationContext()); - - // TODO: consider to create callback mechanism to transfer 'ROUTE_IS_FINISHED' event from - // the core to the platform code (https://github.com/organicmaps/organicmaps/issues/3589), - // because calling the native method 'nativeIsRouteFinished' - // too often can result in poor UI performance. - if (Framework.nativeIsRouteFinished()) - routing.cancel(); } /** diff --git a/android/app/src/main/java/app/organicmaps/MwmApplication.java b/android/app/src/main/java/app/organicmaps/MwmApplication.java index 2da9ae5110..2ba2e362c8 100644 --- a/android/app/src/main/java/app/organicmaps/MwmApplication.java +++ b/android/app/src/main/java/app/organicmaps/MwmApplication.java @@ -145,8 +145,6 @@ public class MwmApplication extends Application implements Application.ActivityL mSubwayManager = new SubwayManager(this); mIsolinesManager = new IsolinesManager(this); mSensorHelper = new SensorHelper(this); - - mPlayer = new MediaPlayerWrapper(this); } /** diff --git a/android/app/src/main/java/app/organicmaps/base/MediaPlayerWrapper.java b/android/app/src/main/java/app/organicmaps/base/MediaPlayerWrapper.java index ed4c7bb37e..1f5caba400 100644 --- a/android/app/src/main/java/app/organicmaps/base/MediaPlayerWrapper.java +++ b/android/app/src/main/java/app/organicmaps/base/MediaPlayerWrapper.java @@ -1,6 +1,5 @@ package app.organicmaps.base; -import android.app.Application; import android.content.Context; import android.media.MediaPlayer; import android.os.AsyncTask; @@ -9,23 +8,19 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RawRes; -import app.organicmaps.MwmApplication; - public class MediaPlayerWrapper { private static final int UNDEFINED_SOUND_STREAM = -1; - @NonNull - private final Application mApp; @Nullable private MediaPlayer mPlayer; - @Nullable - private MediaPlayer.OnCompletionListener mCompletionListener; private int mStreamResId = UNDEFINED_SOUND_STREAM; + @NonNull + final private Context mContext; - public MediaPlayerWrapper(@NonNull Application application) + public MediaPlayerWrapper(@NonNull Context context) { - mApp = application; + mContext = context; } private boolean isCurrentSoundStream(@RawRes int streamResId) @@ -33,25 +28,18 @@ public class MediaPlayerWrapper return mStreamResId == streamResId; } - @NonNull - private Application getApp() - { - return mApp; - } - private void onInitializationCompleted(@NonNull InitializationResult initializationResult) { - releaseInternal(); + release(); mStreamResId = initializationResult.getStreamResId(); mPlayer = initializationResult.getPlayer(); if (mPlayer == null) return; - mPlayer.setOnCompletionListener(mCompletionListener); mPlayer.start(); } - private void releaseInternal() + public void release() { if (mPlayer == null) return; @@ -68,14 +56,7 @@ public class MediaPlayerWrapper return new InitPlayerTask(wrapper); } - public void release() - { - releaseInternal(); - mCompletionListener = null; - } - - public void playback(@RawRes int streamResId, - @Nullable MediaPlayer.OnCompletionListener completionListener) + public void playback(@RawRes int streamResId) { if (isCurrentSoundStream(streamResId) && mPlayer == null) return; @@ -86,7 +67,6 @@ public class MediaPlayerWrapper return; } - mCompletionListener = completionListener; mStreamResId = streamResId; AsyncTask task = makeInitTask(this); task.execute(streamResId); @@ -100,18 +80,6 @@ public class MediaPlayerWrapper mPlayer.stop(); } - public boolean isPlaying() - { - return mPlayer != null && mPlayer.isPlaying(); - } - - @NonNull - public static MediaPlayerWrapper from(@NonNull Context context) - { - MwmApplication app = (MwmApplication) context.getApplicationContext(); - return app.getMediaPlayer(); - } - @SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3632 private static class InitPlayerTask extends AsyncTask { @@ -129,7 +97,7 @@ public class MediaPlayerWrapper if (params.length == 0) throw new IllegalArgumentException("Params not found"); int resId = params[0]; - MediaPlayer player = MediaPlayer.create(mWrapper.getApp(), resId); + MediaPlayer player = MediaPlayer.create(mWrapper.mContext, resId); return new InitializationResult(player, resId); } diff --git a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java index 124546499a..a393f1821f 100644 --- a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java +++ b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java @@ -1,10 +1,5 @@ package app.organicmaps.routing; -import android.app.Activity; -import android.app.Application; -import android.content.Context; -import android.media.MediaPlayer; -import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.ImageView; @@ -17,11 +12,8 @@ import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.RecyclerView; import app.organicmaps.Framework; -import app.organicmaps.MwmActivity; import app.organicmaps.R; -import app.organicmaps.base.MediaPlayerWrapper; import app.organicmaps.maplayer.traffic.TrafficManager; -import app.organicmaps.sound.TtsPlayer; import app.organicmaps.util.UiUtils; import app.organicmaps.util.Utils; import app.organicmaps.widget.menu.NavMenu; @@ -50,9 +42,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, private final RecyclerView mLanes; @NonNull private final LanesAdapter mLanesAdapter; - - @NonNull - private final MediaPlayer.OnCompletionListener mSpeedCamSignalCompletionListener; private final NavMenu mNavMenu; View.OnClickListener mOnSettingsClickListener; @@ -112,9 +101,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, navigationBarBackground.getLayoutParams().width = mFrame.findViewById(R.id.nav_bottom_sheet).getWidth(); return windowInsets; }); - - final Application app = (Application) mFrame.getContext().getApplicationContext(); - mSpeedCamSignalCompletionListener = new CameraWarningSignalCompletionListener(app); } private void updateVehicle(@NonNull RoutingInfo info) @@ -170,7 +156,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, updateStreetView(info); mNavMenu.update(info); - playbackSpeedCamWarning(info); } private void updateStreetView(@NonNull RoutingInfo info) @@ -183,16 +168,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, mNextStreet.setText(info.nextStreet); } - private void playbackSpeedCamWarning(@NonNull RoutingInfo info) - { - if (!info.shouldPlayWarningSignal() || TtsPlayer.INSTANCE.isSpeaking()) - return; - - Context context = mFrame.getContext(); - MediaPlayerWrapper player = MediaPlayerWrapper.from(context); - player.playback(R.raw.speed_cams_beep, mSpeedCamSignalCompletionListener); - } - public void show(boolean show) { if (show && !UiUtils.isVisible(mFrame)) @@ -268,11 +243,6 @@ public class NavigationController implements TrafficManager.TrafficCallback, // no op } - public void destroy() - { - MediaPlayerWrapper.from(mFrame.getContext()).release(); - } - @Override public void onSettingsClicked() { @@ -285,20 +255,4 @@ public class NavigationController implements TrafficManager.TrafficCallback, RoutingController.get().cancel(); } - private static class CameraWarningSignalCompletionListener implements MediaPlayer.OnCompletionListener - { - @NonNull - private final Application mApp; - - CameraWarningSignalCompletionListener(@NonNull Application app) - { - mApp = app; - } - - @Override - public void onCompletion(MediaPlayer mp) - { - TtsPlayer.INSTANCE.playTurnNotifications(mApp); - } - } } diff --git a/android/app/src/main/java/app/organicmaps/routing/NavigationService.java b/android/app/src/main/java/app/organicmaps/routing/NavigationService.java index 1035abc614..8f53220bfb 100644 --- a/android/app/src/main/java/app/organicmaps/routing/NavigationService.java +++ b/android/app/src/main/java/app/organicmaps/routing/NavigationService.java @@ -29,12 +29,15 @@ import androidx.core.content.ContextCompat; import app.organicmaps.Framework; import app.organicmaps.MwmActivity; import app.organicmaps.R; +import app.organicmaps.base.MediaPlayerWrapper; import app.organicmaps.location.LocationHelper; import app.organicmaps.location.LocationListener; import app.organicmaps.sound.TtsPlayer; import app.organicmaps.util.Graphics; import app.organicmaps.util.log.Logger; +import java.util.Objects; + public class NavigationService extends Service implements LocationListener { private static final String TAG = NavigationService.class.getSimpleName(); @@ -46,6 +49,10 @@ public class NavigationService extends Service implements LocationListener @NonNull private NotificationCompat.Builder mNotificationBuilder; + @SuppressWarnings("NotNullFieldNotInitialized") + @NonNull + MediaPlayerWrapper mPlayer; + /** * Start the foreground service for turn-by-turn voice-guided navigation. * @@ -124,6 +131,8 @@ public class NavigationService extends Service implements LocationListener .setColorized(isColorizedSupported()) .setColor(ContextCompat.getColor(this, R.color.notification)); + mPlayer = new MediaPlayerWrapper(getApplicationContext()); + /* * Subscribe to location updates. */ @@ -143,6 +152,8 @@ public class NavigationService extends Service implements LocationListener final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.cancel(NOTIFICATION_ID); + mPlayer.release(); + // Restart the location to resubscribe with a less frequent refresh interval (see {@link onStartCommand() }). LocationHelper.INSTANCE.restart(); } @@ -199,21 +210,31 @@ public class NavigationService extends Service implements LocationListener if (!routingController.isNavigating()) return; - // Voice the turn notification first. + // TODO: consider to create callback mechanism to transfer 'ROUTE_IS_FINISHED' event from + // the core to the platform code (https://github.com/organicmaps/organicmaps/issues/3589), + // because calling the native method 'nativeIsRouteFinished' + // too often can result in poor UI performance. + if (Framework.nativeIsRouteFinished()) + routingController.cancel(); + final String[] turnNotifications = Framework.nativeGenerateNotifications(); if (turnNotifications != null) TtsPlayer.INSTANCE.playTurnNotifications(getApplicationContext(), turnNotifications); + final RoutingInfo routingInfo = Framework.nativeGetRouteFollowingInfo(); + if (routingInfo == null) + return; + + if (routingInfo.shouldPlayWarningSignal()) + mPlayer.playback(R.raw.speed_cams_beep); + // Don't spend time on updating RemoteView if notifications are not allowed. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ActivityCompat.checkSelfPermission(this, POST_NOTIFICATIONS) != PERMISSION_GRANTED) return; - final RoutingInfo routingInfo = Framework.nativeGetRouteFollowingInfo(); - if (routingInfo == null) - return; - - final Drawable drawable = AppCompatResources.getDrawable(this, routingInfo.carDirection.getTurnRes()); + final Drawable drawable = Objects.requireNonNull(AppCompatResources.getDrawable(this, + routingInfo.carDirection.getTurnRes())); final Bitmap bitmap = isColorizedSupported() ? Graphics.drawableToBitmap(drawable) : Graphics.drawableToBitmapWithTint(drawable, ContextCompat.getColor(this, R.color.base_accent)); diff --git a/android/app/src/main/java/app/organicmaps/sound/TtsPlayer.java b/android/app/src/main/java/app/organicmaps/sound/TtsPlayer.java index 8d1bdc67c9..e497bfc4de 100644 --- a/android/app/src/main/java/app/organicmaps/sound/TtsPlayer.java +++ b/android/app/src/main/java/app/organicmaps/sound/TtsPlayer.java @@ -10,11 +10,9 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import app.organicmaps.Framework; import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.base.Initializable; -import app.organicmaps.base.MediaPlayerWrapper; import app.organicmaps.util.Config; import app.organicmaps.util.log.Logger; @@ -183,11 +181,6 @@ public enum TtsPlayer implements Initializable // No op. } - public boolean isSpeaking() - { - return mTts != null && mTts.isSpeaking(); - } - private static boolean isReady() { return (INSTANCE.mTts != null && !INSTANCE.mUnavailable && !INSTANCE.mInitializing); @@ -210,23 +203,8 @@ public enum TtsPlayer implements Initializable } } - public void playTurnNotifications(@NonNull Context context) - { - if (MediaPlayerWrapper.from(context).isPlaying()) - return; - // It's necessary to call Framework.nativeGenerateTurnNotifications() even if TtsPlayer is invalid. - final String[] turnNotifications = Framework.nativeGenerateNotifications(); - - if (turnNotifications != null && isReady()) - for (String textToSpeak : turnNotifications) - speak(textToSpeak); - } - public void playTurnNotifications(@NonNull Context context, @NonNull String[] turnNotifications) { - if (MediaPlayerWrapper.from(context).isPlaying()) - return; - if (isReady()) for (String textToSpeak : turnNotifications) speak(textToSpeak);