Android call TTS in BroadcastReceiver - android

I need to call TTS service within subclass of BroadcastReceiver. When I am implement that class from OnInitListener, it gave run-time error.
Is there any other-way to implement TTS within BroadcastReceiver?
Thank You,
Sorry Code:
public class TextApp extends BroadcastReceiver implements OnInitListener {
private TextToSpeech tts;
private String message = "Hello";
#Override
public void onReceive(Context context, Intent intent) {
tts = new TextToSpeech(context, this);
message = "Hello TTS";
}
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS)
{
tts.speak(message, TextToSpeech.QUEUE_FLUSH, null);
}
}
}

Your code didn't work on :
tts = new TextToSpeech(context, this);
Context on BroadcastReceiver is a "restricted context". It means you cannot start service on context in BroadcastReceiver. Because TTS is a service, so it doesn't call anyting.
The Best Solutions is you start another intent on BroadcastReceiver with activity that call the service.
public void onReceive(Context context, Intent intent) {
....
Intent speechIntent = new Intent();
speechIntent.setClass(context, ReadTheMessage.class);
speechIntent.putExtra("MESSAGE", message.getMessageBody().toString());
speechIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(speechIntent);
....
}
And then on the activity you call the TTS service with parameter from extras
public class ReadTheMessage extends Activity implements OnInitListener,OnUtteranceCompletedListener {
private TextToSpeech tts = null;
private String msg = "";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent startingIntent = this.getIntent();
msg = startingIntent.getStringExtra("MESSAGE");
tts = new TextToSpeech(this,this);
}
#Override
protected void onDestroy() {
super.onDestroy();
if (tts!=null) {
tts.shutdown();
}
}
// OnInitListener impl
public void onInit(int status) {
tts.speak(msg, TextToSpeech.QUEUE_FLUSH, null);
}
// OnUtteranceCompletedListener impl
public void onUtteranceCompleted(String utteranceId) {
tts.shutdown();
tts = null;
finish();
}
}

You can try using either JobIntentService (Post Android-O) or IntentService to invoke TTS from Broadcast receiver. It has less overhead than launching an activity for the sake of giving TTS a correct context. Note that you cannot give a broadcast receiver's context to TTS.
Here is my code snippet where I acheived same thing using JobIntentService.
Inside your custom Broadcast receiver's onReceive() invoke your custom JobIntentService like this:
Intent speechIntent = new Intent();
speechIntent.putExtra("MESSAGE", "Bluetooth is on.");
MySpeakService.enqueueWork(context, speechIntent);
And MySpeakService.java is this:
import android.content.Context;
import android.content.Intent;
import android.speech.tts.TextToSpeech;
import android.support.annotation.NonNull;
import android.support.v4.app.JobIntentService;
public class MySpeakService extends JobIntentService {
private TextToSpeech mySpeakTextToSpeech = null;
private boolean isSafeToDestroy = false;
public static void enqueueWork(Context context, Intent intent) {
enqueueWork(context, MySpeakService.class, 1, intent);
}
#Override
protected void onHandleWork(#NonNull Intent intent) {
String message = intent.getStringExtra("MESSAGE");
mySpeakTextToSpeech = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
mySpeakTextToSpeech.speak(message, TextToSpeech.QUEUE_ADD, null, null);
while (mySpeakTextToSpeech.isSpeaking()) {
}
isSafeToDestroy = true;
}
});
}
#Override
public void onDestroy() {
if (isSafeToDestroy) {
if (mySpeakTextToSpeech != null) {
mySpeakTextToSpeech.shutdown();
}
super.onDestroy();
}
}
}

Al Zil answer is not totally correct. Android TTS is a bounded service. Broadcast receivers truly has a limited context but they can't bind themselves to any service. However, they can START a service. Starting the tts from activity is ok, but if you don't need UI you can also initialize it from a service. Look at this answer to see how it's done
Good luck.

Related

Wrong when register two receiver in an activity Android

