I am trying to verify the phone number of an Android device by having the device send an SMS to itself, and automatically checking if the SMS has been received. How can I do this?
To begin, this will require two permissions; one to send SMS messages, and one to receive them. The following needs to be in your AndroidManifest.xml, between the <manifest> tags, but outside of the <application> tags.
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
These are both dangerous permissions, so you will need to handle them accordingly if your app is to run on Marshmallow (API level 23) or above, and has a targetSdkVersion of 23+. Information on how to request these permissions at runtime can be found on this developer page.
The Java classes you will need are in the android.telephony package; specifically android.telephony.SmsManager and android.telephony.SmsMessage. Do make certain you've got the correct classes imported for both.
To send the outgoing SMS, you will use SmsManager's sendTextMessage() method, which has the following signature:
sendTextMessage(String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent)
Only two arguments are required in this method call - destinationAddress and text; the first being the phone number, the second being the message content. null can be passed for the rest. For example:
String number = "1234567890";
String message = "Verification message.";
SmsManager sm = SmsManager.getDefault();
sm.sendTextMessage(number, null, message, null, null);
It's important to keep the message text relatively short, as sendTextMessage() will usually fail silently if the text length exceeds the character limit for a single message.
To receive and read the incoming message, you will need to register a BroadcastReceiver with an IntentFilter for the "android.provider.Telephony.SMS_RECEIVED" action. This Receiver can be registered either statically in the manifest, or dynamically on a Context at runtime.
Statically registering the Receiver class in the manifest will allow your app to receive the incoming message even if your app should happen to be killed before receipt. It may, however, take a little extra work to get the results where you want them. Between the <application> tags:
<receiver
android:name=".SmsReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
The PackageManager#setComponentEnabledSetting() method can be used to enable and disable this <receiver> as needed.
Dynamically registering a Receiver instance on a Context can be a little easier to manage, code-wise, as the Receiver class could be made an inner class on whichever component registers it, and therefore have direct access to that component's members. However, this approach might not be as reliable as static registration, as a few different things could prevent the Receiver from getting the broadcast; e.g., your app's process being killed, the user navigating away from the registering Activity, etc.
SmsReceiver receiver = new SmsReceiver();
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(receiver, filter);
Do remember to unregister the Receiver when appropriate.
In the Receiver's onReceive() method, the actual message comes as an array of byte arrays attached to the Intent as an extra. The decoding details vary depending on the Android version, but the result here is a single SmsMessage object that will have the phone number and message you're after.
class SmsReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
SmsMessage msg;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
SmsMessage[] msgs = Telephony.Sms.Intents.getMessagesFromIntent(intent);
msg = msgs[0];
} else {
Object pdus[] = (Object[]) intent.getExtras().get("pdus");
msg = SmsMessage.createFromPdu((byte[]) pdus[0]);
}
String number = msg.getOriginatingAddress();
String message = msg.getMessageBody();
...
}
}
At this point, you simply compare the number here to the one passed to the sendTextMessage() call. It's advisable to use PhoneNumberUtils.compare() for this, since the number retrieved in the Receiver might be in a different format than the one addressed.
Notes:
The example demonstrated here is using one single-part message, thus why the message text should be restricted to a relatively short length. If you do want to send a longer message, for some reason, the sendMultipartTextMessage() method can be used instead. You would need to split up the text first, using SmsManager#divideMessage(), and passing the resulting ArrayList to that method, in lieu of the message String. To reassemble the complete message in the Receiver, you'd have to decode each byte[] into an SmsMessage, and concatenate the message bodies.
Since KitKat (API level 19), if your app is not the default messaging app, the messages used here are going to be saved to the SMS Provider by the system and default app, and will therefore be available to any other app that uses the Provider. There's not much you can do about that, but if you really want to avoid it, this same technique can be used with data SMS, which do not trigger the default app, and won't be saved to the Provider.
For this, the sendDataMessage() method is used, which will need an additional short argument for the (arbitrary) port number, and the message is passed as a byte[], rather than a String. The action to filter for is "android.intent.action.DATA_SMS_RECEIVED", and the filter will need a data scheme and authority (host and port) set. In the manifest, it would look like:
<intent-filter>
<action android:name="android.intent.action.DATA_SMS_RECEIVED" />
<data
android:scheme="sms"
android:host="localhost"
android:port="1234" />
</intent-filter>
and there are corresponding methods in the IntentFilter class to set those dynamically.
Decoding the SmsMessage is the same, but the message byte[] is retrieved with getUserData(), rather than getMessageBody().
Prior to KitKat, apps were responsible for writing their own outgoing messages, so you can just not do that on those versions, if you don't want any record of it.
Incoming messages could be intercepted, and their broadcasts aborted before the main messaging app could receive and write them. To accomplish this, the filter's priority is set to the maximum, and abortBroadcast() is called in the Receiver. In the static option, the android:priority="999" attribute is added to the opening <intent-filter> tag. Dynamically, the IntentFilter#setPriority() method can do the same.
This is not at all reliable, as it is always possible for another app to have a higher precedence than yours.
I've omitted securing the Receiver with the broadcaster's permission in these examples, partly for simplicity and clarity, and partly because the nature of the thing wouldn't really leave you open to any sort of spoofing that could do harm. However, if you'd like to include this, then you merely need to add the android:permission="android.permission.BROADCAST_SMS" attribute to the opening <receiver> tag for the static option. For the dynamic, use the four-parameter overload of the registerReceiver() method, passing that permission String as the third argument, and null as the fourth.
Related
I'm making an Android app with incoming calls.
Is there any way in my app, to know if user rejected any incoming call before answering it?
First, you have to add a permission in manifest file.
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Then you have to register a broadcast receiver in the manifest file.
<receiver android:name=".YourReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
Then write in your broadcast receiver's onReceive method:
public void onReceive(Context context, Intent intent) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if(state.equals(TelephonyManager.EXTRA_STATE_RINGING)){
//If the phone is **Ringing**
}else if(state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK){
//If the call is **Received**
}else if (state.equals(TelephonyManager.EXTRA_STATE_IDLE)){
//If the call is **Dropped** or **Rejected**
}
}
If the state changes from TelephonyManager.EXTRA_STATE_RINGING to TelephonyManager.EXTRA_STATE_IDLE, then it will be a missed call.
Like this you have to check the conditions.
Please put a log in those conditions. Then see the logs. I think you will get your answer.
It's been a while since you asked, but I think it might be useful for future reference for others.
If you extract data from Android's calllog (e.g to XML or in your case to a variable in your app) you have two fields of interest:
1) numbertype e.g 1 for Incoming, 2 for Outgoing, 3 for missed
2) duration (in seconds)
Rejected number is (as you correctly mentioned) treated as numbertype=1. However if you combine numbertype=1 AND duration=0 (since any call answered will have duration>0) then this hopefully solves your problem.
I'm using Android 6, not sure if the types changed since then, but with me the above method works 99.9% of the time. Never managed to hang up the phone in less than a second :)
I am trying to register a BroadcastReceiver programmatically to receive android.intent.action.MEDIA_MOUNTED. However, the onReceive() method never gets triggered. The same BroadcastReceiver class I created works fine if I register is statically in the app's manifest file.
Why is this the case? Is there a way to troubleshoot this? I need to register dynamically because my BroadcastReceiver class contains members that I want a service to query later on. If I catch this Intent statically then I have no easy way of querying these members because I believe the instance of BroadcastReceiver gets deleted as soon as onReceive() finishes. Is this correct? I am pretty sure this is the case considering the Android documentation has the following to say about this:
If this BroadcastReceiver was launched through a tag, then the object is no longer alive after returning from this function.
UPDATE 1:
Here is my dynamic registration code:
if (externalStorageListener == null)
{
Log.d(TAG, "creating externalStorageListener...");
IntentFilter filterExternalStorage = new IntentFilter();
filterExternalStorage.addAction(Intent.ACTION_MEDIA_MOUNTED);
filterExternalStorage.addDataScheme("file");
filterExternalStorage.setPriority(Integer.MAX_VALUE);
externalStorageListener = new ExternalStorageBroadcastReceiver();
registerReceiver(externalStorageListener, filterExternalStorage);
}
Here is the intent filter in my manifest that actually works:
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file"/>
</intent-filter>
Occasionally, to receive android.intent.action.MEDIA_MOUNTED with dynamically created BroadcastReceiver one is to have android.permission.MOUNT_UNMOUNT_FILESYSTEMS defined in manifest:
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
android.intent.action.MEDIA_MOUNTED action can be handled by privileged system application only. That action can not be received by 3rd party applications
May be when you create receiver dynamically your intent filter is different compared to when it is created through the <receiver/> tag? Could you show us both your manifest part and the code part?
Also, instead of storing data in receiver's fields, you could consider other options:
A content provider
Shared preferences
A singleton object
In that case there will be no difference between statically and dynamically created receivers.
EDIT: Based on the provided code, the problem is probably in priority setting:
Applications must use a value that is larger than SYSTEM_LOW_PRIORITY and smaller than SYSTEM_HIGH_PRIORITY .
SYSTEM_HIGH_PRIORITY = 1000
I have been able to get my BroadcastReceiver running with this:
<receiver
android:name=".AlarmReceiver"
android:enabled="true"
android:exported="false">
</receiver>
As you can see no <intent-filter>. It gets called correctly with the right Intent extras. But I have been looking around and I am confused as to whether I need one or not? I do have a setAction() method called on my Intents but to make them unique from others to ensure a specific issue with notifications, not actually using that action's string. But what exactly is the correlation? Thanks in advance.
Intent intent = new Intent(this.getContext(), AlarmReceiver.class);
intent.setAction("com.something"+System.currentTimeMillis());
//... extras are here
PendingIntent pi = PendingIntent.getBroadcast(this.getContext(), 123, intent, PendingIntent.FLAG_CANCEL_CURRENT|Intent.FILL_IN_DATA);
AlarmManager alarm = (AlarmManager)getContext().getSystemService(Activity.ALARM_SERVICE);
alarm.set(AlarmManager.RTC_WAKEUP, time.getTimeInMillis(), pi);
Using that with what I have in the manifest file works.
EDIT
I found this on the Android Developers blog, and it states this:
Implicit Intents only specify “what” they should match, using actions, categories, data,
MIME types, and so on. The exact components that they will find are only determined at
run-time, by the Package Manager matching it against the current applications.
Explicit Intents specify a single explicit “who” they should match, through a
ComponentName. Regardless of whatever else is in the Intent, it is only associated with
the exact manifest package name and class name as given in its ComponentName.
I am still slightly confused with this explanation, but it seems to be the closest thing to what I should be doing. So I am sure I am using an implicit intent. Question is, is it ok that I am leaving out <intent-filter>? I am matching it to a specific class. There may not be an actual action tying them together, perse, but is the class enough?
<intent-filter> is required when you want to start your receiver using implicit intents, if you are always using explicit intents to start your broadcast receiver then intent-filters are not required.
see this SO Post.
The entire way a broadcast receiver works is it involves using an Intent Filter to catch specific broadcast intents that are sent within the system. So if you give the receiver no intent filter, then NOTHING will be sent to it and its code will never execute.
Please read the documentation of Intent and learn how Intent allows IPC.
Update
Also its possible that without any declarations it could be receiving all public broadcasts and the way your code handles it.. is it ignores all the broadcasts except the ones it cares about. I've never done that this kind of implementation because I follow the standard. Which is to declare either as a dynamic listener (Programatic) or a static listener (XML declaration), where the dynamic listener would have its Intent Filter set in code.
I want to know about intercepting an incoming SMS message for a specific key word; for example, "Hi", so that I can read that sms containing "Hi" in it and delete it after reading the message, and if that message doesn't contain any such text then it wouldn't be deleted and instead saved in the inbox.
Look for Broadcast Receiver, this is dependent on the apps installed on the phone but you can give your app priority for listening to messages. Although, when a notification is shown, the message won't be in the SMS Database yet, so you will need to use abortBroadcast() to stop other apps being notified. See example below:
public class MessageReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Bundle pudsBundle = intent.getExtras();
Object[] pdus = (Object[]) pudsBundle.get("pdus");
SmsMessage messages =SmsMessage.createFromPdu((byte[]) pdus[0]);
Log.i(TAG, messages.getMessageBody());
if(messages.getMessageBody().contains("Hi")) {
abortBroadcast();
}
}
And you would need to declare the receiver in the manifest, like so:
<receiver android:name="com.encima.smsreceiver.MessageReceiver" android:exported="true">
<intent-filter android:priority="999">
<action android:name="android.provider.Telephony.SMS_RECEIVED"></action>
</intent-filter>
</receiver>
Finally, make sure you have the permission to RECEIVE_SMS in the manifest.
As of Android 4.4, what you ask to do in the question is impossible
Since Android version 4.4, If your app is not the default messaging app, you won't be able to perform those things promised in the accepted answer. You can neither stop the default messaging app from getting notified, nor you can delete an sms message.
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();
}
}