Raise your (phone's) voice as we build out our Text To Speech app for Android.
Application for TTS
In a prior article we explored using the Text To Speech (TTS) capabilities native to Android.
In this article we begin to apply the TTS capabilities into an application that has (slightly) more utility.
The reasons for using Text To Speech range from the practical, safety-minded applications to the “just for fun”. The application we build in this article is arguably a little bit of both.
While our prior look at Text To Speech was geared around the mechanics of using the TTS features, this application spends a bit more time with the context of the application and leverages what we have previously learned.
Reading your messages
The name of the sample application is “linuxdlsazine SMS Reader”, or LMSMSReader.
The application works as follows:
- When an incoming text message arrives, our application is notified.
- The application obtains a copy of the inbound message.
- A new Activity is launched to “process” the message.
- The “processing” of this message causes the TTS subsystem to “read” the message to us.
- The incoming text message still winds up in the normal “inbox” as expected.
Before diving into the code, let’s enumerate from a high-level just what we’re going to need here:
- A BroadcastReceiver class, to be notified of incoming SMS messages.
- An Activity class to interact with the TTS services
- Some “glue” to connect the BroadcastReceiver to the Activity.
- An appropriately apportioned AndroidManifest.xml to wire everything up properly.
Let’s start by examining the code for our implementation of a BroadcastReceiver: LMSMSReader.java.
package com.navitend.lm.smsreader;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.os.Bundle;
import android.telephony.SmsMessage;
public class LMSMSReader extends BroadcastReceiver {
private final String tag = "LMSMSReader";
@Override
public void onReceive(Context context, Intent intent) {
Log.i(tag,"onReceive invoked!");
Bundle extras = intent.getExtras();
if (extras == null) {
Log.i(tag,"didn't like this ....message");
return;
}
Object[] pdus = (Object[]) extras.get("pdus");
Log.i(tag,"there are [" + pdus.length + "] messages");
SmsMessage message = SmsMessage.createFromPdu((byte[]) pdus[0]);
String fromAddress = message.getOriginatingAddress();
Log.i(tag,"message from : " + fromAddress + " " + message.getMessageBody().toString());
Intent rtmIntent = new Intent();
rtmIntent.setClass(context, ReadThisMessage.class);
rtmIntent.putExtra("MESSAGE", message.getMessageBody().toString());
rtmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(rtmIntent);
}
}
A quick glance at this code reveals that the class LMSMSReader is extending the BroadcastReceiver class. You may recall that the BroadcastReceiver is one of the four main classes that can comprise an Android application. The others are: Actvity, Service, and ContentProvider.
A BroadcastReceiver’s onReceive method is invoked when a particular event occurs. In this case, we are interested in the scenario where a Text (or SMS) message has been received. When we look at the AndroidManifest.xml file in detail we will see how things are wired up. For now, know that the onReceive method of the LMSMSReader class is invoked when an SMS message is received.
The two arguments to the onReceive method include an application Context and an Intent. The Intent contains the information about our message. To obtain the message data itself, we need to extract the “extras” from the Intent with a call to the getExtras() method.
Assuming we don’t have a false alarm (i.e. no Extras), we extract the data labeled as “pdus”. PDU stands for “Protocol Description Unit”. You can learn more on the ‘net. A quick Google search brings up this site which provides some more specifics.
Multiple PDU’s may be found in an incoming message but for our purposes here, we’re just going to work with the first message.
We can extract some data including the sender’s address and the message body. You can learn more about the SMS message class by browsing the documentation.
Now that we’ve got a message, we would like the application to read it to us!
In order to accomplish this we need to package up the message contents into an Intent and then pass this on to our Activity which will take care of the TTS details for us.
We create a new Intent and explicitly tell it that we want to work with the ReadThisMessage class which we’ll review in a moment.
In addition to the class name, we also need to pass along the message contents which we do by adding an “Extra” with a name of “MESSAGE” and the value set to the body of the SMS Message.
The other tailoring we want to perform to our newly created Intent is to set some flags. There are many flags to choose from, so why these two?
A well-behaved BroadcastReceiver does it’s job of reacting to various notifications/stimuli, dispatching its work, and then getting out of the way. The job of this particular BroadcastReceiver implementation is simply to react to the incoming SMS and then pass off the data to our Activity. We want a new task to be created to provide allow the BroadcastReceiver to terminate and then let the newly created Activity do as it wishes so we set the flag named FLAG_ACTIVITY_NEW_TASK. In fact, if you try to call startActivity() without this flag, Android throws an exception.
The other flag we want to set has a little less obvious reason. If you press and hold the “Home” key on your Android device, you will see the most recently run applications. Some might be still running and others are just in the “most recently used”, or “MRU” list. This kind of functionality may or may not be what you want, so you can add or remove the FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS flag. If you want to be able to listen again to the most recently received Text Message, leave this flag out of the call to addFlags(). If you would prefer to not have the application listed in your “Recents”, then add the flag. Your choice.
Now it is time to look at the ReadThisMessage class implementation.
Reading the message
ReadThisMessage.java contains the code which interacts with the TTS service.
package com.navitend.lm.smsreader;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.util.Log;
import java.util.HashMap;
public class ReadThisMessage extends Activity implements OnInitListener,OnUtteranceCompletedListener {
private TextToSpeech tts = null;
private final String tag = "LMSMSReader";
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();
Log.i(tag,"onDestroy");
if (tts!=null) {
tts.shutdown();
}
}
// OnInitListener impl
public void onInit(int status) {
Log.i(tag,"onInit");
HashMap hm = new HashMap();
hm.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "smsmessage");
tts.setOnUtteranceCompletedListener(this);
tts.speak(msg, TextToSpeech.QUEUE_FLUSH, hm);
}
// OnUtteranceCompletedListener impl
public void onUtteranceCompleted(String utteranceId) {
Log.i(tag,"onUtteranceCompleted :" + utteranceId);
tts.shutdown();
tts = null;
finish();
}
}
Note the import statements for the TextToSpeech classes. Also, we have imported the java.util.HashMap which is used to help track when the reading of our message is complete.
This Activity implements two listeners, one called OnInitListener and the other OnUtteranceCompletedListener.
The code for this Activity is fairly straight-forward though it does rely heavily upon the “callback” mechanism associated with the two listeners implemented.
In the onCreate method we extract the Intent that started the activity — we do this via a call to getIntent().
We then extract the “MESSAGE” parameter which is just a String and store it in a class-level variable named msg.
Next, we start-up the TTS functionality by creating an instance of the TextToSpeech class, passing in a Context which is satisfied by the “this” pointer which refers to the Activity. The next required parameter is a reference to a class which implements the OnInitListener. Again, “this” satisfies the requirement.
From here on out we are relying upon the callbacks. First, the onInit method is invoked when the TTS interface is ready.
In onInit() we setup a HashMap which is a simple reference to this message. Ideally we would use a unique identifier for each message to be spoken. In our case we simply pass in a value of “smsmessage” with a key value of TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID.
We tell this TextToSpeech object instance that we want to be notified when any utterances (i.e. speech) are completed. The key/value pair in the HashMap are used to differentiate between multiple utterances.
Then we ask for the speech to be uttered with a call to the speak() method, passing in the msg text, a parameter to flush any waiting phrases and finally our HashMap with the utterance id provided.
When the utterance has completed the onUtteranceCompleted method is invoked and we call the shutdown() method of the TextToSpeech object to properly “cleanup” and unbind our Activity from the TTS service.
In the case that this method is never invoked for whatever reason, we want to be a good Android-citizen and cleanup in the onDestroy method by conditionally calling the shutdown() method as necessary.
When an incoming message is received you should not only hear the message spoken, but also see some relevant action in the logcat.
04-02 17:37:48.374: INFO/LMSMSReader(7651): onReceive invoked!
04-02 17:37:48.374: INFO/LMSMSReader(7651): there are [1] messages
04-02 17:37:48.394: INFO/LMSMSReader(7651): message from :What is for dinner?
04-02 17:37:48.674: INFO/LMSMSReader(7651): onInit
04-02 17:37:50.254: INFO/LMSMSReader(7651): onUtteranceCompleted :smsmessage
04-02 17:37:50.394: INFO/LMSMSReader(7651): onDestroy
Plumbing
That’s cool, but how does Android know to call our application when a message arrives? The answer lies in the AndroidManifest.xml file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.navitend.lm.smsreader"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name" >
<receiver android:name=".LMSMSReader">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"></action>
</intent-filter>
</receiver>
<activity android:name=".ReadThisMessage" />
</application>
<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
</manifest>
The “receiver” tag calls out the BroadcastReceiver and the IntentFilter specifies which event we are interested in.
The permission: “android.permission.RECEIVE_SMS” is also required in order to process the incoming messages.
Lastly, don’t forget to define the Activity “ReadThisMessage”, otherwise it won’t start when asked to read the message!. Note that unlike many Android applications the sole Activity in this application does NOT show up on the home screen and in fact doesn’t even have a UI.
Of course, there is always more to do. I often get automated text messages related to voice mail notifications, backup notifications or after-hours service emergencies, etc. I certainly don’t want everyone of those messages read aloud. Nor do I necessarily want them read while I am sleeping. Or perhaps I do to help me wake up? Regardless of the specifics, it looks like there is more work to do here. Allowing the user to customize the behavior of the application via Android Preferences will be necessary and something we look at in a future article.
Comments on "Putting Text to Speech to Work"
Also visit my blog … buy hip hop beats online; Danny,
Check out my webpage – buy rap beats [Maurine]
my homepage … buy hip hop beats (Rosalyn)
Here is my web site … buy hip hop beats online [Mathias]
Review my page; beats for sale – Robin -
Here is my web site: rap beats for sale (Norris)
My web site – rap beats for sale (Dean)
Here is my web blog – buy hip hop beats (http://www.codefarms.com)
Also visit my site; buy beats online; Earnestine,
Hi, I read your blog like every week. Your story-telling style is witty,
keep doing what you’re doing!
my web site; serious side effects [Gabriele]
I have been exploring for a little bit for any high-quality articles or weblog posts in this kind of
house . Exploring in Yahoo I ultimately stumbled upon this web site.
Studying this information So i am happy to express that I’ve an incredibly just right
uncanny feeling I discovered exactly what I needed.
I so much indisputably will make sure to don?t omit this site and give
it a look regularly.
Here is my page :: garcinia cambogia goes – Son -
Also visit my blog – rap beats for sale – Rod,
My web blog :: beats for sale (Eleanor)
my web blog … rap beats; Selene,
My site – buy rap beats online (Regena)
Also visit my weblog hip hop beats for sale (Edgar)
Feel free to surf to my web site: hip hop beats for sale (Marcelino)
I’m amazed, I have to admit. Rarely do I come across a blog that’s equally educative
and interesting, and without a doubt, you have hit the nail on the head.
The problem is something too few people are speaking intelligently about.
I am very happy that I stumbled across this in my hunt for something concerning this.
Also visit my website – fat burning in your body (Randolph)
Review my site – buy beats (Doug)
Here is my site – buy hip hop beats – Charlie,
Look into my site buy rap beats online
Feel free to surf to my web blog: buy rap beats (Tia)
Unquestionably believe that which you stated. Your favourite reason appeared to be at the
web the easiest factor to bear in mind of. I say to you,
I definitely get irked even as other folks consider worries
that they just don’t realize about. You controlled to hit the nail upon the top and outlined out the whole thing with no need side-effects ,
other people can take a signal. Will probably be
again to get more. Thanks
My web-site; improved website last
Nice post. I was checking continuously this weblog and I am impressed!
Very helpful information specially the remaining phase :) I care for such info
a lot. I was seeking this certain information for a long time.
Thanks and good luck.
Check out my page: incorporating responsive (go.1O3.me)