Android Question TTS setEngineByPackage SDK>=14 - Is there a way to implement this in B4a

hatzisn

Expert
Licensed User
Longtime User
Hi everyone,

I noticed that in newer androids when trying to set the engine with reflection as seen in my post in additional libraries, classes and official updates does not work (click the link to see the post):
https://www.b4x.com/android/forum/threads/ttsfunctions.94306/

I did some research on the internet and found that the following can be done:

Quoting from stackoverflow:
The above answer is no longer valid as the method is deprecated. Android API 14 and above you need to use the constructor for setting the engine:

TextToSpeech(Context context, TextToSpeech.OnInitListener listener, String engine)

The "engine" String is the package name of the TTS engine you want to use.

Is there a way to implement it in B4A?


Edit - Please skip directly to my last message. (#10)


Cheers
 
Last edited:

hatzisn

Expert
Licensed User
Longtime User
Continuing the research both in B4A and the whole internet I came up with this:

B4X:
Dim m As JavaObject
m.InitializeNewInstance("android.speech.tts.TextToSpeech", Array(GetContext, Listener, sPackage))

Sub GetContext As JavaObject
  Return GetBA.GetField("context")
End Sub

Sub GetBA As JavaObject
  Dim jo As JavaObject
  Dim cls As String = Me
  cls = cls.SubString("class ".Length)
  jo.InitializeStatic(cls)
  Return jo.GetFieldJO("processBA")
End Sub

Is it correct this way so far? How can I implement the listener if it is correct so far?

Cheers
 
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
I came up with this:

B4X:
Sub SetEngineByPackage(sPackage As String, t As TTS, activity As Object) As TTS
    Dim ph As Phone
    If ph.SdkVersion < 14 Then
        Dim r As Reflector
        r.Target = t
        Log("Setting Engine " & sPackage & " as default.")
        r.RunMethod2("setEngineByPackageName", sPackage, "java.lang.String")
        Return t
    Else
        Dim Listener As JavaObject
        Listener.InitializeStatic("android.speech.tts.TextToSpeech")
        Listener.CreateEvent("android.speech.tts.TextToSpeech.OnInitListener", "setTTS_Ready", False)
        Dim m As JavaObject

        m.InitializeNewInstance("android.speech.tts.TextToSpeech", Array(GetContext(activity), Listener, sPackage))
        Return m
    End If
End Sub

Sub setTTS_Ready (Success As Boolean)
 
End Sub


Sub GetContext(activity As Object) As JavaObject
    Return GetBA(activity).GetField("context")
End Sub

Sub GetBA(activity As Object) As JavaObject
    Dim jo As JavaObject
    Dim cls As String = activity
    cls = cls.SubString("class ".Length)
    jo.InitializeStatic(cls)
    Return jo.GetFieldJO("processBA")
End Sub

Is it correct?
 
Last edited:
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
The code compiles ok but when the app starts I get this error:

*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
Error occurred on line: 64 (TTSFunctions)
java.lang.RuntimeException: Constructor not found.
at anywheresoftware.b4j.object.JavaObject.InitializeNewInstance(JavaObject.java:94)
at dhqi.trial.tts.ttsfunctions._setenginebypackage(ttsfunctions.java:146)
at dhqi.trial.tts.main._activity_create(main.java:430)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:710)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:342)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
at dhqi.trial.tts.main.afterFirstLayout(main.java:104)
at dhqi.trial.tts.main.access$000(main.java:19)
at dhqi.trial.tts.main$WaitForLayout.run(main.java:82)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5319)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)
** Activity (main) Resume **



The error line is in the GetBA function:

B4X:
Return jo.GetFieldJO("processBA")

I call it from main Activity with this line:

B4X:
Starter.tts = TTSFunctions.SetEngineByPackage(TTSFunctions.GetDefaultEngine(Starter.tts), Starter.tts, Me)

I use B4A v7.80

What might be the problem?
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
I don´t look at your code.

But i just made a small test with java...

Don´t know if it helps but you can try this library

Please note that there yould be errors in it. I did not test it much.
Also note that it is not a full wrap... there are mssing much i guess :D
 

Attachments

  • AndroidTTSEx.zip
    8 KB · Views: 291
  • AndroidTTSV0.01.zip
    5.7 KB · Views: 304
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
Thanks a lot Don Manfred for your response. I could use though some help with the code. If I get everything ready I will never learn.
 
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
Thank you Don Manfred for this code. It is a good resource to learn some java as my java knowledge is limited to none. What I meant actually is that I want some help with my code if you can make out what is wrong with the code I posted...

Edit - I saw your java code and what you do to set the Engine is limited to android api lower than 14. That is my main problem in this thread. Thanks anyway though because it is a great resource.
 
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
I tried the following:

I moved the subs GetContext and GetBA to the Main Activity and I send now the JavaObject in the sub. Now my code looks like this:

B4X:
Sub SetEngineByPackage(sPackage As String, t As TTS, jo As JavaObject) As TTS
    Dim ph As Phone
    Dim r As Reflector
    If ph.SdkVersion <= 14 Then
        r.Target = t
        Log("Setting Engine " & sPackage & " as default.")
        r.RunMethod2("setEngineByPackageName", sPackage, "java.lang.String")
        Return t
    Else
        Dim Listener As JavaObject
        Listener.InitializeStatic("android.speech.tts.TextToSpeech")
        Listener.CreateEvent("android.speech.tts.TextToSpeech.OnInitListener", "Starter.tts_Ready", False)
        'Dim j As JavaObject
        'j.InitializeContext
       
        Dim m As JavaObject
        'm.InitializeNewInstance("android.speech.tts.TextToSpeech", Array(j.GetField("context"), Listener, sPackage))
        'm.InitializeNewInstance("android.speech.tts.TextToSpeech", Array(GetContext(activity), Listener, sPackage))
       
        m.InitializeNewInstance("android.speech.tts.TextToSpeech", Array(jo, Listener, sPackage))
        Return m
    End If
End Sub

'
'Sub GetContext(activity As Object) As JavaObject
'    Return GetBA(activity).GetField("context")
'End Sub
'
'Sub GetBA(activity As Object) As JavaObject
'    Dim jo As JavaObject
'    Dim cls As String = activity
'    cls = cls.SubString("class ".Length)
'    jo.InitializeStatic(cls)
'    Return jo.GetFieldJO("processBA")
'End Sub

and the code in Main Activity is:

B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.

End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.

End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")
    Activity.LoadLayout("1")
    Starter.tts = TTSFunctions.SetEngineByPackage(TTSFunctions.GetDefaultEngine(Starter.tts), Starter.tts, GetContext(Me))
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub GetContext(Act As Object) As JavaObject
    Return GetBA(Act).GetField("context")
End Sub

Sub GetBA(Act As Object) As JavaObject
    Dim jo As JavaObject
    Dim cls As String = Act
    cls = cls.SubString("class ".Length)
    jo.InitializeStatic(cls)
    Return jo.GetFieldJO("processBA")
End Sub

Now when the app starts I get the following error:

*** Service (starter) Create ***
** Service (starter) Start **
** Service (starter) Destroy **
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **

Error occurred on line: 54 (TTSFunctions)
java.lang.RuntimeException: Constructor not found.
at anywheresoftware.b4j.object.JavaObject.InitializeNewInstance(JavaObject.java:94)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runVoidMethod(Shell.java:755)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:345)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
at dhqi.trial.tts.main.afterFirstLayout(main.java:102)
at dhqi.trial.tts.main.access$000(main.java:17)
at dhqi.trial.tts.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5319)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)

** Activity (main) Resume **

It gets in the TTSFunctions.SetEngineByPackage sub which means it gets the context from Main activity and stops in the line:

B4X:
m.InitializeNewInstance("android.speech.tts.TextToSpeech", Array(jo, Listener, sPackage))

I don't know what to do any more as I tried almost everything.

Any help will be highly appreciated.
 
Last edited:
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
I decided to do it with in line Java code. Here is what the code turned to:

B4X:
Sub SetEngineByPackage(sPackage As String, t As TTS, jo As JavaObject) As TTS
    Dim ph As Phone
    Dim r As Reflector
    If ph.SdkVersion < 14 Then
        r.Target = t
        Log("Setting Engine " & sPackage & " as default.")
        r.RunMethod2("setEngineByPackageName", sPackage, "java.lang.String")
        Return t
    Else

        Dim ttsNew As JavaObject
        ttsNew.InitializeNewInstance("GetNewTTS", Null)   
        Return ttsNew.RunMethod("ttsN", Array(r.GetContext, sPackage))
    End If
End Sub

#IF JAVA
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;

public class GetNewTTS {

public void Initialize()  {

};

public TextToSpeech ttsN(android.content.Context cnt, String engine) {
        TextToSpeech tts;
 
        tts = new TextToSpeech(cnt, new OnInitListener(){

            @Override
            public void onInit(int i) {

            }}, engine);
        return tts;
};
};
#End If

Now I get this error:

** Activity (main) Resume **
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **

Error occurred on line: 55 (TTSFunctions)
java.lang.ClassNotFoundException: java$lang$GetNewTTS
at anywheresoftware.b4j.object.JavaObject.getCorrectClassName(JavaObject.java:288)
at anywheresoftware.b4j.object.JavaObject.InitializeNewInstance(JavaObject.java:83)
at dhqi.trial.tts.ttsfunctions._setenginebypackage(ttsfunctions.java:55)
at dhqi.trial.tts.main._activity_create(main.java:379)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:710)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:342)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
at dhqi.trial.tts.main.afterFirstLayout(main.java:102)
at dhqi.trial.tts.main.access$000(main.java:17)
at dhqi.trial.tts.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5319)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)

** Activity (main) Resume **

Line 55 is the following line of code:

ttsNew.InitializeNewInstance("GetNewTTS", Null)



