Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nour test branch #116

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .idea/migrations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,4 @@ dependencies {
androidTestImplementation 'org.mockito:mockito-android:5.8.0'

androidTestUtil 'androidx.test:orchestrator:1.4.2'
}
}
3 changes: 3 additions & 0 deletions src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ limitations under the License.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />



<application
android:name=".Startup"
android:allowBackup="true"
Expand All @@ -85,6 +87,7 @@ limitations under the License.
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<activity
android:name=".publicapi.StopRecording"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,96 +16,144 @@

package de.dennisguse.opentracks.services.announcement;

import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.text.Spannable;
import android.util.Log;
import android.content.Context; // Base class for interacting with the Android system
import android.media.AudioAttributes; // Encapsulates the audio attributes of an audio stream
import android.media.AudioManager; // Provides access to the system volume controls
import android.media.MediaPlayer; // Provides a way to control playback of audio files/streams
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import java.util.List;
import java.util.Locale;
import android.speech.tts.TextToSpeech; // Convert text to speech
import android.speech.tts.UtteranceProgressListener; // Used to receive callbacks when the synthesis of an utterance starts/end or at an error
import android.text.Spannable; //Mark up text with style information
import android.text.SpannableStringBuilder;
import android.util.Log; // Provides methods to log messages (debugging purposes)
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import androidx.annotation.NonNull; // Provides helper classes
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

public class TTSManager {
import java.util.List; //Ordered collection of elements
import java.util.Locale; // Represents a locale, which is a specific geographic, political, or cultural region

import de.dennisguse.opentracks.R; // Generated by the Android build system. It contains references to all the resources in the application
import de.dennisguse.opentracks.settings.PreferencesUtils; // A custom class that provides helper methods for working with user preferences


public class TTSManager extends AppCompatActivity {

public final static int AUDIO_STREAM = TextToSpeech.Engine.DEFAULT_STREAM;
// TextToSpeech: class part of the Android SDK used to convert tts
// Engine is an inner class of TextToSpeech
// DEFAULT_STREAM : static field of the 'Engine' class => 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.");
}
}

@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.");
}
}

@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 {
Expand All @@ -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();
}
Expand All @@ -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");
}
Expand All @@ -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
//
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -79,14 +81,20 @@ 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);
}

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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +30,7 @@
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;

class VoiceAnnouncementUtils {
private static final String TAG2 = TTSManager.class.getSimpleName();

private VoiceAnnouncementUtils() {
}
Expand Down Expand Up @@ -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
Expand Down
Loading