Android TTS different languages supported each time when checked - android

I am struggling with one very strange bug in my app.
I have added TTS to it, and I am using the build one. The user can choose the language from the spinner which is filled in during AsyncTask started in onResume().
The AsyncTask looks like this:
private class AsyncTTSDownload extends AsyncTask<Void, Integer, String> {
#Override
protected String doInBackground(Void... params) {
try {
languagesTTS = tts.testLang();
} catch (Exception ex) {
if (D)
Log.e(TAG, ex.toString());
}
return null;
}
#Override
protected void onPostExecute(String result) {
ttsUpdate.dismiss();
TTSSpinnerAdapter adapterTTS = new TTSSpinnerAdapter(
MyTTS.this, android.R.layout.simple_spinner_item,
languagesTTS);
int savedLangTTS = ttsLang.getInt("savedTTS", -1);
langTTS.setAdapter(adapterTTS);
if (savedLangTTS == -1)
{
try {
int langObject = languagesTTS.indexOf(tts.getLanguage());
langTTS.setSelection(langObject);
} catch (IndexOutOfBoundsException ie) {
langTTS.setSelection(0);
}
} else {
langTTS.setSelection(savedLangTTS);
}
Locale langChoosen = (Locale) langTTS.getItemAtPosition(langTTS
.getSelectedItemPosition());
tts.setTTSLanguage(langChoosen);
}
#Override
protected void onPreExecute() {
ttsUpdate = ProgressDialog.show(MyTTS.this, "Wait",
"Loading TTS...");
ttsUpdate.setCancelable(false);
}
}
the thing is, that I am from time to time getting different number of languages supported. This is on this same device, during this same run. Just I open and close Activity with TTS. This bug is causing IndexOutOfBoundsException. This is my way of getting TTS languages:
public List<Locale> testLang() {
Locale[] AvalLoc = Locale.getAvailableLocales();
List<Locale> listaOK = new ArrayList<Locale>();
String tester = "";
for (Locale l : AvalLoc) {
if(tester.contains(l.getLanguage()))
{
continue;
}
int buf = tts.isLanguageAvailable(l);
if (buf == TextToSpeech.LANG_MISSING_DATA
|| buf == TextToSpeech.LANG_NOT_SUPPORTED) {
//TODO maybe
} else {
listaOK.add(l);
tester += l.getLanguage() + ";";
}
}
tts.setLanguage(Locale.ENGLISH);
return listaOK;
}
For now I've only find out a small hack for not showing this error, just save in shared preferences number of languages and compare it with what tts received, but it is not working well at all. Each time I am getting different number.
For me it seems, that something is not finished or started when I am starting again this same activity after return, because this is tts.isAvaliableLanguage(l) who is deciding whether language is supported or not and from time to time, one language is not supported and after reload it is.
EDIT:
As there appeared new comment about my question I need to add one important thing about TTS engine itself.
testLang() is a method inside my class Called TTSClass, that is implementing TextToSpeech.OnInitListener. tts object is created in onCreate of MyTTS activity and this constructor looks like this in TTSClass:
public TTSClass(Context context, Locale language) {
contextTTS = context;
languageTTS = language;
tts = new TextToSpeech(contextTTS, this);
}
and call in activity:
tts = new TTSClass(getApplicationContext(), Locale.ENGLISH);
Because TTSClass implements TextToSpeech.OnInitListener there is also onInit() method which looks like this:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = 0;
result = tts.setLanguage(languageTTS);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
if(D) Log.e(TAG, "This Language is not supported");
}
if(D) Log.d(TAG,"Initialized");
} else {
if(D) Log.e(TAG, "Initilization Failed!");
}
}
So, this is everything connecting to this class and problem I think. If anything is missing, let me now.
EDIT2:
Suggested by shoe rat comment I've run few more tests, and the outcome is just amazing, or extraordinary, I think it is better word.
So what I've done was adding 3 Log from different places in code informing me about list size on different stages.
First was added in onInit() in if status == TextToSpeech.SUCCESS. This one is just simple call of testLang().size(). The outcome is 5 languages - that is the correct number and it is always like this, no matter if there is or isn't an exception.
Second was added there:
protected String doInBackground(Void... params) {
try {
Log.w(TAG,"before: "+tts.testLang().size());
languagesTTS = tts.testLang();
}
and this one is starting to act quite weird. It is sometimes, or even quite often, showing number lower than 5. But this is not the strangest thing.
The third one is just at the beginning of onPostExecute checking the size of languagesTTS. And believe or not, the number is quite often totally different from the second log. However, it is never smaller. It can be equal or bigger.
Does anyone know, what is going one?

I've found solution. It came out that indeed it was initialization problem.
I'm not sure if documentation is saying anything about it, but it seem like the TTS engine initialization is done asynchronously, so it can finish at any time.
My solution was to change the doInBackground() method like this:
#Override
protected String doInBackground(Void... params) {
try {
while(!TTSClass.isInit){}
languagesTTS = tts.testLang();
} catch (Exception ex) {
if (D)
Log.e(TAG, ex.toString());
}
return null;
}
and in onInit() method I've added isInit public static boolean variable:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = 0;
result = tts.setLanguage(languageTTS);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
if(D) Log.e(TAG, "This Language is not supported");
}
if(D) Log.d(TAG,"initialized");
isInit = true;
} else {
if(D) Log.e(TAG, "Initilization Failed!");
}
}
Hope, that someone will find it helpful.

