I would like to intercept incomming MMS to enable mobile data. For that, I need to intercept them before any other app.
I have setup my intent filter to receive WAP_PUSH_RECEIVED_ACTION broadcasts with the highest possible priority.
But, in the Android documentation (https://developer.android.com/reference/android/provider/Telephony.Sms.Intents.html), there are the two following broadcasts:
WAP_PUSH_DELIVER_ACTION (Only sent to default sms app)
WAP_PUSH_RECEIVED_ACTION (Sent to all apps)
Please, can you tell me which of these broadcasts is sent first (WAP_PUSH_DELIVER_ACTION or WAP_PUSH_RECEIVED_ACTION) and where did you find this information ?
From where are them send in Android source code ?
Does listening for WAP_PUSH_RECEIVED_ACTION with the highest possible priority let me be the first to receive WAP PUSH broadcasts ?
Thanks
This topic seems to be not so popular!
I tried to answer the question myself and I found something interesting.
Analysis
SMS and MMS reception are mainly managed in the file InboundSmsHandler.java.
This file starts with a comment block that explains the SMS/MMS receiving state machine.
Here is an extract of this comment with explanations:
The state machine starts in InboundSmsHandler.IdleState state.
When the SMSDispatcher receives a new SMS from the radio, it calls dispatchNormalMessage(com.android.internal.telephony.SmsMessageBase), which transitions to InboundSmsHandler.DeliveringState state.
From the InboundSmsHandler.DeliveringState state, processMessagePart(InboundSmsTracker tracker) is called. Within this method, if the destination port number of the SMS is SmsHeader.PORT_WAP_PUSH (in other words if the SMS is an MMS), the WapPushOverSms.dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler) method is called.
Inside the dispatchWapPdu method, they call InboundSmsHandler.dispatchIntent(Intent intent, String permission, int appOp, BroadcastReceiver resultReceiver, UserHandle user). They check if there is a default MMS app and if it's the case, configure the intent to only be delivered to this app.
Code:
// Direct the intent to only the default MMS app. If we can't find a default MMS app
// then sent it to all broadcast receivers.
ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true);
if (componentName != null) {
// Deliver MMS message only to this receiver
intent.setComponent(componentName);
if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
" " + componentName.getClassName());
}
handler.dispatchIntent(intent, permission, appOp, receiver, UserHandle.OWNER);
Inside the dispatchIntent we have what we are looking for, the call to Context.sendOrderedBroadcastAsUser(...). So, it is this method that sends the WAP_PUSH_DELIVER_ACTION broadcast as an ordered broadcast.
This broadcast is also handled (default app and SmsBroadcastReceiver) by the SmsBroadcastReceiver.onReceive(Context context, Intent intent) handler located in InboundSmsHandler.java. Inside this handler, the WAP_PUSH_DELIVER_ACTION case is processed. The intent is changed to WAP_PUSH_RECEIVED_ACTION and broadcasted again through the InboundSmsHandler.dispatchIntent(Intent intent, String permission, int appOp, BroadcastReceiver resultReceiver, UserHandle user) method. This time, not only the default app is concerned, but all interested apps.
Code:
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intents.SMS_FILTER_ACTION)) {
// ...
} else if (action.equals(Intents.SMS_DELIVER_ACTION)) {
// ...
} else if (action.equals(Intents.WAP_PUSH_DELIVER_ACTION)) {
// Now dispatch the notification only intent
intent.setAction(Intents.WAP_PUSH_RECEIVED_ACTION);
intent.setComponent(null);
// Only the primary user will receive notification of incoming mms.
// That app will do the actual downloading of the mms.
dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
AppOpsManager.OP_RECEIVE_SMS, this, UserHandle.OWNER);
} else {
// ...
}
}
Conclusion (Quick answer to the original question)
When an MMS is received, WAP_PUSH_DELIVER_ACTION is broadcasted first to the default app followed by the WAP_PUSH_RECEIVED_ACTION.
Both broadcasts are ordered broadcasts that means that priorities can be used.
Well, it's a bad news for me because it also means that I cannot be the first to be notified for an incoming MMS and turn on the modile data before the MMS app is notified.
Ahh Google, with Lollipop, you make the things harder for us : Android Issue 78084 - setMobileDataEnabled removed
So, I have to look for another way in order to do that.
Related
I am trying to work with an android broadcast receiver, which i want to be invoked upon completion of BT file transfer (in case of receiving file).
Use Case :
Device (A) is already paired with another Bluetooth enabled device (B) and when B sends a file to device A, I want my broadcast receiver to execute.
Is there any way to achieve it?
I tried filtering for below events, but it seems to be private events of default Bluetooth app and is intented only for BluetoothShare component that pushes the notification on completion of file reception and NOT broadcasted publicly. As a result, my broadcast receiver is NOT invoked real device with actual file transfers but when I send any of the below intent from ADB shell its working in both emulator and real device.
Intents filtered in AndroidManifest.xml:
"android.btopp.intent.extra.**BT_OPP_TRANSFER_STATUS**"
"android.btopp.intent.action.**BT_OPP_TRANSFER_DONE**"
"android.intent.action.**DOWNLOAD_COMPLETE**"
Any leads would be greatly appreciated.
Thanks
You can monitor the Connectivity action of Bluetooth. There is a connecting action when the device begin to receive file and when its compeleted there will be a disconnecting action. You can register for ACTION_ACL_DISCONNECTED and onReceive take some action
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == BluetoothDevice.ACTION_ACL_DISCONNECTED) {
}
}
}
Register receiver as follows:
IntentFileter blutoothFilter= new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
this.registerReceiver(mReceiver, blutoothFilter);
I want to implement pub/sub in my app, for that server send notification in specific event, I will do some modification on data which is display in my activity
onMessageReceived() doesn't need PendingIntent to be call. It will always call if you have correct setup. This link provide the data type you should sent to FCM server
With FCM, you can send two types of messages to clients:
Notification messages, sometimes thought of as "display messages."
Data messages, which are handled by the client app.
If you would like to always trigger use just data message so it will always trigger the onMessageReceived().If you try to use data-message and notification-message together the onMessageReceived() will not get trigger when your app is in background.
Just do anything you would like to do such as save to database, sharedPeference etc inside your onMessageReceived()
So how you sent to the activity?
Use Broadcast Receiver here is how you sent a broadcast receiver in your case you will like to put it inside your onMessageReceived() so anytime you received a new notification this code will help you sent the data to the specific activity.
Intent intent = new Intent("Use anything you like");
intent.putExtra("data","The data you receive");
sendBroadcast(intent);
In your activity register it in your onStart()
registerReceiver(broadCastReceiver,new IntentFilter("must match the intent filter parameter"));
Here is how you handle your data
class broadCastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("Your data",intent.getData());
}
}
Note: your intent filter parameter must match the intent parameter you set in your onMessageReceived()
If your app never received data from the FCM this answer will be useless since your question still unclear this is the best I can do for you.
I want to do the following:
Send an SMS
Check if it was sent
Store it in a SQLite instance if it wasn't.
Resend any SMS that got stored before.
So you got a main SMS sending action, which will require feedback on its status (to tell the user if it could be sent or not), and a background SMS sending action which will just try to resend previously unsent SMS silently.
The solution I came up with involves an IntentService which has two actions:
Send an SMS message.
Try to send previously stored SMS messages.
So far so good, this exact same thing worked wonders for sending a TCP message to a server. Now the issue is that I can't seem to be able to send SMS from the IntentService.
The idea was to have the IntentService create a PendingIntent, stuff the main Activity provided PendingIntent inside (thats basically the callback to tell the activity the SMS was sent), then send the SMS with it.
Then with a static receiver fetch the PendingIntent and start a new action on the IntentService to delete the SMS from the SQLite instance if it was sent properly.
This is the main message sending method in the IntentService:
private void sendMessage(Configuration cfg, SMSMessage msg, PendingIntent resultIntent) {
final Intent smsIntent;
// Select which kind of intent we're creating.
if (resultIntent == null) {
// This one is the silent background SMS.
smsIntent = new Intent(this.getApplicationContext(), RedirectPendingMessages.class);
} else {
// This one is the one from the application.
smsIntent = new Intent(this.getApplicationContext(), RedirectMessage.class);
smsIntent.putExtra(EXTRA_PENDING_RESULT, resultIntent);
}
// Now store the message.
smsIntent.putExtra(EXTRA_SMS, msg);
// Construct broadcast intent.
PendingIntent pi = PendingIntent.getBroadcast(this.getApplicationContext(), 0, smsIntent, 0);
// Now send message.
SmsManager smsMng = SmsManager.getDefault();
smsMng.sendTextMessage(cfg.phoneNumberFor(msg.level), null, msg.content, pi, null);
}
It gets to the 'SmsManager.sendTextMessage' method fine but nothing happens, even if I hardcode the phone number and dont pass any PendingIntent the SMS still doesn't gets sent. It might be because the IntentService ceases to exist after the method call?
The receivers both just grab the broadcasted Intent, fetch the data inside, and start appropiate actions in the IntentService to delete the SMSs if they have been sent, and to broadcast the application's PendingIntent so the UI can give some feedback to the user ("SMS sent", "Error", etc).
My TCP implementation of the same thing pretty much just has a Socket write instead of that 'sendTextMessage', it blocks the IntentService until its done and works fine.
Any ideas on why the SMS isn't being sent or how to better implement this?
It seems that your message exceeds the character limit for the alphabet you're using, which is causing the SmsManager#sendTextMessage() method to fail silently.
For the basic 7-bit default alphabet, the character limit is 160; for 8-bit, it's 140; and for 16-bit, which sounds like your situation, it's 70, explaining why your 120-character message split into two parts.
As you discovered, you can use the SmsManager#divideMessage() method to split a message into useable parts, and the SmsManager#sendMultipartTextMessage() to send the parts correctly.
SmsManager Reference
I'm looking for an app that disables sending or receiving or both - of SMS messages from a android device . Once the app is installed it checks a web url for a boolean value 0 or 1, if it's zero, sms is disabled, else it's enabled.
Can anyone guide me from where to start ?Is this possible?
You can register a BroadcastReceiver for incoming SMS and use this.abortBroadcast(); to abort its further processing:
Inside you manifest:
<receiver android:name=".SMSBroadcastReceiver">
<intent-filter>
<action android:name="android.provider.telephony.SMS_RECEIVED"></action>
</intent-filter>
</receiver>
Implementation of the receiver:
public class SMSBroadcastReceiver extends BroadcastReceiver {
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == SMS_RECEIVED) {
Bundle bundle = intent.getExtras();
...
this.abortBroadcast();
}
}
}
However - it is not possible to abort an outgoing SMS that way. Please note that if more than one BroadcastReceivers are registered, the one with the highest priority (android:priority) will be processed first.
Imho you cant disable SMS receiving/sending at all, but you can catch the SMS received Broadcast Intent.
The SMS received Broadcast Intent is a ordered Broadcast, it will be delivered to all Broadcast Receivers in order of the Priority defined in the Receiver.
If you register a Broadcast Receiver with Priority 0 you can catch the SMS Broadcast and cancel it.
Keep in mind that other SMS Apps can also register with Priority 0 the behavior is undefined for same priority values. Also the SMS is received anyway, you only hide the Resulting Notifications/Ringtone.
Another approach would be to set the SMS Central Number to some invalid Values, required a rooted Phone though
At the moment I am developing an application which catches the action NEW_OUTGOING_CALL with the help of a BroadcastReceiver. I am aborting the call by calling setResultData(null). After that I am showing the user a dialog which allows him to decide if he wants to use my application to rewrite its number. When the users decision has happened I am placing the new call depending on the decision. Now my broadcast receiver gets called up once again.
What is the correct way of getting to know that I have already processed the number? I got a working solution that uses a timestamp to guess if it could be already processed. Another solution would be to add a "+" at the end of the processed number.
These methods are working fine for my application being the only one catching the NEW_OUTGOING_CALL event. But what should I do when other applications (like Sipdroid or Google Voice) are also sitting there catching the NEW_OUTGOING_CALL broadcast aborting it and restarting it again? I don't see a possibility to get to know if we are still in the same "call flow" and if I already processed the number.
I would love to hear your ideas about this problem!
What API level are you working with? If it's >= 11, check out the new BroadcastReceiver.goAsync function that lets you extend the processing of the broadcast outside of the onReceive function of your receiver. This could bypass the need to loop altogether.
If, like me, you're stuck trying to do this before level 11, it is surprisingly tricky to do this elegantly. You may have done this as well, but I tried to include a "processed" flag as an extra in the ACTION_CALL intent that my code generated, hoping that it would somehow get included in the resulting ACTION_NEW_OUTGOING_CALL broadcast, but that sadly does not work.
The best solution I have been able to find is including a fragment in the URI for the ACTION_CALL intent that you generate. This fragment will be included for the resulting ACTION_NEW_OUTGOING_CALL broadcast, so your broadcast receiver can differentiate between the original call and the one that you generate, but it won't interfere with handlers that aren't looking for it.
Here's the basic code.
In your BroadcastReceiver for the ACTION_NEW_OUTGOING_CALL
public class YourBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// extract the fragment from the URI
String uriFragment = Uri.parse(
intent.getStringExtra("android.phone.extra.ORIGINAL_URI")).getFragment();
// if the fragment is missing or does not have your flag, it is new
if (uriFragment == null || !uriFragment.contains("your_flag")) {
// launch your activity, pass the phone number, etc.
// use getResultData to get the number in order to respect
// earlier broadcast receivers
...
// abort the broadcast
this.setResultData(null);
this.abortBroadcast();
}
// otherwise, your code is there, this call was triggered by you
else {
// unless you have a special need, you'll probably just let the broadcast
// go through here
// note that resultData ignores the fragment, so other receivers should
// be blissfully unaware of it
}
}
}
When the user first dials the number, the fragment will either be missing altogether or your flag won't be present, so you'll abort the broadcast and start your activity. In your activity, if you decide to place the call again, do something like the following:
startActivity(new Intent(Intent.ACTION_CALL,
Uri.parse("tel:" + modified_number + "#your_flag")));
The "your_flag" fragment will then be present in the subsequent NEW_OUTGOING_CALL broadcast and thus allow you to handle this case differently in your broadcast receiver.
The nice thing about this is the the fragment is completely ignored unless you look for it in the ORIGINAL_URI, so other broadcast receivers can continue to function. If you want to be really nice, you may want to look for an existing fragment and add your flag to it (perhaps with a comma separator).
I hope that helps. Good luck!
I don't see a possibility to get to
know if we are still in the same "call
flow" and if I already processed the
number.
Technically, you are not in the same "call flow" as placing a new call is asynchronous. You have to use hints (such as a timestamp) as you seem to be doing already.
If you are confident that other applications will not rewrite the number except to change the prefix or to add a suffix, you may want to add another "proximity check" hint to avoid false positives/negatives, but I'm afraid that's about all you can do.
The onReceive() method in Broadcast receiver receives an Intent as an argument.
Extract the Bundle from the Intent using Intent.getExtras().
This Bundle contains 3 key-value pairs as follows :
android.phone.extra.ALREADY_CALLED = null
android.intent.extra.PHONE_NUMBER = 98xxxxxx98
android.phone.extra.ORIGINAL_URI = tel:98xxxxxx98
98xxxxxx98 is the number dialled by the user.
When the onReceive() is called again, this number changes to 98xxxxxx98* or 0*
By checking for the asterisk(*) at the end of the dialled number, it can be inferred if the onReceive() method is called for the first time or the next subsequent times.
One of the answers would be to track the boolean extra in the intent. It is done in similar way by the Google Phone app. You can check this BroadcastReceiver here (look for alreadyCalled usage)
The other way would be just to pass that "rewritten" number from your broadcast to the next broadcast receiver down the road (can be any app, like Sipdroid, Google Voice, or custom VoIP app) without calling ACTION_CALL intent (this is why you get loop and you broadcast receiver called again) The following code is example of how I am handling call in my custom VoIP app. When I intercept NEW_OUTGOING_CALL in my broadcast receiver, I first check if there is internet connection. If phone is connected to internet I use custom defined intent action of my activity to place call through my VoIP app. If there is no internet connection, I just set original phone number to the broadcast receiver result data. This is used by the next broadcast receiver (probably default phone app, but doesn't have to be) in the flow to place a call.
public class BHTTalkerCallReceiver extends BroadcastReceiver {
private static final String TAG = "BHTTalkerCallReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Broadcast successfull ... ");
// Extract phone number reformatted by previous receivers
String phoneNumber = getResultData();
if (phoneNumber == null) {
// No reformatted number, use the original
phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
}
if (isNetworkAvailable(context)) { // Make sure this app handles call only if there is internet connection
// My app will bring up the call, so cancel the broadcast
setResultData(null);
// Start my app to bring up the call
Intent voipCallIntent = new Intent(context, TalkerActivity.class);
voipCallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
voipCallIntent.putExtra(TalkerActivity.OUT_CALL_NUMBER, phoneNumber);
voipCallIntent.setAction(TalkerActivity.BHT_TALKER_OUT_CALL);
context.startActivity(voipCallIntent);
} else { //otherwise make a regular call...
// Forward phone data to standard phone call
setResultData(phoneNumber);
}
}
private boolean isNetworkAvailable(final Context context) {
final ConnectivityManager connectivityManager = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
return connectivityManager.getActiveNetworkInfo() != null && connectivityManager.getActiveNetworkInfo().isConnected();
}
}