leaked ServiceConnection android.speech.tts.TextToSpeech$Connection - android

I have a text to speech fragment. The fragment consists of button and if the button is pressed, tts speaks given text.
When I tried to run this fragment with real device, HTC HTL21 (API 16), I get "leaked ServiceConnection android.speech.tts.TextToSpeech$Connection"
Before this leak error, I can see tts stop/shutdown waring. But I can' t figure out what is wrong with my code.
Strange thing is I don't get this waring/error with AVD (Android Virtual Device) or Genymotion.
I'm very appreciated for any kind help...
Here's part of log cat:
10-15 18:23:45.524 14811-14811/jp.miyamura.hds_r I/TextToSpeech: Sucessfully bound to com.google.android.tts
10-15 18:23:45.574 14811-14811/jp.miyamura.hds_r W/TextToSpeech: stop failed: not bound to TTS engine
10-15 18:23:45.574 14811-14811/jp.miyamura.hds_r W/TextToSpeech: shutdown failed: not bound to TTS engine
10-15 18:23:45.654 14811-14811/jp.miyamura.hds_r I/TextToSpeech: Sucessfully bound to com.google.android.tts
10-15 18:23:45.654 14811-14811/jp.miyamura.hds_r W/TextToSpeech: shutdown failed: not bound to TTS engine
10-15 18:23:45.664 14811-14811/jp.miyamura.hds_r I/TextToSpeech: Sucessfully bound to com.google.android.tts
10-15 18:23:45.794 14811-14811/jp.miyamura.hds_r E/ActivityThread: Activity jp.miyamura.hds_r.MainActivity has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection#40fd08e8 that was originally bound here
10-15 18:23:45.794 14811-14811/jp.miyamura.hds_r E/ActivityThread: android.app.ServiceConnectionLeaked: Activity jp.miyamura.hds_r.MainActivity has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection#40fd08e8 that was originally bound here
And here's my speech fragment:
package jp.miyamura.hds_r;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.os.Build;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import java.util.HashMap;
import java.util.Locale;
public class SpeechFragment extends Fragment implements TextToSpeech.OnInitListener, View.OnClickListener {
private int buttonID = 0;
private TextToSpeech tts;
private String queuedText;
private String splitChar;
// Instantiate Speech fragment itself
public static SpeechFragment newInstance(String speechString, String buttonString, int buttonID, String splitChar) {
// Store data to be passed to the fragment in bundle format
Bundle args = new Bundle();
args.putString("Speech", speechString);
args.putString("ButtonTitle", buttonString);
args.putInt("ButtonID", buttonID);
args.putString("splitChar", splitChar);
// Instantiate Speech fragment and set arguments
SpeechFragment newFragment = new SpeechFragment();
newFragment.setArguments(args);
return newFragment;
}
// Declare interface to communicate with it's container Activity
public interface onSpeechFragmentListener {
void onUtteranceStatus(boolean status);
void onClickStatus(int buttonID);
}
private onSpeechFragmentListener myCallback;
#Override
#SuppressWarnings("deprecation")
public void onAttach(Activity activity) {
super.onAttach(activity);
// Check container Activity implements interface listener or not
try {
myCallback = (onSpeechFragmentListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement onSpeechFragmentListener");
}
}
#Override
public void onDetach() {
super.onDetach();
myCallback = null;
}
// Required empty public constructor
public SpeechFragment() {}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
#SuppressWarnings("deprecation")
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Instantiate TTS
if (tts == null) tts = new TextToSpeech(getActivity(), this);
// Retrieve root view of fragment
View rootView = inflater.inflate(R.layout.fragment_speech, container, false);
String button_title = "";
splitChar = "";
// Receive arguments from it's container Activity
if (getArguments() != null) {
queuedText = getArguments().getString("Speech");
button_title = getArguments().getString("ButtonTitle");
buttonID = getArguments().getInt("ButtonID");
splitChar = getArguments().getString("splitChar");
}
// Retrieve button and set onClick listener
Button button = (Button) rootView.findViewById(R.id.speech_Button);
button.setText(button_title);
button.setId(buttonID);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // ToDo: Should be Build.VERSION_CODES.MARSHMALLOW
button.setTextAppearance(android.R.style.TextAppearance_Medium);
} else {
button.setTextAppearance(getActivity(), android.R.style.TextAppearance_Medium);
}
button.setOnClickListener(this);
return rootView;
}
#Override
public void onStop() {
if(tts !=null) {
tts.stop();
}
super.onStop();
}
#Override
public void onDestroyView() {
if(tts !=null) {
tts.shutdown();
}
super.onDestroyView();
}
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS && tts != null) {
// Set TTS Locale
tts.setLanguage(Locale.getDefault());
}
}
#Override
public void onClick(View v) {
// tts must not be null
if (tts != null) {
String utteranceId = this.hashCode() + "";
// TTS Branch with Android OS Version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ttsGreater21(queuedText, utteranceId);
} else {
ttsUnder20(queuedText, utteranceId);
}
}
myCallback.onClickStatus(buttonID);
}
// Speech method before LOLLIPOP
#SuppressWarnings("deprecation")
private void ttsUnder20(String text, String utteranceId) {
HashMap<String, String> map = new HashMap<>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
// Split speech text with new line character and speak silence for each separation
String[] splitSpeech = text.split(splitChar);
for (int i = 0; i < splitSpeech.length; i++) {
if (i == 0) { // Use for the first split text to flush on audio stream
tts.speak(splitSpeech[i].trim(),TextToSpeech.QUEUE_FLUSH, map);
} else { // add the new text on previous then play the TTS
tts.speak(splitSpeech[i].trim(), TextToSpeech.QUEUE_ADD,map);
}
// Play Silence between text split
tts.playSilence(750, TextToSpeech.QUEUE_ADD, null); // ToDo Length of silence should be optimized
}
setTTSListener();
}
// Speech method for LOLLIPOP and after
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void ttsGreater21(String text, String utteranceId) {
// Split speech text with new line character and speak with silence
String[] splitSpeech = text.split(splitChar);
for (int i = 0; i < splitSpeech.length; i++) {
if (i == 0) { // Use for the first split text to flush on audio stream
tts.speak(splitSpeech[i].trim(), TextToSpeech.QUEUE_FLUSH, null, utteranceId);
} else { // add the new text on previous then play the TTS
tts.speak(splitSpeech[i].trim(), TextToSpeech.QUEUE_ADD, null, utteranceId);
}
// Play Silence between text split
tts.playSilentUtterance(750, TextToSpeech.QUEUE_ADD, null); // ToDo Length of silence should be optimized
}
setTTSListener();
}
// TTS Status listener method
private void setTTSListener(){
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String utteranceId) {
}
#Override
public void onDone(String utteranceId) {
myCallback.onUtteranceStatus(true);
}
#Override
public void onError(String utteranceId) {
myCallback.onUtteranceStatus(false);
}
});
}
}

tts stop/shutdown fail was caused by screen rotation. Once failed to stop/shutdown, a leak error was triggered. So nothing related with above fragment codes...
In my activity code, I re-created fragment on screen rotation. The reason why I re-create is that I forget to inherit onSavedinstancestate. And then fragment was lost by screen rotation.
So the countermeasure are:
#Override
public void onSaveInstanceState(#NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState); // Add this line
savedInstanceState.putString("inputWords", tv_input_word.getText().toString());
}
And
if (savedInstanceState == null) { // Add this line
// Setup Fragment
transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container1,
SpeechFragment.newInstance(speechString, buttonString, buttonID1, splitChar));
transaction.commit();
} // Add this line

