I have an interesting issue that I have been trying to fix for over a week. Its on Android and involves a service running in the background to send a message on an app.
It is quite complex so I'll list the stages below:
1 - User enters message
2 - User selects 'send' button which launches the apps main service (ComService/START_STICKY) and activity (HomeScreen) hides the EditText box used for the message, replacing it with a TextView with the words 'Sending'
3 - Service spawns worker thread
4 - Service gets entered text off of activity and connects to server
5 - Service sends the message and then gets result from server
6 - Service disconnects from server
7 - Service updates activity to show sent message and shows the edit text
box again, as well as hides the TextView with the 'Sending' word
The issue is with stage 7. It uses a handler and message to communicate with the activity, as well as a separate class which holds the state of all of the activities in the app (to check if the UI is ok to update), but I commented this out from the code and the issue still exists so it is not this. The current set up works completely fine when the debugger is attached without any issues (why there is no logcat) and on the odd occasion when closing the app down and starting it up again. The problem begins when the debugger is detached and the app closed (via recent apps) for over around 5 seconds. The service completed its job by sending the message, as the message is added to the database and the user on the other end gets it, it is only the updating of the UI that is a problem, everything else seems to work fine!
Before sending the message, the service also connects to the server if there are any unread messages to indicate to other users that the user just read it. It follows very similar steps as above but was commented out and the issue still stays the same.
Ill post the code for the relevant steps below:
Stage 2
// Starts service to communicate with the server to send a message
Intent service = new Intent(this, ComService.class);
service.putExtra(ComService.requestType, ComService.sendTextMessage);
startService(service);
Stage 3
// Run when the service is being created
#Override
public int onStartCommand(Intent newIntent, int flags, int startId)
{
intent = newIntent;
currentInstance = this;
// Launches processing thread
ServiceHelper serviceHelper = new ServiceHelper(newIntent, this);
serviceHelper.start();
return Service.START_STICKY;
}
Stage 4/5/6/7
// Sends a message with only text content
public void startText()
{
// Initialises the class holding activity data
StateManager sm = new StateManager(context);
// Gets the data ready to be sent
if(sm.getHomeScreen())
{
// Friend id and time
friendId = HomeScreen.getFriendId();
// Gets the message text
messageTextContent = HomeScreen.getTextMessage(); // STAGE 4
}
else
{
allGood = false;
}
try
{
// Checks if any errors
if(allGood)
{
// Checks if message to be sent is blank
if(!messageTextContent.equals("") & messageTextContent.equals(" ") & !(messageTextContent == "") & !(messageTextContent == " "))
{
// Connects
(sh.new Utility()).connect(); // STAGE 4
// Checks if logged in
if((sh.new CheckLogin()).start())
{
// Sends request
sh.getNetwork().sendData(ServiceHelper.sendMessageTextRequest);
// Sends the friend id who message is addressed to
sh.getNetwork().sendData(friendId);
// Gets message date
messageDate = sh.getNetwork().getDataAsString();
// Sends the message type
sh.getNetwork().sendData(textType);
// Sends the message text
sh.getNetwork().sendData(messageTextContent); // STAGE 5
// Gets the message number
messageNumber = Integer.parseInt(sh.getNetwork().getDataAsString());
// Gets result and check if successful
String result = sh.getNetwork().getDataAsString(); // STAGE 5
if(!result.equals(ServiceHelper.requestSuccessful))
{
// Not successful
allGood = false;
errorMessage = result;
}
else
{
// Successful and saves data to database
addDatabaseTextMessage();
}
// Sends received indicator
sh.getNetwork().sendData(ServiceHelper.receivedIndicator);
}
else
{
allGood = false;
errorMessage = tryAgainMsg;
}
// Closes connection
(sh.new Utility()).finishConnection(); // STAGE 6
// Gets current friend record
UserDatabase db = new UserDatabase(context);
FriendRecord fr = db.getFriendRecord(Integer.parseInt(friendId));
// Increments message numbers by one and updates database
fr.setTotalExchanged(fr.getTotalExchanged() + 1);
db.updateFriendRecord(fr);
}
else
{
allGood = false;
errorMessage = msgBlank;
}
}
else
{
allGood = false;
errorMessage = tryAgainMsg;
}
} catch(IOException e)
{
allGood = false;
errorMessage = checkConnectionMsg;
// Adds to log cat
Log.e(this.getClass().getSimpleName(), e.getMessage());
} catch(Exception e2)
{
allGood = false;
errorMessage = tryAgainMsg;
// Adds to log cat
Log.e(this.getClass().getSimpleName(), e2.getMessage());
}
// Decides on result
if(allGood)
{
// Refreshes the ui
if(sm.getHomeScreen()) // STAGE 6
{
Message msg = HomeScreen.homeScreenInterface.obtainMessage(HomeScreen.sendSuccess); // STAGE 7
HomeScreen.homeScreenInterface.sendMessage(msg); // STAGE 7
}
}
else
{
// Indicates error on ui
if(sm.getHomeScreen())
{
Message msg = HomeScreen.homeScreenInterface.obtainMessage(HomeScreen.messageSendError); // STAGE 7
msg.obj = errorMessage; // STAGE 6
HomeScreen.homeScreenInterface.sendMessage(msg); // STAGE 7
}
}
}
State 7 (updating UI)
// Deals with service responses
public static Handler homeScreenInterface = new Handler()
{
#Override
public void handleMessage(Message msg)
{
switch(msg.what)
{
...
...
case HomeScreen.sendSuccess:
// Enables and clears input
HomeScreen.messageInput.setEnabled(true);
HomeScreen.messageInput.setText("");
// Shows the message send menu and hides sending text
HomeScreen.sendMenu.setVisibility(View.VISIBLE);
HomeScreen.sendingText.setVisibility(View.GONE);
// Clears message variable
message = "";
attachment = null;
try
{
// Reloads the message list
messageFriendId = "";
currentInstance.loadMessageList(true);
} catch (Exception e)
{
currentInstance.loadingScreen.open("Error", "Try again later!", true, "Ok", currentInstance);
}
// Checks if the message is sending
isMessageSending = false;
break;
...
...
}
}
};
Try using Broadcast Receiver instead of a handler to refresh your ui.
http://developer.android.com/reference/android/content/BroadcastReceiver.html
private BroadcastReceiver bReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("UPDATE")) {
String text = intent.getStringExtra("output");
updateUI(text);
}
else if(intent.getAction().equals("RESTART")) {
//other stuff
Board.this.startService(i);
}
}
};
//from Service
Intent intent = new Intent();
intent.setAction("UPDATE");
intent.putExtra("output",modifiedSentence);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
//onStart
LocalBroadcastManager bManager = LocalBroadcastManager.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction("UPDATE");
filter.addAction("RESTART");
bManager.registerReceiver(bReceiver, filter);
//onPause
LocalBroadcastManager bManager = LocalBroadcastManager.getInstance(this);
bManager.unregisterReceiver(bReceiver);
Related
I was trying to add sip incoming calls with linphone sdk, The registration is successful and I can make out going calls and the call status is logging as expected, but I am not able to receive incoming calls. I am using intent service to handle connection.
Here is my code:
protected void onHandleIntent(Intent intent) {
String sipAddress = intent.getStringExtra("address");
String password = intent.getStringExtra("password");
final LinphoneCoreFactory lcFactory = LinphoneCoreFactory.instance();
// First instantiate the core Linphone object given only a listener.
// The listener will react to events in Linphone core.
try {
lc = lcFactory.createLinphoneCore(new LinphoneCoreListenerBase() {
#Override
public void callState(LinphoneCore lc, LinphoneCall call, LinphoneCall.State state, String message) {
super.callState(lc, call, state, message);
Log.i(TAG, "callState: ");
}
}, getApplication());
} catch (LinphoneCoreException e) {
e.printStackTrace();
}
lc.setUserAgent("Test app", "1.0");
try {
LinphoneAddress address = lcFactory.createLinphoneAddress(sipAddress);
String username = address.getUserName();
String domain = address.getDomain();
if (password != null) {
lc.addAuthInfo(lcFactory.createAuthInfo(username, password, null, domain));
}
// create proxy config
LinphoneProxyConfig proxyCfg = lc.createProxyConfig(sipAddress, domain, null, true);
proxyCfg.setExpires(2000);
lc.addProxyConfig(proxyCfg); // add it to linphone
lc.setDefaultProxyConfig(proxyCfg);
running = true;
while (running) {
lc.iterate(); // first iterate initiates registration
sleep(20);
}
} catch (LinphoneCoreException e) {
e.printStackTrace();
}
}
What is wrong with my code?
As the IntentService document (https://developer.android.com/reference/android/app/IntentService) stated:
the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
I think you should not put the listener in an IntentService. Instead, put it in a long running Service so that the listener can actually keep staying there to receive events.
I have a bluetooth printer integrated to my app and if I do some transactions, I can print the receipt to the customer. I have a method to handle the printing of receipts. Currently, I can print only one receipt but I would like to print the receipts twice.
Should I run the for loop twice so the method which prints my receipt is executed twice.
private void printReceipt(final Transaction transaction) {
showProgressPopup(getString(R.string.printing_dialog_message));
Runnable printThread = new Runnable() {
#Override
public void run() {
final BitSet resultBit = new BitSet(1);
try {
final ReceiptMetadata receiptMetadata =
AirFiUtils.getPaymentDeviceReceiptMetaData(getAirlineProfile(),
AirFiUtils.getMerchantAccount(getAirFiActivity()));
if (null != receiptMetadata) {
PrinterManager.printReceipt(PrinterType.valueOf(receiptMetadata.getPrinter().get(0)),
ReceiptType.CASH, receiptMetadata, transaction, getActivity().getApplicationContext(),
transaction.isSignatureCard());
resultBit.set(0, true);
}
} catch (Exception e) {
LOG.error("Error in printing ", e);
resultBit.set(0, false);
}
}
};
new Thread(printThread).start();
}
use for loop inside your if statement e.g
if(your_condition){
for(int i=0;i<2;i++){
//your desired code to print receipt
}}
or call your specific function two time which is need to be print receipt by applying for loop
I have:
One bindable service setup to hand out android.os.Messenger's.
Two Fragment inside one activity bind to the service and receive their Messengers.
Both Fragments are active at the same time as they are part of a ViewPager.
When dispatching a message to both Fragments at the same time, The app crashes because of ANR
code that sends the message: (from this example)
public void sendMessageToUI(Message msg) {
for (int i = mClients.size() - 1; i >= 0; i--) {
try {
msg.replyTo = mMessenger;
Messenger client = mClients.get(i);
client.send(msg);
} catch (RemoteException e) {
mClients.remove(i);
}
}
}
More info
After extensive testing I've concluded that the messenging system works when targeting just the first, or second fragment, but crashes when targeting both.
Somewhere in the guts of the messenger, messages are apparently unique to the messenger they are being sent to. If you look at the example on the android docs here: (RemoteMessengerServiceSample) you'll see that a new message must be obtained PER messenger
mValue = msg.arg1;
for (int i=mClients.size()-1; i>=0; i--) {
try {
mClients.get(i).send(Message.obtain(null,
MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
// The client is dead. Remove it from the list;
// we are going through the list from back to front
// so this is safe to do inside the loop.
mClients.remove(i);
}
}
I have been able to create the if statement that checks for the string and it returns the toast message that i created but it keeps showing the toast message every time i open the chat. even if the most recent message doesn't contain the string I am looking for so i am assume it isn't checking to see if it is the last message received and it doesn't check to see if it is unread. the code is below. the reason i am trying to do this is because my parents share a facebook account and i want an easy way to display if the message is signed mom or dad. the code below only has the check for mom once it works i will be adding the check for dad signature. I am using the open source message client Xabber. Thank you for help.
public void setVisibleChat(String account, String user) {
final boolean remove = !AccountManager.getInstance()
.getArchiveMode(account).saveLocally();
AbstractChat chat = getChat(account, user);
if (chat == null)
chat = createChat(account, user);
else {
// Mark messages as read and them delete from db if necessary.
final ArrayList<MessageItem> messageItems = new ArrayList<MessageItem>();
for (MessageItem messageItem : chat.getMessages()) {
if (!messageItem.isRead()) {
messageItem.markAsRead();
messageItems.add(messageItem);
}
if (chat.getLastText().contains("Mom") && (!messageItem.isRead()));{
Toast.makeText(Application.getInstance(), "Message from Mom!", Toast.LENGTH_SHORT).show();
}
}
Application.getInstance().runInBackground(new Runnable() {
#Override
public void run() {
Collection<Long> ids = getMessageIds(messageItems, remove);
if (remove)
MessageTable.getInstance().removeMessages(ids);
else
MessageTable.getInstance().markAsRead(ids);
}
});
}
visibleChat = chat;
}
You've got an extra semi-colon here
if (chat.getLastText().contains("Mom") && (!messageItem.isRead())); <------
So your next block of code containing the Toast show statement will always be executed.
Remove the semi-colon
Still new to android/java and quite confused about bundles, messaging and handlers (apologies if my terminology is not quite correct). I have a custom dialog class which can display multiple dialogs. Listeners are set up to pass back data via a handler to the main calling activity.
The data passed back might be one item or many. In my testing I am attempting to send two items back. I have tried this a number of ways. I am always successful in transmitting a single item and having the handler receive it and extract it. I fail when doing multiple items (in slightly different ways.)
If I put two items into the bundle and send just one message, the handler appears to only receive the 2nd bundle item, not the first.
If I put one item in the bundle, send, clear the bundle and then put the 2nd item in the bundle and send, nothing seems to be received by the handler and the activity hangs.
I have also used the output of msg.toString() and note that if two messeages are sent, the "when" of the second is 0. Don't know if that matters or not.
Also, I have tried passing message by use of msg.sendToTarget as well as handler.sendMessage(msg) but it does not seem to matter which is used.
Code snips and output here: http://pastebin.com/xtUatEVu
I've left in but commented out some of the other things tried. I really do not understand what I am doing wrong.
Well after some extensive experimentation I've discovered a few things. The primary problem was that a message can only be used once, i.e, it is not a reusable envelope which you can put new letters and send again. Additionally, make sure the bundle key is unique. Note that I also discovered msg.what which is used to store the dialogID.
The Handler
private Handler uiMsgHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
Log.i(TAG, "%%%% in the uiMsgHandler");
// msg.what() contains the dialogID. The bundle key is the itemID
if (msg != null) {
int DlgId = 0;
int ItemId = 0;
String value = ""; //leave it to each case to recast the value
String element = "";
Bundle b = new Bundle();
Set<String> s = null;
b = msg.getData();
Log.i(TAG,"bundle is "+b);
DlgId = msg.what;
Log.i(TAG,"DigId is "+DlgId);
switch (DlgId) {
case 1:
s = b.keySet(); //find the set of keys in this message bundle
Log.i(TAG,"s is "+s.toString());
Log.i(TAG,"s.size is "+s.size());
if (s.size() < 100) { // we allow up to 100 items, 99 is reserved
Iterator itr = s.iterator();
while (itr.hasNext()) {
element = (String) itr.next();
ItemId = Integer.parseInt(element.substring(0,1));
value = b.getString(element);
Log.i(TAG,"ItemID is "+ItemId+" value is "+value+" itr.next "+itr.hasNext());
// now we can handle each specific item
switch (ItemId) {
case 1:
Log.i(TAG,"Bundle data for Dialog "+DlgId+" Item "+ItemId+" is "+value);
// do something
// close the dialog
break;
case 2:
Log.i(TAG,"Bundle data for Dialog "+DlgId+" Item "+ItemId+" is "+value);
// do something
// close the dialog
break;
case 99:
Log.i(TAG,"Bundle data for Dialog "+DlgId+" Item "+ItemId+" is "+value);
// Cancel button was pressed
// close the dialog
break;
default: /* item out of range */
Log.i(TAG,"Error ItemID OoR: DialogID "+DlgId+" with message item id "+ItemId+" is out of range");
// close the dialog and toast w/ message
break;
} // end of ItemID switch
} // end of ItemID iterator while
} else { // end of number of items size check
Log.i(TAG,"too many items, must be < 100 but is "+s.size());
}
break; // end of case 1
default: /* dialog id was out of range */
Log.i(TAG,"Error: dialog id was out of range");
Log.i(TAG,"Bundle data for Dialog "+DlgId+" Item "+ItemId+"is "+value.substring(2));
// close the dialog
// toast with a message
break;
} // end of Dialog switch
} // msg null check
}
};
=====================================================================
Portion of the custom dialog
acceptButton = (Button) dialogview.findViewById(r_id_accept);
rejectButton = (Button) dialogview.findViewById(r_id_reject);
acceptButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Inform the user the button has been clicked
Log.i(TAG, "ACCEPT BUTTON PRESSED");
// now we are going to do the messaging
// addOptQty is item id 01
// addOptPrice is item id 02
// msg.what contains the dialogID
msg.what=id;
b.putString("1",""+addOptQty);
b.putString("2",""+addOptPrice);
msg.setData(b);
// now send the message to the handler
try {
mResponseHandler.sendMessageDelayed(msg,10);
} catch (Exception e) {
Log.i(TAG, "ERROR SENDING MESSAGE");
}
}
});
rejectButton.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick(View v) {
//Inform the user the button has been clicked
Log.i(TAG,"LOCI BUTTON PRESSED");
msg.what=id;
b.putString("99","CANCEL");
msg.setData(b);
// now send the message to the handler
try {
mResponseHandler.sendMessageDelayed(msg,10);
} catch (Exception e) {
Log.i(TAG, "ERROR SENDING MESSAGE");
}
}
});