Android - Activity doesn't reach onSaveInstanceState - android

I have an Android application that simply saves data and displays them in a list view, very similar to the Notepad tutorial. Unfortunately my Add activity seems to have stopped working somewhere along the line. When I attempt to add data and press Confirm, it reloads the Activity (the screen flickers slightly) and any data I have entered into the fields is cleared . I have confirmed that it is not reaching onSaveInstanceState(). I was under the impression that this method was called automatically upon finish(), and like I mentioned it was working at one time. Maybe someone can spot where I have introduced an error into my code? I'll paste what I believe are the relevant parts:
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
//Verify that fields are filled out
String description = mDescriptionText.getText().toString();
String amount = mAmountText.getText().toString();
if(description.length() == 0 || amount.length() == 0) {
if(description.length() == 0) {
Context context = getApplicationContext();
CharSequence text = "A description is required";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
else {
Context context = getApplicationContext();
CharSequence text = "An amount is required";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
}
else {
/* Call this to set the result that your activity will return to its caller */
setResult(RESULT_OK);
/* Call this when your activity is done and should be closed. The ActivityResult is propagated back to
whoever launched you via onActivityResult() */
finish();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
Log.i("expEdit","onSaveInstance State Reached");
super.onSaveInstanceState(outState);
saveState();
outState.putSerializable(EZBudgetDbAdapter.KEY_ROWID, mRowId);
}
private void saveState() {
Log.i("expEdit","saveState Reached");
String description = mDescriptionText.getText().toString();
String amount = mAmountText.getText().toString();
Double dAmount = 0.0;
if(amount != "") {
dAmount = Double.valueOf(amount).doubleValue();
}
if (mRowId == null) {
long id = mExpDbHelper.createExpenditure(description, dAmount);
if (id > 0) {
mRowId = id;
}
if (mSaveDesc.isChecked()) {
// Save the description to the CommonDesc table
mCommDbHelper = new CommonDescDbAdapter(this);
mCommDbHelper.open();
mCommDbHelper.createCommonDesc(description);
}
} else {
mExpDbHelper.updateExpenditure(mRowId, description, dAmount);
if (mSaveDesc.isChecked()) {
// Save the description to the CommonDesc table
mCommDbHelper = new CommonDescDbAdapter(this);
mCommDbHelper.open();
mCommDbHelper.createCommonDesc(description);
}
}
}

onSaveInstanceState() is not called after finish(). See following onSaveInstanceState
and your code is not clean. Never duplicate code for nothing. You should use
Context context = getApplicationContext();
CharSequence text;
int duration = Toast.LENGTH_SHORT;
if(description.length() == 0) {
text = "A description is required";
} else {
text = "An amount is required";
}
Toast toast = Toast.makeText(context, text, duration);
toast.show();

Related

How to use Textview output for another class method?

I have just started using eclipse adt(android developer tool) and i am working on electricbill calculator app. The app interface looks like this: App interface
The user will input a value in previous consumption and current consumption then calculate(1st button) to get the total consumption. Then enter the rate and compute(2nd button, multiplies total consumption and rate) to get the electricbill.
My main activity contains onClickListeners for the buttons. The first part of the calculation works, but when i try to input a value and rate and compute it, the application crashes and i dont know where is the problem. Here is my code:
Calculate Button
public class MainActivity extends Activity {
EditText PrevConEdt, CurrConEdt, RateEdt;
TextView ConsumptionTv, ElectricBillTv;
Button CalculateBtn, ComputeBtn, ClearBtn, ClearBtn2;
Double rateDbl, cbillDbl, consumptionDbl, prevDbl, currentDbl;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PrevConEdt = (EditText) findViewById(R.id.txtPreviousConsumption);
CurrConEdt = (EditText) findViewById(R.id.txtCurrentConsumption);
RateEdt = (EditText) findViewById(R.id.txtEnterRate);
ConsumptionTv = (TextView) findViewById(R.id.txtConsumption);
ElectricBillTv = (TextView) findViewById(R.id.txtEbill);
CalculateBtn = (Button) findViewById(R.id.btnCalculate);
CalculateBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View Calculate) {
String prev, current, consumption;
prev = PrevConEdt.getText().toString();
current = CurrConEdt.getText().toString();
if(PrevConEdt.length() == 0){
Toast.makeText(MainActivity.this, "Consumption Required", Toast.LENGTH_LONG).show();
return;
}else if(CurrConEdt.length() == 0){
Toast.makeText(MainActivity.this, "Consumption Required", Toast.LENGTH_LONG).show();
return;
}else{
Double prevDbl, currentDbl, consumptionDbl;
prevDbl = Double.parseDouble(prev);
currentDbl = Double.parseDouble(current);
if(currentDbl < prevDbl){
//String msg = "Current should be greater than previous.";
//int duration = Toast.LENGTH_LONG;
//Toast.makeText(getBaseContext(), msg, duration).show();
//PrevConEdt.requestFocus();
CurrConEdt.setError("Current should be greater than previous.");
return;
}else{
consumptionDbl = currentDbl - prevDbl;
String consumptionStr = String.format("%.2f kWh", consumptionDbl);
ConsumptionTv.setText(consumptionStr);
}
}
}
});
Compute Button
ComputeBtn = (Button) findViewById(R.id.btnCompute);
ComputeBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View compute) {
String rate, consumption;
rate = RateEdt.getText().toString();
consumption = ConsumptionTv.getText().toString();
if(RateEdt.length() == 0){
RateEdt.setError("Rate is required.");
return;
}else{
Double rateDbl, cbillDbl, consumptionDbl;
rateDbl = Double.parseDouble(rate);
consumptionDbl = Double.parseDouble(consumption);
cbillDbl = consumptionDbl * rateDbl;
String billStr = String.format("Php %.2f", cbillDbl);
ElectricBillTv.setText(billStr);
}
}
});

