Listen to SMS Messages in Morse Code
SMorSe is an Android application that converts incoming text messages to Morse code based on the ITU-R M.1677-1 recommendation. Simply enable the app, and all incoming text messages will be converted to Morse code (even if the app is not running). If the ringer mode is set to normal, the message is played at the frequency and speed settings chosen. If the ringer mode is set to vibrate, the message is vibrated at the speed setting chosen. Each message begins with the starting signal ( — • — • — ), after which the message is played. The cross signal ( • — • — • ) indicates the end of the message. The source code is freely available on GitHub.
Since it was published on July 12, 2014, the SMorSe app has been installed on 226 devices, 45 of which are current installs. This app was created in Eclipse using the Android Developer Tools, and it targets Android version 2.2 and up.
- If the user is on a call when the message is received, the message is not played.
- If the user receives a call when the message is playing, the message is stopped.
- To manually cancel playback of a message, the user changes the screen state twice using the phone’s power button.
- The application does not have to be running to receive text messages in morse code.
- If music is playing when a message is received, the music is paused during message playback and resumed after message playback is completed.
This app uses the RECEIVE_SMS, VIBRATE, and READ_PHONE_STATE permissions. RECEIVE_SMS is required to read incoming text messages, and VIBRATE is used to vibrate those messages as morse code when the phone is set to vibrate. The READ_PHONE_STATE permission is used to stop the service when the user is on a call, or when a call is received during morse code playback.
How it Works
This application is composed of three components: the main activity, a broadcast receiver, and a service. The main activity allows the user to enable the broadcast receiver and set the frequency and speed of the morse code playback. The broadcast receiver is invoked whenever a text message is received and starts the service, passing the received text message to the service. The service then converts the text message to morse code and plays or vibrates the morse code message.
The main activity allows the user to enable or disable the broadcast receiver. The broadcast receiver is registered within the AndroidManifest.xml file with the SMS_RECEIVED intent filter, and is initially disabled. A toggle button in the main activity class enables and disables the broadcast receiver using the PackageManager class. This method allows the broadcast receiver to persist even when the application is closed.
The main activity also allows the user to select the frequency and speed of morse code playback. The frequency affects the pitch of the tone affecting only audible playback. The speed affects both audio and vibratory playback, and is set in “words per minute” units. The speed is based on the word “Paris” which is the traditionally selected average word length. The frequency can be set in the range of 400Hz to 1000Hz (incremented by 50Hz), and the speed setting can be set in the range of 10 WPM to 40 WPM (incremented by 10 WPM). The ranges of these settings were restricted to eliminate a glitching affect that I will elaborate on below.
Finally, the main activity allows the user to enter a string and play that string in morse code with the selected settings. Playing the string is accomplished by starting the service and passing the string to the service, effectively bypassing the broadcast receiver.
All settings are saved using Android’s SharedPreferences class.
When a text message is received, the broadcast receiver is invoked. The broadcast receiver extracts the message body and passes it to the service, starting the service in the process. The broadcast receiver does this by creating an intent to start the service, adding the body of the text message to that intent, and finally starting the service. To ensure existing calls are not interrupted, the service is not started if the broadcast receiver detects an existing call.
The service does the bulk of the work. The service’s onCreate method is only called when the service is first started. If the service is already running when another intent is received, the onCreate method is not called again. This service’s onCreate method does the following:
- If the ringer mode is set to silent, the service is terminated.
- A manual cancelation feature is implemented by creating a temporary broadcast receiver that listens for the screen turning on or off. If the screen state is changed twice (by pushing the power button twice), the service is terminated.
- A queue of intents is instantiated that stores the intents generated from all incoming text messages to ensure no incoming messages are lost during morse code playback.
- If the ringer mode is set to normal, the audio focus of the music stream is requested to temporarily stop music while the morse code track is played, and an MorseCodeAudioTrack is instantiated with the user selected frequency and speed.
- If the ringer mode is set to vibrate a MorseCodeVibrator is instantiated with the user selected speed.
- The ringer mode is switched to silent while the service is playing messages to disable any tones that may be played when a new message arrives.
- A PhoneStateListener is instantiate that listens for a phone state change to ringing or offhook. This listener stops the morse code service if the user receives a call.
The onStart method is called each time an intent is sent to the service, and the intent is added to the message queue. If this is the initial intent that started the service, a service started flag is set to true and the getNextMessage method is called. This flag ensures that the onStart only calls getNextMessage when the service is first started. In subsequent calls, the onStart method will only add the intent to the end of the messages queue. The getNextMessage method retrieves the intent at the head of the messages queue, extracts the message body from that intent, and plays or vibrates the morse code message based on the chosen settings for frequency and speed. After finishing message playback, if there are more messages in the queue the getNextMessage method is called again. The service is terminated when the messages queue is empty.
Converting to Morse Code
Messages are converted to morse code based on the ITU-R M.1677-1 recommendation. The converted message is a string of 1’s and 0’s. The 1’s indicate one unit length of sound or vibration, and the 0’s indicate one unit length of silence. The spacing and length of signals are as follows:
- A dot is equal to one unit length of sound or vibration (“1”).
- A dash is equal to three unit lengths of sound or vibration (“111”).
- The space between the signals forming the same letter is equal to one unit length of silence (“0”).
- The space between two letters of the same word is equal to three unit lengths of silence (“000”).
- The space between two words is equal to seven unit lengths of silence (“0000000”).
For example, the word “Paris”, which is the frequently chosen average word length when determining words per minute, translates to • — — • • — • — • • • • • • in morse code. Using the rules for the spacing and length of signal, this word is converted to the following string of 1’s and 0’s: “10111011101000101110001011101000101000101010000000” (note, this string includes a space between this word and the next indicated by the seven 0’s at the end of the string. Including the space at the end, the word “Paris” translates to a morse code string that is 50 units of length long. The speed, , chosen by the user in the application settings is converted from words per minute to an individual unit of length in milliseconds via the following equation:
Vibrating the Morse Code
Once converted to a string of 1’s and 0’s, vibrating the morse code is simple. For each 1, the phone is vibrated for the calculated unit length. For each 0, the phone is not vibrated for the calculated unit length. The message is vibrated using a Vibrator object within the MorseCodeVibrator.
Playing the Morse Code
Playing the morse code track is a little more challenging. The first step is to determine the total duration of the sound to be played. This duration can be calculated as the length of the morse code string x unit length. A sine wave is constructed for the calculated total duration and stored in an array of shorts. The sine wave equation is as follows:
is the amplitude, is the angular frequency, is the user selected frequency, and is elapsed time. This results in the following sine wave:
The amplitude for this sine wave is taken as short.MAX_VALUE the morse code string value at that location (0 or 1). This silences the sine wave for every 0, resulting in a sine wave similar to that shown in the following graph:
This constructed sine wave is played via an AudioTrack object within the MorseCodeAudioTrack.
An early iteration of the app used Google’s TextToSpeech to announce the sender of the message prior to playing the message in morse code. It would extract the number of the sender and look for the contact in the contacts list. If the contact was found, the name would be announced. If the contact wasn’t found, the sender’s number would be announced. This feature was removed to simplify usage for languages other than english and to ensure compatibility with the maximum number of devices.
Initially, the user could set the frequency and duration without restriction. This resulted in an annoying glitching sound during playback. Upon further analysis, the glitching was a result of the morse code switching from a 1 to 0 when the value of the sine wave was not 0. The sound was being abruptly cut off when the sine wave was silenced. The following graph depicts this phenomenon:
To eliminate this glitching, the unit length of time and frequency must be set such that when the amplitude changes from 1 to 0, . At every multiple of the unit length of time, must equal 0. This was accomplished by limiting the user selected frequency to a range of 400Hz to 1000Hz (incremented by 50Hz), and the user selected speed to 10, 20, 30, or 40 words per minute.
I built this app specifically for my father who has had a fascination with morse code since his days in the navy. Ironically, he traded his Android phone in for an iPhone the day this app was available for him to download from the Google Play store…
For such a seemingly simple application, this project turned out to be much more involved than expected. It required thinking outside of the typical mobile application box, and taking advantage of many features that are unique to Android. The goal was to go beyond the typical morse code app available on google play at the time, which typically just converted a supplied message string to morse code within the main activity. I wanted the application to convert incoming messages even when the main activity was not running. To do this, I had to take advantage of a persistent broadcast receiver that could be enabled and disabled within the main activity. The various possible activities of the phone’s user when a message is received had to be considered. This included the following possible activities:
- The phone is set to silent.
- The phone is set to vibrate.
- The phone is set to normal mode.
- The user is on a call.
- The user receives a call during message playback.
- The user is listening to music when the message is received.
- The user wishes to manually cancel message playback.
Of particular interest is the method chosen to manually cancel playback. There was no way to directly listen for hardware button presses from the service. To indirectly listen for a hardware button press, this application’s service listens for the screen state. If the screen state changes twice, the service is cancelled. I chose twice to allow the user to turn the screen on to read the message while still listening to the morse code playback.
This was a fantastic exercise in exploring the various features available on the Android platform. It was also an eye opening experience discovering just how much information an Android app can access given the appropriate permissions. Feel free to check out the source code on GitHub.