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.
Related
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
I have call receiver which I want to display dialog on incoming call only. For that I have created a global Boolean variable and trying to changes its value to true in ringing state. But when call disconnects, code always picks default value of Boolean not the updated value given in ringing state. The variable is num. Why it always give false value though its value getting true in ringing state only. Here is the code:
public class phonerece extends BroadcastReceiver{
private Boolean num = false;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
//some task here
}
} else if (extraState != null) {
if (extraState.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
//task
} else if (extraState
.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
if (num) {
phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
//call dialog }
}
} else if (extraState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
if (checknumber() != null) {
Log.e("Nummber", "found");
} else {
Log.e("Number", "Not Found");
num = true;
}
}
}
}
public String checknumber() {
String res = null;
try {
ContentResolver resolver = context.getContentResolver();
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
Cursor c = resolver.query(uri, new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
if (c != null) { // cursor not null means number is found contactsTable
if (c.moveToFirst()) { // so now find the contact Name
res = c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
}
c.close();
}
} catch (Exception ex) {
/* Ignore */
}
return res;
}
}
You should use static variables (private static num = false) or save your variable in SharedPreferences (it's better), because BroadcastReceivers are not saved between broadcasts. Every broadcast will create a new instance of the BroadcastReceiver, at least if registered automatically via the manifest.
(Your code snippet looks broken, the num variable is missing its type? This answer assumes its type is boolean.)
This sounds like a multithreading problem. Threads in java may cache values of variables, because synchronizing through the main memory is more expensive. You can force the synchronization by flagging the field in question as volatile. This keyword is explained here.
When a field is flagged as volatile, Threads may not cache its value, and all modifications to the variable become visible to all other Threads.
private volatile boolean num = false;
hypothesis.getHypstr() is always one value, even after I change the keyword!
I am using pocketsphinx to do speech recognition, and I let the user change what to listen for. This value is stored in my shared preferences. My problem is that hypothesis.getHypstr() is only called when the previous keyword is spoken.
For example:
If it is set to default keyword (oranges and rainbows), then the recognition works fine. But, if the user changes it to "hello computer" then the onPartialResult method still only gets called when the user says hello, and hypothesis.getHypstr() is still oranges and rainbows.
onCreate:
try {
Assets assets = new Assets(MyService.this);
File assetDir = assets.syncAssets();
setupRecognizer(assetDir);
Log.v(TAG, "SET UP DIRECTORIES STARTING LISTENING!");
mSpeechRecognizer.startListening("usersKeyword");
} catch (IOException e) {
e.printStackTrace();
Log.v(TAG, e.toString());
}
setupRecognizer()
public void setupRecognizer(File sphinxDir) {
try {
mSpeechRecognizer = defaultSetup()
.setAcousticModel(new File(sphinxDir, "en-us-ptm"))
.setDictionary(new File(sphinxDir, "cmudict-en-us.dict"))
.setBoolean("-allphone_ci", true)
.setKeywordThreshold(1e-40f)
.getRecognizer();
} catch (IOException e) {
e.printStackTrace();
}
mSpeechRecognizer.addListener(this);
mSpeechRecognizer.addKeyphraseSearch("usersKeyword", keyword.getString("keyword", "oranges and rainbows"));
}
onPartialResult:
#Override
public void onPartialResult(Hypothesis hypothesis) {
if (hypothesis == null) { //no one spoke
return;
}
String text = hypothesis.getHypstr();
Log.v(TAG, "TEXT: " + text + "hypothesis.getHypstr: " + hypothesis.getHypstr());
if (text.equals(keyword.getString("keyword", "oranges and rainbows"))) { //Only happens when text is oranges and rainbows, even after changing preference value!!!
Log.v(TAG, "Heard user keyword!");
mSpeechRecognizer.cancel();
mSpeechRecognizer.startListening("usersKeyword");
}
}
Why is hypothesis.getHypstr() always only one value, even after I change the value of the addKeyphraseSearch?
Thanks,
Ruchir
EDIT:
I actually stop and start the service every time the user changes their input, and so onCreate() is called every time the user changes their data.
FULL CODE:
https://gist.github.com/anonymous/47efc9c1ca08d808e0be
You do not need to destroy the service, you create it once with onCreate.
You can set the command in onStartCommand:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
recognizer.cancel();
recognizer.addKeyphraseSearch("usersKeywords", intent.getStringExtra("keyword"););
recognizer.startListening("usersKeywords");
}
From the other class which is a user of the service you start service with intent:
Intent i = new Intent(this, MyService);
i.putExtra("keyword", "hello");
startService(i);
For more details read documentation
You need to call mSpeechRecognizer.addKeyphraseSearch() every time you want to change the key phrase.
I have a single incident where a complete duplicate of a entry was made into the database (the same user comment appeared twice). They had different object IDs but were otherwise the exact same. It was slower than usual to finish the posting and only happened once out of dozens of comments, so I want to say it was a Parse issue during the saveInBackground call. Even so, I expect a service like Parse to be a little more robust. As my first time working with Android though, I also can't be sure nothing is wrong on my end. Any help? Also just any criticisms? This is the method called when the user hits a comment submission button:
private void submitComment() {
String text = commentText.getText().toString().trim();
Intent intent = getIntent();
String ID = intent.getStringExtra("imageID");
String parentID = intent.getStringExtra("parent");
// Set up a progress dialog
final ProgressDialog loadingDialog = new ProgressDialog(CommentSubmitActivity.this);
loadingDialog.setMessage(getString(R.string.publishing_comment));
loadingDialog.show();
Comment comment = new Comment();
comment.setText(text);
comment.setUser((ParseUser.getCurrentUser()));
if (ID.equals("#child")) {
comment.setParent(parentID);
comment.setImage("#child");
ParseQuery<ParseObject> query = ParseQuery.getQuery("Comment");
query.getInBackground(parentID, new GetCallback<ParseObject>() {
public void done(ParseObject parentComment, ParseException e) {
if (e == null) {
int numChild = parentComment.getInt("numChild");
parentComment.put("numChild", ++numChild);
parentComment.saveInBackground();
} else {
Log.d("numChild: ", "error");
}
}
});
} else {
comment.setImage(ID);
comment.put("numChild", 0);
ParseQuery<ParseObject> query = ParseQuery.getQuery("ImageUpload");
query.getInBackground(ID, new GetCallback<ParseObject>() {
public void done(ParseObject image, ParseException e) {
if (e == null) {
int numComments = image.getInt("numComments");
image.put("numComments", ++numComments);
image.saveInBackground();
} else {
Log.d("numComments: ", "error");
}
}
});
}
comment.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
loadingDialog.dismiss();
finish();
}
}
});
}
I encountered similar problem like yours.
I created an app where user can create account and add photo to it and list of objects (friends in my case).
Once when I was testing it user was created twice.
I went through my code and my my suspicions are connected with async calls.
I see that you use asynchronous parse api in you application so no fragment of code is waiting for response and blocking the rest of operations.
You cannot control when parse server will response.
What I did I just put all synchronous requests in my custom async code (AsyncTask in Android).
Hope that my answer somehow meeets your expectations.
I tried this test but getHits() always return 0. Anyone can help me?
public void testSettingsAboutShazamClickOnLink() {
Instrumentation inst = getInstrumentation();
IntentFilter intentFilter = new IntentFilter(android.content.Intent.ACTION_SENDTO);
intentFilter.addDataScheme("mailto");
ActivityMonitor monitor = inst.addMonitor(intentFilter, null, false);
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto:"));
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
assertEquals(0, monitor.getHits());
inst.getContext().startActivity(emailIntent);
monitor.waitForActivityWithTimeout(5000);
assertEquals(1, monitor.getHits());
inst.removeMonitor(monitor);
}
I solved this problem. I used Solo of Robotium and apparently Solo inserts an ActivityMonitor that matches every Filter, so when you call startActivity, it calls execStartActivity in the Instrumentation class, whose code is
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
As soon as there is an ActivityMonitor that matches something the mHits of that monitor is increased end the "cycle for" breaks. Given that calling Solo was the first thing I did, the first ActivityMonitor to check is the one of Solo, given that this matches everything, any other ActivityMonitors added after it is not checked, so no mHits variable is increased for the other monitors. If you want to understand more see the execStartMonitor method of Instrumentation class, remembering that this method is called after startActivity is called.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/app/Instrumentation.java#Instrumentation.execStartActivity%28android.content.Context%2Candroid.os.IBinder%2Candroid.os.IBinder%2Candroid.app.Activity%2Candroid.content.Intent%2Cint%29