I've been working for a while around a lack of ability to restrict Crashlytics network usage under certain conditions. For example - on roaming, on metered networks and so on..
According to SDK documentation, only two options I found addressing somehow this:
"Opt Out" on runtime by simply not initialize Crashlytics
built-in User consent dialog before sending a crash report
This API's are very limited, because:
Not initialize Crashlytics not only prevents network access but also prevents any chance Crashlytics will save locally the crash report so that eventually the event will be sent. Not to mention there is no good way to opt out in runtime, besides overriding brutally the Thread.setUncaughtExceptionHandler
consent dialog not making any sense to the user if a crash happens in the background.
My question basically:
Am I missing something?
Is there any way to restrict Crashlytics network access?
My motivation comes from a need to prevent situation my app uses network bandwidth potentially can cost money to the user under certain conditions, although "cellular network" or "use data over roaming" device settings are enabled.
There is two step process which we are using in our app, this is not using Mobile Network and also not related to roaming as well.
Saving crash logs to file in app data partition i.e. on device:
Refer to this link
Upload crash data to server when WiFi network is connected:
public class ConnectivityStatusReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
final ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
if (activeNetworkInfo != null && activeNetworkInfo.getTypeName() == "WIFI") {
// post your crash logs to server
}
}
}
There is not a way to restrict the internet usage for Crashlytics in an application. But how I would fix it is to either give the user information that Crashlytics is using roaming or just save the crash report locally and send them once the user in connected with a wifi network. Also you could give the user the choice if he prefers to save the crash reports locally or send them right away over roaming.
Save the ErrorLog locally on the device
Upload the ErrorLog once a connection with a wifi is established
You should be able to use the ConnectivityManager to get the state of the Wi-Fi adapter. From there you can check if it is connected or even available.
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (mWifi.isConnected()) {
// post error logs
}
I'm the former maintainer of the Crashlytics SDK for iOS/macOS. I'm relatively unfamiliar with the Android version of the SDK, and definitely unfamiliar with Android in general. But, I'll give this a shot.
What you want to do is something that has been requested on the iOS side a few times. I would have loved to do it actually, because it seems pretty terrible to force end-users to incur these costs. However, the iOS SDK's networking and start up routine are both very complex and very delicate. It is highly challenging to guarantee that crashes are delivered and that there are zero possibilities for inconsistent states. I believe that Android is simpler here, but I cannot say this with authority.
The iOS SDK, however, does have some hooks for additional client-level functionality. Check out the warning around one of those APIs:
* #warning Just implementing this delegate method will disable all forms of synchronous report submission. This can
* impact the reliability of reporting crashes very early in application launch.
Basically, in order to satisfy the contract of this particular API, some techniques to improve reporting reliability have to be disabled. The thing is, sometimes it's worth it. Lots of apps decide to make this tradeoff. Many apps also delay initializing Crashlytics to eek out extra performance. This has a huge impact on reporting reliability, but that's another tradeoff app developers have to make.
I think you should seriously consider just not enabling Crashlytics in these situations, if you can easily detect them. Maybe Android even allows end-users to do this on a per-app basis? In that case, you'd never get any reports anyways. I would imagine that your user base is diverse enough that missing some reports in these situations wouldn't be that terrible. Or, perhaps you'd like to surface it as a user-facing option.
You could even do something totally crazy, like override Thread.setUncaughtExceptionHandler yourself, and buffer up exceptions during this situation to disk. And then, replay them to Crashlytics when things are better. Turn it into an open source lib. I bet people will love it! Possibly not the Crashlytics' Android team though ;) (Hi!)
This is also basically the same recommendation Gastón offered above, with just some extra context around what I've seen on the iOS side. Also shoot the Crashlytics people an email asking for this. I think it's a great idea.
I was reading the docs at fabric and I just found something interesting:
Crashlytics processes exceptions on a dedicated background thread, so
the performance impact to your app is minimal. To reduce your users’
network traffic, Crashlytics batches logged exceptions together and
sends them the next time the app launches.
So I was thinking about a workaround since the crashes without network are being sent when the app is initialized, you could prompt any dialog to the user at startup telling if they want to connect to the internet to send crash reports to solve current problems in the app. ( so you are using their network data with the user consent)
The thing here is we don't know how to stop crashlytics from sending this reports, they will store it at the device if the device is offline and send it back after the device is just with connection again as it states here
Another way out could be just log important fatal issues with the custom login they offer and just send them, you can find more about it here
To make sure that sending crash reports has the smallest impact on
your user’s devices, Crashlytics logs have a maximum size of 64 KB.
When a log exceeds 64 KB, the earliest logged values will be dropped
in order to maintain this threshold.
In conclusion, after reading the docs, there is no way to disable Crashlytics to constantly send reports, you can only manage the network connection of the user when you want them to send or not reports. It's like connectivity is the switch on and off of Crashlytics at the moment.
It just talks about "Reducing network traffic" but not about disabling Crashlytics network at all.
Another way that comes to my head is to make a flag for starting crashlytics, and then use inside a condition Crashlytics.start()
When you want to disable it just do the following
CrashlyticsCore core = new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build();
Fabric.with(this, new Crashlytics.Builder().core(core).build());
Playing with these two things is the only way I think it's possible to reduce network usage of Crashlytics at the moment.
You can restrict Crashlytics network usage by a static field.
Define a static global variable, according to its value write logic for your Crashlytics.
private static boolean INROAMING = false;
Now you can use below logic for your purpose. Like don't provide co
if(isInternetIsConnected(this).equals("MOBILE")){
if(INROAMING){
//write your logic for context here, when phone is in roaming
//restrict logic for crashlytics
}else{
//write your logic for context herem, when phone is not in roaming
//un-restrict logic for crashlytics
}
}
public boolean checkForRoaming() {
final TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
PhoneStateListener phoneStateListener = new PhoneStateListener() {
#Override
public void onServiceStateChanged(ServiceState serviceState) {
super.onServiceStateChanged(serviceState);
if (telephonyManager.isNetworkRoaming()) {
// In Roaming
INROAMING = true;
} else {
// Not in Roaming
INROAMING = false;
}
// You can also check roaming state using this
if (serviceState.getRoaming()) {
// In Roaming
INROAMING = true;
} else {
// Not in Roaming
INROAMING = false;
}
}
};
}
public String isInternetIsConnected(Context context) {
try {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
assert cm != null;
#SuppressLint("MissingPermission") NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork != null) { // connected to the internet
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
// connected to wifi
return "WIFI";
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
// connected to the mobile provider's data plan
checkForRoaming();
return "MOBILE";
}
} else {
// not connected to the internet
return "NO CONNECTION";
}
} catch (Exception e) {
e.printStackTrace();
}
return "NO CONNECTION";
}
}
There is not a way to restrict the internet usage for Crashlytics in an application. You can give choice to user, if he prefers to save the crash reports locally or send them right away over roaming.
Save the ErrorLog locally on the device
Upload the ErrorLog once a connection with a wifi is established.
You can use ConnectivityManager to get the state of the network. You can check if it is connected or even available.
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (mWifi.isConnected()) {
// send error logs
}
Above code you can add in broadcastreceiver which will notify connection
Example:
public class ConnectivityStatusReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (mWifi.isConnected()) {
// send error logs
}
}
}
I want to write an xposed module where I can redirect VoLTE calls through VoWiFi. I want to know which method decides if the call is going through VoLTE or VoWifi and I would hook that method and get the work done.
Basically using WiFi I want to give the network an illusion that the device is using mobile data and send calls through wifi
Note : I am new to android programming. Excuse me if my question looks vague.
So you want to give illusion that you are connected to WIFI even though you are on mobile data.
the way we check that is this:
ConnectivityManager cm =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
So what you can do is, hook getType method of ConnectivityManager and in afterhook method always return ConnectivityManager.TYPE_WIFI
the way to do that is by calling
param.setResult(ConnectivityManager.TYPE_WIFI);//may be you need casting here
in afterHook.
read more about connectivity here:
https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html
Edit: I hope you are familiar with afterHook and beforeHook methods and how xposed works.
I want to create a method which tells if the device is online. As far as I understand ConnectivityManager tells only if the device is connected to a network. This doesn't mean that the device is connected to the internet. To ensure that the device is online I'm using InetAddress.getByName("google.com").isReachable(3); but I can't use it on the main thread. I can create a separate thread to check the connectivity and then use a callback function but is there another way? I don't want my app to do anything before it is connected. Do you have any solutions? Thank you!
With any networking, there isn't a guaranteed way to check whether or not you are connected to an endpoint without actually sending data. Even if the device is connected to a network, has an ip address, recently received data, e.t.c, it doesn't mean that you still have a connection.
I would suggest allowing the user to progress into your application as much as possible, queuing up the requests to your server in the background whilst a connection is established. Use the same framework to resend data if the connection is lost whilst the user is using the app. Make the connection to the server as transparent to the user as possible, unless it fails to connect after ~1 minute
try this:
public static boolean isOnline(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnected()) {
return true;
}
return false;
}
Is there a way to access Android's broadcast that the WiFi connection is currently a captive portal (requires web login)? Android seems to do have this built in. If not a broadcast receiver, is there a way to check for the result of the captive portal check? I believe it's using this class, which is hidden from the API:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.L_preview/android/net/CaptivePortalTracker.java
Prior to 4.2, it was probably using:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.2_r1/android/net/wifi/WifiWatchdogStateMachine.java
Background:
I had been using my own method to detect whether WiFi likely required a login. I would wait for a WiFi connected state, then ping a site and make sure there was a response. This seemed to work great in most cases. Another strategy is to do a HttpRequest and check for a redirect or the response body you receive back, similar to Android's strategy in the classes listed above.
However, new to Lollipop is that the mobile data connection is used when WiFi does not have connectivity. This means my ping method will still return results, and that a redirect would not happen, as the request would be routed over the mobile data.
Is there a way to get Android's current status of a WiFi captive portal? If not, can we make sure a request goes over WiFi even when there's no connectivity as seen by Android?
You'd have to use the new ConnectivityManager.setProcessDefaultNetwork API to force your app to communicate over the captive portal. See https://github.com/pawitp/muwifi-autologin/commit/f045fe36f1fd98a106ea652e2d56f7ddfc871760 for an example.
Complete code added:
final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
for (Network net : cm.getAllNetworks()) {
if (cm.getNetworkInfo(net).getType() == ConnectivityManager.TYPE_WIFI) {
Utils.logDebug(TAG, "Seting process network to " + net);
/*Since API 23 ConnectivityManager.setProcessDefaultNetwork(net);
is deprecated, use: */
cm.bindProcessToNetwork(net);
}
}
Does anybody know of a way to have my application constantly check firstly if there is internet connectivity..and secondly if there is internet connection to call a function...It needs to check for internet coverage say every 10 minutes etc?
Use ConnectivityManager to check for network connectivity.
ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo.isConnected())
{
// do something
}
To wake the device every 10 minutes, use the API provided by AlarmManager. I don't have practical experience using the AlarmManager; please refer to this tutorial, which seems fairly comprehensive
Do keep in mind that waking the device this often will have a noticeable effect on battery life.