Why is the android thread starting on its own

I have a written a receiver for a NEW_OUTGOING_CALL intent (static receiver). In order not to hold the system, I do the lengthy part of the process in a AsyncTask.
Based on the number dialed, I may or may not start the AsyncTask (and proceed with regular processing). However, the tasks starts on its own, with the right param passed, and I cant figure out how !!
I've grep'ed the project, and there are no other calls to LongOperation other than the one in the CallOneShot function - but the traces surrounding the 'new' statement do not appear.
How can this happen ?
Please find the code attached, sorry for the length, I've tried to cut it down a bit
Thanks for the help
J.
package com.iper.phoneeco;
public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "XXBroadcastReceiver";
FileWriter fDevLog;
MyPrefs myprefs=null;
public final static String EXTRA_MESSAGE = "com.iper.phoneeco.msg1";
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equalsIgnoreCase("android.intent.action.NEW_OUTGOING_CALL"))
{
Log.d(TAG,"OUTGOING CALL RECEIVED");
String phoneNumber = getResultData();
if (phoneNumber == null) {
// No reformatted number, use the original
phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
}
Log.d(TAG,"phone number:"+phoneNumber);
if (IsToProcess(phoneNumber)) {
Log.d (TAG,"Trapping the call");
// Lets Roll
CallOneShot(phoneNumber);
// and prevent other apps from calling as well
setResultData(null);
// abortBroadcast();
}
else {
Log.d (TAG,"Standard processing");
Toast.makeText(context, "standard processing" , Toast.LENGTH_LONG).show();
}
Log.d (TAG,"Finished processing intent");
}
//
// check is number against a list of exceptions, that we dont handle
//
private boolean IsToProcess(String num){
String[] excluded = {"15","17","18","112","911","991","08.*","^\\*.*","^#.*"};
for (String ex : excluded){
Log.d(TAG,"Exclusion test: "+ex + "versus: "+num);
if (num.matches(ex)) {
Log.d(TAG,"Exclusion FOUND: "+ex);
return false;
}
}
if (num.length() < myprefs.minLen) {
Log.d(TAG,"Exclusion FOUND: Numero trop court");
return false;
}
Log.d(TAG,"Exclusion not found: ");
return true;
}
//
// Displays a toast
//
void MyToast(String s, int col, int dur ) {
Toast toast=Toast.makeText(myprefs.ctx, s, dur);
toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0);
toast.getView().setBackgroundColor(col );
LinearLayout toastLayout = (LinearLayout) toast.getView();
TextView toastTV = (TextView) toastLayout.getChildAt(0);
toastTV.setTextSize(20);
toast.show();
}
void MyToast(String s, int col) {
MyToast(s,col,Toast.LENGTH_LONG);
}
public void CallOneShot(String phoneNumber) {
Log.d (TAG,"CallOneShot");
MyToast (myprefs.ctx.getResources().getString(R.string.callbackipg)+" "+phoneNumber,Color.BLUE);
new LongOperation().execute(phoneNumber);
}
//
// the meat....
//
public class LongOperation extends AsyncTask<String, Void, String> {
String numToCall;
#Override
protected String doInBackground(String... params) {
int bytesRead;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
String msgres;
String response;
Log.d(TAG, "Clientthread started");
numToCall=params[0];
Log.d(TAG, "numTocall"+numToCall);
// and add to the call log
ContentValues values = new ContentValues();
values.put(CallLog.Calls.NUMBER, numToCall);
values.put(CallLog.Calls.DATE, System.currentTimeMillis());
values.put(CallLog.Calls.DURATION, 0);
values.put(CallLog.Calls.TYPE, CallLog.Calls.OUTGOING_TYPE);
values.put(CallLog.Calls.NEW, 1);
values.put(CallLog.Calls.CACHED_NAME, "");
values.put(CallLog.Calls.CACHED_NUMBER_TYPE, 0);
values.put(CallLog.Calls.CACHED_NUMBER_LABEL, "");
Log.d(TAG, "Inserting call log placeholder for " + numToCall);
ContentResolver resolver = myprefs.ctx.getContentResolver();
resolver.insert(CallLog.Calls.CONTENT_URI, values);
response=myprefs.ctx.getResources().getString(R.string.errundef);
return response;
}
protected void onPostExecute (String s) {
if (!s.equals("ok")) {
Log.d(TAG,"OnPostExecute - failed: "+s);
MyToast (myprefs.ctx.getResources().getString(R.string.errcallback)+"\n"+s,Color.RED);
}
}
}
}
Have you assign value to myprefs. It seems that you have initialized it to null and never assign it to any value
ok - stupid me is the answer - I had changed the name of the package, and an old version of the package was still on the emulator, trapping the intent ! once I removed it, it all went back to normal...
Many thanks for your help anyway

