Skip to content

Commit

Permalink
Merge pull request #2011 from nextcloud/fix/receivers
Browse files Browse the repository at this point in the history
Use ConnectivityManager Instead Deprecated IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) and BroadcastReceiver
  • Loading branch information
tobiasKaminsky authored Feb 19, 2024
2 parents f006d0d + 2b3e1ef commit ea2e0ca
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,18 @@
import static it.niedermann.owncloud.notes.shared.util.NoteUtil.generateNoteExcerpt;
import static it.niedermann.owncloud.notes.widget.notelist.NoteListWidget.updateNoteListWidgets;
import static it.niedermann.owncloud.notes.widget.singlenote.SingleNoteWidget.updateSingleNoteWidgets;

import android.accounts.NetworkErrorException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.AnyThread;
import androidx.annotation.ColorInt;
import androidx.annotation.MainThread;
Expand All @@ -31,14 +29,13 @@
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.preference.PreferenceManager;

import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
Expand All @@ -48,7 +45,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import it.niedermann.android.sharedpreferences.SharedPreferenceIntLiveData;
import it.niedermann.owncloud.notes.BuildConfig;
import it.niedermann.owncloud.notes.R;
Expand All @@ -71,6 +67,7 @@
import it.niedermann.owncloud.notes.shared.model.NotesSettings;
import it.niedermann.owncloud.notes.shared.model.SyncResultStatus;
import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil;
import it.niedermann.owncloud.notes.shared.util.ConnectionLiveData;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
import it.niedermann.owncloud.notes.shared.util.SSOUtil;
import retrofit2.Call;
Expand All @@ -83,23 +80,39 @@ public class NotesRepository {
private static NotesRepository instance;

private final ApiProvider apiProvider;
private final ExecutorService executor;
private ExecutorService executor;
private final ExecutorService syncExecutor;
private final ExecutorService importExecutor;
private final Context context;
private Context context;
private final NotesDatabase db;
private final String defaultNonEmptyTitle;

/**
* Track network connection changes using a {@link BroadcastReceiver}
*/
private final LiveData<ConnectionLiveData.ConnectionType> connectionLiveData;
private boolean isSyncPossible = false;
private boolean networkConnected = false;
private String syncOnlyOnWifiKey;
private boolean syncOnlyOnWifi;
private final MutableLiveData<Boolean> syncStatus = new MutableLiveData<>(false);
private final MutableLiveData<ArrayList<Throwable>> syncErrors = new MutableLiveData<>();

private final Observer<? super ConnectionLiveData.ConnectionType> syncObserver = (Observer<ConnectionLiveData.ConnectionType>) connectionType -> {
observeNetworkStatus(connectionType);

if (context == null || executor == null) {
return;
}

if (isSyncPossible() && SSOUtil.isConfigured(context)) {
executor.submit(() -> {
try {
scheduleSync(getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(context).name), false);
} catch (NextcloudFilesAppAccountNotFoundException |
NoCurrentAccountSelectedException e) {
Log.v(TAG, "Can not select current SingleSignOn account after network changed, do not sync.");
}
});
}
};

/**
* @see <a href="https://stackoverflow.com/a/3104265">Do not make this a local variable.</a>
*/
Expand All @@ -111,22 +124,6 @@ public class NotesRepository {
}
};

private final BroadcastReceiver networkReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateNetworkStatus();
if (isSyncPossible() && SSOUtil.isConfigured(context)) {
executor.submit(() -> {
try {
scheduleSync(getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(context).name), false);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
Log.v(TAG, "Can not select current SingleSignOn account after network changed, do not sync.");
}
});
}
}
};

