diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 000000000..f8051a6f9 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1761f192b..ad98f2481 100644 --- a/build.gradle +++ b/build.gradle @@ -145,4 +145,4 @@ dependencies { androidTestImplementation 'org.mockito:mockito-android:5.8.0' androidTestUtil 'androidx.test:orchestrator:1.4.2' -} +} \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index c0b67324d..80ab72a04 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -64,6 +64,8 @@ limitations under the License. + + + specifies default stream of audio + // Adding log messages related to the TTSManager class private static final String TAG = TTSManager.class.getSimpleName(); + //Super important : Context class provides access to system services & application resources private final Context context; + //AudioManager Class : Manage audio focus on the device (audio playback & recording) private final AudioManager audioManager; + //AudioManager.OnAudioFocusChangeListener : Interface in Android SDK + // Monitors audio focus change and returns a LOG of the change + private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { + @Override - public void onAudioFocusChange(int focusChange) { - Log.d(TAG, "Audio focus changed to " + focusChange); + public void onAudioFocusChange(int focusChange) { // Method implementation of interface AudioManager.OnAudioFocusChangeListener + + Log.d(TAG, "Audio focus changed to " + focusChange); // Logging a message to the class + + //Constants that can be returned by the AudioManager Class + // _LOSS : system lost audio focus and can no longer be used + // _LOSS_TRANSIENT : Temporary loss of audio focus + // ''_CAN_DUCK : Lowers the volume of the media player (if another app is playing for example) boolean stop = List.of(AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) - .contains(focusChange); + .contains(focusChange); // checking the state of 'focusChange' parameter if (stop && tts != null && tts.isSpeaking()) { + + // Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue. + // Returns ERROR or SUCCESS. tts.stop(); Log.i(TAG, "Aborting current tts due to focus change " + focusChange); } } }; - + // 'UtteranceProgressListener' : interface & defines methods that can be called to monitor the progress of TTS utterance private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() { + // These methods can be really good for BackEnd to UI implementation @Override - public void onStart(String utteranceId) { + public void onStart(String utteranceId) { //when utterance begins int result = audioManager.requestAudioFocus(audioFocusChangeListener, AUDIO_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { Log.w(TAG, "Failed to request audio focus."); @@ -69,7 +100,7 @@ public void onStart(String utteranceId) { } @Override - public void onDone(String utteranceId) { + public void onDone(String utteranceId) { // when utterance has been successfully spoken int result = audioManager.abandonAudioFocus(audioFocusChangeListener); if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { Log.w(TAG, "Failed to relinquish audio focus."); @@ -77,35 +108,52 @@ public void onDone(String utteranceId) { } @Override - public void onError(String utteranceId) { + public void onError(String utteranceId) { // Called if an error occurs during the utterance Log.e(TAG, "An error occurred for utteranceId " + utteranceId); } }; - private TextToSpeech tts; + private TextToSpeech tts; //Conversion MY TTS OBJECT // Response from TTS after its initialization - private int ttsInitStatus = TextToSpeech.ERROR; + private int ttsInitStatus = TextToSpeech.ERROR; //Stores initialization status => + // Can/will change to .SUCCESS or .FAILED_SYNTHESIS + // A boolean field that indicates whether the text-to-speech engine has been properly initialized +// and is ready to perform text-to-speech conversion. +// private boolean ttsReady = false; private boolean ttsReady = false; private MediaPlayer ttsFallback; +// MediaPlayer class: used to play a pre-recorded audio file as a fallback mechanism when +// the TTS engine is unable to perform the TTS conversion. - TTSManager(Context context) { + TTSManager(Context context) { // Constructor of the TTSManager class this.context = context; audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); +// TTSManager constructor initializes audioManager by calling +// getSystemService(Context.AUDIO_SERVICE) on the Context object, which returns an +// AudioManager object. } - public void start() { + public void start() { // Called in the VoiceAnnouncementManager Class + + Log.d(TAG, "Start"); if (tts == null) { + tts = new TextToSpeech(context, status -> { Log.i(TAG, "TextToSpeech initialized with status " + status); + ttsInitStatus = status; }); } if (ttsFallback == null) { ttsFallback = MediaPlayer.create(context, R.raw.tts_fallback); + + // initialize a new instance of MediaPlayer & associates it with a sound file resource in the app's raw directory + //R.raw.tts_fallback : contains reference to the sound file + if (ttsFallback == null) { Log.w(TAG, "MediaPlayer for ttsFallback could not be created."); } else { @@ -118,10 +166,14 @@ public void start() { } } - public void announce(@NonNull Spannable announcement) { - synchronized (this) { - if (!ttsReady) { + public void announce(@NonNull Spannable announcement) { // parameter cannot be null + synchronized (this) { // One announcement at a time + + if (!ttsReady) { // checking if ready to make an announcement ttsReady = ttsInitStatus == TextToSpeech.SUCCESS; + ; + Log.d(TAG, "TTS initialized successfully."); + // Update the UI to indicate that TTS is ready. if (ttsReady) { onTtsReady(); } @@ -145,7 +197,7 @@ public void announce(@NonNull Spannable announcement) { return; } - if (announcement.length() > 0) { + if (announcement.length() > 0) { //Where it is gonna speak and start announcement / IMPORTANT // We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called. tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used"); } @@ -163,19 +215,91 @@ public void stop() { } } - private void onTtsReady() { - Locale locale = Locale.getDefault(); - int languageAvailability = tts.isLanguageAvailable(locale); - if (languageAvailability == TextToSpeech.LANG_MISSING_DATA || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) { - Log.w(TAG, "Default locale not available, use English."); - locale = Locale.ENGLISH; - /* - * TODO: instead of using english, load the language if missing and show a toast if not supported. - * Not able to change the resource strings to English. - */ +// void onTtsReady() { +// Locale locale = Locale.getDefault(); // Get default geolocation / region etc +// int languageAvailability = tts.isLanguageAvailable(locale); //Method in the TTS class : +// // checks is the engine can speak specified language +// if (languageAvailability == TextToSpeech.LANG_MISSING_DATA || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) { +// Log.w(TAG, "Default locale not available, use English."); +// locale = Locale.ENGLISH; +// +// +// } +// tts.setLanguage(locale); //set method in android sdk +// tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate()); // Set speech rate output based on app preferences (set by user) +// // track the progress of the TTS utterance,and to perform certain actions +// // when specific events in the lifecycle of an utterance occur. +// tts.setOnUtteranceProgressListener(utteranceListener); +// +// +// } + + //method is where you initialize your activity, set up the user interface, + // and perform any other necessary setup tasks. + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Call onTtsReady for initialization + onTtsReady(); + } + + void onTtsReady() { + // setContentView(R.layout.track_stopped); + // ImageView finish = findViewById(R.id.finish_button); + + +// Set the content view of the activity to the layout defined in 'track_recording.xml' + setContentView(R.layout.track_recording); + // Find the FloatingActionButton with the id 'track_recording_fab_action' in the layout + FloatingActionButton run = findViewById(R.id.track_recording_fab_action); + + if (run != null) { + // Initialize TextToSpeech instance with the application context + tts = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() { + @Override + public void onInit(int ttsInitStatus) { + // Get the default locale of the device + Locale locale = Locale.getDefault(); + int languageAvailability = tts.isLanguageAvailable(locale); + if (languageAvailability == TextToSpeech.LANG_MISSING_DATA || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) { + Log.w(TAG, "Default locale not available, use English."); + // Set TextToSpeech language to English + locale = Locale.ENGLISH; + tts.setLanguage(locale); + // Set speech rate according to user preferences + tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate()); + tts.setOnUtteranceProgressListener(utteranceListener); + + // Set OnClickListener for FloatingActionButton 'run' + run.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + //Triggers the speaking FloatingActionButton is clicked + String speech = "Hello User!"; + tts.speak(speech, TextToSpeech.QUEUE_FLUSH, null, null); + } + }); + }else { + // If language other than English is selected, show a toast notification + Toast.makeText(getApplicationContext(), "Please note: Selected language may not be fully supported.", Toast.LENGTH_SHORT).show(); + } + } + }); + } else { + //Log error if not found + Log.e(TAG, "FloatingActionButton not found"); } - tts.setLanguage(locale); - tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate()); - tts.setOnUtteranceProgressListener(utteranceListener); + + + + + } + } + + + +//Write a method that converts text to speech +// diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java index 7b5a4d5b0..10d77ae7c 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementManager.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.text.Spannable; +import android.text.SpannableString; import android.util.Log; import androidx.annotation.NonNull; @@ -52,6 +53,7 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe private TTSManager voiceAnnouncement; private TrackStatistics trackStatistics; +// trackStatistics.toString(); to be done in the code private static final Distance DISTANCE_OFF = Distance.of(Double.MAX_VALUE); private Distance distanceFrequency = DISTANCE_OFF; @@ -79,7 +81,7 @@ public VoiceAnnouncementManager(@NonNull Context context) { public void start(@Nullable TrackStatistics trackStatistics) { voiceAnnouncement = new TTSManager(context); - voiceAnnouncement.start(); + voiceAnnouncement.start(); // instance of TTSManager to call the start method which initializes the TTS engine update(trackStatistics); } @@ -87,6 +89,12 @@ void update(@Nullable TrackStatistics trackStatistics) { this.trackStatistics = trackStatistics; updateNextDuration(); updateNextTaskDistance(); +// Create a Spannable object with the text from TrackStatistics.toString() +// if(trackStatistics !=null){ +// Spannable announcement = new SpannableString(trackStatistics.toString()); +// // Pass the Spannable object to the announce() method of TTSManager +// voiceAnnouncement.announce(announcement); + //} } private boolean shouldNotAnnounce() { diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java index 05179f1b7..c541d34cd 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java @@ -13,6 +13,7 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.TtsSpan; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -29,6 +30,7 @@ import de.dennisguse.opentracks.ui.intervals.IntervalStatistics; class VoiceAnnouncementUtils { + private static final String TAG2 = TTSManager.class.getSimpleName(); private VoiceAnnouncementUtils() { } @@ -77,6 +79,7 @@ static Spannable createStatistics(Context context, TrackStatistics trackStatisti double distanceInUnit = totalDistance.toKM_Miles(unitSystem); if (shouldVoiceAnnounceTotalDistance()) { + Log.d(TAG2, "in if statement"); builder.append(context.getString(R.string.total_distance)); // Units should always be english singular for TTS. // See https://developer.android.com/reference/android/text/style/TtsSpan?hl=en#TYPE_MEASURE diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java index 41fa6d8e7..d1fa17894 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java @@ -72,7 +72,8 @@ public TrackStatistics() { * * @param other another statistics data object to copy from */ - public TrackStatistics(TrackStatistics other) { + public TrackStatistics(TrackStatistics other) { // Need to create objects of this in order to activate + //VoiceAnnouncementsManager startTime = other.startTime; stopTime = other.stopTime; totalDistance = other.totalDistance; diff --git a/src/main/res/layout/track_stopped.xml b/src/main/res/layout/track_stopped.xml index 4aca4c419..e18967c65 100644 --- a/src/main/res/layout/track_stopped.xml +++ b/src/main/res/layout/track_stopped.xml @@ -184,7 +184,7 @@ android:orientation="vertical" app:layout_constraintGuide_percent="0.5" /> - +