Advice needed with edit text fields

Hey guys I have an Activity with some EditText fields.
The user needs to fill up all the text fields before pressing the save Button.
Here is what I thought would work. I checked the texts entered in the EditText fields, if the text is not null, then the respective data is saved in
the data base, but if the text is null, a Toast is displayed displaying a message to re enter the text. here's the code:
account_Number.setText(null);
customer_Number.setText(null);
account_Holder.setText(null);
bank_Name.setText(null);
branch_Name.setText(null);
branch_Address.setText(null);
Ifsc.setText(null);
Micr.setText(null);
current_Balance.setText(null);
AddAccount = (Button) findViewById(R.id.addAccount);
AddAccount.setOnClickListener(this);
}
// only after all the fields have been filled, should the message data added
// successfully be displayed.
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.addAccount:// try making the user enter in all the fields
boolean work = true;
if (account_Number.getText() != null
&& customer_Number.getText() != null
&& account_Holder.getText() != null
&& bank_Name.getText() != null
&& branch_Name.getText() != null
&& branch_Address.getText() != null
&& Ifsc.getText() != null && Micr.getText() != null
&& current_Balance.getText() != null) {
try {
String accountNumber = account_Number.getText().toString();
String customerNumber = customer_Number.getText()
.toString();
String accountHolder = account_Holder.getText().toString();
String bankName = bank_Name.getText().toString();
String branchName = branch_Name.getText().toString();
String branchAddress = branch_Address.getText().toString();
String ifsc = Ifsc.getText().toString();
String micr = Micr.getText().toString();
String currentBalance = current_Balance.getText()
.toString();
DatabaseClass entry = new DatabaseClass(AddnewAccount.this);
entry.open();
entry.createEntry(accountNumber, customerNumber,
accountHolder, bankName, branchName, branchAddress,
ifsc, micr, currentBalance);
} catch (Exception e) {
work = false;
Dialog message = new Dialog(this);
message.setTitle("Error");
String error = e.toString();
TextView tv = new TextView(this);
tv.setText(error);
message.setContentView(tv);
message.show();
} finally {
Toast message = Toast.makeText(AddnewAccount.this,
"Data Added Successfully", Toast.LENGTH_SHORT);
message.show();
Intent goBackHome = new Intent(AddnewAccount.this,
AccountManagerActivity.class);
startActivity(goBackHome);
finish();
}
} else {
Toast message = Toast.makeText(AddnewAccount.this, // not getting executed
"Please Fill All The Fields", Toast.LENGTH_LONG);
message.show();
Intent refill = new Intent(AddnewAccount.this,
AddnewAccount.class);
startActivity(refill);
finish();
}
break;
}
}
A blank EditText field does not return null for getText(), but rather returns "".
Just check if .getText().toString().trim().equals("") for each field.
check like,,,
String a_number = account_Number.getText().tostring().trim();
String c_number = customer_Number.getText().tostring().trim();
if(TextUtils.isEmpty(a_number) || TextUtils.isEmpty(c_number)){
Toast.makeText(AddnewAccount.this,
"Please Fill All The Fields", Toast.LENGTH_LONG).show();
}else{
//Nothing is empty...
}

