Android: how to use ContentResolver in background - android

I have class that extends BroadcastReceiver and gets all new sms.
When I get new sms I want to search the phonebook to check if phone numbers inside this message are in phone book, and if they are, send sms with result to another phone.
I use ContentResolver to check the phone numbers, but to use it I need to use it inside Activity or have pointer to activity, but my sms listener works in background.
Is there a way to search phone book without activity or to get valid activity from context or intent that I get when recieving new sms?
I tried to use something like that:
Activity act = new Activity();
DBManager dbm = new DBManager(act);
ArrayList<MyContact> res = dbm.phoneSearch(sms_ar[1]);
SmsSender sms = new SmsSender(act);
if(res.size() > 0){
String answer = res.get(0).getName()+CSStatic.SMS_SEPARATOR+res.get(0).getPhone();
for(int z = 1; z < res.size(); z++){
answer = answer+CSStatic.SMS_SEPARATOR+res.get(z).getName()+CSStatic.SMS_SEPARATOR+res.get(z).getPhone();
}
Log.d("sms", "Answer: "+answer);
sms.smsAnswer(answer, sms_ar[2]);
} else {
String phone = dbm.getPhoneToQuery(sms_txt);
sms.smsQueryNext(sms_txt, phone);
}
act.finish();
but it don't work :P
In DBManager I search for phones and in SmsSender I send new sms.

You need a valid Context object (an activity or service for example, these are subclasses of Context). These are created by android, do not use the constructor to create these objects!
To get the needed context in a BroadcastReceiver is explained here: https://stackoverflow.com/a/6074829/1127492, its the first parameter of onReceive().

Related

How to use Room Database inside BroadcastReceiver?

Good day. My app needs to receive SMS and what I am trying to do right now is I want to insert the data to the database from BroadcastReceiver as soon as the app received the message but I failed. Here is my code.
public class SmsReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ViewModel model = ViewModelProviders.of((FragmentActivity) context).get(ViewModel.class);
Bundle bundle = intent.getExtras();
Object[] objects = (Object[]) Objects.requireNonNull(bundle).get("pdus");
SmsMessage[] smsMessages = new SmsMessage[Objects.requireNonNull(objects).length];
Sms sms = new Sms();
for (int i = 0; i < smsMessages.length; i++) {
smsMessages[i] = SmsMessage.createFromPdu((byte[]) objects[i]);
sms = new Sms(
smsMessages[i].getOriginatingAddress(),
smsMessages[i].getMessageBody(),
smsMessages[i].getTimestampMillis()
);
}
model.insert(new ReplyToVotersQueue(sms.getBody(),sms.getAddress()));
}
}
As we can see, I am trying to insert the data to database but I've got an error that says
java.lang.RuntimeException: Unable to start receiver
mgb.com.smspoll.Services.SmsReceiver: java.lang.ClassCastException:
android.app.ReceiverRestrictedContext cannot be cast to
androidx.fragment.app.FragmentActivity
I found from the internet while searching the answer to this problem that BroadcastReceiver cannot convert to Activity because BroadcastReceiver can run without context. If that so then is there any
way to insert the data to database using room database after receiving the SMS? Since my app is an SMS Poll, I need to run my app in the background process.
The problem here is that you're trying to retrieve the ViewModel outside an activity or a fragment, which is not a good practice and also doesn't work.
The solution is this:
Separate your Room DB operation from ViewModel in a separate singleton class. Use it in ViewModel and any other place required. When Broadcast is received, write data to DB through this singleton class rather than ViewModel.
For more, please take a look at this post:
The correct way to obtain a ViewModel instance outside of an Activity or a Fragment

Trouble Accurately Logging Several Back-to-Back SMS Delivery Intents for Android