I have an activity. It will be receive two variable from an service. In the service, I will send two variable to the activity by
// Send first variable
sendBroadcast(new Intent("first_one"));
// Send second variable
sendBroadcast(new Intent("second_one"));
Now, In the activity, I used bellow code to receive the data. There are
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerReceiver(firstRec, new IntentFilter("first_one"));
registerReceiver(secondRec, new IntentFilter("second_one"));
}
#Override
protected void onPause() {
super.onPause();
if (firstRec != null) {
unregisterReceiver(firstRec);
firstRec = null;
}
if (secondRec != null) {
unregisterReceiver(secondRec);
secondRec = null;
}
}
private BroadcastReceiver firstRec = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("TAG","OK first");
}
};
private BroadcastReceiver secondRec = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("TAG","OK second");
}
};
However, I cannot print the log "OK second" when I called sendBroadcast(new Intent("second_one")); in the service. What is happen? How can I fix it? Thank you
UPDATE: my activity is an accept calling activity get from #notz
How can incoming calls be answered programmatically in Android 5.0 (Lollipop)?. Then I create an service as following
public class myService extends Service{
#Override
public void onCreate() {
super.onCreate();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent answerCalintent = new Intent(getApplicationContext(), AcceptCallActivity.class);
answerCalintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
answerCalintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(answerCalintent);
//Send the second command after 10 second and make the calling in background
new CountDownTimer(10000, 100) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
sendBroadcast(new Intent("second_one"));
}
}.start();
return START_STICKY;
}
}
You should unregister your reсeiver in a method opposite to that in which you register it:
If you registered receiver in onCreate() - then you should unregister it in onDestroy().
But as i know, for most cases, the best practice is to register receiver in onResume() and unregister it in onPause().

TTS not stop in Service: stop failed: not bound to TTS engine

My TTS in service is not stopping and speak two times.
In logcat the error is "stop failed: not bound to TTS engine"
I am stuck here that why TTS is not stoping. What wil I do here to stop it when speaked.
Here is my code
public class Speaker extends Service implements TextToSpeech.OnInitListener {
public static TextToSpeech mtts;
String speech = "";
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
}
#Override
public void onStart(Intent intent, int startid) {
speech=intent.getExtras().getString("speech");
mtts = new TextToSpeech(getApplicationContext(), this);
}
#Override
public void onDestroy() {
if (mtts != null) {
Log.e("Destroy", "Service");
mtts.stop();
mtts.shutdown();
}
super.onDestroy();
}
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS)
mtts.speak(speech, TextToSpeech.QUEUE_ADD, null);
}
Log.e("onInit ends", "Service");
}
}
}
One reason is that when a phone call is incoming then at this time your broadcast receiver would be calling this activity again and again unless you pick the phone
If you want to stop this service after make it speak for once.
Code for stopping the service.
Intent in = new Intent(this, Speaker.class);
stopService(in);
Use this code inside onDestroy() or in onStart() method.
Why you are using the service for TTS?, you can use any activity or class etc.

TextToSpeech using WakefulBroadcastReceiver