Related

My app crashes when it tries to display an error message from AsyncTask when it runs in background

I am displaying an error message from an AsyncTask to my Fragment but when my app is on background the app is crashing.
the code is below:
import android.support.v4.app.FragmentManager;
public class AppUtil {
public static void showErrorDialog(FragmentManager fm, int messageResourceId) {
if (fm != null && !fm.isDestroyed()) {
InfoDialogFragment.newInstance("", messageResourceId).show(fm, "ERROR");
}
}
public static boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}
}
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class BlankFragment extends Fragment implements ILoginable,
View.OnClickListener {
private ProgressDialog pd ;
public BlankFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_blank,
container, false);
rootView.findViewById(R.id.pressBT).setOnClickListener(this);
return rootView;
}
#Override
public void starProgress(int messageId) {
if (pd == null) {
pd = new ProgressDialog(getContext(), R.string.loading);
pd.show();
}
}
#Override
public void endProgress() {
if(pd != null && isAdded()) {
pd.dismiss();
pd = null;
}
}
#Override
public void displayError(int messageId) {
if (isAdded()) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
}
}
#Override
public void loginResult(boolean success) {
}
#Override
public void onClick(View v) {
if (R.id.pressBT == v.getId()) {
new LoginAsyncTask(this).execute("x");
}
}
}
public interface ILoginable {
void starProgress(int messageId);
void endProgress();
void displayError(int messageId);
void loginResult(boolean success);
}
import android.os.AsyncTask;
import java.lang.ref.WeakReference;
public class LoginAsyncTask extends AsyncTask<String, Void, String> {
private WeakReference<ILoginable> reference;
public LoginAsyncTask(ILoginable iLoginable) {
reference = new WeakReference<>(iLoginable);
}
#Override
protected void onPreExecute() {
reference.get().starProgress(R.string.loading);
}
#Override
protected String doInBackground(String... strings) {
return "xx";
}
#Override
protected void onPostExecute(String s) {
if (reference.get() != null) {
reference.get().endProgress();
reference.get().displayError(R.string.hello_blank_fragment);
}
reference.clear();
}
}
As you can see, this is a simple behavior for a basic asyncTask. However I can not figure out why the isAdded method returns true when the app is on background and also why the app is crashing with the message:
*** Process: com.roscasend.android.testadd, PID: 2891
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2053)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2079)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:678)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:632)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:143)
at com.roscasend.android.testadd.AppUtil.showErrorDialog(AppUtil.java:17)
at com.roscasend.android.testadd.BlankFragment.displayError(BlankFragment.java:51)
at com.roscasend.android.testadd.LoginAsyncTask.onPostExecute(LoginAsyncTask.java:37)
at com.roscasend.android.testadd.LoginAsyncTask.onPostExecute(LoginAsyncTask.java:15)***
Best Regards,
Aurelian
IllegalStateException: Can not perform this action after onSaveInstanceState
The Android FragmentManager uses the savedInstanceState bundle to track which fragments are currently shown to the user. If state is saved and you try to perform a fragment transaction, the system crashes because the transaction would never be seen by the user.
Newer versions of the support library offer an isStateSaved() method on the FragmentManager that you can use to check whether or not you're in the safe window to show the dialog.
#Override
public void displayError(int messageId) {
FragmentManager fm = getFragmentManager();
if (!fm.isStateSaved()) {
AppUtil.showErrorDialog(fm, R.string.app_name);
}
}
However, this still leaves you with a problem. If your AsyncTask finishes and tries to show an error when state has already been saved, the user will never see the error.
You can have the app record that it tried to show an error, and then also perform a check when your app resumes to see if it should show the error now. We need a place to save the information that we tried to show a dialog, and it can't just be a boolean on the activity/fragment (because that would be subject to the same state saving problem). I recommend SharedPreferences to store the info.
Add this code to your fragment:
private static final String SHOW_DIALOG = "SHOW_DIALOG";
private SharedPreferences getSharedPreferences() {
return getActivity().getPreferences(Context.MODE_PRIVATE);
}
And then use this code to show the dialog:
#Override
public void displayError(int messageId) {
FragmentManager fm = getFragmentManager();
if (!fm.isStateSaved()) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
} else {
getSharedPreferences()
.edit()
.putBoolean(SHOW_DIALOG, true)
.apply();
}
}
And this code to show the dialog when the app resumes:
#Override
public void onResume() {
super.onResume();
SharedPreferences prefs = getSharedPreferences();
boolean triedToShowInBackground = prefs.getBoolean(SHOW_DIALOG, false);
if (triedToShowInBackground) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
prefs.edit().remove(SHOW_DIALOG).apply();
}
}
the fragment is always attached even your app is in background, i think that u can use isVisible(), return true if the fragment is currently visible to the user. so if the fragment is visible show dialog
If you try to show the FragmentDialog using commitAllowingStateLoss() instead of commit(), the problem get solved. To imitate the parent show method and set internal fields, we must use reflection. You can override show method in your InfoDialogFragment class as following:
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import java.lang.reflect.Field;
public class InfoDialogFragment extends DialogFragment {
#Override
public void show(FragmentManager manager, String tag) {
try {
Field mDismissedField = DialogFragment.class.getField("mDismissed");
mDismissedField.setAccessible(true);
mDismissedField.setBoolean(this, false);
Field mShownByMeField = DialogFragment.class.getField("mShownByMe");
mShownByMeField.setAccessible(true);
mShownByMeField.setBoolean(this, true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commitAllowingStateLoss();
}
// Other parts of code ...
}
Try adding this in your fragment
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser && isResumed())
onResume();
}
#Override
public void onResume() {
super.onResume();
if(!getUserVisibleHint()){
return;
}
// Populate UI
}

UtteranceProgressListener not being reliably called

