From b75bf84d435cef9aceff84b745d8d4037f5f2ec8 Mon Sep 17 00:00:00 2001
From: opyale <opyale@noreply.codeberg.org>
Date: Thu, 14 Jan 2021 17:33:50 +0100
Subject: [PATCH] Rewriting deprecated code and improving offline mode. (#800)

Moving UI-related code to message queue of main thread.

Removing NetworkObserver in favor of NetworkStatusObserver

Some fixes.

Removing deprecated PreferenceManager.

Fixing crash.

Merge branch 'master' of https://codeberg.org/gitnex/GitNex into fix-performance-regression

 Conflicts:
	app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java

Rewriting deprecated code.

Improving degraded performance due to unnecessary object allocation.

Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/800
Reviewed-by: M M Arif <mmarif@noreply.codeberg.org>
Co-Authored-By: opyale <opyale@noreply.codeberg.org>
Co-Committed-By: opyale <opyale@noreply.codeberg.org>
---
 app/src/main/AndroidManifest.xml              |   1 +
 .../mian/gitnex/activities/LoginActivity.java |  37 +++---
 .../mian/gitnex/clients/RetrofitClient.java   |   4 +-
 .../java/org/mian/gitnex/helpers/AppUtil.java |  25 +---
 .../mian/gitnex/helpers/NetworkObserver.java  |  82 -------------
 .../gitnex/helpers/NetworkStatusObserver.java | 109 ++++++++++++++++++
 .../java/org/mian/gitnex/helpers/TinyDB.java  |   3 +-
 7 files changed, 138 insertions(+), 123 deletions(-)
 delete mode 100644 app/src/main/java/org/mian/gitnex/helpers/NetworkObserver.java
 create mode 100644 app/src/main/java/org/mian/gitnex/helpers/NetworkStatusObserver.java

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c03c9b95..80e1c62d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
 
     <application
         android:allowBackup="true"
diff --git a/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java b/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java
index c7bb694a..049b5978 100644
--- a/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java
+++ b/app/src/main/java/org/mian/gitnex/activities/LoginActivity.java
@@ -2,6 +2,7 @@ package org.mian.gitnex.activities;
 
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
 import android.util.Log;
 import android.view.View;
 import android.widget.ArrayAdapter;
@@ -17,7 +18,7 @@ import org.mian.gitnex.clients.RetrofitClient;
 import org.mian.gitnex.database.api.UserAccountsApi;
 import org.mian.gitnex.database.models.UserAccount;
 import org.mian.gitnex.helpers.AppUtil;
-import org.mian.gitnex.helpers.NetworkObserver;
+import org.mian.gitnex.helpers.NetworkStatusObserver;
 import org.mian.gitnex.helpers.PathsHelper;
 import org.mian.gitnex.helpers.TinyDB;
 import org.mian.gitnex.helpers.Toasty;
@@ -63,7 +64,7 @@ public class LoginActivity extends BaseActivity {
 
 		super.onCreate(savedInstanceState);
 
-		NetworkObserver networkMonitor = new NetworkObserver(ctx);
+		NetworkStatusObserver networkStatusObserver = NetworkStatusObserver.get(ctx);
 
 		loginButton = findViewById(R.id.login_button);
 		instanceUrlET = findViewById(R.id.instance_url);
@@ -79,7 +80,7 @@ public class LoginActivity extends BaseActivity {
 		ArrayAdapter<Protocol> adapterProtocols = new ArrayAdapter<>(LoginActivity.this, R.layout.list_spinner_items, Protocol.values());
 
 		protocolSpinner.setAdapter(adapterProtocols);
-
+		protocolSpinner.setSelection(0);
 		protocolSpinner.setOnItemClickListener((parent, view, position, id) -> {
 
 			selectedProtocol = String.valueOf(parent.getItemAtPosition(position));
@@ -115,18 +116,24 @@ public class LoginActivity extends BaseActivity {
 			}
 		});
 
-		networkMonitor.onInternetStateListener(isAvailable -> {
+		Handler handler = new Handler(getMainLooper());
 
-			if(isAvailable) {
+		networkStatusObserver.registerNetworkStatusListener(hasNetworkConnection -> {
 
-				enableProcessButton();
-			}
-			else {
+			handler.post(() -> {
 
-				disableProcessButton();
-				loginButton.setText(getResources().getString(R.string.btnLogin));
-				Toasty.error(ctx, getResources().getString(R.string.checkNetConnection));
-			}
+				if(hasNetworkConnection) {
+
+					enableProcessButton();
+				}
+				else {
+
+					disableProcessButton();
+					loginButton.setText(getResources().getString(R.string.btnLogin));
+					Toasty.error(ctx, getResources().getString(R.string.checkNetConnection));
+				}
+
+			});
 		});
 
 		loadDefaults();
@@ -267,8 +274,10 @@ public class LoginActivity extends BaseActivity {
 
 					if(gitea_version.less(getString(R.string.versionLow))) {
 
-						AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(ctx).setTitle(getString(R.string.versionAlertDialogHeader))
-							.setMessage(getResources().getString(R.string.versionUnsupportedOld, version.getVersion())).setIcon(R.drawable.ic_warning)
+						AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(ctx)
+							.setTitle(getString(R.string.versionAlertDialogHeader))
+							.setMessage(getResources().getString(R.string.versionUnsupportedOld, version.getVersion()))
+							.setIcon(R.drawable.ic_warning)
 							.setCancelable(true);
 
 						alertDialogBuilder.setNegativeButton(getString(R.string.cancelButton), (dialog, which) -> {
diff --git a/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java b/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java
index 2a6fd6d2..9304d051 100644
--- a/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java
+++ b/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java
@@ -36,8 +36,6 @@ public class RetrofitClient {
 
 		TinyDB tinyDB = TinyDB.getInstance(context);
 
-		final boolean connToInternet = AppUtil.hasNetworkConnection(context);
-
 		int cacheSize = FilesData.returnOnlyNumber(tinyDB.getString("cacheSizeStr")) * 1024 * 1024;
 		Cache cache = new Cache(new File(context.getCacheDir(), "responses"), cacheSize);
 
@@ -59,7 +57,7 @@ public class RetrofitClient {
 
 					Request request = chain.request();
 
-					request = connToInternet ?
+					request = AppUtil.hasNetworkConnection(context) ?
 						request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build() :
 						request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build();
 
diff --git a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java
index fdfefe85..6b3e7d5c 100644
--- a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java
+++ b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java
@@ -5,13 +5,12 @@ import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
 import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.View;
 import androidx.annotation.ColorInt;
+import androidx.core.content.pm.PackageInfoCompat;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
@@ -34,32 +33,14 @@ public class AppUtil {
 
 	public static boolean hasNetworkConnection(Context context) {
 
-		boolean haveConnectedWifi = false;
-		boolean haveConnectedMobile = false;
-
-		ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-		assert cm != null;
-		NetworkInfo[] netInfo = cm.getAllNetworkInfo();
-		for(NetworkInfo ni : netInfo) {
-			if(ni.getTypeName().equalsIgnoreCase("WIFI")) {
-				if(ni.isConnected()) {
-					haveConnectedWifi = true;
-				}
-			}
-			if(ni.getTypeName().equalsIgnoreCase("MOBILE")) {
-				if(ni.isConnected()) {
-					haveConnectedMobile = true;
-				}
-			}
-		}
-		return haveConnectedWifi || haveConnectedMobile;
+		return NetworkStatusObserver.get(context).hasNetworkConnection();
 	}
 
 	public static int getAppBuildNo(Context context) {
 
 		try {
 			PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
-			return packageInfo.versionCode;
+			return (int) PackageInfoCompat.getLongVersionCode(packageInfo);
 		}
 		catch(PackageManager.NameNotFoundException e) {
 			throw new RuntimeException("Could not get package name: " + e);
diff --git a/app/src/main/java/org/mian/gitnex/helpers/NetworkObserver.java b/app/src/main/java/org/mian/gitnex/helpers/NetworkObserver.java
deleted file mode 100644
index 6d557f20..00000000
--- a/app/src/main/java/org/mian/gitnex/helpers/NetworkObserver.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.mian.gitnex.helpers;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.OnLifecycleEvent;
-
-/**
- * Author M M Arif
- */
-
-public class NetworkObserver implements LifecycleObserver {
-
-    private ConnectivityManager mConnectivityMgr;
-    private Context mContext;
-    private NetworkStateReceiver mNetworkStateReceiver;
-
-    public interface ConnectionStateListener {
-        void onAvailable(boolean isAvailable);
-    }
-
-    public NetworkObserver(Context context) {
-        mContext = context;
-        mConnectivityMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        ((AppCompatActivity) mContext).getLifecycle().addObserver(this);
-    }
-
-
-    public void onInternetStateListener(ConnectionStateListener listener) {
-
-        mNetworkStateReceiver = new NetworkStateReceiver(listener);
-        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(mNetworkStateReceiver, intentFilter);
-
-    }
-
-
-    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
-    public void onDestroy() {
-
-        ((AppCompatActivity) mContext).getLifecycle().removeObserver(this);
-
-        if (mNetworkStateReceiver != null) {
-            mContext.unregisterReceiver(mNetworkStateReceiver);
-        }
-
-    }
-
-
-    public class NetworkStateReceiver extends BroadcastReceiver {
-
-        ConnectionStateListener mListener;
-
-        public NetworkStateReceiver(ConnectionStateListener listener) {
-            mListener = listener;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getExtras() != null) {
-                NetworkInfo activeNetworkInfo = mConnectivityMgr.getActiveNetworkInfo();
-
-                if (activeNetworkInfo != null && activeNetworkInfo.getState() == NetworkInfo.State.CONNECTED) {
-
-                    mListener.onAvailable(true); // connected
-
-                } else if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, Boolean.FALSE)) {
-
-                    mListener.onAvailable(false); // disconnected
-
-                }
-            }
-        }
-    }
-
-}
diff --git a/app/src/main/java/org/mian/gitnex/helpers/NetworkStatusObserver.java b/app/src/main/java/org/mian/gitnex/helpers/NetworkStatusObserver.java
new file mode 100644
index 00000000..5845be53
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/helpers/NetworkStatusObserver.java
@@ -0,0 +1,109 @@
+package org.mian.gitnex.helpers;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import androidx.annotation.NonNull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author opyale
+ */
+public class NetworkStatusObserver {
+
+	private static NetworkStatusObserver networkStatusObserver;
+
+	private final AtomicBoolean hasNetworkConnection = new AtomicBoolean(false);
+	private final List<NetworkStatusListener> networkStatusListeners = new ArrayList<>();
+
+	private final Object mutex = new Object();
+	private boolean hasInitialized = false;
+
+	private NetworkStatusObserver(Context context) {
+
+		ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+		NetworkRequest networkRequest = new NetworkRequest.Builder()
+			.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+			.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+			.build();
+
+		cm.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback() {
+
+			@Override
+			public void onAvailable(@NonNull Network network) {
+				hasNetworkConnection.set(true);
+				networkStatusChanged();
+				checkInitialized();
+			}
+
+			@Override
+			public void onLost(@NonNull Network network) {
+				hasNetworkConnection.set(false);
+				networkStatusChanged();
+				checkInitialized();
+			}
+
+			private void checkInitialized() {
+
+				if(!hasInitialized) {
+					hasInitialized = true;
+					synchronized(mutex) {
+						mutex.notify();
+					}
+				}
+			}
+
+		});
+
+		synchronized(mutex) {
+			try {
+				// This is actually not the recommended way to do this, but there
+				// is no other option besides upgrading to API level 26
+				// in order to use the built-in timeout functionality of {@code requestNetwork()}
+				// which in turn gives us access to {@code onUnavailable()} .
+				mutex.wait(5);
+			} catch(InterruptedException ignored) {}
+		}
+
+		if(!hasInitialized) {
+			hasInitialized = true;
+		}
+	}
+
+	private void networkStatusChanged() {
+		boolean hasNetworkConnection = hasNetworkConnection();
+
+		for(NetworkStatusListener networkStatusListener : networkStatusListeners) {
+			networkStatusListener.onNetworkStatusChanged(hasNetworkConnection);
+		}
+	}
+
+	public boolean hasNetworkConnection() {
+		return hasNetworkConnection.get();
+	}
+
+	public void registerNetworkStatusListener(NetworkStatusListener networkStatusListener) {
+		networkStatusListeners.add(networkStatusListener);
+		networkStatusListener.onNetworkStatusChanged(hasNetworkConnection());
+	}
+
+	public void unregisterNetworkStatusListener(NetworkStatusListener networkStatusListener) {
+		networkStatusListeners.remove(networkStatusListener);
+	}
+
+	public interface NetworkStatusListener { void onNetworkStatusChanged(boolean hasNetworkConnection); }
+
+	public static NetworkStatusObserver get(Context context) {
+		if(networkStatusObserver == null) {
+			networkStatusObserver = new NetworkStatusObserver(context);
+		}
+
+		return networkStatusObserver;
+	}
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/helpers/TinyDB.java b/app/src/main/java/org/mian/gitnex/helpers/TinyDB.java
index 0cc7486f..d4e782a8 100644
--- a/app/src/main/java/org/mian/gitnex/helpers/TinyDB.java
+++ b/app/src/main/java/org/mian/gitnex/helpers/TinyDB.java
@@ -6,7 +6,6 @@ import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
 import android.os.Environment;
-import android.preference.PreferenceManager;
 import android.text.TextUtils;
 import android.util.Log;
 import java.io.File;
@@ -25,7 +24,7 @@ public class TinyDB {
     private String lastImagePath = "";
 
 	private TinyDB(Context appContext) {
-        preferences = PreferenceManager.getDefaultSharedPreferences(appContext);
+        preferences = appContext.getSharedPreferences(appContext.getPackageName() + "_preferences", Context.MODE_PRIVATE);
     }
 
 	public static synchronized TinyDB getInstance(Context context) {
