diff --git a/app/src/main/java/org/bepass/oblivion/LogActivity.java b/app/src/main/java/org/bepass/oblivion/LogActivity.java index b1c6aae3..de9e4cbd 100644 --- a/app/src/main/java/org/bepass/oblivion/LogActivity.java +++ b/app/src/main/java/org/bepass/oblivion/LogActivity.java @@ -1,17 +1,26 @@ package org.bepass.oblivion; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.widget.Button; import android.widget.ImageView; import android.widget.ScrollView; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; +import com.google.android.material.button.MaterialButton; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; public class LogActivity extends AppCompatActivity { @@ -27,11 +36,15 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_log); ImageView back = findViewById(R.id.back); + Button copyToClip = findViewById(R.id.copytoclip); logs = findViewById(R.id.logs); logScrollView = findViewById(R.id.logScrollView); setupScrollListener(); back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); + + copyToClip.setOnClickListener(v -> copyLast100LinesToClipboard()); + logUpdater = new Runnable() { @Override public void run() { @@ -81,4 +94,24 @@ private void readLogsFromFile() { e.printStackTrace(); } } + + private void copyLast100LinesToClipboard() { + String logText = logs.getText().toString(); + String[] logLines = logText.split("\n"); + int totalLines = logLines.length; + + // Use Deque to efficiently get the last 100 lines + Deque last100Lines = new ArrayDeque<>(100); + last100Lines.addAll(Arrays.asList(logLines).subList(Math.max(0, totalLines - 100), totalLines)); + + StringBuilder sb = new StringBuilder(); + for (String line : last100Lines) { + sb.append(line).append("\n"); + } + + String last100Log = sb.toString(); + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Log", last100Log); + clipboard.setPrimaryClip(clip); + } } diff --git a/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java b/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java index 14809caa..0a768ad3 100644 --- a/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java +++ b/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java @@ -39,6 +39,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; @@ -74,8 +75,10 @@ public void run() { handler.postDelayed(this, 2000); // Poll every 2 seconds } }; - + // For JNI Calling in a new threa private final Executor executorService = Executors.newSingleThreadExecutor(); + // For PingHTTPTestConnection to don't busy-waiting + private ScheduledExecutorService scheduler; private Notification notification; private ParcelFileDescriptor mInterface; private String bindAddress; @@ -226,28 +229,46 @@ private Set getSplitTunnelApps() { return fileManager.getStringSet("splitTunnelApps", new HashSet<>()); } + private void performConnectionTest(String bindAddress, ConnectionStateChangeListener changeListener) { if (changeListener == null) { return; } - long startTime = System.currentTimeMillis(); + scheduler = Executors.newScheduledThreadPool(1); + + final long startTime = System.currentTimeMillis(); + final long timeout = 60 * 1000; // 1 minute + + Runnable pingTask = () -> { + if (System.currentTimeMillis() - startTime >= timeout) { + changeListener.onChange(ConnectionState.DISCONNECTED); + stopForegroundService(); + scheduler.shutdown(); + return; + } - while (System.currentTimeMillis() - startTime < 60 * 1000) { // 1 minute boolean result = pingOverHTTP(bindAddress); if (result) { changeListener.onChange(ConnectionState.CONNECTED); - return; + scheduler.shutdown(); } + }; - try { - Thread.sleep(1000); // Sleep before retrying - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; + + // Schedule the ping task to run with a fixed delay of 1 second + scheduler.scheduleWithFixedDelay(pingTask, 0, 1, TimeUnit.SECONDS); + } + + private void stopForegroundService() { + stopForeground(true); + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + notificationManager.cancel(1); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notificationManager.deleteNotificationChannel("oblivion"); } } - changeListener.onChange(ConnectionState.DISCONNECTED); } private String getBindAddress() { @@ -307,7 +328,7 @@ private void start() { if (wLock == null) { wLock = ((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "oblivion:vpn"); wLock.setReferenceCounted(false); - wLock.acquire(); + wLock.acquire(3*60*1000L /*3 minutes*/); } executorService.execute(() -> { @@ -326,6 +347,9 @@ private void start() { onRevoke(); } setLastKnownState(state); + // Re-create the notification when the connection state changes + createNotification(); + startForeground(1, notification); // Start foreground again after connection }); }); } @@ -374,18 +398,7 @@ public void onRevoke() { setLastKnownState(ConnectionState.DISCONNECTED); Log.i(TAG, "Stopping VPN"); - // Stop foreground service and notification - try { - stopForeground(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager notificationManager = getSystemService(NotificationManager.class); - if (notificationManager != null) { - notificationManager.deleteNotificationChannel("oblivion"); - } - } - } catch (Exception e) { - Log.e(TAG, "Error stopping foreground service and notification", e); - } + stopForegroundService(); // Release the wake lock if held try { @@ -429,10 +442,24 @@ public void onRevoke() { } } + // Shutdown scheduler if it is running + shutdownScheduler(); Log.i(TAG, "VPN stopped successfully"); } - - + private void shutdownScheduler() { + if (scheduler != null && !scheduler.isShutdown()) { + scheduler.shutdown(); + try { + if (!scheduler.awaitTermination(500, TimeUnit.MILLISECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + Log.e(TAG, "Scheduler termination interrupted", e); + } + } + } private void publishConnectionState(ConnectionState state) { if (!connectionStateObservers.isEmpty()) { diff --git a/app/src/main/res/drawable/button.xml b/app/src/main/res/drawable/button.xml new file mode 100644 index 00000000..ec3a2e1d --- /dev/null +++ b/app/src/main/res/drawable/button.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_log.xml b/app/src/main/res/layout/activity_log.xml index 8156e88d..bef38151 100644 --- a/app/src/main/res/layout/activity_log.xml +++ b/app/src/main/res/layout/activity_log.xml @@ -1,66 +1,76 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/background_gradient" + tools:context="org.bepass.oblivion.LogActivity"> + android:id="@+id/top_bar" + android:layout_width="match_parent" + android:layout_height="48dp" + android:layout_marginTop="8dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:id="@+id/back" + android:layout_width="36dp" + android:layout_height="36dp" + android:background="?selectableItemBackgroundBorderless" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginStart="16dp" + android:src="@drawable/ic_back" + app:tint="#000000" /> - + android:id="@+id/logScrollView" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginHorizontal="16dp" + android:layout_marginTop="16dp" + app:layout_constraintTop_toBottomOf="@id/top_bar" + app:layout_constraintBottom_toTopOf="@id/copytoclip" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + android:id="@+id/logs" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Start Logging Here.." + android:textSize="11sp" /> +