Related

How to make sure TextToSpeech is initialized before using speak?

I'd like to ask you for some help with Android TextToSpeech feature.
Basically, I'd like to develop a simple AI which speaks, asking a question then waits for an answer, and at last, based on answer asks another question and so on, until user pronounces a keyword which stops everything.
Now I know TextToSpeech has to be initialized before using speak method, and I'm trying to take this into account by using onActivityResult method.
Below some code:
Activity class:
public class MainActivity extends AppCompatActivity implements OnInitListener, Button.OnClickListener{
Button sayHello;
TextView textView;
private static final int CHECK_DATA = 0;
private static final Locale defaultLocale = Locale.UK; // British English
private static final String TAG = "TTS";
private TextToSpeech tts;
private boolean isInit = false;
sayIt Method: used to speak:
public void sayIt(String text, boolean flushQ){
if(isInit){
if(flushQ){
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null);
} else {
tts.speak(text, TextToSpeech.QUEUE_ADD, null, null);
}
} else {
Log.i(TAG, "Failure: TTS instance not properly initialized");
}
}
TextToSpeech Listener:
#Override
public void onInit(int status){
if(status == TextToSpeech.SUCCESS){
isInit = true;
// Enable input text field and speak button now that we are initialized
sayHello.setEnabled(true);
// Set to a language locale after checking availability
Log.i(TAG, "available="+tts.isLanguageAvailable(Locale.UK));
tts.setLanguage(defaultLocale);
// Examples of voice controls. Set to defaults of 1.0.
tts.setPitch(1.0F);
tts.setSpeechRate(1.0F);
// Issue a greeting and instructions in the default language
tts.speak("Initialized!", TextToSpeech.QUEUE_FLUSH, null, Integer.toString(12));
} else {
isInit = false;
Log.i(TAG, "Failure: TTS instance not properly initialized");
}
}
Button Listener:
#Override
public void onClick(View v){
if(isInit)
sayIt("You clicked!", true);
}
onActivityResult Method:
// Create the TTS instance if TextToSpeech language data are installed on device. If not
// installed, attempt to install it on the device.
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CHECK_DATA) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
// Success, so create the TTS instance. But can't use it to speak until
// the onInit(status) callback defined below runs, indicating initialization.
Log.i(TAG, "Success, let's talk");
tts = new TextToSpeech(this, this);
// Use static Locales method to list available locales on device
Locale[] locales = Locale.getAvailableLocales();
Log.i(TAG,"Locales Available on Device:");
for(int i=0; i<locales.length; i++){
String temp = "Locale "+i+": "+locales[i]+" Language="
+locales[i].getDisplayLanguage();
if(locales[i].getDisplayCountry() != "") {
temp += " Country="+locales[i].getDisplayCountry();
}
Log.i(TAG, temp);
}
} else {
// missing data, so install it on the device
Log.i(TAG, "Missing Data; Install it");
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
}
}
And, at last, onCreate Method:
#Override
public void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
setContentView(R.layout.activity_main);
sayHello = findViewById(R.id.sayBtn);
textView = findViewById(R.id.textView);
sayHello.setEnabled(false);
sayHello.setOnClickListener(this);
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, CHECK_DATA);
/* THIS SPEAK DOES NOT WORK! */
sayIt("Speech from method!", true);
}
Issue is: Button successfully gets enabled when onInit method initialises TextToSpeech and successfully pronounces text.
My goal is to make the Activity speak from onCreate method, since at the moment it only works from onInit and onClick listeners, bot not in onCreate, even if I check for tts initialization using onActivityResult.
Basically I want the TextToSpeech to speak with no Buttons involved.
I know very similar questions were already posted, but none solved my problem. Have some idea?
Hope I've been clear, Thank you!
UPDATE: Log shows ERROR detected occurs in else branch of onInit method, where Log.i(TAG, "Failure: TTS instance not properly initialized"); line is.
SOLUTION:
The only thing to do here is to wait a little time in order to let TextToSpeech initialize for good.
A good way seems to be by using a delayed Handler as follows:
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
//Waiting for RobotTextToSpeech initialization for 1500ms
rtts.speak("This speak will work!");
rtts.speak("This other speak will work too!");
}
}, 1500);
}
By doing this, looks like TextToSpeech works well even in onCreate method, we just have to wait little time.
Hope this can help.