I am writing an Android app to fetch the latest email from a folder and play it using TTS. I want to be able to use it whilst driving so it has to be mostly automatic. Everything so far is working fine until I attempt to capture when the TextToSpeech has finished speaking so we can move on to the next email.
Here is the complete MainActivity.java file:
package uk.co.letsdelight.emailreader;
import android.os.AsyncTask;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.util.Properties;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.MimeBodyPart;
public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {
public TextToSpeech tts;
private Bundle ttsParam = new Bundle();
public UtteranceProgressListener utListener;
private boolean isPlaying = false;
private Properties imap = new Properties();
private String textToSpeak = "";
#Override
public void onInit(int ttsStatus) {
if (ttsStatus == TextToSpeech.SUCCESS) {
utListener = new UtteranceProgressListener() {
#Override
public void onStart(String s) {
TextView status = findViewById(R.id.status);
status.setText("started reading (Listener)");
}
#Override
public void onDone(String s) {
Toast.makeText(getApplicationContext(), "Done Event Listener", Toast.LENGTH_LONG).show();
TextView status = findViewById(R.id.status);
status.setText("finished reading (Listener)");
/*ImageButton i = findViewById(R.id.playButton);
i.setImageResource(R.drawable.button_play);*/
isPlaying = false;
}
#Override
public void onStop(String s, boolean b) {
Toast.makeText(getApplicationContext(), "Stop Event Listener", Toast.LENGTH_LONG).show();
TextView status = findViewById(R.id.status);
status.setText("stopped reading (Listener)");
/*ImageButton i = findViewById(R.id.playButton);
i.setImageResource(R.drawable.button_play);*/
isPlaying = false;
}
#Override
public void onError(String s) {
Toast.makeText(getApplicationContext(), "Error Event Listener", Toast.LENGTH_LONG).show();
TextView status = findViewById(R.id.status);
status.setText("Error reading email");
ImageButton i = findViewById(R.id.playButton);
i.setImageResource(R.drawable.button_play);
isPlaying = false;
}
};
tts.setOnUtteranceProgressListener(utListener);
TextView status = findViewById(R.id.status);
status.setText("initialised");
} else {
TextView status = findViewById(R.id.status);
status.setText("failed to initialise");
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
imap.setProperty("mail.store.protocol", "imap");
imap.setProperty("mail.imaps.port", "143");
tts = new TextToSpeech(this,this);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void restartPressed(View v) {
if (isPlaying) {
tts.stop();
speak();
}
}
public void playPressed(View v) {
ImageButton i = (ImageButton) v;
if (isPlaying) {
isPlaying = false;
i.setImageResource(R.drawable.button_play);
TextView status = findViewById(R.id.status);
status.setText("");
if (tts != null) {
tts.stop();
}
} else {
isPlaying = true;
i.setImageResource(R.drawable.button_stop);
new Reader().execute();
}
}
class Reader extends AsyncTask<String, Void, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
TextView status = findViewById(R.id.status);
status.setText("fetching email");
}
#Override
protected String doInBackground(String... params) {
String toRead = "nothing to fetch";
try {
Session session = Session.getDefaultInstance(imap, null);
Store store = session.getStore();
store.connect(getText(R.string.hostname).toString(), getText(R.string.username).toString(), getText(R.string.password).toString());
Folder inbox = store.getFolder("INBOX.Articles.listen");
if (inbox.exists() && inbox.getMessageCount() > 0) {
inbox.open(Folder.READ_ONLY);
Message msg = inbox.getMessage(inbox.getMessageCount() - 6);
if (msg.getContentType().contains("multipart")) {
Multipart multiPart = (Multipart) msg.getContent();
MimeBodyPart part = (MimeBodyPart) multiPart.getBodyPart(multiPart.getCount() - 1);
toRead = part.getContent().toString();
} else {
toRead = msg.getContent().toString();
}
} else {
toRead = "The folder is empty or doesn't exist";
}
} catch (Throwable ex) {
toRead = "Error fetching email - " + ex.toString();
}
return toRead;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
String body;
TextView status = findViewById(R.id.status);
status.setText("");
try {
Document doc = Jsoup.parse(s);
body = doc.body().text();
} catch (Throwable ex) {
body = "Error parsing email - " + ex.toString();
}
status.setText("email successfully fetched");
textToSpeak = body;
if (isPlaying) {
speak();
}
}
}
private void speak() {
int maxLength = TextToSpeech.getMaxSpeechInputLength();
if (textToSpeak.length() > maxLength) {
textToSpeak = "The email text is too long! The maximum length is " + maxLength + " characters";
}
ttsParam.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "EmailReader");
tts.speak(textToSpeak, TextToSpeech.QUEUE_FLUSH, ttsParam, "EmailReader");
}
#Override
protected void onDestroy() {
if (tts != null) {
tts.stop();
tts.shutdown();
}
super.onDestroy();
}
}
The inner class Reader works fine. doInBackground fetches the email and onPostExec strips out any HTML to leave the actual text content of the email. This is passed to the speak() method which does the actual speaking and works.
The issue is with the onUtteranceProgressListener.
Sometimes the onStart(String s) method is called, sometimes it isn't! It seems to never be called the first time the email read out. Mostly it is called for subsequent calls to speak() but not always. About 1 in 5 times it fails to get called. If the listener is called the status displays 'started reading (Listener)' otherwise it shows 'email successfully fetched'.
onDone, onError and onStop are never called.
I have tried using different utteranceID and Bundle values in the tts.speak() call but this makes to difference.
When the app is started, the first status display is 'initialised' which means that the onUtteranceListener must have been set within the onInit method. The TextToSpeech object is instantiated within the activity's onCreate method.
I have looked through all the information I can find which has mostly suggested getting the utteranceID correct. What else can I try in order to get a better understanding of this problem please?
The problem is that the onDone() method (and in fact any of the progress callbacks) is run on a background thread, and therefore the Toast is not going to work, and any code that accesses your UI such as setText(...) may or may not work.
So... the methods probably are being called, but you just can't see that.
The solution to this would be to surround the code in your callbacks with runOnUiThread() like this:
#Override
public void onDone(String s) {
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), "Done Event Listener", Toast.LENGTH_LONG).show();
TextView status = findViewById(R.id.status);
status.setText("finished reading (Listener)");
/*ImageButton i = findViewById(R.id.playButton);
i.setImageResource(R.drawable.button_play);*/
isPlaying = false;
}
});
}
Note: It's probably best to initialize your TextView in onCreate() along with everything else instead of in the progress callbacks.
Also, the purpose of the utteranceID is to give each call to speak() a unique identifier that is then passed back to you as the "String s" argument in the progress callbacks.
It's a good idea to give each call to speak a new ("most recent") ID using some kind of random number generator, and then checking it in the progress callbacks.
You can see a similar question and answer regarding this here.
Side note:
Since you have a "restart" button, you should know that on APIs <23, calls to TextToSpeech.stop() will cause the onDone() in your progress listener to be called. On APIs 23+, it calls onStop() instead.
First, make sure you actually have a problem and not a race between who sets the text in what order. Use log statements to make sure it is not actually called.
Try setting queueMode to QUEUE_ADD like:
tts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, ttsParam, "EmailReader");
maybe subsequent calls are cancelling the listener's events from previous texts inputs, as QUEUE_FLUSH suggests.
Also the bundle isn't really needed there, you can set it to null.
Hope any of these helps.

when using a void in other class, get error on a null object reference