I am developing an android app that rapidly sends multiple SMS messages almost simultaneously to the same number (Please note this is for research purposes). I need this app to also track it's delivery intent, which I do by adding a bundle containing the information I want the intent to carry. The problem I am running into, however, is as follows:
If I use no pending intent flag (aka 0) or FLAG_IMMUTABLE, the bundle value always remains the same as the first message sent and I get a dump of all the messages delivered.
If I use FLAG_UPDATE_CURRENT, I get a bundle value that updates sporadically/randomly (multiple intents contain the same bundle value), and I get a dump of all the messages delivered.
If I use FLAG_ONE_SHOT, I get the accurate bundle value assigned (the value as it is meant to be received) BUT I only get a dump of a very small amount of the intents, and that amount stays small(~1%) even if I happen to send over 1000 messages back-to-back.
Here is the portion of the message sending method responsible for sending the SMSs in MainActivity:
int z = 0;
for(int j=1; j<=messageCnt;j++){
try {
Intent dI = new Intent("SMS_DELIVERED");
Bundle b = new Bundle();
b.putString("MSGNUM",Integer.toString(z+1));
dI.putExtras(b);
dI.putExtra("MNUMSTR",Integer.toString(z+1));
PendingIntent deliveredIntent = PendingIntent.getBroadcast(getApplicationContext(),0,dI,PendingIntent.FLAG_ONE_SHOT);
deliveredPendingIntents.add(z,deliveredIntent);
SmsManager.getDefault().sendTextMessage(phoneNum, null, message[z], null, deliveredIntent);
b.clear();
}
catch (Exception e){
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
crashTrack.add((Long.toString(System.currentTimeMillis())).concat(",").concat("'").concat(sw.toString()).concat("'\n"));
//reset all incremental value back one for proper repeat
j=j-1;
continue; //jump back to for loop and reattempt send.
}
}
Then, wherever I want (whether it be in a new app or in the current app), I put the following to register my receiver class.
//Create Broadcast receivers for sent and delivered intents
SmsDeliveredReceiver SDR = new SmsDeliveredReceiver();
//register the receivers
registerReceiver(SDR, new IntentFilter("SMS_DELIVERED"));
And finally, my broadcast reciever class for the intent, SmsDeliveredReciever:
public class SmsDeliveredReceiver extends BroadcastReceiver {
protected static int sentCount = 0;
#Override
public void onReceive(Context context, Intent dI){
/*File delivDumpName = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),MainActivity.dateTime.concat("DelivDump.txt"));
File delivfailDumpName = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),MainActivity.dateTime.concat("DelivFailDump.txt"));*/
Bundle bundle = dI.getExtras();
switch (getResultCode()){
case Activity.RESULT_OK:
if(bundle!=null){
String msgNum = bundle.getString("MSGNUM","0");
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Msg ").concat(msgNum).concat("'\n"));
} else {
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Msg ").concat("Number Unknown'\n"));//Integer.toString(sentCount)).concat("'\n"));
}
break;
case Activity.RESULT_CANCELED:
if(bundle!=null){
String msgNum = bundle.getString("MSGNUM","0");
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Sms Failed to Deliver.',' Msg ").concat(msgNum).concat("'\n"));
} else {
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Sms Failed to Deliver.',' Msg ").concat("Number Unknown'\n"));//Integer.toString(sentCount)).concat("'\n"));
}
break;
}
/*MainActivity.writeFiles(delivDumpName,String.valueOf(MainActivity.delvDump));
MainActivity.writeFiles(delivfailDumpName, String.valueOf(MainActivity.delvFailDump));*/
sentCount++;
}
}
Note that the exact same thing happens for the SendIntent. I've read all of the Google Android SDK Docs, and stackoverflow so far has provided answers that only work for alerts/alarms and not SMS intents, where the only existing tutorials and forum entries for SMS are single-send SMS. And no, I am not trying to send a multi-part message.
Desired output:
All SMS delivery intents should be captured in the broadcast receiver
Each intent captured must output its own unique message association number via the bundle.
The amount of intents captured should equal the number of messages successfully sent (yes, I do have a way to check whether the message got received or not)
Is this desired output even possible, or am I asking too much out of Android?
Whether the receiver is a separate app or not does not matter to me, though it is preferred if its a separate app.
Note: Everything in the app is functional and error-free except accurately tracking/logging the sent/delivered intents.

How to get the class name that fired an Activity? [duplicate]

