I have the following code called in onCreate to keep track of whether the user is connected to firebase/can connect to firebase:
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
connected = snapshot.getValue(Boolean.class);
}
#Override
public void onCancelled(DatabaseError error) {
connected = false; // not called in the observations below
}
});
Observed behavior:
A. When initialized:
onDataChange is called with connected set to false
onDataChange is called with connected set to true quickly thereafter (assuming had internet connection)
B. Before the 1 minute:
Turning off wifi or data will cause connected set to false
Turning on wifi or data will cause connected set to true
C. At 1 minute
connected set to false via onDataChange
D. After 1 minute
onDataChange is not called regardless of going on wifi/data
Expected behavior:
A'. Same as A
B'. Same as B
C'. Nothing
D'. Same as B.
I've seen other questions regarding losing authentication, but I haven't seen auth issues in my logs. Additionally the precise time of one minute seems very specific and was not seen in other similar question/answers.
Note that I'm using firebase 9.0.2.
Based on what I have observed on 9.0.2, the connection is closed after one minute when it is not needed. To leave the connection open would needlessly drain the battery.
A connection is needed when:
DatbaseReference.setValue() is executed.
Query.addListenerForSingleValueEvent() is executed
A ValueEventListener or ChildEventListener has been added and not removed
After a connection has been closed, one of the listed events will cause it to reopen. For events 1 and 2, the connection will close again after one minute. When a listener has been added (not for single value), the connection remains open until the listener is removed.
Related
I am implementing a service that uses the autoconnect feature of bluetoothGatt to connect to the device and monitor it while it is being connected.
I work on the assumption that the device is already bonded (a coworker is responsible for that part) so autoconnect should not have any problems
my code is as follows:
//the callback is for the class I have created that actually does the connection
class BTService: Service(), CoroutineScope, BTConnection.Callback {
private val btReceiver by lazy { BluetoothStateReceiver(this::btStateChange) } //receiver for bt adapter changes
private var connection:BTConnection? = null
private var readJob:Job? = null
override fun onCreate() {
buildNotificationChannels()
registerReceiver(btReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) //since I can't register this receiver in AndroidManifest any more I did it here
}
private fun btStateChange(enabled: Boolean) {
if (enabled)
startConnecting()
else
stopConnection()
}
private fun startConnecting() {
val address = prefs.address //get the current saved address
val current = connection //get the current connection
//try to stop the current connection if it is different than the one we want to set up
if (current != null && !current.address.equals(address, true))
current.stop()
if (address.isNullOrBlank())
return
//then we create a new connection if needed
val new = if (current == null || !current.address.equals(address, true)) {
Injections.buildConnection(application, address, this)
} else {
current
}
connection = new
new.connect()
}
//this is one of the callbacks from BTConnection.Callback
override fun connected(address: String) {
if (address != connection?.address) return
val cn = connection ?: return
showConnectionNotification()
val notification = buildForegroundNotification()
startForeground(FOREGROUND_ID, notification)
readJob?.cancel()
readJob = launch {
cn.dataFlow //this is a flow that will be emmitting read data
.cancellable()
.flowOn(Dispatchers.IO)
.buffer()
.onEach(this#BTService::parseData)
.flowOn(Dispatchers.Default)
}
}
private suspend fun parseData(bytes:ByteArray) { //this is where the parsing and storage etc happens
}
private fun stopConnection() {
val cn = connection
connection = null
cn?.stop()
}
override fun disconnected(address: String) { //another callback from the connection class
showDisconnectNotification()
stopForeground(true)
}
my code that stops the connection is
fun stop() {
canceled = true
if (connected)
gatt?.disconnect()
launch(Dispatchers.IO) {
delay(1000)
gatt?.close()
gatt = null
}
}
my code is based (and affected) by this really good article I read:
https://medium.com/#martijn.van.welie/making-android-ble-work-part-2-47a3cdaade07
I have also created a receiver for boot events that will call
context.startService(Intent(context, BTService::class.java))
just to make sure that the service is created at least once and the bt receiver is registered
my questions are:
a) is there a chance that my service will be destroyed while it is not in foreground mode? i.e. when the device is not near by and bluetoothGat.connect is suspending while autoconnecting? is it enough for me to return START_STICKY from onStartCommand() to make sure that even when my service is destroyed it will start again?
b) if there is such a case, is there a way to at least recreate the service so the btReceiver is at least registered?
c) when should close() be called on bluetoothGatt in case of autoconnect = true? only when creating a new connection (in my example where I call Injections.buildConnection)? do I also call it when the bluetoothadapter is disabled? or can I reuse the same connection and bluetoothGatt if the user turns the bluetooth adapter off and on again?
d) is there a way to find out if autoconnect has failed and will not try again? and is there a way to actually test and reproduce such an effect? the article mentioned above says it can happen when the batteries of the peripheral are almost empty, or when you are on the edge of the Bluetooth range
thanks in advance for any help you can provide
a-b) If your app does not have an activity or a service that is in the foreground, the system may kill it at anytime. Pending or active BLE connections doesn't affect the system's point of view when to kill the app whatsoever. (When it comes to scanning for advertisements, the story is completely different though.)
The general approach to make sure autoConnects stay alive is to have a foreground service running at all the time. So don't stop it while the device is currently not connected, if you want to have a pending connection. There is no point in using Job Scheduler, WorkManagers etc. since having a foreground service should be enough to keep the app process alive, and pending/active connections are kept alive as long as the app is. The app does not use any cpu% at all when waiting for pending BLE connections. However some Chinese phone makers are known to not follow the Android documentation, by sometimes killing apps even though they have running foreground services.
c) Each BluetoothGatt object represents and refers to an object inside the Bluetooth process running on the same phone. By default the system allows a total of 32 such objects (last time I checked). In order to release these precious resources, you call close(). If you forget, you will have a leak, meaning your app or some other app might not be able to create a BluetoothGatt object. (When app processes exit, their BluetoothGatt objects are however closed automatically). The API is a bit strangely designed, that there is both a disconnect method and a close method. But anyway, the disconnect method gracefully initiates a disconnection of the connection and you will then get an onConnectionStateChange callback telling when the disconnection is complete. You must however call close in order to free the resource, or call connect if you'd like to re-connect, or you can take an action a bit later. Calling close on a connected BluetoothGatt object will also disconnect, but you won't get any callback due to the object is being destroyed at the same time.
Since all BluetoothGatt objects represents objects in the Bluetooth process, these will "die" or stop working when you turn off Bluetooth, since that involves shutting down the Bluetooth process. This means you need to recreate all BluetoothGatt objects when Bluetooth is restarted. You can call close on the old objects, but it won't do anything since they're dead. Since the documentation doesn't say anything about this, I suggest you call close anyway to be on the safe side if the behaviour is changed in the future.
d) To detect if a connectGatt call fails and will not try again, you can listen to the onConnectionStateChange callback. If this gives an error code, such as 257, it usually means that the system has reached maximum number of connections, or maximum number of some resource. You can test this out by simply initiating pending connections to a bunch of different Bluetooth device addresses.
I would not trust the statement that new connection attempts would be aborted if the peripheral is low on battery or being on the "edge of Bluetooth range". I'd be glad to see a pin point to Android's Bluetooth source code where this happens, since I really believe this is not true at all.
First of all, if you are intending to distribute your app to Google Play Store, you need to be targeting minimum api level 29 if I'm not mistaken, hence you should be using either JobService along with JobScheduler or WorkManager, instead of Service. This is to support the background limitations from Oreo(26) onwards.
a) if you properly implement any of the two options I mentioned above, you can write a proper service that will not terminate unless you stop it. Here are some resources on JobService : (resource1, resource2, resource3)
b) You can re-register as you please upon the onStartJob() method of your JobService, which will recreate your app.
c) Each time you are done with the peripheral ble device, you need to close the gatt connection with it. Here is a snippet from the BluetoothGatt class
/**
* Close this Bluetooth GATT client.
*
* Application should call this method as early as possible after it is done with
* this GATT client.
*/
public void close() {
Also, from the BluetoothAdapter class javadoc, you can see that all the connections are terminated gracefully when ble is disabled.
/**
* Turn off the local Bluetooth adapter—do not use without explicit
* user action to turn off Bluetooth.
* <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth
* system services, and powers down the underlying Bluetooth hardware.
* <p class="caution"><strong>Bluetooth should never be disabled without
* direct user consent</strong>. The {#link #disable()} method is
* provided only for applications that include a user interface for changing
* system settings, such as a "power manager" app.</p>
* <p>This is an asynchronous call: it will return immediately, and
* clients should listen for {#link #ACTION_STATE_CHANGED}
* to be notified of subsequent adapter state changes. If this call returns
* true, then the adapter state will immediately transition from {#link
* #STATE_ON} to {#link #STATE_TURNING_OFF}, and some time
* later transition to either {#link #STATE_OFF} or {#link
* #STATE_ON}. If this call returns false then there was an
* immediate problem that will prevent the adapter from being turned off -
* such as the adapter already being turned off.
*
* #return true to indicate adapter shutdown has begun, or false on immediate error
*/
#RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean disable() {
d) I am not sure on what callback will be triggered. To reproduce, the two items you mentioned seem like valid cases to try.
I hope this helps you perfect your project!
For the past few days i've been trying to show the online/offline status of a user.. For this i have a register activity where they register and their info gets saved in firebase and if they exit an activity i have overriden its onstop method and made the value to set to offline... but if the user suddenly loses internet connection it still shows online.. i cant change it to offline because internet is needed to make a change in the database and the use doesn't have internet... SO how do i set the database value to offline... i googled quite some stuff about this but didnt find anything... Can anyone please help me out please
My code
#Override
protected void onStart() {
super.onStart();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue("True");
}
#Override
protected void onStop() {
super.onStop();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue(false);
}
What you're trying to do is known as a presence system. The Firebase Database has a special API to allow this: onDisconnect(). When you attach a handler to onDisconnect(), the write operation you specify will be executed on the server when that server detects that the client has disconnected.
From the documentation on managing presence:
Here is a simple example of writing data upon disconnection by using the onDisconnect primitive:
DatabaseRef presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");
In your case this could be as simple as:
protected void onStart() {
super.onStart();
fetchData();
DatabaseReference onlineRef = mDatabaseReference.child("UserData").child(UID).child("Online");
onlineRef.setValue("True");
onlineRef.onDisconnect().setValue("False");
}
Note that this will work in simple cases, but will start to have problems for example when your connection toggles rapidly. In that case it may take the server longer to detect that the client disappears (since this may depends on the socket timing out) than it takes the client to reconnect, resulting in an invalid False.
To handle these situations better, check out the sample presence system in the documentation, which has more elaborate handling of edge cases.
As explained in some answers:
On Android, Firebase automatically manages connection state to reduce bandwidth and battery usage. When a client has no active listeners, no pending write or onDisconnect operations, and is not explicitly disconnected by the goOffline method, Firebase closes the connection after 60 seconds of inactivity.
The problem is that after 60s, even after I go to an activity with a complete new reference, event listener, etc.. It still says it is disconnect, when in fact, it is not.
val connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected")
var connectListener : ValueEventListener? = null
fun checkConnection() {
connectListener = connectedRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val connected = snapshot.getValue(Boolean::class.java)!!
if (connected) {
Log.d("FRAG", "CONNECTED")
else{
Log.d("FRAG", "DISCONNECTED")
}
}
override
fun onCancelled(error: DatabaseError) {
System.err.println("Listener was cancelled")
}
})
}
override fun onDetach() {
super.onDetach()
if (connectListener != null){
connectedRef.removeEventListener(connectListener)
}
}
How can I make sure I maintain or create a new connection to Firebase? I call the checkConnection method every onAttach of a fragment and onStart of an activity.
If you have an active listener on any data that is read from the server, the connection should remain open unless you've explicitly called goOffline() in your code. Note that .info/connected itself does not require reading from the server, so does not keep the connection open.
It seems you're using the realtime database to build an presence system on an otherwise Firestore based app. In that case: Cloud Firestore uses a gRPC-based protocol to talk between client and server, while the Firebase Realtime Database uses web sockets. They're in no way compatible or even comparable. Keeping an active listener on data in Firestore does not keep a connection to RTDB open. That's why the example in the Firestore documentation also writes an actual data node to the realtime database.
Stream<Event> checkInternetConectivity() {
Stream<Event> connectivityCheck = _firebaseDatabase.reference().child('.info/connected').onValue;
Stream<Event> randomCheck = _firebaseDatabase.reference().child('connected').onValue;
return Rx.combineLatest2(connectivityCheck, randomCheck,(connectivityCheck, _) => connectivityCheck as Event);}
}
Firebase automatically disconnects from the realtime database in android after 60 seconds if there are no active listeners and listening to '.info/connected' isn't enough to keep the connection active. Creating another stream to listen to a random node in realtime database as a way around to this automatic disconnection.
This is my workaround to this problem in Dart/Flutter
I've got an app which connect itself programatically to a wifi connection. My problem is, I want to handle the case, that the password is wrong. I want to detect that the password is not correct in runtime. To be precise I've got a progressdialog running while the connection is established, so if the password is wrong the progressdialog is just shown all the time and can't be skipped. A further note: I handled a password which is less than 8 characters by using this code:
if(!m_wifiManager.enableNetwork(netId, true)) {
progressDialogConnecting.dismiss();
createInfoMessageDialog(CONST.WIFI_CON_FAILED_TITLE, CONST.WIFI_CON_FAILED_MSG_CONFAILURE);
m_wifiManager.reconnect();
return;
}
If the key for the wifi connection is less than 8 characters, this if-case gets triggered. But if it is longer than 8 characters and wrong I get an endless state of showing the progress dialog.
What I exactly want to ask: how do I handle 1. wrong password 2. connection states (just like Android system showing me the toasts "Connected to Wifi xyz") ? AND is it even possible to handel the first one (wrong password)?
Here is the code, that did not work for handling connection established event (this is just the wifirecevier, I also registered it in the activity):
public class WifiReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
if (intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false)){
if(wrongNetworkConnected)
progressDialogConnecting.dismiss();
}
}
} else {
}
}
}
}
Edit: What I am currently doing, is that I have a Handler which tells me to whom I am connected. That's useful because I can say that after the reconnect() I am reconnected to the old network (current network) and not the new one - so apparently the password could be wrong (or something else), because I could not connect to the new network.
The problem about this method is that first of all it takes too much time and secondly it is not reliable. I can lie and say that if you will get reconnected to your current network it is the fault of a wrong password, but actually it is not 100% sure that you cannot reconnect because of this - it may also have other reasons. So I am still searching for a simple feedback/handle from the suplicant that the password is wrong, just like the android api does in the wifi settings of each android device...
My problem is, I want to handle the case, that the password is wrong.
After some research I found this post which is not marked as answered but it still worked for me very well.
Here is the if-case in which the program jumps (already tested several times by me) if there is an authentication error --> e.g. wrong password:
int supl_error=intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
if(supl_error==WifiManager.ERROR_AUTHENTICATING){
// DO SOMETHING
}
NOTE: As seen in the linked post above this if-case should appear in a BroadcastReceiver adding the intent WifiManager.SUPPLICANT_STATE_CHANGED_ACTIONto the receiver-registration in your activity-class.
I'm working on an Android app that supports sending music to a ChromeCast. We'd like users to be able to cast entire music playlists while the app runs in the background.
When my Nexus 7 is not connected to USB power and I turn the screen inactivity timeout to 15 seconds in the settings, the app will disconnect from the ChromeCast about 90 seconds after the device powers off its screen.
I've identified that I'm getting a MediaRouter.Callback call to onRouteUnselected, and since that's the callback I get when a user disconnects from a route, I'm handling it by tearing down the ApplicationSession.
When I plug back in and check the logcat, I see this message around the same time:
I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }
Can I do anything to avoid the route being unselected when the app is in the background, or is there something else I can do to get the behavior I want?
I eventually got around this by refusing to disconnect the message streams and tear down the session when the route was disconnected under these conditions, and silently re-select the route when it became available again. The route gets deselected, but it does not affect my casting session.
To do this, I check to see if the route exists when it's unselected.
public void onRouteUnselected(final MediaRouter router, final RouteInfo route) {
if (!onUiThread()) {
new Handler(Looper.getMainLooper()).post((new Runnable() {
#Override
public void run() {
onRouteUnselected(router, route);
}
}));
return;
}
boolean isThisRouteAvailable = doesRouterContainRoute(router, route);
mRouteToReconnectTo = null;
if (isThisRouteAvailable) {
// Perform code to close the message streams and tear down the session.
} else {
// The route was unselected because it's no longer available from the router,
// so try to just keep playing until the message streams get disconnected.
mRouteToReconnectTo = route;
// Short-circuited a disconnect.
}
}
Later, when the route comes back, we can immediately re-select it.
#Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
super.onRouteAdded(router, route);
// if mRouteToReconnectTo is not null, check to see if this route
// matches it, and reconnect if it does with router.selectRoute(route)
}
#Override
public void onRouteSelected(final MediaRouter router, final RouteInfo route) {
if (!onUiThread()) {
new Handler(Looper.getMainLooper()).post((new Runnable() {
#Override
public void run() {
onRouteSelected(router, route);
}
}));
return;
}
if (areRoutesEqual(mRouteToReconnectTo, route)) {
// Short-circuited a reconnect.
mRouteToReconnectTo = null;
return;
}
mRouteToReconnectTo = null;
// Standard post-selection stuff goes here
}
There's no good way to compare two RouteInfo's, so I ended up writing a helper function that compared their description strings.
Rooster's answer is perfectly feasible and actually provides good insight as to how to re-connect to a route once it comes back online....
but....just to give further insight on what's going on....
You're getting...
I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }
because when the device goes to sleep and is NOT plugged into a power source, the WIFI hardware is going into a low-power profile mode (and possibly shutting down entirely). This results in packet loss and subsequently causes the MedaRouter to fire the onRouteUnselected callback.
To prevent the Wifi from turning off you could set a WakeLock on the Wifi in the following manner:
WifiLock wifiLock;
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF , "MyWifiLock");
wifiLock.acquire();
Using the flag WifiManager.WIFI_MODE_FULL_HIGH_PERF will keep the WIFI hardware alive and active when the device goes to sleep. Caution, this flag is only available to API 12 and above.
I tried using the WifiManager.WIFI_MODE_FULL flag when creating the WifiLock, but that didn't seem to do the trick.
Obviously anyone using any type of WifiLock or WakeLock should take considerable care in making sure locks released when no longer needed. Also, beware this will cause battery drain when the device screen is off.
If you used the sample code (Android in this case), you're probably doing this...
mSession.setStopApplicationWhenEnding(true);
mSession.endSession();
...when the route is unselected. If you instead do this...
mSession.setStopApplicationWhenEnding(false);
mSession.endSession();
...then you can clean up the session, but the Chromecast will keep the application alive. When the route becomes available again (or possibly when the user picks the device again) you can build a new session. I have yet to explore how to determine if the new session is talking to a "brand new" instance of the application or to the application left running from another session, but I'll update this answer when I do.