How can I use the the Text To Speech functionality onPause method using WakefulBroadcastReceiver I made the following classes for the purpose:
I am using Receiver for the GCM Push Notification and this code works fine onReceive method but when app is onPause state and the notification dispaly in NotificatioManager at that moment app crashed, help me to sort this problem
GcmBroadcastReceiver
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
Context mContext;
#Override
public void onReceive(Context context, Intent intent)
{
mContext = context;
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> services = activityManager.getRunningTasks(Integer.MAX_VALUE);
if (!services.get(0).topActivity.getPackageName().toString().equalsIgnoreCase(context.getPackageName().toString()))
{
Speaker speaker = new Speaker(mContext);
speaker.allow(true);
speaker.speak("asdas","asdas");
}
}
}
Speaker.class
public class Speaker implements OnInitListener {
private TextToSpeech tts;
private boolean ready = false;
private boolean allowed = false;
public Speaker(Context context){
tts = new TextToSpeech(context, this);
}
public void allow(boolean allowed){
this.allowed = allowed;
}
#Override
public void onInit(int status) {
if(status == TextToSpeech.SUCCESS){
tts.setLanguage(new Locale("en-AU"));
ready = true;
}
else{
ready = false;
}
}
public void speak(String welcomeMessage, String body){
if(allowed) {
HashMap<String, String> hash = new HashMap<String,String>();
hash.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_NOTIFICATION));
tts.setSpeechRate((float) 0.8);
tts.speak(welcomeMessage, TextToSpeech.QUEUE_ADD, hash);
tts.playSilence(500, TextToSpeech.QUEUE_ADD, null);
tts.speak(body, TextToSpeech.QUEUE_ADD, hash);
}
}
}
Following exception i have faced
Caused by: android.content.ReceiverCallNotAllowedException:
BroadcastReceiver components are not allowed to bind to services
at android.app.ReceiverRestrictedContext.bindService(ContextImpl.java:173)
at android.speech.tts.TextToSpeech.connectToEngine(TextToSpeech.java:627)
at android.speech.tts.TextToSpeech.initTts(TextToSpeech.java:597)
at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:553)
at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:527)
at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:512)
at sss.sss.sss.Speaker.<init>(Speaker.java:20)
at sss.sss.sss.GcmBroadcastReceiver.onReceive(GcmBroadcastReceiver.java:47)
You should start your service in onReceive passing the message to speak
#Override
public void onReceive(Context context, Intent intent)
{
intent.setAction("speak");
intent.putExtra("welcome_message", "asdas");
intent.putExtra("body", "asdas");
startService(intent.setComponent(new ComponentName(context.getPackageName(),
MyService.class.getName())));
// Where MyService is the service that you implements TTS
}
Then in the service class
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
if (intent != null)
{
String action = intent.getAction();
if ("speak".equals(action))
{
// check if TTS is initialize if so speak the extra
// otherwise save the extra to class members and when onInit is called
// check if these class members are null then speak
}
}
}

Do intents change when they are sent through IPC?

I'm using a ResultReceiver to allow a service to pass data through to an activity. I'm experiencing some difficulties with comparing intents that have been sent through IPC, it looks like the objects are changing, and thus can't be compared using a standard hashcode(.equals) comparison. I've created some sample code that will reproduce the scenario:
MyActivity.java:
public class MyActivity extends Activity {
private final Handler mHandler = new Handler();
private Intent serviceIntent;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyReceiver receiver = new MyReceiver(mHandler);
serviceIntent = new Intent(this, MyService.class);
serviceIntent.putExtra("receiver", receiver);
startService(serviceIntent);
}
public class MyReceiver extends ResultReceiver {
public MyReceiver(Handler handler) {
super(handler);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
Intent intent = resultData.getParcelable("intent");
if(intent.equals(serviceIntent)) {
Log.d("TEST", "Same intent!");
} else {
Log.d("TEST", "Different intents!");
}
}
}
}
MyService.java
public class MyService extends Service {
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
ResultReceiver receiver = intent.getExtras().getParcelable("receiver");
Bundle b = new Bundle();
b.putParcelable("intent", intent);
receiver.send(100, b);
stopSelf();
return Service.START_NOT_STICKY;
}
}
Don't forget to register the service in the manifest if you want to run this.
So the intent is sent back and forth, no change is made in the process and yet my activity insists that the two references differ. What's going on here?
The intent created by Intent intent = resultData.getParcelable("intent") and private Intent serviceIntent are not the same object, even if they are created to contain the exact same data. The current .equals() function simply checks if the intents are the same object; you will have to write your own .equals() function to determine if the intents are the same by whatever definition fits your application. See here.

how to start different activities with one background service

