I need the ability to both send and delete SMSes from my app. The code I'm using is shown. Sending an SMS does not require my app to be the default SMS app, however deleting does.
What is weird is that the code to send the SMS does work when the app is not default, but not when it is (the code to delete SMSes works the other way around, however this is expected).
I assume that this problem arises because even though another application may be sending the SMSes, only the default SMS app can write to the SMS provider, so I need to write to them manually. However, when having my app as default, other apps can still send SMSes using the same code provided, which is strange.
I have also tried writing to the SMS providers manually, specifically to content://sms/outbox and tocontent://sms/sent, none of which have worked. Although, the latter does display the message in the preinstalled SMS app.
public static void sendSMS(String phoneNo, String msg, Context context) {
try {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNo, null, msg, null, null);
Toast.makeText(context, "Message Sent",
Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(context,e.getMessage().toString(),
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
public static void deleteSMS(Context context, String message, String number) {
try {
Log.i(MainActivity.TAG, "delteSMS: try statement");
Uri uriSms = Uri.parse("content://sms/sent");
Cursor c = context.getContentResolver().query(uriSms,
new String[] { "_id", "thread_id", "address",
"person", "date", "body" }, null, null, null);
if (c != null && c.moveToFirst()) {
do {
long id = c.getLong(0);
long threadId = c.getLong(1);
String address = c.getString(2);
String body = c.getString(5);
// Log.i(MainActivity.TAG, "Found SMS: " + body);
if (message.equals(body) && address.equals(number)) {
Log.i(MainActivity.TAG, "Deleting sms with" + threadId);
context.getContentResolver().delete(
Uri.parse("content://sms/" + id), null, null);
}
} while (c.moveToNext());
}
} catch (Exception e) {
Log.i(MainActivity.TAG, "Could not delete SMS from inbox: " + e.getMessage());
}
}
TL;DR
I need to know how to send SMSes from an application which is set as default, since the usual procedure does not work.
Thanks
Related
I want to write simple text data to my NXP MiFARE DesFire EV1 (NDEF Type 4 Tag). However, the writing process always fails with an IOExcetion
For writing I get the NFC-Tag I use the function write:
private void write(String mimeType, String text, Tag tag) throws IOException, FormatException {
NdefRecord[] records = {createRecord(mimeType, text)};
NdefMessage message = new NdefMessage(records);
Ndef ndef = Ndef.get(tag);
ndef.connect();
ndef.writeNdefMessage(message);
ndef.close();
}
The result in the third line (Ndef ndef = Ndef.get(tag)) is the following:
TAG: Tech [android.nfc.tech.IsoDep, android.nfc.tech.NfcA, android.nfc.tech.Ndef]
From this I assumed, the Tag is formatted correclty (as NDEF).
Now, when calling ndef.connect() it just says java.io.exception without any additional information about the error. The other parts of the code, that get called is appended.
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
String serialId = Utility.bytesToHex(tag.getId());
Log.d("[WriteCard]", "Serial Number: " + serialId);
Toast.makeText(this, serialId, Toast.LENGTH_SHORT).show();
}
}
}
// When the write Button is clicked
public void onClick(View view) {
if (nfc_adapter == null) {
Toast.makeText(this, "No NFC", Toast.LENGTH_SHORT).show();
return;
}
int id = view.getId();
Intent intent = getIntent();
try {
write("type/1", spinner_location.toString(), tag);
}
catch(Exception e) {
Log.d("[WriteCard]", e.toString());
Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show();
}
}
The NXP Tag Info App reports the following:
IC Type: MiFARE DESFire EV1
Type 4 Tag
NFC data set access: Read&Write
Additional Info: The writing process with Android-Apps like NFC TagWriter by NXP or wakdev NFC Tools works without any problem, thus I assume, the Tag is working correctly.
Really trying to write to a Tag on a Button click will always be unreliable and also using enableForeGroundDispatch is also unreliable in real life as it is highly likely that the Tag will be moved slightly that it will go out of range and thus generate I/O errors.
The two Apps you mention don't do it the way you are trying to do.
Also the documentation says connect and writeNdefMessage
May cause RF activity and may block. Must not be called from the main application thread.
and you are calling these from the main (UI) thread.
Your button just needs to setup the action that "when Tag comes in to range, immediately write text"
e.g. some thing like
private String text;
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
if(text.isEmpty()) {
// Nothing to write so read
String serialId = Utility.bytesToHex(tag.getId());
Log.d("[WriteCard]", "Serial Number: " + serialId);
Toast.makeText(this, serialId, Toast.LENGTH_SHORT).show();
} else {
// Have some text to write
try {
write("type/1", text, tag);
} catch(Exception e) {
Log.d("[WriteCard]", e.toString());
Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show();
}
// Reset the write trigger
text = "";
}
}
}
// When the write Button is clicked
public void onClick(View view) {
text = spinner_location.toString();
}
Also you really need to check that your Tag is a Formatted Ndef Tag before your try and write to it.
e.g. something like
private void write(String mimeType, String text, Tag tag) throws IOException, FormatException {
NdefRecord[] records = {createRecord(mimeType, text)};
NdefMessage message = new NdefMessage(records);
Ndef ndef = Ndef.get(tag);
if(ndef != null) {
// It's an already formatted Ndef Tag
ndef.connect();
ndef.writeNdefMessage(message);
ndef.close();
} else {
// Try and format at write
.... "get(tag)" for Ndef formattable type and check not null
}
}
The final point is using the old enableForegroundDispatch is unreliable, so use the Newer and better enableReaderMode and onTagDiscovered API instead.
This also solves the calling connect etc on the wrong thread as onTagDiscovered is automatically in it's own thread.
Also enableReaderMode when you disable the "Platform" sounds it does not prompt the user to remove the Tag from range before you have had a chance to write to it (You can play your own sound after a successful write)
See https://stackoverflow.com/a/64921434/2373819 for an example of enableReaderMode
I have an auto reply sms Android application I built and I don't want the auto reply (sent sms) to show in the default messaging app. I have searched and searched and couldn't find an answer. Is there a way to bypass writing the sent sms into the default messaging app?
Here my BroadcastReciever I am using to get the data and send out the message
public class SmsReceiver extends BroadcastReceiver {
ParseUser user = ParseUser.getCurrentUser();
// Auto reply message composed of the current reply and url from that business
String msg = user.getString("myCurrentReply") + " " + user.getString("couponUrlChosen");
List smsFromList = user.getList("smsFrom");
String userName = (String) user.get("username");
#Override
public void onReceive(final Context context, Intent intent) {
Bundle bundle = intent.getExtras();
Object messages[] = (Object[]) bundle.get("pdus");
SmsMessage smsMessage[] = new SmsMessage[messages.length];
for (int n = 0; n < messages.length; n++) {
smsMessage[n] = SmsMessage.createFromPdu((byte[]) messages[n]);
}
final String pno = smsMessage[0].getOriginatingAddress();
user.put("lastSmsFrom", pno);
user.saveInBackground();
// show first message
Toast toast = Toast.makeText(context, "Received SMS: " + smsMessage[0].getMessageBody(), Toast.LENGTH_LONG);
toast.show();
// Check Phone Number from SMS Received against Array in User Row
ParseQuery<ParseObject> query = ParseQuery.getQuery("_User");
Log.d("Username: ", userName);
query.whereEqualTo("username", userName);
query.whereContainedIn("lastSmsFrom", smsFromList);
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> smsList, ParseException e) {
if (e == null) {
Log.d("Errors", "none");
if (smsList.size() == 0) {
// Send SMS
sendSms(pno, msg);
// Add Phone number to smsFrom in currentUsers Row
user.addUnique("smsFrom", pno);
// Save Phone Number in Array
user.saveInBackground();
Log.d("List size: ", " " + smsList.size());
}
} else {
Log.d("Error Message: ",
e.getMessage());
}
Log.d("Already sent to this number today. ", " " + smsList.size());
}
});
}
private void sendSms(String phonenumber, String message) {
SmsManager manager = SmsManager.getDefault();
manager.sendTextMessage(phonenumber, null, message, null, null);
}
}
Prior to KitKat, SMS sent using SmsManager require the app sending the message to insert it into the Provider, so it would just be a matter of omitting that.
Starting with KitKat, any app that is not the default SMS app and uses SmsManager to send messages will have the messages automatically written to the Provider for it by the system. There's no way to prevent this, and, furthermore, the app won't be able to delete those messages, either, as it won't have write access to the Provider.*
The app that is the default SMS app is responsible for writing its outgoing messages, so it would be able to omit that step. The system does no automatic writes for the default SMS app.
* There is a security hole in 4.4 only, by which a non-default app can gain write access to the Provider. It is detailed in my answer here, but it will not work in versions after KitKat.
I'm trying to get my device registration ID with GCM. My code to do so is contained within an AsyncTask which is called from my main thread.
Main code
try
{
String deviceId = new Gcm().execute(this.activity).get(5, TimeUnit.SECONDS);
Log.i("Login", "User device id returned as " + deviceId);
return deviceId;
}
catch (Exception e)
{
Log.e("Login", "Exception", e);
}
GCM Class
public class Gcm extends AsyncTask<Activity,Void,String>
{
#Override
protected String doInBackground(Activity... params)
{
Log.i("GCM", "Attempting to get device id");
Activity activity = params[0];
try
{
Log.i("GCM", "Getting GCM instance");
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(activity.getApplicationContext());
Log.i("GCM", "Registering with GCM");
String regId = gcm.register(PROJECT_NUMBER);
Log.i("GCM", "Device registered, registration ID=" + regId);
return regId;
}
catch (IOException e)
{
throw new IllegalStateException(e);
}
}
}
And here is my log dump
07-28 13:07:39.093 I/GCM﹕ Attempting to get device id
07-28 13:07:39.093 I/GCM﹕ Getting GCM instance
07-28 13:07:39.093 I/GCM﹕ Registering with GCM
07-28 13:07:44.103 E/Login﹕ Exception
java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:176)
at android.os.AsyncTask.get(AsyncTask.java:507)
I/GCM﹕ Device registered, registration ID=XXXXXX
So for some reason, calling gcm.register() is blocking until my timeout exception is hit. Does anyone have any idea why that might be happening?
The reason is cause you're executing gcm with
.get(5, TimeUnit.SECONDS);
This call blocks the thread for 5 seconds, however due to different reasons like unstable network connection the registration process can take more than 5 seconds. It is not the best approach to do what you want.
Take a look at this example, taken from official GCM demo:
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your app.
sendRegistrationIdToBackend();
// For this demo: we don't need to send it because the device will send
// upstream messages to a server that echo back the message using the
// 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
Incorporate this example and everything should work.
Using an AsyncTask like that doesn't make sense. AsyncTasks are used to avoid blocking the thread, but by using the blocking get() call you are blocking the thread anyway. You may as well call register() directly in that case.
You are getting a timeout because you're blocking for 5s, but GCM is taking longer than 5s to register. This could be due to bad network conditions as it can take a while for a network request to time out. Or maybe it is just taking more than 5s to get the registration ID.
I'm trying to send an email through the Android's email client. However, when I attempt to send the E-mail to the client, it says the email is null. I find that strange since I'm passing the correct parameters in JavaScript, but the app wont read it?
I have been researching this issue for awhile now and have searched Stack Overflow for awhile.
Anyways, my goal with this is to understand why my parameters are not being passed through.
JavaScript:
function sendEmailToAndroid (emailAddresses, CC, subject) {
alert(emailAddresses); //This displays the correct address
if (IsAndroidDevice()) {
window.ccs.sendEmail(emailAddresses, CC, subject);
}
}
Java:
#JavascriptInterface
public void sendEmail(String[] emailAddress, String[] CC, String subject){
Intent email = new Intent(Intent.ACTION_SEND);
email.putExtra(Intent.EXTRA_EMAIL, emailAddress);
email.putExtra(Intent.EXTRA_CC, CC);
email.putExtra(Intent.EXTRA_SUBJECT, subject);
email.setType("message/rfc822");
if(emailAddress == null || emailAddress.length == 0){
Toast ctoast = Toast.makeText(getApplicationContext(), "There is no email for this customer", Toast.LENGTH_LONG);
ctoast.setGravity(Gravity.CENTER, 0, 0);
ctoast.show();
Log.d(LOG, "Email: "+emailAddress);
}
else{
try{
startActivity(Intent.createChooser(email, "Send mail..."));
} catch(ActivityNotFoundException e){
Toast.makeText(getApplicationContext(), "Failed to initalize email client!", Toast.LENGTH_LONG).show();
}
}
}
Now when I debug the JavaScript, all parameters are being passed correctly. However, when it is read in the android app, all the values come back as null, or undefined. Which is not correct.
I know this question has been asked multiple times, but nobody has been able to come up with a working answer from what I have seen.
Im working on an app to intercept text messages and depending on the sending #, pop up with a custom alert. I have it working beautifully with a broadcast receiver, however if the user has goSms installed the onReceive() method is never called as goSms aborts it before it ever reaches my app.
To get around this, Im trying a content observer on content://sms/
Its working just fine, however the onChange() is called twice, with exactly the same parameters. Ive tried to check the time stamps, but they are the same, as is the type and every other parameter I have set.
From what I've seen, this is a common issue, but not one that I've seen answered anywhere.
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
querySMS();
}
protected void querySMS() {
Cursor cur = getContentResolver().query(u, null, null, null, null);
cur.moveToNext(); // this will make it point to the first record, which is the last SMS sent
String type = cur.getString(cur.getColumnIndex("type"));
String body = cur.getString(cur.getColumnIndex("body")); //content of sms
String add = cur.getString(cur.getColumnIndex("address")); //phone num
if (type.equals("1")) {
if (add.equals(Test.SENDER)) {
String[] bodys = body.split(" ", 7);
if (bodys[0].equals("test")) {
test = true;
}
cat = bodys[1];
level = bodys[2];
urgency = bodys[3];
certainty = bodys[4];
carrier = bodys[5];
message = bodys[6];
final Intent intent = new Intent(context, AlertActivity.class);
Bundle b = new Bundle();
b.putString("title", cat);
b.putString("certainty", certainty);
b.putString("urgency", urgency);
b.putString("level", level);
b.putString("message", message);
b.putBoolean("test", test);
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
carrierName = manager.getNetworkOperatorName();
if (carrierName.replaceAll(" ", "").equals(carrier)) {
context.startActivity(intent);
} else {
//testing
Toast.makeText(context, carrierName.replaceAll(" ", ""), Toast.LENGTH_LONG).show();
}
}
}
}
Because of the onChange() being fired twice, Im getting two alerts as well. I cannot for the life of me figure out a way around this.
If the two are identical:
store each message recv'd
compare it to previous messages recv'd
if not found, process
if found, discard the message
The life of the messages stored should be infinitesimal, a little circular buffer of 5 messages should be fine.
here is my code, it works fine for me
public class SmsObserver extends ContentObserver {
private Context context;
private static int initialPos;
private static final String TAG = "SMSContentObserver";
private static final Uri uriSMS = Uri.parse("content://sms/sent");
public SmsObserver(Handler handler, Context ctx) {
super(handler);
context = ctx;
initialPos = getLastMsgId();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
queryLastSentSMS();
}
public int getLastMsgId() {
Cursor cur = context.getContentResolver().query(uriSMS, null, null, null, null);
cur.moveToFirst();
int lastMsgId = cur.getInt(cur.getColumnIndex("_id"));
Log.i(TAG, "Last sent message id: " + String.valueOf(lastMsgId));
return lastMsgId;
}
protected void queryLastSentSMS() {
new Thread(new Runnable() {
#Override
public void run() {
Cursor cur =
context.getContentResolver().query(uriSMS, null, null, null, null);
if (cur.moveToNext()) {
try {
if (initialPos != getLastMsgId()) {
// Here you get the last sms. Do what you want.
String receiver = cur.getString(cur.getColumnIndex("address"));
System.out.println(" Receiver Ph no :"+receiver);
// Then, set initialPos to the current position.
initialPos = getLastMsgId();
}
} catch (Exception e) {
// Treat exception here
}
}
cur.close();
}
}).start();
}
}//End of class SmsObserver
You can save last message's id and compare it to the id of the message that is returned by cur in onChange. you then can simply disregard the message if ids are the same.
// might contain mistakes, but you'll get the idea:
protected void querySMS() {
Cursor cur = getContentResolver().query(u, null, null, null, null);
cur.moveToNext();
if (lastId == cur.getLong(cur.getColumnIndex("_id")))
return;
lastId = cur.getLong(cur.getColumnIndex("_id"));
... //continue as it was
}
However - GO SMS only prevents other app's from recieving Broadcast if the user selected this option (Recieve Settings - Disable other message notification) - so if the user does not want other apps to disturb him - I think it's good idea not to do so.
I just use SharedPreference to remark last SMS info (like: id\type ...). if it is the same, I will return.