Android Making a Call Only Works the Second Time

So I'm making a Speech to Text app using the voice assistant. I'm trying to make a phone call feature so the user can speak a number and it will call it.
I'm almost there but the number only rings the second time I speak it. The first time it says "Call not sent".
I figured out the reason for this is; when the user speaks the number it's not updating the variable first and then calling the "call" function. I've tried almost everything but it doesn't update the variable correctly.
I.e.
private TextView txtSpeechInput;
public String num = "123";
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQ_CODE_SPEECH_INPUT: {
if (resultCode == RESULT_OK && null != data) {
ArrayList<String> result = data
.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
txtSpeechInput.setText(result.get(0).replaceAll("\\s+", ""));
num = txtSpeechInput.getText().toString();
}
break;
}
}
}
public void dialPhoneNumber(String phone) {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phone));
if (intent.resolveActivity(getPackageManager()) != null) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
startActivity(intent);
}
return ;
}
}
private void processResult(String command) {
command = command.toLowerCase();
if(command.indexOf("time") != -1) {
Date now = new Date();
String time = DateUtils.formatDateTime(this, now.getTime(), DateUtils.FORMAT_SHOW_TIME);
speak("The time is " + time);
}
if(command.indexOf("date") != -1) {
String date = DateFormat.getDateInstance().format(new Date());
speak("The date is " + date);
}
else if (command.indexOf("open") != -1) {
if(command.indexOf("browser") != -1) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.co.uk/"));
startActivity(intent);
}
}
if(command.indexOf("call") != -1) {
promptSpeechInput();
try {
Thread.sleep(18000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dialPhoneNumber(num);
}
}
In this code, wen the use says "make a call" it opens another prompt to take the speech input. Stores it in txtSpeechInput (Where it says results.get0) and then at that stage I update the "num" variable and convert it to a string.
It then runs dialPhoneNumber
Now let's say I run it the first time and speak "07123456789", it will say call not sent because its trying to call the default 123, if i speak it again or a different number then it will ring the 07123456789.
How and why does it not update before calling the phone feature?
Based on danny117's comment...
promptSpeechInput();
try {
Thread.sleep(18000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dialPhoneNumber(num);
promptSpeechInput starts a new activity, and the result from that activity is the spoken text? If that's right, then why are you prompting for input, sleeping for 18 seconds (never sleep on the main thread by the way), and then assuming your input is ready? As danny says, prompting for input should be the last thing that if block does. Dialing the number should be initiated from onActivityResult.
Also, why is there a default phone number of "123"? That will never be correct, so is not a sensible default. And to reiterate, if you are putting the thread to sleep for a fixed amount of time waiting for something else to happen, you're almost certainly approaching it the wrong way. And if you're putting the main thread to sleep in Android you're absolutely doing it the wrong way.

Strange behavior - App suddenly producing ANR

I know it sounds strange/ridiculous, but I am having this issue
Update#2
I am sharing the code that is indicated by #EpicPandaForce.
SyncService.onNetworkSuccess
public void onNetworkCallSuccess(Response response) {
List<TransactionHistory> historyList = (List<TransactionHistory>) response.body();
if(historyList != null && historyList.size() > 0) {
TransactionHistory max = Collections.max(historyList, new Comparator<TransactionHistory>() {
#Override
public int compare(TransactionHistory o1, TransactionHistory o2) {
return o1.getUpdatedAt().compareTo(o2.getUpdatedAt());
}
});
if(max != null) {
session.putStringForKey(Session.timeStamp, String.valueOf(max.getUpdatedAt()));
}
for(TransactionHistory history : historyList) {
String id;
if(history.getTo().equals(history.getFrom()) ||
history.getFrom().equals(session.getStringForKey(Session.fpIdKey)))
id = history.getTo();
else id = history.getFrom();
LatestTransactionResponse latestTransactionResponse = new LatestTransactionResponse();
DateTransactionResponse dateTransactionResponse = new DateTransactionResponse(DateUtility.getDateFromEpoch(history.getEpoch()));
dateTransactionResponse.addTransaction(history);
latestTransactionResponse.setArchived(history.isArchived());
latestTransactionResponse.addTransaction(history);
latestTransactionResponse.setId(id);
dateTransactionResponse.setId(id);
LatestTransactionRepository.getInstance().addLatestTransaction(realm,
latestTransactionResponse);
ContactTransactionRepository.getInstance().addNewTransaction(realm, dateTransactionResponse, id);
}
try {
Activity temp = MyFirebaseMessagingService.getRunningActivity();
if(temp != null) {
if(temp instanceof MainActivity) {
((MainActivity) temp).refreshLatestTransactions();
} else if(temp instanceof TransactionDetailActivity) {
((TransactionDetailActivity) temp).refreshOnMainThread();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
addNewTransaction
public void addNewTransaction(Realm realm, final DateTransactionResponse response, final String id) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
List<TransactionHistory> tempHistoryList;
DateTransactionResponse temp = realm
.where(DateTransactionResponse.class)
.equalTo("id", id)
.equalTo("date", response.getDate())
.findFirst();
if(temp == null)
realm.insertOrUpdate(response);
else {
tempHistoryList = temp.getTransactions();
for(TransactionHistory history : response.getTransactions()) {
boolean found = false;
for(int i=0; i < tempHistoryList.size(); i++) {
if (history.getId().equals(tempHistoryList.get(i).getId())) {
if(history.getStatus().equals(tempHistoryList.get(i).getStatus())) {
found = true;
break;
} else {
tempHistoryList.get(i).setStatus(history.getStatus());
}
}
}
if(!found)
tempHistoryList.add(history);
}
//realm.insertOrUpdate(temp);
realm.copyToRealm(temp);
//DateTransactionResponse transactionResponse = temp;
//temp.deleteFromRealm();
//realm.insertOrUpdate(temp);
}
}
});
//removeDuplicateTransactions(realm);
}
removeDuplicateTransaction
private void removeDuplicateTransactions(Realm realm) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmQuery<DateTransactionResponse> query = realm.where(DateTransactionResponse.class);
RealmResults<DateTransactionResponse> results = query.findAll();
List<DateTransactionResponse> transactions = new ArrayList<>(results);
for(DateTransactionResponse response : transactions) {
List<TransactionHistory> historyList = response.getTransactions();
Set<TransactionHistory> historySet = new LinkedHashSet<>(historyList);
RealmList<TransactionHistory> histories = new RealmList<>();
histories.addAll(new ArrayList<>(historySet));
response.setTransactions(histories);
realm.copyToRealm(response);
}
}
});
}
Update#1
There are 3 tabs with RecyclerViews on my main screen. Below are the implementation of Adapter for all three.
I have been developing an App for quite a time. It has been working just fine and I occasionally work to improve its performance. It is still under development. Some days ago, I cut-out the branch and done nothing notable (just one or two bug fixes) and started testing it and OOPS it started giving ANR's. I revert back to previous branch and very strangely it started giving me the same result. I have removed all changes and tried, still the same result. I am not sure what's happening. I tried to study traces.txt, but couldn't find waiting to lock as suggested in this SO answer.
I have also difficulty reading traces, couldn't find the culprit. Here is the traces.txt file.
I am using Realm as Database in my application and couldn't find a way to perform operations on Realm on other thread. I tried to find any other culprit in code, but all is till the same as before which was working perfectly fine.
Hierarchy
Here is the App Hierarchy.
Login screen is shown and user enters PIN. Then comes the main screen. Main screen contains 4 tabs, just like WhatsApp i.e first tab is camera and rest contains RecyclerViews in which data is being populated from Realm. ANR is only happening here. Keeping in mind that it was literally perfect some days ago until I took branch out and fixed some bugs, which were not even related to the main screen.
Any help or direction is highly appreciated.