Is there a way for an Activity to find out who (i.e. class name) has sent an Intent? I'm looking for a generic way for my Activity to respond to a received intent by sending one back to the sender, whoever that may be.
There may be another way, but the only solution I know of is having Activity A invoke Activity B via startActivityForResult(). Then Activity B can use getCallingActivity() to retrieve Activity A's identity.
Is it an external app you receive the intent from? You could use the getReferrer() method of the activity class
A simple example: I opened google map app to share some location with my app by using the share option of google maps. Then my app opens and this method call in the Activity:
this.getReferrer().getHost()
will return:
com.google.android.apps.maps
see documentation here: https://developer.android.com/reference/android/app/Activity.html#getReferrer()
Note that this requires API 22. For older Android versions see answer from ajwillliams
A technique I use is to require the application sending the relevant Intent to add a PendingIntent as a Parcelable extra; the PendingIntent can be of any type (service, broadcast, etc.). The only thing my service does is call PendingIntent.getCreatorUid() and getCreatorPackage(); this information is populated when the PendingIntent is created and cannot be forged by the app so I can get the info about an Intent's sender.
Only caveat is that solution only works from Jellybean and later which is my case.
Hope this helps,
This isn't incredibly direct but you can get a list of the recent tasks from ActivityManager. So the caller would essentially be the task before yours and you can fetch info on that task.
Example usage:
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasks(10000,ActivityManager.RECENT_WITH_EXCLUDED);
The above will return a list of all the tasks from most recent (yours) to the limit specified. See docs here for the type of info you can get from a RecentTaskInfo object.
Generally you don't need to know this. If the calling activity uses startActivityForResult(Intent, int), the callee can use setResult(int, Intent) to specify an Intent to send back to the caller. The caller will receive this Intent in its onActivityResult(int, int, Intent) method.
Based on your question, since you want to send an intent back to the sender startActivityForResult is a better choice than what I am going to suggest. But I needed to start activity B when a notification is clicked by the user and execute some code in activity B only if the sender activity is activity A. This is how I did it quite simply.
Inside Activity A:
String senderName = this.getClass().getSimpleName();
Intent clickIntent = new Intent(ActivityA.this, ActivityB.class);
clickIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
clickIntent.putExtra("SENDER_CLASS_NAME", senderName);
//I use PendingIntent to start Activity B but you can use what you like such as this.startActivity(clickIntent);
PendingIntent.getActivity(ActivityA.this, NOTIFICATION_ID, clickIntent, PendingIntent.FLAG_ONE_SHOT);
Inside Activity B:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
if(bundle.containsKey("SENDER_CLASS_NAME")){
String senderName = bundle.getString("SENDER_CLASS_NAME");
//Execute some code
Log.d("GCM", "Notifications clicked");
}
}
}
}
In my case, neither the accepted here and another most voted answer works perfectly.
Activity.getCallerActivity() works only for the sender which starts your activity by startActivityForResult, meaning that if the sender is also in your app and you have full control, it works, but not every external app starts others in that way.
Another most voted answer provides the solution for external app, but it too has issue. First I would prefer getAuthority() instead of getHost(), secondly, if the sender is a browser kind of app, like Chrome, both host and authority will give you the browsing web page's address host, such as www.google.com, instead of the app itself. So it depends on how you define 'sender', if you need to find out which web page starts you, the authority/host is good enough, but if you need to find out which app starts you, I am afraid authority/host can be trusted only when getScheme() gives you android-app instead of http.
Use UsageStatsManager and the old RecentTaskInfo to get the intent sender for OnCreate or onNewIntent:
public static String getTopMostThirdPartyPackage(Context context) {
String thisPak = null, tmp, top = null;
try {
thisPak = context.getPackageName();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
UsageStatsManager man = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
long now = System.currentTimeMillis();
UsageEvents uEvts = man.queryEvents(now - 5000,now); // query in 5 sec
UsageEvents.Event e = new UsageEvents.Event();
while (uEvts.getNextEvent(e)){
tmp = e.getPackageName();
if (!thisPak.equals(tmp)) {
top = tmp;
break;
}
}
} else {
ActivityManager man = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> tasks = man.getRecentTasks(3, 0);
for(ActivityManager.RecentTaskInfo info:tasks) {
tmp = info.baseIntent.getComponent().getPackageName();
if (!thisPak.equals(tmp)) {
top = tmp;
break;
}
}
}
} catch (Exception e) {
}
return top;
}
permissions :
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.GET_TASKS" />
intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

