@@ -98,7 +98,11 @@ public class ConfigRealtimeHttpClient {
9898 private boolean isRealtimeDisabled ;
9999
100100 /** Flag to indicate whether or not the app is in the background or not. */
101- private boolean isInBackground ;
101+ private Boolean isInBackground ;
102+
103+ // The HttpUrlConnection and auto-fetcher for this client. Only one of each exist at a time.
104+ private HttpURLConnection httpURLConnection ;
105+ private ConfigAutoFetch configAutoFetch ;
102106
103107 private final int ORIGINAL_RETRIES = 8 ;
104108 private final ScheduledExecutorService scheduledExecutorService ;
@@ -391,14 +395,44 @@ public void run() {
391395 }
392396 }
393397
394- void setRealtimeBackgroundState (boolean backgroundState ) {
395- isInBackground = backgroundState ;
398+ public void setRealtimeBackgroundState (boolean backgroundState ) {
399+ // Make changes in synchronized block so only one thread sets the background state and calls
400+ // disconnect.
401+ synchronized (isInBackground ) {
402+ isInBackground = backgroundState ;
403+
404+ // Propagate to ConfigAutoFetch as well.
405+ if (configAutoFetch != null ) {
406+ configAutoFetch .setBackgroundState (backgroundState );
407+ }
408+ // Close the connection if the app is in the background and their is an active
409+ // HttpUrlConnection.
410+ if (isInBackground ) {
411+ if (httpURLConnection != null ) {
412+ httpURLConnection .disconnect ();
413+ }
414+ }
415+ }
396416 }
397417
398418 private synchronized void resetRetryCount () {
399419 httpRetriesRemaining = ORIGINAL_RETRIES ;
400420 }
401421
422+ /**
423+ * The check and set http connection method are combined so that when canMakeHttpStreamConnection
424+ * returns true, the same thread can mark isHttpConnectionIsRunning as true to prevent a race
425+ * condition with another thread.
426+ */
427+ private synchronized boolean checkAndSetHttpConnectionFlagIfNotRunning () {
428+ boolean canMakeConnection = canMakeHttpStreamConnection ();
429+ if (canMakeConnection ) {
430+ setIsHttpConnectionRunning (true );
431+ }
432+
433+ return canMakeConnection ;
434+ }
435+
402436 private synchronized void setIsHttpConnectionRunning (boolean connectionRunning ) {
403437 isHttpConnectionRunning = connectionRunning ;
404438 }
@@ -469,7 +503,7 @@ private String parseForbiddenErrorResponseMessage(InputStream inputStream) {
469503 */
470504 @ SuppressLint ({"VisibleForTests" , "DefaultLocale" })
471505 public void beginRealtimeHttpStream () {
472- if (!canMakeHttpStreamConnection ()) {
506+ if (!checkAndSetHttpConnectionFlagIfNotRunning ()) {
473507 return ;
474508 }
475509
@@ -489,17 +523,21 @@ public void beginRealtimeHttpStream() {
489523 this .scheduledExecutorService ,
490524 (completedHttpUrlConnectionTask ) -> {
491525 Integer responseCode = null ;
492- HttpURLConnection httpURLConnection = null ;
526+ // Get references to InputStream and ErrorStream before listening on the stream so
527+ // that they can be closed without getting them from HttpUrlConnection.
528+ InputStream inputStream = null ;
529+ InputStream errorStream = null ;
493530
494531 try {
495532 // If HTTP connection task failed throw exception to move to the catch block.
496533 if (!httpURLConnectionTask .isSuccessful ()) {
497534 throw new IOException (httpURLConnectionTask .getException ());
498535 }
499- setIsHttpConnectionRunning (true );
500536
501537 // Get HTTP connection and check response code.
502538 httpURLConnection = httpURLConnectionTask .getResult ();
539+ inputStream = httpURLConnection .getInputStream ();
540+ errorStream = httpURLConnection .getErrorStream ();
503541 responseCode = httpURLConnection .getResponseCode ();
504542
505543 // If the connection returned a 200 response code, start listening for messages.
@@ -509,23 +547,32 @@ public void beginRealtimeHttpStream() {
509547 sharedPrefsClient .resetRealtimeBackoff ();
510548
511549 // Start listening for realtime notifications.
512- ConfigAutoFetch configAutoFetch = startAutoFetch (httpURLConnection );
550+ configAutoFetch = startAutoFetch (httpURLConnection );
513551 configAutoFetch .listenForNotifications ();
514552 }
515553 } catch (IOException e ) {
516- // Stream could not be open due to a transient issue and the system will retry the
517- // connection
518- // without user intervention.
519- Log .d (
520- TAG ,
521- "Exception connecting to real-time RC backend. Retrying the connection..." ,
522- e );
554+ if (isInBackground ) {
555+ // It's possible the app was backgrounded while the connection was open, which
556+ // threw an exception trying to read the response. No real error here, so treat
557+ // this as a success, even if we haven't read a 200 response code yet.
558+ resetRetryCount ();
559+ } else {
560+ // If it's not in the background, there might have been a transient error so the
561+ // client will retry the connection.
562+ Log .d (
563+ TAG ,
564+ "Exception connecting to real-time RC backend. Retrying the connection..." ,
565+ e );
566+ }
523567 } finally {
524- closeRealtimeHttpStream (httpURLConnection );
568+ // Close HTTP connection and associated streams.
569+ closeRealtimeHttpConnection (inputStream , errorStream );
525570 setIsHttpConnectionRunning (false );
526571
572+ // Update backoff metadata if the connection failed in the foreground.
527573 boolean connectionFailed =
528- responseCode == null || isStatusCodeRetryable (responseCode );
574+ !isInBackground
575+ && (responseCode == null || isStatusCodeRetryable (responseCode ));
529576 if (connectionFailed ) {
530577 updateBackoffMetadataWithLastFailedStreamConnectionTime (
531578 new Date (clock .currentTimeMillis ()));
@@ -556,24 +603,34 @@ public void beginRealtimeHttpStream() {
556603 }
557604 }
558605
606+ // Reset parameters.
607+ httpURLConnection = null ;
608+ configAutoFetch = null ;
609+
559610 return Tasks .forResult (null );
560611 });
561612 }
562613
563- // Pauses Http stream listening
564- public void closeRealtimeHttpStream (HttpURLConnection httpURLConnection ) {
565- if (httpURLConnection != null ) {
566- httpURLConnection .disconnect ();
567-
568- // Explicitly close the input stream due to a bug in the Android okhttp implementation.
569- // See github.com/firebase/firebase-android-sdk/pull/808.
614+ private void closeHttpConnectionInputStream (InputStream inputStream ) {
615+ if (inputStream != null ) {
570616 try {
571- httpURLConnection .getInputStream ().close ();
572- if (httpURLConnection .getErrorStream () != null ) {
573- httpURLConnection .getErrorStream ().close ();
574- }
575- } catch (IOException e ) {
617+ inputStream .close ();
618+ } catch (IOException ex ) {
619+ Log .d (TAG , "Error closing connection stream." , ex );
576620 }
577621 }
578622 }
623+
624+ // Pauses Http stream listening by disconnecting the HttpUrlConnection and underlying InputStream
625+ // and ErrorStream if they exist.
626+ @ VisibleForTesting
627+ public void closeRealtimeHttpConnection (InputStream inputStream , InputStream errorStream ) {
628+ // Disconnect only if the connection is not null and in the foreground.
629+ if (httpURLConnection != null && !isInBackground ) {
630+ httpURLConnection .disconnect ();
631+ }
632+
633+ closeHttpConnectionInputStream (inputStream );
634+ closeHttpConnectionInputStream (errorStream );
635+ }
579636}
0 commit comments