No way out... I am through...
Attached is the project in case anyone would like to make a lucky guess.
 

Attachments

  • TrialTTS.zip
    9.1 KB · Views: 261
Last edited:
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
The fact that a method is deprecated doesn't mean that it cannot be used. What happens when you call setEngineByPackageName?

Hi Erel,

I have two devices (one Android 7 samsung j730 and one android 4.2.2 samsung i9060). Both have the internal samsung TTS which does not support Greek. Android 7 has the new Google TTS which supports Greek and Android 4.2.2 has the old Google TTS (not upgradable) which does not support Greek. In order to have Greek in android 4.2.2 I have bought the Svox classic TTS before two years. I have written a sub which starts from the default TTS and checks if it has Greek installed and if not goes to the next TTS to check and so on until it will return to the default TTS package if it has not located the Greek language and reports that none of the installed TTSs supports the Greek language. I check with "if tts.setlanguage("el","gr") = true then....". In android 7 the default TTS is Google TTS and it locates directly the Greek language. In android 4.2.2 if I set the Google TTS as default, it goes through the whole cycle of TTSs and it does not locate the Greek language. On the contrary if I set Svox classic TTS as default it locates the Greek language.

Therefore I am forced to suppose that the setEngineByPackageName does not work. As a result I created this thread and I ended up with the code in post #10. What is wrong with my code in this post? Can you provide a working example with my code?

Thanks in advance
 
Last edited:
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
Thanks Erel for the quick reply.

I did what you said and it recognizes the class if I put the code module name in lowercase.
Now I have another problem. I get this error:

Logger connected to: samsung GT-I9060
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **

Trying with the com.samsung.SMT
Error occurred on line: 45 (Main)
java.lang.RuntimeException: Error speaking text.
at anywheresoftware.b4a.obejcts.TTS.Speak(TTS.java:45)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runVoidMethod(Shell.java:755)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:345)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
at dhqi.trial.tts.main.afterFirstLayout(main.java:102)
at dhqi.trial.tts.main.access$000(main.java:17)
at dhqi.trial.tts.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5319)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)

(Exception) java.lang.Exception: java.lang.RuntimeException: Error speaking text.
Trying with the com.google.android.tts
Error occurred on line: 45 (Main)
java.lang.RuntimeException: Error speaking text.
at anywheresoftware.b4a.obejcts.TTS.Speak(TTS.java:45)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runVoidMethod(Shell.java:755)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:345)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
at dhqi.trial.tts.main.afterFirstLayout(main.java:102)
at dhqi.trial.tts.main.access$000(main.java:17)
at dhqi.trial.tts.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5319)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)

(Exception) java.lang.Exception: java.lang.RuntimeException: Error speaking text.
Trying with the com.svox.classic
Error occurred on line: 45 (Main)
java.lang.RuntimeException: Error speaking text.
at anywheresoftware.b4a.obejcts.TTS.Speak(TTS.java:45)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runVoidMethod(Shell.java:755)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:345)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
at dhqi.trial.tts.main.afterFirstLayout(main.java:102)
at dhqi.trial.tts.main.access$000(main.java:17)
at dhqi.trial.tts.main$WaitForLayout.run(main.java:80)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:5319)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)

(Exception) java.lang.Exception: java.lang.RuntimeException: Error speaking text.
** Activity (main) Resume **

What is wrong? Is java TextToSpeech object that is returned different than B4A's TTS object?

I attach the project in case you want to have a look.

Thanks in advance...
 

Attachments

  • TrialTTS.zip
    9.2 KB · Views: 270
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
I'm sorry but if you decided to implement it with inline Java then I cannot write the Java code for you.

You don't have to be angry Erel. I don't want you to write the Java code for me. All I asked is what is wrong and why does it behave like that? If you do not want to answer that then it's ok. There will not be any problem. So to summarize I would like to ask you two questions (and if you want to you can answer them):

1) What is wrong and why does it behave like that?
2) Is there a different implementation of what I am doing (since you say "you decided to implement it with java") ?

As I wrote several times the behavior will be exactly the same as with calling setEngineByPackageName.

You are a better programmer than I am and maybe you know better. The fact is that I am facing a problem with setEngineByPackageName and I am trying to check the behavior with the other option.
 
Upvote 0

hatzisn

Expert
Licensed User
Longtime User
Never mind Erel. What I asked was idiotic. It is obvious that the java TextToSpeech object that the function returns is different than the B4A TTS object since the second is a wrapper for the first and supports only a few methods of the first. Maybe you should consider including in the TTS library an "Initialize2" method that would also include the TTS package f.e. Initialize2(eventname as string, enginepackage as string). Just an idea! Thanks anyway and sorry if I turned to (even temporarily) in a "pain in the ass".

Edit - You can get the Initialize2 method directly from DonManfred's earlier post. He already has done it. DonManfred I have to apologize since I did not checked the constructor method of your code but scanned directly to find the "setEngine" keyword with notepad.
 
Last edited:
Upvote 0
Top