r4 - trunk/src/org/chorem/android/saymytexts
Author: kmorin Date: 2014-03-24 22:11:15 +0100 (Mon, 24 Mar 2014) New Revision: 4 Url: http://forge.chorem.org/projects/say-my-texts/repository/revisions/4 Log: fixes #1003 Read out loud when the phone is connected by bluetooth to a headset or car system Modified: trunk/src/org/chorem/android/saymytexts/NewTextBroadcastReceiver.java trunk/src/org/chorem/android/saymytexts/SayMyTextService.java trunk/src/org/chorem/android/saymytexts/SayMyTextsApplication.java Modified: trunk/src/org/chorem/android/saymytexts/NewTextBroadcastReceiver.java =================================================================== --- trunk/src/org/chorem/android/saymytexts/NewTextBroadcastReceiver.java 2014-03-22 11:16:53 UTC (rev 3) +++ trunk/src/org/chorem/android/saymytexts/NewTextBroadcastReceiver.java 2014-03-24 21:11:15 UTC (rev 4) @@ -1,18 +1,13 @@ package org.chorem.android.saymytexts; +import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.media.AudioManager; import android.os.Bundle; -import android.preference.PreferenceManager; import android.telephony.SmsMessage; -import java.util.ArrayList; -import java.util.List; - /** * Receives the SMSs and if the headset is plugged, start the service to say it out loud. * @@ -21,70 +16,52 @@ */ public class NewTextBroadcastReceiver extends BroadcastReceiver { - protected static final List<BluetoothDevice> BT_DEVICES = new ArrayList<>(); - @Override public void onReceive(Context context, Intent intent) { + Intent serviceIntent = new Intent(context, SayMyTextService.class); + String action = intent.getAction(); if ("android.provider.Telephony.SMS_RECEIVED".equals(action)) { - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); - String readingEnabledKey = context.getString(R.string.preference_enable_reading_key); - boolean readingEnabled = sharedPref.getBoolean(readingEnabledKey, true); + // get the SMS message passed in + Bundle bundle = intent.getExtras(); + if (bundle != null) { + SmsMessage[] msgs = null; + String messageReceived = ""; - // if the headset is plugged - AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); -// if (am.isWiredHeadsetOn() || !BT_DEVICES.isEmpty()) { - if (readingEnabled && (am.isWiredHeadsetOn() || - am.isBluetoothScoAvailableOffCall() && am.isBluetoothScoOn() - || am.isBluetoothA2dpOn())) { + // retrieve the SMS message received + Object[] pdus = (Object[]) bundle.get("pdus"); + msgs = new SmsMessage[pdus.length]; + for (int i = 0; i < msgs.length; i++) { + msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); + messageReceived += msgs[i].getDisplayMessageBody() + " "; + } - // get the SMS message passed in - Bundle bundle = intent.getExtras(); + // Get the Sender Phone Number + String senderPhoneNumber = msgs[0].getDisplayOriginatingAddress(); - if (bundle != null) { - SmsMessage[] msgs = null; - String messageReceived = ""; + // start the service to say it out loud + serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_SMS_BODY, messageReceived); + serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_SMS_SENDER, senderPhoneNumber); + context.startService(serviceIntent); + } - // retrieve the SMS message received - Object[] pdus = (Object[]) bundle.get("pdus"); - msgs = new SmsMessage[pdus.length]; - for (int i = 0; i < msgs.length; i++) { - msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); - messageReceived += msgs[i].getDisplayMessageBody() + " "; - } + } else { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - // Get the Sender Phone Number - String senderPhoneNumber = msgs[0].getDisplayOriginatingAddress(); - - if (am.isBluetoothScoAvailableOffCall()) { - am.startBluetoothSco(); - } else { - am.stopBluetoothSco(); - } - // start the service to say it out loud - Intent serviceIntent = new Intent(context, SayMyTextService.class); - serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_SMS_BODY, messageReceived); - serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_SMS_SENDER, senderPhoneNumber); + if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) { + int majorDeviceClass = device.getBluetoothClass().getMajorDeviceClass(); + if (majorDeviceClass == BluetoothClass.Device.Major.AUDIO_VIDEO) { + serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_BT_DEVICE, device); + serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_ADD_BT_DEVICE, true); context.startService(serviceIntent); } - } - } else { -// BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); -// -// if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) { -// int majorDeviceClass = device.getBluetoothClass().getMajorDeviceClass(); -// Toast.makeText(context, "ACTION_ACL_CONNECTED " + majorDeviceClass, Toast.LENGTH_LONG).show(); -// if (majorDeviceClass == BluetoothClass.Device.Major.AUDIO_VIDEO) { -// Toast.makeText(context, "device ok", Toast.LENGTH_LONG).show(); -// BT_DEVICES.add(device); -// } -// -// } else { -// Toast.makeText(context, "btHeadsetConnected false", Toast.LENGTH_LONG).show(); -// BT_DEVICES.remove(device); -// } + } else { + serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_BT_DEVICE, device); + serviceIntent.putExtra(SayMyTextService.INTENT_EXTRA_ADD_BT_DEVICE, false); + context.startService(serviceIntent); + } } } } Modified: trunk/src/org/chorem/android/saymytexts/SayMyTextService.java =================================================================== --- trunk/src/org/chorem/android/saymytexts/SayMyTextService.java 2014-03-22 11:16:53 UTC (rev 3) +++ trunk/src/org/chorem/android/saymytexts/SayMyTextService.java 2014-03-24 21:11:15 UTC (rev 4) @@ -1,22 +1,32 @@ package org.chorem.android.saymytexts; import android.app.Service; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.Cursor; +import android.media.AudioManager; import android.net.Uri; import android.os.IBinder; import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.provider.ContactsContract; import android.speech.tts.TextToSpeech; +import android.speech.tts.UtteranceProgressListener; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; import android.util.Log; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; /** * Service which say out loud the text passed in the intent @@ -26,25 +36,63 @@ */ public class SayMyTextService extends Service implements TextToSpeech.OnInitListener { - private static final String TAG = "SayMyTextActivity"; + private static final String TAG = "SayMyTextService"; + /** Body of the SMS */ public static final String INTENT_EXTRA_SMS_BODY = "smsBody"; + /** Number of the sender of the SMS */ public static final String INTENT_EXTRA_SMS_SENDER = "smsSender"; + /** Bluetooth device which has just connected or disconnected */ + public static final String INTENT_EXTRA_BT_DEVICE = "btDevice"; + /** If true, the device has just connected, else disconnected */ + public static final String INTENT_EXTRA_ADD_BT_DEVICE = "addBtDevice"; + /** utterance id when the bluetooth device is connected */ + protected static final String BT_UTTERANCE_ID = "btUtteranceId"; + + protected AudioManager audioManager; + + /** null if the texttospeech is not initialized */ + protected Boolean canSpeak = null; + protected TextToSpeech textToSpeech; - // texts to read, received before the textospeech is ready + + /** texts to read, received before the textospeech is ready or while a call is in progress */ protected List<String> awaitingTexts = new ArrayList<>(); + /** bluetooth devices which are currently connected */ + protected Map<BluetoothDevice, Integer> bluetoothDevices = new HashMap<>(); + + /** + * Listener to clal state change + */ + protected final PhoneStateListener callStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + super.onCallStateChanged(state, incomingNumber); + if (canSpeak != null) { + setCanSpeak(state == TelephonyManager.CALL_STATE_IDLE); + } + } + }; + @Override public void onCreate() { super.onCreate(); textToSpeech = new TextToSpeech(this, this); + + audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); + + TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + tm.listen(callStateListener, PhoneStateListener.LISTEN_CALL_STATE); } @Override public void onDestroy() { super.onDestroy(); + TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + tm.listen(callStateListener, PhoneStateListener.LISTEN_NONE); textToSpeech.shutdown(); } @@ -55,65 +103,169 @@ @Override public int onStartCommand(Intent intent, int flags, int startId) { - super.onStartCommand(intent, flags, startId); + int result = super.onStartCommand(intent, flags, startId); - String sms = intent.getStringExtra(INTENT_EXTRA_SMS_BODY); - String sender = intent.getStringExtra(INTENT_EXTRA_SMS_SENDER); - sender = getContactDisplayNameByNumber(sender); - String text = getString(R.string.sms_received, sender, sms); + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + String readingEnabledKey = getString(R.string.preference_enable_reading_key); + boolean readingEnabled = sharedPref.getBoolean(readingEnabledKey, true); - if (awaitingTexts != null) { - awaitingTexts.add(text); - } else { - readText(text); + if (intent != null) { + BluetoothDevice device = intent.getParcelableExtra(INTENT_EXTRA_BT_DEVICE); + // if a device is passed to the service + // add or remove the device from the connected devices + if (device != null) { + boolean addBtDevice = intent.getBooleanExtra(INTENT_EXTRA_ADD_BT_DEVICE, false); + if (addBtDevice) { + bluetoothDevices.put(device, device.getBluetoothClass().getDeviceClass()); + } else { + bluetoothDevices.remove(device); + } + + + // else if the user enabled the reading and + // if the headset is plugged, or if there is a bluetooth device connected + } else if (readingEnabled && (audioManager.isWiredHeadsetOn() || !bluetoothDevices.isEmpty())) { + + String sms = intent.getStringExtra(INTENT_EXTRA_SMS_BODY); + String sender = intent.getStringExtra(INTENT_EXTRA_SMS_SENDER); + sender = getContactDisplayNameByNumber(sender); + String text = getString(R.string.sms_received, sender, sms); + + if (canSpeak != null && canSpeak) { + requestReading(text); + } else { + awaitingTexts.add(text); + } + } + + result = START_STICKY; } - return START_STICKY; + return result; } @Override public void onInit(int status) { - Log.d(TAG, "initialized " + status); if (status == TextToSpeech.SUCCESS) { + // init texttospeech + textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() { + @Override + public void onStart(String utteranceId) { + } + + @Override + public void onError(String utteranceId) { + Log.e(TAG, "Error speaking: " + utteranceId); + } + + @Override + public void onDone(String utteranceId) { + // when the text has been read by the bluetooth device, stop the connection + audioManager.stopBluetoothSco(); + audioManager.setMode(AudioManager.MODE_NORMAL); + } + }); + + TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + setCanSpeak(tm.getCallState() == TelephonyManager.CALL_STATE_IDLE); + + } else { + setCanSpeak(null); + textToSpeech = new TextToSpeech(this, this); + } + } + + /** + * Sets if the texts can be read + * + * @param canSpeak null if the texttospeech is not ready, + * false if a call is in progress + * true otherwise + */ + protected void setCanSpeak(Boolean canSpeak) { + this.canSpeak = canSpeak; + if (canSpeak != null && canSpeak) { for (String text : awaitingTexts) { - readText(text); + requestReading(text); } - awaitingTexts = null; + awaitingTexts.clear(); + } + } + /** + * Requests the reading of one text through the wired headset or bluetooth device + * @param text the text to read + */ + protected void requestReading(String text) { + requestReading(Collections.singletonList(text)); + } + + /** + * Requests the reading of a list of texts through the wired headset or bluetooth device + * @param texts the texts to read + */ + protected void requestReading(List<String> texts) { + if (bluetoothDevices.isEmpty()) { + readText(texts, false); } else { - textToSpeech = new TextToSpeech(this, this); + requestReadingOverBt(texts); } } /** + * Starts the connection with the bluetooth device and requests the reading + * @param texts the texts to read + */ + protected void requestReadingOverBt(final List<String> texts) { + registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getExtras().getInt(AudioManager.EXTRA_SCO_AUDIO_STATE); + if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) { + context.unregisterReceiver(this); + readText(texts, true); + } + } + }, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)); + audioManager.setMode(AudioManager.MODE_IN_CALL); + audioManager.startBluetoothSco(); + } + + /** * Reads the texts out loud - * @param text the text to read + * @param texts the texts to read + * @param btConnected if true, adds the utterance id for the bluetooth device */ - protected void readText(String text) { + protected void readText(List<String> texts, boolean btConnected) { HashMap<String, String> params = new HashMap<>(); -// params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_VOICE_CALL)); + params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_VOICE_CALL)); + if (btConnected) { + params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, BT_UTTERANCE_ID); + } SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); String heisendroidModeEnabledKey = getString(R.string.preference_enable_heisendroid_mode_key); boolean heisendroidModeEnabled = sharedPref.getBoolean(heisendroidModeEnabledKey, true); - if (heisendroidModeEnabled) { - textToSpeech.setLanguage(Locale.US); - textToSpeech.setSpeechRate(0.3f); - textToSpeech.setPitch(0.1f); - textToSpeech.speak("Say my text.", TextToSpeech.QUEUE_ADD, params); - } + for (String text : texts) { + if (heisendroidModeEnabled) { + textToSpeech.setLanguage(Locale.US); + textToSpeech.setSpeechRate(0.3f); + textToSpeech.setPitch(0.1f); + textToSpeech.speak("Say my text.", TextToSpeech.QUEUE_ADD, params); + } - textToSpeech.setLanguage(Locale.getDefault()); - textToSpeech.setSpeechRate(1f); - textToSpeech.setPitch(1f); - textToSpeech.speak(text, TextToSpeech.QUEUE_ADD, params); + textToSpeech.setLanguage(Locale.getDefault()); + textToSpeech.setSpeechRate(1f); + textToSpeech.setPitch(1f); + textToSpeech.speak(text, TextToSpeech.QUEUE_ADD, params); - if (heisendroidModeEnabled) { - textToSpeech.setLanguage(Locale.US); - textToSpeech.setSpeechRate(0.3f); - textToSpeech.setPitch(0.1f); - textToSpeech.speak("You're goddamn right.", TextToSpeech.QUEUE_ADD, params); + if (heisendroidModeEnabled) { + textToSpeech.setLanguage(Locale.US); + textToSpeech.setSpeechRate(0.3f); + textToSpeech.setPitch(0.1f); + textToSpeech.speak("You're goddamn right.", TextToSpeech.QUEUE_ADD, params); + } } } Modified: trunk/src/org/chorem/android/saymytexts/SayMyTextsApplication.java =================================================================== --- trunk/src/org/chorem/android/saymytexts/SayMyTextsApplication.java 2014-03-22 11:16:53 UTC (rev 3) +++ trunk/src/org/chorem/android/saymytexts/SayMyTextsApplication.java 2014-03-24 21:11:15 UTC (rev 4) @@ -7,7 +7,7 @@ /** * @author Kevin Morin (Code Lutin) - * @since x.x + * @since 1.0 */ @ReportsCrashes(formKey = "", // will not be used mailTo = "Say-my-texts-devel@list.chorem.org",
participants (1)
-
kmorin@users.chorem.org