When I'm trying to use other RFID SDK to get Tag Code. And send to the server to fetch the tag information. But I only can get the RFID SDK demon working. So I just try to add HTTP request method into the demon. but I always get an error about I'm calling a null object reference. when I use
IAcitivity.IMakeHttpCall();
Inventory Activity
import com.android.volley.AuthFailureError;
import com.android.volley.NetworkError;
import com.android.volley.NoConnectionError;
import com.android.volley.ParseError;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.ServerError;
import com.android.volley.TimeoutError;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.Volley;
import com.uk.tsl.rfid.ModelBase;
import com.uk.tsl.rfid.TSLBluetoothDeviceActivity;
import com.uk.tsl.rfid.WeakHandler;
import com.uk.tsl.rfid.asciiprotocol.AsciiCommander;
import com.uk.tsl.rfid.asciiprotocol.DeviceProperties;
import com.uk.tsl.rfid.asciiprotocol.commands.FactoryDefaultsCommand;
import com.uk.tsl.rfid.asciiprotocol.enumerations.QuerySession;
import com.uk.tsl.rfid.asciiprotocol.enumerations.TriState;
import com.uk.tsl.rfid.asciiprotocol.parameters.AntennaParameters;
import com.uk.tsl.rfid.asciiprotocol.responders.LoggerResponder;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class InventoryActivity extends TSLBluetoothDeviceActivity {
// Debug control
private static final boolean D = BuildConfig.DEBUG;
private RequestQueue mVolleyQueue;
// OkHttpClient httpClient = new OkHttpClient();
// The list of results from actions
private ArrayAdapter<String> mResultsArrayAdapter;
private ListView mResultsListView;
private ArrayAdapter<String> mBarcodeResultsArrayAdapter;
private ListView mBarcodeResultsListView;
// The text view to display the RF Output Power used in RFID commands
private TextView mPowerLevelTextView;
// The seek bar used to adjust the RF Output Power for RFID commands
private SeekBar mPowerSeekBar;
// The current setting of the power level
private int mPowerLevel = AntennaParameters.MaximumCarrierPower;
// Error report
private TextView mResultTextView;
// Custom adapter for the session values to display the description rather than the toString() value
public class SessionArrayAdapter extends ArrayAdapter<QuerySession> {
private final QuerySession[] mValues;
public SessionArrayAdapter(Context context, int textViewResourceId, QuerySession[] objects) {
super(context, textViewResourceId, objects);
mValues = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView)super.getView(position, convertView, parent);
view.setText(mValues[position].getDescription());
return view;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView)super.getDropDownView(position, convertView, parent);
view.setText(mValues[position].getDescription());
return view;
}
}
// The session
private QuerySession[] mSessions = new QuerySession[] {
QuerySession.SESSION_0,
QuerySession.SESSION_1,
QuerySession.SESSION_2,
QuerySession.SESSION_3
};
// The list of sessions that can be selected
private SessionArrayAdapter mSessionArrayAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inventory);
mVolleyQueue = Volley.newRequestQueue(this);
mResultsArrayAdapter = new ArrayAdapter<String>(this,R.layout.result_item);
mBarcodeResultsArrayAdapter = new ArrayAdapter<String>(this,R.layout.result_item);
mResultTextView = (TextView)findViewById(R.id.resultTextView);
// Find and set up the results ListView
mResultsListView = (ListView) findViewById(R.id.resultListView);
mResultsListView.setAdapter(mResultsArrayAdapter);
mResultsListView.setFastScrollEnabled(true);
mBarcodeResultsListView = (ListView) findViewById(R.id.barcodeListView);
mBarcodeResultsListView.setAdapter(mBarcodeResultsArrayAdapter);
mBarcodeResultsListView.setFastScrollEnabled(true);
// Hook up the button actions
Button sButton = (Button)findViewById(R.id.扫描开始);
sButton.setOnClickListener(mScanButtonListener);
Button cButton = (Button)findViewById(R.id.清除按钮);
cButton.setOnClickListener(mClearButtonListener);
// The SeekBar provides an integer value for the antenna power
mPowerLevelTextView = (TextView)findViewById(R.id.powerTextView);
mPowerSeekBar = (SeekBar)findViewById(R.id.powerSeekBar);
mPowerSeekBar.setOnSeekBarChangeListener(mPowerSeekBarListener);
// Set the seek bar current value to maximum and to cover the range of the power settings
setPowerBarLimits();
mSessionArrayAdapter = new SessionArrayAdapter(this, android.R.layout.simple_spinner_item, mSessions);
// Find and set up the sessions spinner
Spinner spinner = (Spinner) findViewById(R.id.sessionSpinner);
mSessionArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(mSessionArrayAdapter);
spinner.setOnItemSelectedListener(mActionSelectedListener);
spinner.setSelection(0);
// Set up Fast Id check box listener
CheckBox cb = (CheckBox)findViewById(R.id.fastIdCheckBox);
cb.setOnClickListener(mFastIdCheckBoxListener);
//
// An AsciiCommander has been created by the base class
//
AsciiCommander commander = getCommander();
// Add the LoggerResponder - this simply echoes all lines received from the reader to the log
// and passes the line onto the next responder
// This is added first so that no other responder can consume received lines before they are logged.
commander.addResponder(new LoggerResponder());
// Add a synchronous responder to handle synchronous commands
commander.addSynchronousResponder();
//Create a (custom) model and configure its commander and handler
mModel = new InventoryModel();
mModel.setCommander(getCommander());
mModel.setHandler(mGenericModelHandler);
}
#Override
public synchronized void onPause() {
super.onPause();
mModel.setEnabled(false);
// Unregister to receive notifications from the AsciiCommander
LocalBroadcastManager.getInstance(this).unregisterReceiver(mCommanderMessageReceiver);
}
#Override
public synchronized void onResume() {
super.onResume();
mModel.setEnabled(true);
// Register to receive notifications from the AsciiCommander
LocalBroadcastManager.getInstance(this).registerReceiver(mCommanderMessageReceiver,
new IntentFilter(AsciiCommander.STATE_CHANGED_NOTIFICATION));
displayReaderState();
UpdateUI();
}
//----------------------------------------------------------------------------------------------
// Menu
//----------------------------------------------------------------------------------------------
private MenuItem mReconnectMenuItem;
private MenuItem mConnectMenuItem;
private MenuItem mDisconnectMenuItem;
private MenuItem mResetMenuItem;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.reader_menu, menu);
mResetMenuItem = menu.findItem(R.id.reset_reader_menu_item);
mReconnectMenuItem = menu.findItem(R.id.reconnect_reader_menu_item);
mConnectMenuItem = menu.findItem(R.id.insecure_connect_reader_menu_item);
mDisconnectMenuItem= menu.findItem(R.id.disconnect_reader_menu_item);
return true;
}
/**
* Prepare the menu options
*/
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean isConnecting = getCommander().getConnectionState() == AsciiCommander.ConnectionState.CONNECTING;
boolean isConnected = getCommander().isConnected();
mResetMenuItem.setEnabled(isConnected);
mDisconnectMenuItem.setEnabled(isConnected);
mReconnectMenuItem.setEnabled(!(isConnecting || isConnected));
mConnectMenuItem.setEnabled(!(isConnecting || isConnected));
return super.onPrepareOptionsMenu(menu);
}
/**
* Respond to menu item selections
*/
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.reconnect_reader_menu_item:
Toast.makeText(this.getApplicationContext(), "重新连接中...", Toast.LENGTH_LONG).show();
reconnectDevice();
UpdateUI();
return true;
case R.id.insecure_connect_reader_menu_item:
// Choose a device and connect to it
selectDevice();
return true;
case R.id.disconnect_reader_menu_item:
Toast.makeText(this.getApplicationContext(), "断开连接中...", Toast.LENGTH_SHORT).show();
disconnectDevice();
displayReaderState();
return true;
case R.id.reset_reader_menu_item:
resetReader();
UpdateUI();
return true;
}
return super.onOptionsItemSelected(item);
}
//
private void UpdateUI() {
//boolean isConnected = getCommander().isConnected();
//TODO: configure UI control state
}
private void scrollResultsListViewToBottom() {
mResultsListView.post(new Runnable() {
#Override
public void run() {
// Select the last row so it will scroll into view...
mResultsListView.setSelection(mResultsArrayAdapter.getCount() - 1);
}
});
}
private void scrollBarcodeListViewToBottom() {
mBarcodeResultsListView.post(new Runnable() {
#Override
public void run() {
// Select the last row so it will scroll into view...
mBarcodeResultsListView.setSelection(mBarcodeResultsArrayAdapter.getCount() - 1);
}
});
}
//----------------------------------------------------------------------------------------------
// AsciiCommander message handling
//----------------------------------------------------------------------------------------------
//
// Handle the messages broadcast from the AsciiCommander
//
private BroadcastReceiver mCommanderMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (D) { Log.d(getClass().getName(), "AsciiCommander state changed - isConnected: " + getCommander().isConnected()); }
String connectionStateMsg = intent.getStringExtra(AsciiCommander.REASON_KEY);
Toast.makeText(context, connectionStateMsg, Toast.LENGTH_SHORT).show();
displayReaderState();
if( getCommander().isConnected() )
{
// Update for any change in power limits
setPowerBarLimits();
// This may have changed the current power level setting if the new range is smaller than the old range
// so update the model's inventory command for the new power value
mModel.getCommand().setOutputPower(mPowerLevel);
mModel.resetDevice();
mModel.updateConfiguration();
}
UpdateUI();
}
};
//----------------------------------------------------------------------------------------------
// Reader reset
//----------------------------------------------------------------------------------------------
//
// Handle reset controls
//
private void resetReader() {
try {
// Reset the reader
FactoryDefaultsCommand fdCommand = FactoryDefaultsCommand.synchronousCommand();
getCommander().executeCommand(fdCommand);
String msg = "Reset " + (fdCommand.isSuccessful() ? "succeeded" : "failed");
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
UpdateUI();
} catch (Exception e) {
e.printStackTrace();
}
}
//----------------------------------------------------------------------------------------------
// Power seek bar
//----------------------------------------------------------------------------------------------
//
// Set the seek bar to cover the range of the currently connected device
// The power level is set to the new maximum power
//
private void setPowerBarLimits()
{
DeviceProperties deviceProperties = getCommander().getDeviceProperties();
mPowerSeekBar.setMax(deviceProperties.getMaximumCarrierPower() - deviceProperties.getMinimumCarrierPower());
mPowerLevel = deviceProperties.getMaximumCarrierPower();
mPowerSeekBar.setProgress(mPowerLevel - deviceProperties.getMinimumCarrierPower());
}
//
// Handle events from the power level seek bar. Update the mPowerLevel member variable for use in other actions
//
private OnSeekBarChangeListener mPowerSeekBarListener = new OnSeekBarChangeListener() {
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Nothing to do here
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
// Update the reader's setting only after the user has finished changing the value
updatePowerSetting(getCommander().getDeviceProperties().getMinimumCarrierPower() + seekBar.getProgress());
mModel.getCommand().setOutputPower(mPowerLevel);
mModel.updateConfiguration();
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
updatePowerSetting(getCommander().getDeviceProperties().getMinimumCarrierPower() + progress);
}
};
private void updatePowerSetting(int level) {
mPowerLevel = level;
mPowerLevelTextView.setText( mPowerLevel + " dBm");
}
//----------------------------------------------------------------------------------------------
// Button event handlers
//----------------------------------------------------------------------------------------------
// Scan action
private OnClickListener mScanButtonListener = new OnClickListener() {
public void onClick(View v) {
try {
mResultTextView.setText("");
// Perform a transponder scan
IMakeHttpCall();
UpdateUI();
} catch (Exception e) {
e.printStackTrace();
}
}
};
// Clear action
private OnClickListener mClearButtonListener = new OnClickListener() {
public void onClick(View v) {
try {
// Clear the list
mResultsArrayAdapter.clear();
mBarcodeResultsArrayAdapter.clear();
UpdateUI();
} catch (Exception e) {
e.printStackTrace();
}
}
};
//----------------------------------------------------------------------------------------------
// Handler for changes in session
//----------------------------------------------------------------------------------------------
private AdapterView.OnItemSelectedListener mActionSelectedListener = new AdapterView.OnItemSelectedListener()
{
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if( mModel.getCommand() != null ) {
QuerySession targetSession = (QuerySession)parent.getItemAtPosition(pos);
mModel.getCommand().setQuerySession(targetSession);
mModel.updateConfiguration();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
};
//----------------------------------------------------------------------------------------------
// Handler for changes in FastId
//----------------------------------------------------------------------------------------------
private OnClickListener mFastIdCheckBoxListener = new OnClickListener() {
public void onClick(View v) {
try {
CheckBox fastIdCheckBox = (CheckBox)v;
mModel.getCommand().setUsefastId(fastIdCheckBox.isChecked() ? TriState.YES : TriState.NO);
mModel.updateConfiguration();
UpdateUI();
} catch (Exception e) {
e.printStackTrace();
}
}
};
public void IMakeHttpCall(){
String url = "http://192.168.1.76:4300/lottem/query/toolsstockInventory/";
Uri.Builder builder = Uri.parse(url).buildUpon();
builder.appendQueryParameter("barCode", "1121212121");
JsonArrayRequest jsonObjRequest = new JsonArrayRequest(builder.toString(),
new Response.Listener<JSONArray>(){
#Override
public void onResponse(JSONArray response) {
try {
JSONObject person;
for (int i = 0; i < response.length(); i++) {
person = response.getJSONObject(i);
String name = person.getString(("toolsName"));
mResultTextView.setText(name);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
// Handle your error types accordingly.For Timeout & No connection error, you can show 'retry' button.
// For AuthFailure, you can re login with user credentials.
// For ClientError, 400 & 401, Errors happening on client side when sending api request.
// In this case you can check how client is forming the api and debug accordingly.
// For ServerError 5xx, you can do retry or handle accordingly.
if (error instanceof NetworkError) {
} else if (error instanceof ServerError) {
} else if (error instanceof AuthFailureError) {
} else if (error instanceof ParseError) {
} else if (error instanceof NoConnectionError) {
} else if (error instanceof TimeoutError) {
}
}
});
//Set a retry policy in case of SocketTimeout & ConnectionTimeout Exceptions. Volley does retry for you if you have specified the policy.
mVolleyQueue.add(jsonObjRequest);
}
}
InventoryModel
//----------------------------------------------------------------------------------------------
// Copyright (c) 2013 Technology Solutions UK Ltd. All rights reserved.
//----------------------------------------------------------------------------------------------
package com.uk.tsl.rfid.samples.inventory;
import android.util.Log;
import com.uk.tsl.rfid.ModelBase;
import com.uk.tsl.rfid.asciiprotocol.commands.BarcodeCommand;
import com.uk.tsl.rfid.asciiprotocol.commands.FactoryDefaultsCommand;
import com.uk.tsl.rfid.asciiprotocol.commands.InventoryCommand;
import com.uk.tsl.rfid.asciiprotocol.enumerations.TriState;
import com.uk.tsl.rfid.asciiprotocol.responders.IBarcodeReceivedDelegate;
import com.uk.tsl.rfid.asciiprotocol.responders.ICommandResponseLifecycleDelegate;
import com.uk.tsl.rfid.asciiprotocol.responders.ITransponderReceivedDelegate;
import com.uk.tsl.rfid.asciiprotocol.responders.TransponderData;
import com.uk.tsl.utils.HexEncoding;
import java.util.Locale;
public class InventoryModel extends ModelBase
{
// Control
private boolean mAnyTagSeen;
private boolean mEnabled;
public boolean enabled() { return mEnabled; }
private InventoryActivity IAcitivity;
public void setEnabled(boolean state)
{
boolean oldState = mEnabled;
mEnabled = state;
// Update the commander for state changes
if(oldState != state) {
if( mEnabled ) {
// Listen for transponders
getCommander().addResponder(mInventoryResponder);
// Listen for barcodes
getCommander().addResponder(mBarcodeResponder);
} else {
// Stop listening for transponders
getCommander().removeResponder(mInventoryResponder);
// Stop listening for barcodes
getCommander().removeResponder(mBarcodeResponder);
}
}
}
// The command to use as a responder to capture incoming inventory responses
private InventoryCommand mInventoryResponder;
// The command used to issue commands
private InventoryCommand mInventoryCommand;
// The command to use as a responder to capture incoming barcode responses
private BarcodeCommand mBarcodeResponder;
// The inventory command configuration
public InventoryCommand getCommand() { return mInventoryCommand; }
public InventoryModel()
{
// This is the command that will be used to perform configuration changes and inventories
mInventoryCommand = new InventoryCommand();
mInventoryCommand.setResetParameters(TriState.YES);
// Configure the type of inventory
mInventoryCommand.setIncludeTransponderRssi(TriState.YES);
mInventoryCommand.setIncludeChecksum(TriState.YES);
mInventoryCommand.setIncludePC(TriState.YES);
mInventoryCommand.setIncludeDateTime(TriState.YES);
// Use an InventoryCommand as a responder to capture all incoming inventory responses
mInventoryResponder = new InventoryCommand();
// Also capture the responses that were not from App commands
mInventoryResponder.setCaptureNonLibraryResponses(true);
// Notify when each transponder is seen
mInventoryResponder.setTransponderReceivedDelegate(new ITransponderReceivedDelegate() {
int mTagsSeen = 0;
#Override
public void transponderReceived(final TransponderData transponder, boolean moreAvailable) {
mAnyTagSeen = true;
final String tidMessage = transponder.getTidData() == null ? "" : HexEncoding.bytesToString(transponder.getTidData());
final String infoMsg = String.format(Locale.US, "\nRSSI: %d PC: %04X CRC: %04X", transponder.getRssi(), transponder.getPc(), transponder.getCrc());
IAcitivity.IMakeHttpCall();
sendMessageNotification("条形码:"+transponder.getEpc());
mTagsSeen++;
if( !moreAvailable) {
sendMessageNotification("");
Log.d("扫描次数",String.format("扫描到的次数: %s", mTagsSeen));
}
}
});
}

Heyzap Button keep disappearing

I'm trying to make ads that give "100 coins" and the button just disappear after 0,2 - 1 second.
I've got no clue why this error could appear. Has someone got an idea how to fix that?
My error
06-03 21:42:16.017: V/PTAdHeyzapBridge(27950): PTAdHeyzapBridge -- Start Session: "MyHeyzapID"
06-03 21:42:16.023: E/Heyzap(27950): Heyzap encountered a runtime exception and is now disabled. Error: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
06-03 21:42:16.023: V/PTAdHeyzapBridge(27950): PTAdHeyzapBridge -- Start Session FAILED : Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
06-03 21:42:16.023: V/PTAdHeyzapBridge(27950): Heyzap SDK Version : 8.4.1
My PTAdHeyzapBridge.java
package com.secrethq.ads;
import java.lang.ref.WeakReference;
import org.cocos2dx.lib.Cocos2dxActivity;
import com.google.android.gms.ads.AdView;
import com.heyzap.sdk.ads.HeyzapAds;
import com.heyzap.sdk.ads.InterstitialAd;
import com.heyzap.sdk.ads.VideoAd;
import com.heyzap.sdk.ads.IncentivizedAd;
import com.heyzap.sdk.ads.BannerAdView;
import com.heyzap.sdk.ads.HeyzapAds.BannerListener;
import com.heyzap.sdk.ads.HeyzapAds.BannerError;
import com.heyzap.sdk.ads.HeyzapAds.OnStatusListener;
import com.heyzap.sdk.ads.HeyzapAds.OnIncentiveResultListener;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
public class PTAdHeyzapBridge {
private static native String bannerId();
private static native String interstitialId();
private static native void interstitialDidFail();
private static native void bannerDidFail();
private static native void rewardVideoComplete();
private static final String TAG = "PTAdHeyzapBridge";
private static Cocos2dxActivity activity;
private static WeakReference<Cocos2dxActivity> s_activity;
private static BannerAdView bannerAdView;
public static void initBridge(Cocos2dxActivity activity){
Log.v(TAG, "PTAdHeyzapBridge -- INIT");
PTAdHeyzapBridge.s_activity = new WeakReference<Cocos2dxActivity>(activity);
PTAdHeyzapBridge.activity = activity;
PTAdHeyzapBridge.initBanner();
PTAdHeyzapBridge.initInterstitial();
PTAdHeyzapBridge.initVideo();
}
public static void initBanner(){
Log.v(TAG, "PTAdHeyzapBridge -- Init Banner");
PTAdHeyzapBridge.s_activity.get().runOnUiThread( new Runnable() {
public void run() {
PTAdHeyzapBridge.bannerAdView = new BannerAdView(PTAdHeyzapBridge.activity);
FrameLayout frameLayout = (FrameLayout)PTAdHeyzapBridge.activity.findViewById(android.R.id.content);
RelativeLayout layout = new RelativeLayout( PTAdHeyzapBridge.activity );
frameLayout.addView( layout );
RelativeLayout.LayoutParams adViewParams = new RelativeLayout.LayoutParams(
AdView.LayoutParams.WRAP_CONTENT,
AdView.LayoutParams.WRAP_CONTENT);
adViewParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
adViewParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
layout.addView(PTAdHeyzapBridge.bannerAdView, adViewParams);
PTAdHeyzapBridge.bannerAdView.setVisibility( View.INVISIBLE );
// Add a listener.
PTAdHeyzapBridge.bannerAdView.setBannerListener(new BannerListener() {
#Override
public void onAdClicked(BannerAdView b) {
// The ad has been clicked by the user.
}
#Override
public void onAdLoaded(BannerAdView b) {
// The ad has been loaded.
}
#Override
public void onAdError(BannerAdView b, BannerError bannerError) {
// There was an error loading the ad.
Log.v(TAG, "PTAdHeyzapBridge -- Banner onAdError : " + bannerError.getErrorMessage());
bannerDidFail();
}
});
}
});
}
public static void initInterstitial(){
Log.v(TAG, "PTAdHeyzapBridge -- Init Interstitial");
InterstitialAd.setOnStatusListener(new OnStatusListener() {
#Override
public void onShow(String tag) {
// Ad is now showing
}
#Override
public void onClick(String tag) {
// Ad was clicked on. You can expect the user to leave your application temporarily.
}
#Override
public void onHide(String tag) {
// Ad was closed. The user has returned to your application.
}
#Override
public void onFailedToShow(String tag) {
// Display was called but there was no ad to show
}
#Override
public void onAvailable(String tag) {
// An ad has been successfully fetched
}
#Override
public void onFailedToFetch(String tag) {
// No ad was able to be fetched
Log.v(TAG, "PTAdHeyzapBridge -- Interstitial onFailedToFetch : " + tag);
interstitialDidFail();
}
#Override
public void onAudioFinished() {
// TODO Auto-generated method stub
}
#Override
public void onAudioStarted() {
// TODO Auto-generated method stub
}
});
}
public static void initVideo() {
IncentivizedAd.setOnIncentiveResultListener(new OnIncentiveResultListener() {
#Override
public void onComplete(String tag) {
Log.v(TAG, "PTAdHeyzapBridge -- IncentivizedAd Complete ");
// Give the player their reward
rewardVideoComplete();
}
#Override
public void onIncomplete(String tag) {
// Don't give the player their reward, and tell them why
Log.v(TAG, "PTAdHeyzapBridge -- IncentivizedAd InComplete ");
}
});
PTAdHeyzapBridge.s_activity.get().runOnUiThread( new Runnable() {
public void run() {
// As early as possible, and after showing a rewarded video, call fetch
IncentivizedAd.fetch();
}
});
}
public static void showRewardedVideo(){
Log.v(TAG, "PTAdHeyzapBridge -- showRewardedVideo");
PTAdHeyzapBridge.s_activity.get().runOnUiThread( new Runnable() {
public void run() {
if (IncentivizedAd.isAvailable()) {
IncentivizedAd.display(PTAdHeyzapBridge.activity);
}
}
});
}
public static void startSession( String sdkKey ){
if(sdkKey != null){
Log.v(TAG, "PTAdHeyzapBridge -- Start Session: " + sdkKey);
try {
HeyzapAds.start(sdkKey, PTAdHeyzapBridge.activity);
} catch (Exception e) {
// TODO: handle exception
Log.v(TAG, "PTAdHeyzapBridge -- Start Session FAILED : " + e.getMessage());
}
Log.v(TAG, "Heyzap SDK Version : " + HeyzapAds.getVersion());
}else{
Log.v(TAG, "Start Session : null ");
}
}
public static void showFullScreen(){
Log.v(TAG, "PTAdHeyzapBridge -- showFullScreen");
PTAdHeyzapBridge.s_activity.get().runOnUiThread( new Runnable() {
public void run() {
// InterstitialAds are automatically fetched from our server
InterstitialAd.display(PTAdHeyzapBridge.activity);
}
});
}
public static void showBannerAd(){
Log.v(TAG, "PTAdHeyzapBridge -- showBannerAd");
PTAdHeyzapBridge.s_activity.get().runOnUiThread( new Runnable() {
public void run() {
if(PTAdHeyzapBridge.bannerAdView != null){
PTAdHeyzapBridge.bannerAdView.setVisibility(View.VISIBLE);
// Load the banner ad.
PTAdHeyzapBridge.bannerAdView.load();
}
}
});
}
public static void hideBannerAd(){
Log.v(TAG, "PTAdHeyzapBridge -- hideBannerAd");
PTAdHeyzapBridge.s_activity.get().runOnUiThread( new Runnable() {
public void run() {
if(PTAdHeyzapBridge.bannerAdView != null){
PTAdHeyzapBridge.bannerAdView.setVisibility(View.INVISIBLE);
}
}
});
}
public static boolean isBannerVisible() {
return (PTAdHeyzapBridge.bannerAdView.getVisibility() == View.VISIBLE);
}
public static boolean isRewardedVideoAvialable(){
return IncentivizedAd.isAvailable();
}
}
My Main activity
package com.lopeostudios.runningpanda;
import org.cocos2dx.lib.Cocos2dxActivity;
import org.cocos2dx.lib.Cocos2dxGLSurfaceView;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.widget.Toast;
import com.secrethq.store.PTStoreBridge;
import com.google.android.gms.games.GamesActivityResultCodes;
import com.lopeostudios.runningpanda.R;
import com.secrethq.ads.*;
import com.secrethq.utils.*;
import com.onesignal.OneSignal;
public class PTPlayer extends Cocos2dxActivity {
private static native void loadModelController();
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.v("----------","onActivityResult: request: " + requestCode + " result: "+ resultCode);
if(PTStoreBridge.iabHelper().handleActivityResult(requestCode, resultCode, data)){
Log.v("-----------", "handled by IABHelper");
}
else if(requestCode == PTServicesBridge.RC_SIGN_IN){
if(resultCode == RESULT_OK){
PTServicesBridge.instance().onActivityResult(requestCode, resultCode, data);
}
else if(resultCode == GamesActivityResultCodes.RESULT_SIGN_IN_FAILED){
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(this, "Google Play Services: Sign in error", duration);
toast.show();
}
else if(resultCode == GamesActivityResultCodes.RESULT_APP_MISCONFIGURED){
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(this, "Google Play Services: App misconfigured", duration);
toast.show();
}
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OneSignal.startInit(this).init();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
#Override
public void onNativeInit(){
initBridges();
}
private void initBridges(){
PTStoreBridge.initBridge( this );
PTServicesBridge.initBridge(this, getString( R.string.app_id ));
if (PTJniHelper.isAdNetworkActive("kChartboost")) {
PTAdChartboostBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kRevMob")) {
PTAdRevMobBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kAdMob") || PTJniHelper.isAdNetworkActive("kFacebook")) {
PTAdAdMobBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kAppLovin")) {
PTAdAppLovinBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kLeadBolt")) {
PTAdLeadBoltBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kVungle")) {
PTAdVungleBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kPlayhaven")) {
PTAdUpsightBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kMoPub")) {
PTAdMoPubBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kFacebook")) {
PTAdFacebookBridge.initBridge(this);
}
if (PTJniHelper.isAdNetworkActive("kHeyzap")) {
PTAdHeyzapBridge.initBridge(this);
}
}
#Override
public Cocos2dxGLSurfaceView onCreateView() {
Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this);
glSurfaceView.setEGLConfigChooser(8, 8, 8, 0, 0, 0);
return glSurfaceView;
}
static {
System.loadLibrary("player");
}
#Override
protected void onResume() {
super.onResume();
if (PTJniHelper.isAdNetworkActive("kChartboost")) {
PTAdChartboostBridge.onResume( this );
}
}
#Override
protected void onStart() {
super.onStart();
if (PTJniHelper.isAdNetworkActive("kChartboost")) {
PTAdChartboostBridge.onStart( this );
}
}
#Override
protected void onStop() {
super.onStop();
if (PTJniHelper.isAdNetworkActive("kChartboost")) {
PTAdChartboostBridge.onStop( this );
}
}
#Override
protected void onDestroy() {
super.onDestroy();
}
}
I'm an engineer at Heyzap. Our SDK will catch exceptions from third party SDKs, and shut down our SDK if we catch one (this is our last line of defense in preventing exceptions from crashing your game). In this case, we're catching this exception:
06-03 21:42:16.023: E/Heyzap(27950): Heyzap encountered a runtime exception and is now disabled. Error: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
We've previously seen this exception caused by an outdated UnityAds SDK. Are you using that network, and if so, can you try UnityAds 1.5.6? If you're not, can you tell us which networks you're using, or the Android package of your game?
Also, I see in yours logs that you're using Heyzap 8.4.1, which was released in June of last year. Can you update to our latest version?

AsyncTaskLoader onLoadFinished with a pending task and config change

I'm trying to use an AsyncTaskLoader to load data in the background to populate a detail view in response to a list item being chosen. I've gotten it mostly working but I'm still having one issue. If I choose a second item in the list and then rotate the device before the load for the first selected item has completed, then the onLoadFinished() call is reporting to the activity being stopped rather than the new activity. This works fine when choosing just a single item and then rotating.
Here is the code I'm using. Activity:
public final class DemoActivity extends Activity
implements NumberListFragment.RowTappedListener,
LoaderManager.LoaderCallbacks<String> {
private static final AtomicInteger activityCounter = new AtomicInteger(0);
private int myActivityId;
private ResultFragment resultFragment;
private Integer selectedNumber;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myActivityId = activityCounter.incrementAndGet();
Log.d("DemoActivity", "onCreate for " + myActivityId);
setContentView(R.layout.demo);
resultFragment = (ResultFragment) getFragmentManager().findFragmentById(R.id.result_fragment);
getLoaderManager().initLoader(0, null, this);
}
#Override
protected void onDestroy() {
super.onDestroy();
Log.d("DemoActivity", "onDestroy for " + myActivityId);
}
#Override
public void onRowTapped(Integer number) {
selectedNumber = number;
resultFragment.setResultText("Fetching details for item " + number + "...");
getLoaderManager().restartLoader(0, null, this);
}
#Override
public Loader<String> onCreateLoader(int id, Bundle args) {
return new ResultLoader(this, selectedNumber);
}
#Override
public void onLoadFinished(Loader<String> loader, String data) {
Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId);
resultFragment.setResultText(data);
}
#Override
public void onLoaderReset(Loader<String> loader) {
}
static final class ResultLoader extends AsyncTaskLoader<String> {
private static final Random random = new Random();
private final Integer number;
private String result;
ResultLoader(Context context, Integer number) {
super(context);
this.number = number;
}
#Override
public String loadInBackground() {
// Simulate expensive Web call
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Item " + number + " - Price: $" + random.nextInt(500) + ".00, Number in stock: " + random.nextInt(10000);
}
#Override
public void deliverResult(String data) {
if (isReset()) {
// An async query came in while the loader is stopped
return;
}
result = data;
if (isStarted()) {
super.deliverResult(data);
}
}
#Override
protected void onStartLoading() {
if (result != null) {
deliverResult(result);
}
// Only do a load if we have a source to load from
if (number != null) {
forceLoad();
}
}
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
result = null;
}
}
}
List fragment:
public final class NumberListFragment extends ListFragment {
interface RowTappedListener {
void onRowTapped(Integer number);
}
private RowTappedListener rowTappedListener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
rowTappedListener = (RowTappedListener) activity;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(getActivity(),
R.layout.simple_list_item_1,
Arrays.asList(1, 2, 3, 4, 5, 6));
setListAdapter(adapter);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
ArrayAdapter<Integer> adapter = (ArrayAdapter<Integer>) getListAdapter();
rowTappedListener.onRowTapped(adapter.getItem(position));
}
}
Result fragment:
public final class ResultFragment extends Fragment {
private TextView resultLabel;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.result_fragment, container, false);
resultLabel = (TextView) root.findViewById(R.id.result_label);
if (savedInstanceState != null) {
resultLabel.setText(savedInstanceState.getString("labelText", ""));
}
return root;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("labelText", resultLabel.getText().toString());
}
void setResultText(String resultText) {
resultLabel.setText(resultText);
}
}
I've been able to get this working using plain AsyncTasks but I'm trying to learn more about Loaders since they handle the configuration changes automatically.
EDIT: I think I may have tracked down the issue by looking at the source for LoaderManager. When initLoader is called after the configuration change, the LoaderInfo object has its mCallbacks field updated with the new activity as the implementation of LoaderCallbacks, as I would expect.
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
LoaderInfo info = mLoaders.get(id);
if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
if (info == null) {
// Loader doesn't already exist; create.
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
if (DEBUG) Log.v(TAG, " Created new loader " + info);
} else {
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
However, when there is a pending loader, the main LoaderInfo object also has an mPendingLoader field with a reference to a LoaderCallbacks as well, and this object is never updated with the new activity in the mCallbacks field. I would expect to see the code look like this instead:
// This line was already there
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
// This line is not currently there
info.mPendingLoader.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
It appears to be because of this that the pending loader calls onLoadFinished on the old activity instance. If I breakpoint in this method and make the call that I feel is missing using the debugger, everything works as I expect.
The new question is: Have I found a bug, or is this the expected behavior?
In most cases you should just ignore such reports if Activity is already destroyed.
public void onLoadFinished(Loader<String> loader, String data) {
Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId);
if (isDestroyed()) {
Log.i("DemoActivity", "Activity already destroyed, report ignored: " + data);
return;
}
resultFragment.setResultText(data);
}
Also you should insert checking isDestroyed() in any inner classes. Runnable - is the most used case.
For example:
// UI thread
final Handler handler = new Handler();
Executor someExecutorService = ... ;
someExecutorService.execute(new Runnable() {
public void run() {
// some heavy operations
...
// notification to UI thread
handler.post(new Runnable() {
// this runnable can link to 'dead' activity or any outer instance
if (isDestroyed()) {
return;
}
// we are alive
onSomeHeavyOperationFinished();
});
}
});
But in such cases the best way is to avoid passing strong reference on Activity to another thread (AsynkTask, Loader, Executor, etc).
The most reliable solution is here:
// BackgroundExecutor.java
public class BackgroundExecutor {
private static final Executor instance = Executors.newSingleThreadExecutor();
public static void execute(Runnable command) {
instance.execute(command);
}
}
// MyActivity.java
public class MyActivity extends Activity {
// Some callback method from any button you want
public void onSomeButtonClicked() {
// Show toast or progress bar if needed
// Start your heavy operation
BackgroundExecutor.execute(new SomeHeavyOperation(this));
}
public void onSomeHeavyOperationFinished() {
if (isDestroyed()) {
return;
}
// Hide progress bar, update UI
}
}
// SomeHeavyOperation.java
public class SomeHeavyOperation implements Runnable {
private final WeakReference<MyActivity> ref;
public SomeHeavyOperation(MyActivity owner) {
// Unlike inner class we do not store strong reference to Activity here
this.ref = new WeakReference<MyActivity>(owner);
}
public void run() {
// Perform your heavy operation
// ...
// Done!
// It's time to notify Activity
final MyActivity owner = ref.get();
// Already died reference
if (owner == null) return;
// Perform notification in UI thread
owner.runOnUiThread(new Runnable() {
public void run() {
owner.onSomeHeavyOperationFinished();
}
});
}
}
Maybe not best solution but ...
This code restart loader every time, which is bad but only work around that works - if you want to used loader.
Loader l = getLoaderManager().getLoader(MY_LOADER);
if (l != null) {
getLoaderManager().restartLoader(MY_LOADER, null, this);
} else {
getLoaderManager().initLoader(MY_LOADER, null, this);
}
BTW. I am using Cursorloader ...
A possible solution is to start the AsyncTask in a custom singleton object and access the onFinished() result from the singleton within your Activity. Every time you rotate your screen, go onPause() or onResume(), the latest result will be used/accessed. If you still don't have a result in your singleton object, you know it is still busy or that you can relaunch the task.
Another approach is to work with a service bus like Otto, or to work with a Service.
Ok I'm trying to understand this excuse me if I misunderstood anything, but you are losing references to something when the device rotates.
Taking a stab...
would adding
android:configChanges="orientation|keyboardHidden|screenSize"
in your manifest for that activity fix your error? or prevent onLoadFinished() from saying the activity stopped?

Categories

Resources