From f091502949a941e427e2e77f801caaac99f43637 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 2 Feb 2022 15:41:50 -0500 Subject: [PATCH] Use newer APIs for detecting network changes. --- .../securesms/NewConversationActivity.java | 2 +- .../jobmanager/impl/NetworkConstraint.java | 12 +- .../impl/NetworkConstraintObserver.java | 136 ++++++++++++++++-- .../securesms/jobs/JobManagerFactories.java | 2 +- .../messages/IncomingMessageObserver.java | 52 +++---- 5 files changed, 153 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java index b8596db3a..17de312d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java @@ -67,7 +67,7 @@ public class NewConversationActivity extends ContactSelectionActivity } else { Log.i(TAG, "[onContactSelected] Maybe creating a new recipient."); - if (SignalStore.account().isRegistered() && NetworkConstraint.isMet(this)) { + if (SignalStore.account().isRegistered() && NetworkConstraint.isMet(getApplication())) { Log.i(TAG, "[onContactSelected] Doing contact refresh."); AlertDialog progress = SimpleProgressDialog.show(this); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraint.java index 493223e8d..e96e7a1ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraint.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraint.java @@ -2,12 +2,8 @@ package org.thoughtcrime.securesms.jobmanager.impl; import android.app.Application; import android.app.job.JobInfo; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import org.thoughtcrime.securesms.jobmanager.Constraint; @@ -43,14 +39,10 @@ public class NetworkConstraint implements Constraint { return "NETWORK"; } - public static boolean isMet(@NonNull Context context) { - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - - return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + public static boolean isMet(@NonNull Application application) { + return NetworkConstraintObserver.getInstance(application).hasInternet(); } - public static final class Factory implements Constraint.Factory { private final Application application; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java index 59ddcbf7c..2155ece85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java @@ -1,38 +1,156 @@ package org.thoughtcrime.securesms.jobmanager.impl; +import android.annotation.TargetApi; import android.app.Application; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkRequest; +import android.os.Build; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + public class NetworkConstraintObserver implements ConstraintObserver { private static final String REASON = Log.tag(NetworkConstraintObserver.class); private final Application application; - public NetworkConstraintObserver(Application application) { + private volatile Notifier notifier; + private volatile boolean hasInternet; + + private final Set networkListeners = new HashSet<>(); + + private static volatile NetworkConstraintObserver instance; + + public static NetworkConstraintObserver getInstance(@NonNull Application application) { + if (instance == null) { + synchronized (NetworkConstraintObserver.class) { + if (instance == null) { + instance = new NetworkConstraintObserver(application); + } + } + } + return instance; + } + + private NetworkConstraintObserver(Application application) { this.application = application; } @Override public void register(@NonNull Notifier notifier) { - application.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - NetworkConstraint constraint = new NetworkConstraint.Factory(application).create(); + this.notifier = notifier; + requestNetwork(0); + } - if (constraint.isMet()) { - notifier.onConstraintMet(REASON); + @TargetApi(19) + private static boolean isActiveNetworkConnected(@NonNull Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + + return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + } + + private void requestNetwork(int retryCount) { + if (Build.VERSION.SDK_INT < 21 || retryCount > 5) { + hasInternet = isActiveNetworkConnected(application); + + application.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + hasInternet = isActiveNetworkConnected(context); + + if (hasInternet) { + notifier.onConstraintMet(REASON); + } + notifyListeners(); } - } - }, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + }, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } else { + NetworkRequest request = new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(); + + ConnectivityManager connectivityManager = Objects.requireNonNull(ContextCompat.getSystemService(application, ConnectivityManager.class)); + connectivityManager.requestNetwork(request, Build.VERSION.SDK_INT >= 26 ? new NetworkStateListener26(retryCount) : new NetworkStateListener21()); + } + } + + public boolean hasInternet() { + return hasInternet; + } + + public void addListener(@Nullable NetworkListener networkListener) { + synchronized (networkListeners) { + networkListeners.add(networkListener); + } + } + + public void removeListener(@Nullable NetworkListener networkListener) { + if (networkListener == null) { + return; + } + + synchronized (networkListeners) { + networkListeners.remove(networkListener); + } + } + + private void notifyListeners() { + synchronized (networkListeners) { + //noinspection SimplifyStreamApiCallChains + networkListeners.stream().forEach(NetworkListener::onNetworkChanged); + } + } + + @TargetApi(21) + private class NetworkStateListener21 extends ConnectivityManager.NetworkCallback { + @Override + public void onAvailable(@NonNull Network network) { + Log.i(REASON, "Network available: " + network.hashCode()); + hasInternet = true; + notifier.onConstraintMet(REASON); + notifyListeners(); + } + + @Override + public void onLost(@NonNull Network network) { + Log.i(REASON, "Network loss: " + network.hashCode()); + hasInternet = false; + notifyListeners(); + } + } + + @TargetApi(26) + private class NetworkStateListener26 extends NetworkStateListener21 { + private final int retryCount; + + public NetworkStateListener26(int retryCount) { + this.retryCount = retryCount; + } + + @Override + public void onUnavailable() { + Log.w(REASON, "No networks available"); + requestNetwork(retryCount + 1); + } + } + + public interface NetworkListener { + void onNetworkChanged(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 6fbf4889f..a6e1f85d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -244,7 +244,7 @@ public final class JobManagerFactories { public static List getConstraintObservers(@NonNull Application application) { return Arrays.asList(CellServiceConstraintObserver.getInstance(application), new ChargingConstraintObserver(application), - new NetworkConstraintObserver(application), + NetworkConstraintObserver.getInstance(application), new SqlCipherMigrationConstraintObserver(), new DecryptionsDrainedConstraintObserver(), new NotInCallConstraintObserver()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.java b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.java index 2aa221838..7f973f264 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.java @@ -2,11 +2,7 @@ package org.thoughtcrime.securesms.messages; import android.app.Application; import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; import android.os.IBinder; import androidx.annotation.NonNull; @@ -21,13 +17,13 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver; import org.thoughtcrime.securesms.jobs.PushDecryptDrainedJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.messages.IncomingMessageProcessor.Processor; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.util.AppForegroundObserver; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; @@ -42,22 +38,22 @@ import java.util.concurrent.atomic.AtomicInteger; /** * The application-level manager of our websocket connection. - * + *

* This class is responsible for opening/closing the websocket based on the app's state and observing new inbound messages received on the websocket. */ public class IncomingMessageObserver { private static final String TAG = Log.tag(IncomingMessageObserver.class); - public static final int FOREGROUND_ID = 313399; - private static final long REQUEST_TIMEOUT_MINUTES = 1; + public static final int FOREGROUND_ID = 313399; + private static final long REQUEST_TIMEOUT_MINUTES = 1; private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(0); - private final Application context; - private final SignalServiceNetworkAccess networkAccess; - private final List decryptionDrainedListeners; - private final BroadcastReceiver connectionReceiver; + private final Application context; + private final SignalServiceNetworkAccess networkAccess; + private final List decryptionDrainedListeners; + private final NetworkConstraintObserver.NetworkListener networkListener; private boolean appVisible; @@ -92,22 +88,18 @@ public class IncomingMessageObserver { } }); - connectionReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (IncomingMessageObserver.this) { - if (!NetworkConstraint.isMet(context)) { - Log.w(TAG, "Lost network connection. Shutting down our websocket connections and resetting the drained state."); - networkDrained = false; - decryptionDrained = false; - disconnect(); - } - IncomingMessageObserver.this.notifyAll(); - } - } - }; + networkListener = this::networkChanged; + NetworkConstraintObserver.getInstance(this.context).addListener(networkListener); + } - context.registerReceiver(connectionReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + private synchronized void networkChanged() { + if (!NetworkConstraint.isMet(context)) { + Log.w(TAG, "Lost network connection. Shutting down our websocket connections and resetting the drained state."); + networkDrained = false; + decryptionDrained = false; + disconnect(); + } + IncomingMessageObserver.this.notifyAll(); } public synchronized void notifyRegistrationChanged() { @@ -160,9 +152,9 @@ public class IncomingMessageObserver { Log.d(TAG, String.format("Network: %s, Foreground: %s, FCM: %s, Censored: %s, Registered: %s, Proxy: %s", hasNetwork, appVisible, fcmEnabled, networkAccess.isCensored(), registered, hasProxy)); - return registered && + return registered && (appVisible || !fcmEnabled) && - hasNetwork && + hasNetwork && !networkAccess.isCensored(); } @@ -177,7 +169,7 @@ public class IncomingMessageObserver { public void terminateAsync() { INSTANCE_COUNT.decrementAndGet(); - context.unregisterReceiver(connectionReceiver); + NetworkConstraintObserver.getInstance(context).removeListener(networkListener); SignalExecutors.BOUNDED.execute(() -> { Log.w(TAG, "Beginning termination.");