Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ConnectivityManager Instead Deprecated IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) and BroadcastReceiver #2011

Merged
merged 5 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
)
}
}
Loading