In my app i have to receive USER_PRESENT and AIRPLANE_MODE intents. But each intent is broadcasted twice (sometimes thrice) which i don't want. I was trying to resolve this issue using SharedPreferences but i need to identify each intent uniquely (with any 'id' if it has or timestamp etc).
The following is the code where i was trying to allow only one onReceive call.
#Override
public void onReceive(Context context, Intent intent) {
final SharedPreferences instanceCount = context.getSharedPreferences("Instances",Context.MODE_WORLD_WRITEABLE);
SharedPreferences.Editor instance_editor = instanceCount.edit();
if(instanceCount.getBoolean("instance",true))
{
instance_editor.putBoolean("instance",false);
instance_editor.commit();
}
else
{
instance_editor.putBoolean("instance",true);
instance_editor.commit();
return;
}
.
.
.
.
}
Can any one please help in solving this. Thank You :)
Those are system broadcasts and you cannot control how many times they are broadcasted. Make your receiver(s) idempotent, e.g., it stays in the same state even if it is invoke multiple times. Instead of counting broadcasts, save the current state of your app and check it when you receive a broadcast. If you are already in the desired state, do nothing. Otherwise, change the state as appropriate and save it.
Related
What are the pro and cons of registering the broadcast receiver via manifest file and also via code?
i registered my receiver via code so that user have the option to start and stop it, somehow, i noticed the receiver is not 'listening' when the app got killed.
It is normal?
thanks.
Yes, its normal. You registered broadcast in activity via code, app got killed and broadcast too.
If U want your broadcast works, when app doesn't launched, define broadcast in AndroidManifest file.
If U want to your user can "unregister" broadcast, you can add extra logic to your onRecieve function.
When you let to your user "unregister" receiver, just save it in your prefs, or in DB, whatever, and check this value before do work:
#Override public void onReceive(Context context, Intent intent) {
boolean isUnregisteredByUser = getSharedPreferences("MyPrefs", context.MODE_PRIVATE)
.getBoolean("IS_UNREGISTERED", false);
if(!isUnregisteredByUser){
/* do stuff, handle intent etc */
}
}
This is easy way, but maybe bad way...
The projcet involves modifying Android to block/deny launching of particular apps. So if user tries to launch any of the pre-listed apps by any means, that app shouldn't get launched.
I'll be modifying Android source code. What are the different ways to do it? Suggestions are welcome.
I think one of the possible ways to do that is to modify Process.java in frameworks/base/core/java/android/os.
This class has start function which receives uid as an input paramenter. I think from this parameter we can get to know that for which app new process is being created. Is this the correct way to do it?
Also, as the apps are launched using intents, is it possible (by modifying any class) to remove particular intents?
In general, when an intent can be handled by an application(its activity, service), and the user prefers that application to handle it. The ActivityManagerService(Ams) in Android framework will first see whether the corresponding application of the target activity/service is still alive. If it hasn't been started yet or killed, the Ams will start that application by calling startProcessLocked method.
final ProcessRecord startProcessLocked(String processName,
ApplicationInfo info, boolean knownToBeDead, int intentFlags,
String hostingType, ComponentName hostingName, boolean allowWhileBooting,
boolean isolated) {
//you can get the uid from ApplicationInfo.uid
...
startProcessLocked(app, hostingType, hostingNameStr);
...
}
private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr) {
//prepare its uid, gid, gids for starting that process
...
Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, null, null);
...
}
So in that method, it start a new process. The Process.start is what you have mentioned in your question, and it finally calls Zygote to fork a new process.
I think intercepting the application startup process in Ams is a better way. You can get more information about this application and it is also the upstream of your Process.start method.
UPDATE:
Just noticed that you were thinking about restricting intent. An intent can be handled by multiple applications, so we cannot restrict applications from sending a particular intent. But we can modify the resolving process. Resolving means the Android framework need to determine which activity/service can handle this intent. If there are multiple choices and the user haven't set any preference, then the following dialog will appear:
So it is possible to modify the resolving process to let the Android framework discard the fact that your specific application is able to handle that intent. I think this is also a way to do your work but it is much more compilicated because Android resolves intent differently for activity, service and receiver. For activity, Ams will call resolveIntent in PackageManagerService to get which activity to start. For service, it is the resolveService method in PackageManagerService get called. So you need to handle them differently. But since they will all get a list of ResolveInfo in their implementation, you can just filter out your application easily. For example, in resolveIntent in PackageManagerService:
#Override
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "resolve intent");
List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);
//filter out here!!!!
return chooseBestActivity(intent, resolvedType, flags, query, userId);
}
You can easily get the ApplicationInfo from the ResolveInfo if you take a look at ResolveInfo.java.
For receiver, it is even more complicated because receivers registered in AndroidManifest.xml and by registerReceiver(...) are different. If the intent in broadcastIntent hasn't set the flag FLAG_RECEIVER_REGISTERED_ONLY(common case), then the resolving result would be a list of receivers that listen to that broadcast, which may contain two kinds of receivers. For those in AndroidManifest.xml, Ams will call queryIntentReceiver in PackageManagerService to get a list of receivers that listen to a broadcast. For those registered dynamically by registerReciever(...), they are managed by ActivityManagerService not PackageManagerService, so Ams will directly call mReceiverResolver.queryIntent to get those receivers. The mReceiverResolver is defined as:
final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
= new IntentResolver<BroadcastFilter, BroadcastFilter>() {
...
}
So what you need to do is override the queryIntent method to filter out the receivers in your application. For ContentProvider, the method is resolveContentProvider in PackageManagerService. Same way to handle it.
How can I tell from my Application whether it was started/resumed from my BroadcastReceiver or not?
I intercept outgoing calls (android.intent.action.NEW_OUTGOING_CALL). If getString(Intent.EXTRA_PHONE_NUMBER) is one of a set of numbers, I abort that call (setResultData(null)) and instead startActivity my app, putExtraing the particular number. If (and only if) coming from the BroadcastReceiver, I want to be able to put up an alert that's basically "use this app with this number/return to call". However, sometimes when I return to the app from elsewhere, the number still seems to be in the extras of the intent, even though I haven't come from the BroadcastReceiver. I tried checking for the FLAG_ACTIVITY_NEW_TASK flag, but it shows up sometimes when not coming from the BroadcastReceiver.
As you said: you can pass any parameters to your activity, indicating that it was called from your BroadcastReceiver. However, when resuming to your activity some code might be executed again - potentially causing unwanted outcomes. When I had once a similar issue I stored/overwrote some information in the intent, e.g.
myActivity.getIntent().putExtra("phoneNumber", "nil");
What worked for me was, that I overwrote the extra in the intent after it has been processed while finishing an ActionMode (let's say with "nil"). So later I was able to evaluate that information in onResume(), e.g.:
#Override
public void onResume() {
super.onResume();
String phoneNumber = getIntent().getExtras().getString("phoneNumber")
if ("nil".equals(PhoneNumber)) {
...
}
}
Just did a small test and it works pretty well.
Hope this helps ... Cheers!
I would like to know if it is possible to replicate in my visible Activity changes which are ocurring in a not visible activity.
For example, I have a loop with an integer incrementing in Activity A, then I invoke Activity B passing the integer as an extra.
Is there a way to see reflected the increments (which are ocurring in A) in B?
Thanks in advance.
Since int is a primitive type, a variable with int type will not be changed when you pass it into activity B.
I would suggest you pass in a wrapper of int type (i.e. class IntWrapper { int value; })
The change will be reflected when you increase value.
You can also pass data using broadcast recievers:
In the listening activity/fragment create the reciever and register it (do this in oncreate or onresume or something, maybe even onrestart). The activity will now listen for a broadcast and you test if it's a broadcast meant for that reciever using a built in keyword. Because a the broadcast is done using an intent, you can also get and use other data stored in that intent (like your int):
my_broadcast_reciever = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {String action = intent.getAction();
if (action.equals("finish_buy_activity"))
//do whatever
}
}
};
registerReceiver(my_broadcast_reciever, new IntentFilter("theKeywordThisBCRRespondsTo"));
Dont forget to unregister any recievers in onpause or ondestroy (whatever makes most sense depending on where you register the reciever):
{unregisterReceiver(my_broadcast_reciever);
Now, in the activity/fragment which you want to use to send the data, fire off the intent like so (you might want to create and keep the intent at a higher level so you don't keep on creating a new object every update):
Intent sintent = new Intent("theKeywordThisBCRRespondsTo");
//maybe also do: sintent.addInt(int); or something
sendBroadcast(sintent);
Keep in mind, this might be a little overkill in certain cases, but it is a great way to communicate and transmit data between activities/fragments.
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();
}
}