When my app is not running (only when not running), why does my BroadcastReceiver give me a nullpointer exception?

I will try to sum up simply because my original code is long. Suppose that My broadcast receiver is like this :
public class SMSBroadcastReceiver extends BroadcastReceiver {
static ArrayList<String> phonenumber_array = new ArrayList<String>();
static ArrayList<String> message_array = new ArrayList<String>();
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
SmsMessage[] sms = new SmsMessage[pdus.length];
for (int i = 0; i < sms.length; i++) {
sms[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
phonenumber_array.add(0,sms[i].getOriginatingAddress());
message_array.add(0,sms[i].getMessageBody());
com.test.ListActivityClass.myadapter.notifyDataSetChanged();
}
}
In my AndroidManifest.xml, The BroadcastReceiver has the IntentFilter "android.provider.Telephony.SMS_RECEIVED".
My ListActivityClass extends ListActivity, contains a myadapter which is an ArrayAdapter, like : myadapter = new CustomAdapter (this,com.test.SMSBroadcastReceiver.phonenumber_array,com.test.SMSBroadcastReceiver.message_array)
So my problem is : I send an sms from eclipse to emulator. whenever myadapter.notifyDataSetChanged(); is called in my BroadcastReceiver when my application is not running(completely stoped, no process launched, no activities) , I get a nullpointer exception.
When I remove myadapter.notifyDataSetChanged(); the problem is gone.
When the app is running, there is no problem as well, my application gets correctly the new sms received and displays it in the list.
So my guess was = When the app is running, the BroadcastReceiver can detect the myadapter, because the app is in memory, so it can notify changes. when the app isn't running, it is not in memory so BroadcastReceiver can't see any myadapter, so it gives a nullpointer exception. it makes sense.
So I tried to check : if the process is in memory, notify changes, else, don't notify ( because my applications gets all the sms received onCreate(), so it is not necessary.
ActivityManager activityManager = (ActivityManager) context
.getSystemService(context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningProcInfo = activityManager
.getRunningAppProcesses(); for (int i1 = 0; i1 <
runningProcInfo.size(); i1++) {
if (runningProcInfo.get(i1).processName.equals("com.test")) {
com.test.ListActivityClass.myadapter.notifyDataSetChanged();
}
But nothing changed. I still have a nullpointer exception when app is not running.
The worst thing is, I tried to replace phonenumber_array by phonenumber_array.add(0,"somephonenumber"); and message_array.add(0,"somemessage"); to check if a BroadcastReceiver runs in the backround even if the app is closed, my thoughts were correct. After the null pointer exception, I launched the app, and somephonenumber and somemessage were both present.
What do I miss? Does a BroadcastReceiver runs in background, or only when application launches? what is wrong?
First, static data members (and the comment-suggested replacement, Application) only live as long as your process lives. Your process may go away microseconds after onReceive() completes.
If you want to use static data members as a cache, that's fine, but durable data needs to live on the filesystem (database, flat file, etc.).
Second, myadapter should not be a static data member. You are leaking memory.
Either:
Move this BroadcastReceiver to be registered by the activity via registerReceiver(), or
Have this BroadcastReceiver send another ordered broadcast to be picked up by the running activity (if it is in the foreground), as is described in this blog post and is demonstrated in this sample project
You can create static boolean variable onScreen and set it to true in onResume method, and set to false in onPause method. It is the easiest way to understand if application is on the screen

How to get the sender of an Intent?

Is there a way for an Activity to find out who (i.e. class name) has sent an Intent? I'm looking for a generic way for my Activity to respond to a received intent by sending one back to the sender, whoever that may be.
There may be another way, but the only solution I know of is having Activity A invoke Activity B via startActivityForResult(). Then Activity B can use getCallingActivity() to retrieve Activity A's identity.
Is it an external app you receive the intent from? You could use the getReferrer() method of the activity class
A simple example: I opened google map app to share some location with my app by using the share option of google maps. Then my app opens and this method call in the Activity:
this.getReferrer().getHost()
will return:
com.google.android.apps.maps
see documentation here: https://developer.android.com/reference/android/app/Activity.html#getReferrer()
Note that this requires API 22. For older Android versions see answer from ajwillliams
A technique I use is to require the application sending the relevant Intent to add a PendingIntent as a Parcelable extra; the PendingIntent can be of any type (service, broadcast, etc.). The only thing my service does is call PendingIntent.getCreatorUid() and getCreatorPackage(); this information is populated when the PendingIntent is created and cannot be forged by the app so I can get the info about an Intent's sender.
Only caveat is that solution only works from Jellybean and later which is my case.
Hope this helps,
This isn't incredibly direct but you can get a list of the recent tasks from ActivityManager. So the caller would essentially be the task before yours and you can fetch info on that task.
Example usage:
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasks(10000,ActivityManager.RECENT_WITH_EXCLUDED);
The above will return a list of all the tasks from most recent (yours) to the limit specified. See docs here for the type of info you can get from a RecentTaskInfo object.
Generally you don't need to know this. If the calling activity uses startActivityForResult(Intent, int), the callee can use setResult(int, Intent) to specify an Intent to send back to the caller. The caller will receive this Intent in its onActivityResult(int, int, Intent) method.
Based on your question, since you want to send an intent back to the sender startActivityForResult is a better choice than what I am going to suggest. But I needed to start activity B when a notification is clicked by the user and execute some code in activity B only if the sender activity is activity A. This is how I did it quite simply.
Inside Activity A:
String senderName = this.getClass().getSimpleName();
Intent clickIntent = new Intent(ActivityA.this, ActivityB.class);
clickIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
clickIntent.putExtra("SENDER_CLASS_NAME", senderName);
//I use PendingIntent to start Activity B but you can use what you like such as this.startActivity(clickIntent);
PendingIntent.getActivity(ActivityA.this, NOTIFICATION_ID, clickIntent, PendingIntent.FLAG_ONE_SHOT);
Inside Activity B:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
if(bundle.containsKey("SENDER_CLASS_NAME")){
String senderName = bundle.getString("SENDER_CLASS_NAME");
//Execute some code
Log.d("GCM", "Notifications clicked");
}
}
}
}
In my case, neither the accepted here and another most voted answer works perfectly.
Activity.getCallerActivity() works only for the sender which starts your activity by startActivityForResult, meaning that if the sender is also in your app and you have full control, it works, but not every external app starts others in that way.
Another most voted answer provides the solution for external app, but it too has issue. First I would prefer getAuthority() instead of getHost(), secondly, if the sender is a browser kind of app, like Chrome, both host and authority will give you the browsing web page's address host, such as www.google.com, instead of the app itself. So it depends on how you define 'sender', if you need to find out which web page starts you, the authority/host is good enough, but if you need to find out which app starts you, I am afraid authority/host can be trusted only when getScheme() gives you android-app instead of http.
Use UsageStatsManager and the old RecentTaskInfo to get the intent sender for OnCreate or onNewIntent:
public static String getTopMostThirdPartyPackage(Context context) {
String thisPak = null, tmp, top = null;
try {
thisPak = context.getPackageName();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
UsageStatsManager man = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
long now = System.currentTimeMillis();
UsageEvents uEvts = man.queryEvents(now - 5000,now); // query in 5 sec
UsageEvents.Event e = new UsageEvents.Event();
while (uEvts.getNextEvent(e)){
tmp = e.getPackageName();
if (!thisPak.equals(tmp)) {
top = tmp;
break;
}
}
} else {
ActivityManager man = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> tasks = man.getRecentTasks(3, 0);
for(ActivityManager.RecentTaskInfo info:tasks) {
tmp = info.baseIntent.getComponent().getPackageName();
if (!thisPak.equals(tmp)) {
top = tmp;
break;
}
}
}
} catch (Exception e) {
}
return top;
}
permissions :
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.GET_TASKS" />
intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

Categories

Resources