I'm new to Android and I'm stuck on a conception problem.
I have an application with several activities, some of those activities are critical and require the user to be logged in a webApp.
When the user clicks a button to reach one of those critical activities, I call a background Service which ask if the user is still connected to the webApp (not timeout). If the user is logged in, the activity is started, otherwise a dialog pops up and ask for username and password. The problem is that there is several protected activities, and I want to use the same service to do the verification. The way I do it for the moment works but it's kind of kludgy.
public class A_Activity extends Activity {
Context context;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = getApplicationContext();
setButtonClickListener();
}
private void setButtonClickListener() {
button_1 = (Button)findViewById(R.id.button1);
button_1.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intentCall = new Intent(context,com.them.cp.ConnexionManagerService.class);
intentCall.putExtra("WHO_IS_CALLED","FIRST_ACTIVITY");
context.startService(intentCall);
}
});
button_2 = (Button)findViewById(R.id.button2);
button_2.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intentCall = new Intent(context,com.them.cp.ConnexionManagerService.class);
intentCall.putExtra("WHO_IS_CALLED","SECOND_ACTIVITY");
context.startService(intentCall);
}
});
}
}
And my service
public class ConnexionManagerService extends Service{
public class IsConnectedAsync extends AsyncTask<String , Void, Void>{
protected Void doInBackground(String... whoIsCalled) {
String redirectedURL = getRedirectedURL();
if(redirectedURL.equalsIgnoreCase(IF_NOT_CONNECTED_URL)){
if(whoIsCalled[0].equalsIgnoreCase("FIRST_ACTIVITY")){
Intent trueIntent = new Intent(getBaseContext(), FirstActivity.class);
trueIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplication().startActivity(trueIntent);
}
else if(whoIsCalled[0].equalsIgnoreCase("SECOND_ACTIVITY")){
Intent trueIntent = new Intent(getBaseContext(), SecondActivity.class);
trueIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplication().startActivity(trueIntent);
}
}
else{
Intent falseIntent = new Intent(getBaseContext(), PopUpLoginActivity.class);
falseIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplication().startActivity(falseIntent);
}
}
return null;
}
}
#Override
public void onCreate() {
Toast.makeText(this, "My Service Created", Toast.LENGTH_LONG).show();
Log.d("service onCreate", "onCreate");
}
public int onStartCommand(Intent intent, int flags, int startId){
String whoIsCalled = intent.getStringExtra("WHO_IS_CALLED");
new IsConnectedAsync().execute(whoIsCalled);
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
with my little knowledge I wish i could just send an intent, but it seems that it's not possible as it's not the UI thread.
My question is: What can I do to make this service more generic ?
I want to use the same service to do the verification.
If you don't destroy the service it will be the same service object. If an activity which started your service finishes or stops the service it could be destroyed if it was the unique activity that started the service. If you want to ensure that the service reminds on background start it on you application class (extending Application) and in each activity you need. When an activity stops the service or finishes the service will not be destroyed because your application class is still connected.
EDIT:
To avoid write putExtra again and again:
public class StartOrder1 extends Intent {
public StartOrder(Context ctx, String activity_name){
super(ctx, ServiceName.class);
if(activity_name != null)
super.putExtra("WHO", activity_name);
else
super.putExtra("WHO", "UNKNOWN");
}
public String getWho(){
reurn.getIntExtra("WHO");
}
}
To start it:
this.startService(new StartOrder1(this, "My activity name"));
The best solution:
public class StartOrder2 extends Intent {
public StartOrder(Activity a){
super(a, ServiceName.class);
super.putExtra("WHO", a.toString());
}
public String getWho(){
reurn.getIntExtra("WHO");
}
}
And you can override toString method in each Activity passing the activity name, class name, whatever you want. Then when you start an intent:
this.startService(new StartOrder2(this));
Or extends Activity with this utility:
public class EnhancedActivity extends Activity{
protected startMyService(String name){
Intent i = new Intent(this, MyService.class);
i.putExtra("who", name);
startService(i);
}
}
And call it on your final activity
[...]
super.startMyService("activity_name");
[...]

Categories

Resources