// current state of the synchronization
private final Map<Long, Boolean> syncActive = new ConcurrentHashMap<>();
private final Map<Long, Boolean> syncScheduled = new ConcurrentHashMap<>();
Expand All @@ -152,9 +149,9 @@ private NotesRepository(@NonNull final Context context, @NonNull final NotesData
this.apiProvider = apiProvider;
this.defaultNonEmptyTitle = NoteUtil.generateNonEmptyNoteTitle("", this.context);
this.syncOnlyOnWifiKey = context.getApplicationContext().getResources().getString(R.string.pref_key_wifi_only);
this.connectionLiveData = new ConnectionLiveData(context);

// Registers BroadcastReceiver to track network connection changes.
this.context.registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
connectionLiveData.observeForever(syncObserver);

final var prefs = PreferenceManager.getDefaultSharedPreferences(this.context);
prefs.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
Expand All @@ -163,9 +160,62 @@ private NotesRepository(@NonNull final Context context, @NonNull final NotesData
updateNetworkStatus();
}

public void updateNetworkStatus() {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

// Accounts
if (connectivityManager == null) {
handleFailedNetworkStatus("ConnectivityManager is null");
return;
}

Network network = connectivityManager.getActiveNetwork();
if (network == null) {
handleFailedNetworkStatus("No network connection");
return;
}

NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
if (networkCapabilities == null) {
handleFailedNetworkStatus("NetworkCapabilities is null");
return;
}

handleNetworkStatus(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
}

private void observeNetworkStatus(ConnectionLiveData.ConnectionType connectionType) {
if (connectionType == ConnectionLiveData.ConnectionType.Lost) {
handleFailedNetworkStatus("No network connection");
} else {
Log.d(TAG, "Network connection established with " + connectionType.name());
handleNetworkStatus(connectionType == ConnectionLiveData.ConnectionType.WiFi);
}
}

private void handleFailedNetworkStatus(String message) {
Log.e(TAG, message);
networkConnected = false;
isSyncPossible = false;
}

private void handleNetworkStatus(boolean isWifiActive) {
networkConnected = true;
isSyncPossible = !syncOnlyOnWifi || isWifiActive;

if (isSyncPossible) {
Log.d(TAG, "Network connection established.");
} else {
Log.d(TAG, "Network connected, but not used because only synced on wifi.");
}
}

@Override
protected void finalize() throws Throwable {
connectionLiveData.removeObserver(syncObserver);
super.finalize();
}

// Accounts
@AnyThread
public LiveData<ImportStatus> addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @Nullable String displayName, @NonNull IResponseCallback<Account> callback) {
final var account = db.getAccountDao().getAccountById(db.getAccountDao().insert(new Account(url, username, accountName, displayName, capabilities)));
Expand Down Expand Up @@ -263,6 +313,7 @@ public void updateCapabilitiesETag(long id, String capabilitiesETag) {
public void updateModified(long id, long modified) {
db.getAccountDao().updateModified(id, modified);
}

public void updateDirectEditingAvailable(final long id, final boolean available) {
db.getAccountDao().updateDirectEditingAvailable(id, available);
}
Expand Down Expand Up @@ -725,12 +776,6 @@ public LiveData<CategorySortingMethod> getCategoryOrder(@NonNull NavigationCateg
return map(new SharedPreferenceIntLiveData(sp, prefKey, CategorySortingMethod.SORT_MODIFIED_DESC.getId()), CategorySortingMethod::findById);
}

@Override
protected void finalize() throws Throwable {
this.context.unregisterReceiver(networkReceiver);
super.finalize();
}

/**
* Synchronization is only possible, if there is an active network connection.
* <p>
Expand Down Expand Up @@ -887,45 +932,6 @@ void onPostExecute(SyncResultStatus status) {
}
}

public void updateNetworkStatus() {
try {
final var connMgr = (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connMgr == null) {
throw new NetworkErrorException("ConnectivityManager is null");
}

final var activeInfo = connMgr.getActiveNetworkInfo();
if (activeInfo == null) {
throw new NetworkErrorException("NetworkInfo is null");
}

if (activeInfo.isConnected()) {
networkConnected = true;

final var networkInfo = connMgr.getNetworkInfo((ConnectivityManager.TYPE_WIFI));
if (networkInfo == null) {
throw new NetworkErrorException("connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI) is null");
}

isSyncPossible = !syncOnlyOnWifi || networkInfo.isConnected();

if (isSyncPossible) {
Log.d(TAG, "Network connection established.");
} else {
Log.d(TAG, "Network connected, but not used because only synced on wifi.");
}
} else {
networkConnected = false;
isSyncPossible = false;
Log.d(TAG, "No network connection.");
}
} catch (NetworkErrorException e) {
Log.i(TAG, e.getMessage());
networkConnected = false;
isSyncPossible = false;
}
}

@NonNull
public LiveData<Boolean> getSyncStatus() {
return distinctUntilChanged(this.syncStatus);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package it.niedermann.owncloud.notes.shared.util

import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import androidx.lifecycle.LiveData

/**
* LiveData subclass that provides network connection status updates.
* It observes changes in network connectivity and posts updates to its observers.
*
* @property context The application context used to access system services.
*/
class ConnectionLiveData(val context: Context) : LiveData<ConnectionLiveData.ConnectionType>() {

private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val networkRequest = NetworkRequest.Builder().build()

/**
* Enum representing different types of network connections.
*/
enum class ConnectionType {
Lost, WiFi, Ethernet, MobileData, Other
}

init {
connectivityManager.registerNetworkCallback(
networkRequest,
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities = connectivityManager.getNetworkCapabilities(network)

if (networkCapabilities != null) {
when {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
postValue(ConnectionType.WiFi)
}

networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> {
postValue(ConnectionType.Ethernet)
}

networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
postValue(ConnectionType.MobileData)
}

else -> {
postValue(ConnectionType.Other)
}
}
}
}

override fun onLost(network: Network) {
super.onLost(network)
postValue(ConnectionType.Lost)
}
}
)
}
}

0 comments on commit ea2e0ca

Please sign in to comment.