Thread not terminating correctly, I think

public String newUser = "false";
public double lat = 0.0, lon = 0.0;
I have the following function in my android app (called when a Button is clicked) which starts a thread:
public void SignUpFunction(View view) {
assignValues();
String filledAll = checkIfFilled();
if (filledAll.equals("true")) {
Log.d("LIFECYCLE", "calling thread..");
//my thread
new validateThread().start();
Log.d("After thread start","This log call does not occur");
if (newUser.equals("true")) {
Toast.makeText(this, "Please wait as we obtain your location", Toast.LENGTH_SHORT).show();
getMyLocationFunction();
} else {
return;
}
}
}
validateThread:
class validateThread extends Thread {
public void run() {
Log.d("LIFECYCLE", "validateThread entered...");
try {
newUser = "true";
Log.d("validateThread", "Validated and found new user");
} catch (Exception e) {
Log.d("validateThread", "Exception in validateThread: " + e.getMessage());
}
}
}
The thread runs correctly...but after the last line, it does not go back to its point of start. I don't understand why this is happening because I've used threads before and they all work correctly.
I know I can just give the getMyLocation function inside the thread but I really need it this way.
I've searched for similar questions but none helped.. What am I doing wrong here?
Thanks in advance.
It's a race. SignUpFunction should wait until validateThread decides whether or not to set newUser = "true". Even with the race your code may work sometimes, but that is by accident.