How to avoid a Toast if there's one Toast already being shown

I have several SeekBar and onSeekBarProgressStop(), I want to show a Toast message.
But if on SeekBar I perform the action rapidly then UI thread somehow blocks and Toast message waits till UI thread is free.
Now my concern is to avoid the new Toast message if the Toast message is already displaying. Or is their any condition by which we check that UI thread is currently free then I'll show the Toast message.
I tried it in both way, by using runOnUIThread() and also creating new Handler.
I've tried a variety of things to do this. At first I tried using the cancel(), which had no effect for me (see also this answer).
With setDuration(n) I wasn't coming to anywhere either. It turned out by logging getDuration() that it carries a value of 0 (if makeText()'s parameter was Toast.LENGTH_SHORT) or 1 (if makeText()'s parameter was Toast.LENGTH_LONG).
Finally I tried to check if the toast's view isShown(). Of course it isn't if no toast is shown, but even more, it returns a fatal error in this case. So I needed to try and catch the error.
Now, isShown() returns true if a toast is displayed.
Utilizing isShown() I came up with the method:
/**
* <strong>public void showAToast (String st)</strong></br>
* this little method displays a toast on the screen.</br>
* it checks if a toast is currently visible</br>
* if so </br>
* ... it "sets" the new text</br>
* else</br>
* ... it "makes" the new text</br>
* and "shows" either or
* #param st the string to be toasted
*/
public void showAToast (String st){ //"Toast toast" is declared in the class
try{ toast.getView().isShown(); // true if visible
toast.setText(st);
} catch (Exception e) { // invisible if exception
toast = Toast.makeText(theContext, st, toastDuration);
}
toast.show(); //finally display it
}
The following is an alternative solution to the most popular answer, without the try/catch.
public void showAToast (String message){
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
mToast.show();
}
A clean solution that works out of the box. Define this on your Activity:
private Toast toast;
/**
* Use this to prevent multiple Toasts from spamming the UI for a long time.
*/
public void showToast(CharSequence text, int duration)
{
if (toast == null)
toast = Toast.makeText(this, text, duration);
else
toast.setText(text);
toast.show();
}
public void showToast(int resId, int duration)
{
showToast(getResources().getText(resId), duration);
}
My solution is:
public class Utils {
public static Toast showToast(Context context, Toast toast, String str) {
if (toast != null)
toast.cancel();
Toast t = Toast.makeText(context, str, Toast.LENGTH_SHORT);
t.show();
return t;
}
}
and the caller should have a Toast member for this method's parameter, or
class EasyToast {
Toast toast;
Context context;
public EasyToast(Context context) {
this.context = context;
}
public Toast show(String str) {
if (toast != null)
toast.cancel();
Toast t = Toast.makeText(context, str, Toast.LENGTH_SHORT);
t.show();
return t;
}
}
have a helper class like this.
keep track of the last time you showed the toast, and make re-showing it a no-op if it falls within some interval.
public class RepeatSafeToast {
private static final int DURATION = 4000;
private static final Map<Object, Long> lastShown = new HashMap<Object, Long>();
private static boolean isRecent(Object obj) {
Long last = lastShown.get(obj);
if (last == null) {
return false;
}
long now = System.currentTimeMillis();
if (last + DURATION < now) {
return false;
}
return true;
}
public static synchronized void show(Context context, int resId) {
if (isRecent(resId)) {
return;
}
Toast.makeText(context, resId, Toast.LENGTH_LONG).show();
lastShown.put(resId, System.currentTimeMillis());
}
public static synchronized void show(Context context, String msg) {
if (isRecent(msg)) {
return;
}
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
lastShown.put(msg, System.currentTimeMillis());
}
}
and then,
RepeatSafeToast.show(this, "Hello, toast.");
RepeatSafeToast.show(this, "Hello, toast."); // won't be shown
RepeatSafeToast.show(this, "Hello, toast."); // won't be shown
RepeatSafeToast.show(this, "Hello, toast."); // won't be shown
this isn't perfect, since the length of LENGTH_SHORT and LENGTH_LONG are undefined, but it works well in practice. it has the advantage over other solutions that you don't need to hold on to the Toast object and the call syntax remains terse.
The enhanced function from above thread, which will show toast only if not visible with same text message:
public void showSingleToast(){
try{
if(!toast.getView().isShown()) {
toast.show();
}
} catch (Exception exception) {
exception.printStackTrace();
Log.d(TAG,"Toast Exception is "+exception.getLocalizedMessage());
toast = Toast.makeText(this.getActivity(), getContext().getString(R.string.no_search_result_fou`enter code here`nd), Toast.LENGTH_SHORT);
toast.show();
}
}
A combined solution
For my case, I needed to cancel the current toast if it shown and display another one.
This was to solve the scenario when the user asks for a service while it is still loading or not available I need to show a toast (might me different if the requested service is different). Otherwise, the toasts will keep showing in order and it will take a very long time to hide them automatically.
So basically I save the instance of the toast am creating and the following code is how to cancel it safly
synchronized public void cancel() {
if(toast == null) {
Log.d(TAG, "cancel: toast is null (occurs first time only)" );
return;
}
final View view = toast.getView();
if(view == null){
Log.d(TAG, "cancel: view is null");
return;
}
if (view.isShown()) {
toast.cancel();
}else{
Log.d(TAG, "cancel: view is already dismissed");
}
}
And to use it I can now not worry about cancelling as in:
if (toastSingleton != null ) {
toastSingleton.cancel();
toastSingleton.showToast(messageText);
}else{
Log.e(TAG, "setMessageText: toastSingleton is null");
}
The showToast is up to you how to implement it as I needed a custom look for my toast.
Good for stopping stacking e.g. click driven toast. Based off #Addi's answer.
public Toast toast = null;
//....
public void favsDisplay(MenuItem item)
{
if(toast == null) // first time around
{
Context context = getApplicationContext();
CharSequence text = "Some text...";
int duration = Toast.LENGTH_SHORT;
toast = Toast.makeText(context, text, duration);
}
try
{
if(toast.getView().isShown() == false) // if false not showing anymore, then show it
toast.show();
}
catch (Exception e)
{}
}
Check for showing toast message on screen either it is displayed or not.
For Showing a toast message Make a separate class. And use the method of this class which display the toast message after checking the visibility of the toast message. Use This Snippet of code:
public class AppToast {
private static Toast toast;
public static void showToast(Context context, String message) {
try {
if (!toast.getView().isShown()) {
toast=Toast.makeText(context, message, Toast.LENGTH_SHORT);
toast.show();
}
} catch (Exception ex) {
toast=Toast.makeText(context,message,Toast.LENGTH_SHORT);
toast.show();
}
}
}
I hope this solution will help you.
Thanks
added timer to remove the toast after 2 seconds.
private Toast toast;
public void showToast(String text){
try {
toast.getView().isShown();
toast.setText(text);
}catch (Exception e){
toast = Toast.makeText(mContext, text, Toast.LENGTH_SHORT);
}
if(toast.getView().isShown()){
new Timer().schedule(new TimerTask() {
#Override
public void run() {
toast.cancel();
}
}, 2000);
}else{
toast.show();
}
}
showToast("Please wait");
Solution for Android 11+ (API level 30 and above)
Toast.getView() is deprecated since API level 30:
This method was deprecated in API level 30. Custom toast views are
deprecated. Apps can create a standard text toast with the
makeText(android.content.Context, java.lang.CharSequence, int) method,
or use a Snackbar when in the foreground. Starting from Android
Build.VERSION_CODES#R, apps targeting API level Build.VERSION_CODES#R
or higher that are in the background will not have custom toast views
displayed.
If you want to avoid Toast overlapping, you could save the time the last Toast was shown using System.currentTimeMillis().
Here's a an example of use case where Toast is instantly overlapped only if the text of the new one is different from the last one, otherwise, it waits a certain amount of time before overlapping it (i.e SAME_TOAST_DURATION_BEFORE_OVERLAP):
public class SingleToast {
private static Toast _toast;
private static String _text;
private static long _lastToast;
private static final int SAME_TOAST_DURATION_BEFORE_OVERLAP = 2000; // in ms
public static void show(Context context, String text, int duration) {
if (_toast == null) {
_toast = Toast.makeText(context.getApplicationContext(), text, duration);
_text = text;
} else {
if (_text.equals(text)) {
if (System.currentTimeMillis() - _lastToast > SAME_TOAST_DURATION_BEFORE_OVERLAP) {
_toast.cancel();
} else {
return;
}
} else {
_text = text;
_toast.cancel();
_toast.setText(_text);
}
}
_lastToast = System.currentTimeMillis();
_toast.show();
}
}

How to pause android.speech.tts.TextToSpeech?

I'm playing text with android TTS - android.speech.tts.TextToSpeech
I use: TextToSpeech.speak to speak and .stop to stop. Is there a way to pause the text also?
The TTS SDK doesn't have any pause functionality that I know of. But you could use synthesizeToFile() to create an audio file that contains the TTS output. Then, you would use a MediaPlayer object to play, pause, and stop playing the file. Depending on how long the text string is, it might take a little longer for audio to be produced because the synthesizeToFile() function would have to complete the entire file before you could play it, but this delay should be acceptable for most applications.
I used splitting of string and used playsilence() like below:
public void speakSpeech(String speech) {
HashMap<String, String> myHash = new HashMap<String, String>();
myHash.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "done");
String[] splitspeech = speech.split("\\.");
for (int i = 0; i < splitspeech.length; i++) {
if (i == 0) { // Use for the first splited text to flush on audio stream
textToSpeech.speak(splitspeech[i].toString().trim(),TextToSpeech.QUEUE_FLUSH, myHash);
} else { // add the new test on previous then play the TTS
textToSpeech.speak(splitspeech[i].toString().trim(), TextToSpeech.QUEUE_ADD,myHash);
}
textToSpeech.playSilence(750, TextToSpeech.QUEUE_ADD, null);
}
}
You can make the TTS pause between sentences, or anywhere you want by adding up to three periods (".") all followed by a single space " ". The example below has a long pause at the beginning, and again before the message body. I'm not sure that is what you are after though.
private final BroadcastReceiver SMScatcher = new BroadcastReceiver() {
#Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(
"android.provider.Telephony.SMS_RECEIVED")) {
// if(message starts with SMStretcher recognize BYTE)
StringBuilder sb = new StringBuilder();
/*
* The SMS-Messages are 'hiding' within the extras of the
* Intent.
*/
Bundle bundle = intent.getExtras();
if (bundle != null) {
/* Get all messages contained in the Intent */
Object[] pdusObj = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[pdusObj.length];
for (int i = 0; i < pdusObj.length; i++) {
messages[i] = SmsMessage
.createFromPdu((byte[]) pdusObj[i]);
}
/* Feed the StringBuilder with all Messages found. */
for (SmsMessage currentMessage : messages) {
// periods are to pause
sb.append("... Message From: ");
/* Sender-Number */
sb.append(currentMessage.getDisplayOriginatingAddress());
sb.append(".. ");
/* Actual Message-Content */
sb.append(currentMessage.getDisplayMessageBody());
}
// Toast.makeText(application, sb.toString(),
// Toast.LENGTH_LONG).show();
if (mTtsReady) {
try {
mTts.speak(sb.toString(), TextToSpeech.QUEUE_ADD,
null);
} catch (Exception e) {
Toast.makeText(application, "TTS Not ready",
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
}
}
}
};
If you omit the space after the last period it will (or may) not work as expected.
In the absence of a pause option, you can add silence for the duration of when you want to delay the TTS Engine speaking. This of course would have to be a predetermined 'pause' and wouldn't help to include functionality of a pause button, for example.
For API < 21 : public int playSilence (long durationInMs, int queueMode, HashMap params)
For > 21 : public int playSilentUtterance (long durationInMs, int queueMode, String utteranceId)
Remember to use TextToSpeech.QUEUE_ADD rather than TextToSpeech.QUEUE_FLUSH otherwise it will clear the previously started speech.
I used a different approach.
Seperate your text into sentences
Speak every sentence one by one and keep track of the spoken sentence
pause will stop the text instantly
resume will start at the beginning of the last spoken sentence
Kotlin code:
class VoiceService {
private lateinit var textToSpeech: TextToSpeech
var sentenceCounter: Int = 0
var myList: List<String> = ArrayList()
fun resume() {
sentenceCounter -= 1
speakText()
}
fun pause() {
textToSpeech.stop()
}
fun stop() {
sentenceCounter = 0
textToSpeech.stop()
}
fun speakText() {
var myText = "This is some text to speak. This is more text to speak."
myList =myText.split(".")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
textToSpeech.speak(myList[sentenceCounter], TextToSpeech.QUEUE_FLUSH, null, utteranceId)
sentenceCounter++
} else {
var map: HashMap<String, String> = LinkedHashMap<String, String>()
map[TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID] = utteranceId
textToSpeech.speak(myList[sentenceCounter], TextToSpeech.QUEUE_FLUSH, map)
sentenceCounter++
}
}
override fun onDone(p0: String?) {
if (sentenceCounter < myList.size) {
speakText()
} else {
speakNextText()
}
}
}
I haven't yet tried this, but I need to do the same thing. My thinking is to first split your speech text into an array of words.
Then create a recursive function that plays the next word after the current word is finished, while keeping a counter of the current word.
divide the messages into parts and listen for last utterance by using onutteranceprogress listener
tts.playSilence(1250, TextToSpeech.QUEUE_ADD, null);
It seems that if you put a period after a word AND start the next word with a capital letter, just like a new sentence, like this:
after we came home. We ate dinner.
the "home. We" will then have a pause in it.
This becomes a grammatically strange way of writing it.
So far I have only tested this in my own language, Swedish.
It might be important that the space is there.
Also, an escaped quote (\") seems to have it pause somewhat as well - at least, if you put it around a word it adds space around the word.
This solution is not perfect, but an alternative to #Aaron C's solution may be to create a custom text to speech class like the below. This solution may work well enough if your text is relatively short and spoken words per minute is accurate enough for the language you are using.
private class CustomTextToSpeech extends TextToSpeech {
private static final double WORDS_PER_MS = (double)190/60/1000;
long startTimestamp = 0;
long pauseTimestamp = 0;
private Handler handler;
private Runnable speakRunnable;
StringBuilder textToSpeechBuilder;
private boolean isPaused = false;
public CustomTextToSpeech(Context context, OnInitListener initListener){
super(context, initListener);
setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onDone(String arg0) {
Log.d(TAG, "tts done. " + arg0);
startTimestamp = 0;
pauseTimestamp = 0;
handler.postDelayed(speakRunnable, TTS_INTERVAL_MS);
}
#Override
public void onError(String arg0) {
Log.e(TAG, "tts error. " + arg0);
}
#Override
public void onStart(String arg0) {
Log.d(TAG, "tts start. " + arg0);
setStartTimestamp(System.currentTimeMillis());
}
});
handler = new Handler();
speakRunnable = new Runnable() {
#Override
public void run() {
speak();
}
};
textToSpeechBuilder = new StringBuilder(getResources().getString(R.string.talkback_tips));
}
public void setStartTimestamp(long timestamp) {
startTimestamp = timestamp;
}
public void setPauseTimestamp(long timestamp) {
pauseTimestamp = timestamp;
}
public boolean isPaused(){
return (startTimestamp > 0 && pauseTimestamp > 0);
}
public void resume(){
if(handler != null && isPaused){
if(startTimestamp > 0 && pauseTimestamp > 0){
handler.postDelayed(speakRunnable, TTS_SETUP_TIME_MS);
} else {
handler.postDelayed(speakRunnable, TTS_INTERVAL_MS);
}
}
isPaused = false;
}
public void pause(){
isPaused = true;
if (handler != null) {
handler.removeCallbacks(speakRunnable);
handler.removeMessages(1);
}
if(isSpeaking()){
setPauseTimestamp(System.currentTimeMillis());
}
stop();
}
public void utter(){
if(handler != null){
handler.postDelayed(speakRunnable, TTS_INTERVAL_MS);
}
}
public void speak(){
Log.d(TAG, "textToSpeechBuilder: " + textToSpeechBuilder.toString());
if(isPaused()){
String[] words = textToSpeechBuilder.toString().split(" ");
int wordsAlreadySpoken = (int)Math.round((pauseTimestamp - startTimestamp)*WORDS_PER_MS);
words = Arrays.copyOfRange(words, wordsAlreadySpoken-1, words.length);
textToSpeechBuilder = new StringBuilder();
for(String s : words){
textToSpeechBuilder.append(s);
textToSpeechBuilder.append(" ");
}
} else {
textToSpeechBuilder = new StringBuilder(getResources().getString(R.string.talkback_tips));
}
if (tts != null && languageAvailable)
speak(textToSpeechBuilder.toString(), TextToSpeech.QUEUE_FLUSH, new Bundle(), "utter");
}
}

Categories

Resources