Android iBeacon Receiving Single Specified Signal

I am receiving a range of signals from onReceive using BroadcastReceiver in my iBeaconProject. What I would like to do is to only keep track of one of the beacons (which I specify) and it's distance from my phone to the beacon. Any ideas, guys? Please help me! I'm using http://www.radiusnetworks.com. I am getting a range of signals using the following onReceive function. How do I go about doing it? Thanks all in advance!
BroadcastReceiver bReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
int countBea = 0;
if (intent.getAction().equals(intentname) && intent.getExtras() != null && intent.getExtras().containsKey(intentname)) {
Collection<IBeacon> beaconsCol = (Collection<IBeacon>)intent.getExtras().getSerializable(intentname);
for (IBeacon bea : beaconsCol) {
Log.d("beac receive!","receive! "+bea.getProximityUuid()+" "+bea.getMajor()+" "+bea.getMinor()+" "+bea.getAccuracy()+" "+bea.getProximity()+" "+bea.getRssi()+" "+bea.getTxPower());
countBea++;
if(((mainActivity)getActivity()).UUIDValue.equalsIgnoreCase(bea.getProximityUuid())
&& ((mainActivity)getActivity()).MajorValue == bea.getMajor()
&& ((mainActivity)getActivity()).MinorValue == bea.getMinor()) {
update(bea.getProximityUuid(), +bea.getMajor(), bea.getMinor(), bea.getAccuracy());
} else if (((mainActivity)getActivity()).UUIDValue.equalsIgnoreCase(bea.getProximityUuid())
&& (((mainActivity)getActivity()).MajorValue == 0 ||
((mainActivity)getActivity()).MinorValue == 0)) {
updateNILMajorMinor();
} else {
updateMultipleBeaconsDetected();
}
}
System.out.println("COUNTBEAC " + countBea);
}
}
};
Good to see the for-each loop.
Inside it, you can identify the beacon that you want to keep track of,
for (IBeacon bea : beaconsCol) {
//in the following if, identify the specified beacon
// this will remain the same for every refresh
if(bea.getProximityUuid().equals("match it here") && bea.getMajor()==major
&& bea.getMinor()==minor){
//now display that beacon's proximity and accuracy
//the same code will update a textview or notification every time
// here you will have 1 beacon at a time, can add that to a global list
}
}
Can you give a precise idea for the implementation?
does your code enter onReceive periodically?
I have never seen anything mention using the Radius Networks SDK by listening for broadcasts. Instead they ask that you implement certain interfaces and register them with an IBeaconManager.
You may find their code samples useful. That page contains the following snippet, which you may recognize as equivalent to the code in your question.
public class RangingActivity extends Activity implements IBeaconConsumer, RangeNotifier {
private static final String TAG = RangingActivity.class.getName();
private IBeaconManager iBeaconManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
iBeaconManager = IBeaconManager.getInstanceForApplication(this);
iBeaconManager.bind(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
iBeaconManager.unBind(this);
}
#Override
public void onIBeaconServiceConnect() {
iBeaconManager.setRangeNotifier(this);
try {
// edit this to match the UUID of your beacon
// or leave null to detect everything
String uuid = null;
Region region = new Region("myRangingUniqueId", uuid, null, null);
iBeaconManager.startRangingBeaconsInRegion(region);
} catch (RemoteException e) {
Log.e(TAG, "problem while starting ranging", e);
}
}
#Override
public void didRangeBeaconsInRegion(Collection<IBeacon> iBeacons, Region region) {
if (!iBeacons.isEmpty()) {
double accuracy = iBeacons.iterator().next().getAccuracy();
Log.i(TAG, "The first iBeacon I see is about " + accuracy + " meters away.");
}
}
}

Categories

Resources