diff --git a/app/src/main/java/org/bepass/oblivion/BugActivity.java b/app/src/main/java/org/bepass/oblivion/BugActivity.java index cb9cf637..04716d8b 100644 --- a/app/src/main/java/org/bepass/oblivion/BugActivity.java +++ b/app/src/main/java/org/bepass/oblivion/BugActivity.java @@ -17,7 +17,6 @@ public class BugActivity extends AppCompatActivity { private final Handler handler = new Handler(Looper.getMainLooper()); - private ImageView back; private TextView logs; private ScrollView logScrollView; private boolean isUserScrollingUp = false; @@ -28,12 +27,12 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bug); - back = findViewById(R.id.back); + ImageView back = findViewById(R.id.back); logs = findViewById(R.id.logs); logScrollView = findViewById(R.id.logScrollView); setupScrollListener(); - back.setOnClickListener(v -> onBackPressed()); + back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); logUpdater = new Runnable() { @Override public void run() { diff --git a/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java b/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java index 3bd50867..70ea2000 100644 --- a/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java +++ b/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java @@ -26,9 +26,6 @@ import java.util.concurrent.Executors; public class BypassListAppsAdapter extends RecyclerView.Adapter { - - private static final String TAG = "InstalledAppsAdapter"; - private final ExecutorService executor = Executors.newSingleThreadExecutor(); private final Handler handler = new Handler(Looper.getMainLooper()); private final FileManager fm; @@ -156,7 +153,7 @@ public static class AppInfo { IconLoader iconLoader; boolean isSelected; - public AppInfo(String name, IconLoader iconLoader, String packageName, boolean isSelected) { + AppInfo(String name, IconLoader iconLoader, String packageName, boolean isSelected) { this.appName = name; this.packageName = packageName; this.iconLoader = iconLoader; diff --git a/app/src/main/java/org/bepass/oblivion/FileManager.java b/app/src/main/java/org/bepass/oblivion/FileManager.java index de91d6bf..024463af 100644 --- a/app/src/main/java/org/bepass/oblivion/FileManager.java +++ b/app/src/main/java/org/bepass/oblivion/FileManager.java @@ -6,7 +6,6 @@ import java.util.Set; public class FileManager { - public static String currentLog; private static FileManager instance; private final SharedPreferences sharedPreferences; diff --git a/app/src/main/java/org/bepass/oblivion/InfoActivity.java b/app/src/main/java/org/bepass/oblivion/InfoActivity.java index c0daaaed..3767966d 100644 --- a/app/src/main/java/org/bepass/oblivion/InfoActivity.java +++ b/app/src/main/java/org/bepass/oblivion/InfoActivity.java @@ -18,21 +18,15 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_info); - init(); - - github.setOnClickListener(v -> openURL("https://github.com/bepass-org/oblivion")); - - back.setOnClickListener(v -> onBackPressed()); - } - - private void init() { back = findViewById(R.id.back); github = findViewById(R.id.github_layout); - } - protected void openURL(String url) { - Uri uri = Uri.parse(url); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(intent); + github.setOnClickListener(v -> { + Uri uri = Uri.parse("https://github.com/bepass-org/oblivion"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + }); + + back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); } } diff --git a/app/src/main/java/org/bepass/oblivion/MainActivity.java b/app/src/main/java/org/bepass/oblivion/MainActivity.java index 292344ad..e2228750 100644 --- a/app/src/main/java/org/bepass/oblivion/MainActivity.java +++ b/app/src/main/java/org/bepass/oblivion/MainActivity.java @@ -2,6 +2,7 @@ import android.Manifest; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.view.View; @@ -16,82 +17,55 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; -public class MainActivity extends ConnectionAwareBaseActivity { - // Views - ImageView infoIcon, bugIcon, settingsIcon; - TouchAwareSwitch switchButton; - TextView stateText, publicIP; - ProgressBar ipProgressBar; - FileManager fileManager; - PublicIPUtils pIPUtils; - private ActivityResultLauncher pushNotificationPermissionLauncher; - private ActivityResultLauncher vpnPermissionLauncher; +import java.util.HashSet; +import java.util.Set; + +public class MainActivity extends StateAwareBaseActivity { + private TouchAwareSwitch switchButton; + private TextView stateText, publicIP; + private ProgressBar ipProgressBar; + private PublicIPUtils pIPUtils; private long backPressedTime; private Toast backToast; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - // Custom back pressed logic here - if (backPressedTime + 2000 > System.currentTimeMillis()) { - if (backToast != null) backToast.cancel(); - finish(); // or super.handleOnBackPressed() if you want to keep default behavior alongside - } else { - if (backToast != null) - backToast.cancel(); // Cancel the existing toast to avoid stacking - backToast = Toast.makeText(MainActivity.this, "برای خروج، دوباره بازگشت را فشار دهید.", Toast.LENGTH_SHORT); - backToast.show(); - } - backPressedTime = System.currentTimeMillis(); - } - }); - pushNotificationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (!isGranted) { - Toast.makeText(this, "Permission denied", Toast.LENGTH_LONG).show(); - } - }); - vpnPermissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() != RESULT_OK) { - Toast.makeText(this, "Really!?", Toast.LENGTH_LONG).show(); - } - switchButton.setChecked(false); - }); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - pushNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - } + cleanOrMigrateSettings(); - fileManager = FileManager.getInstance(getApplicationContext()); + // Get the global PublicIPUtils instance pIPUtils = PublicIPUtils.getInstance(getApplicationContext()); - infoIcon = findViewById(R.id.info_icon); - bugIcon = findViewById(R.id.bug_icon); - settingsIcon = findViewById(R.id.setting_icon); + // Set the layout of the main activity + setContentView(R.layout.activity_main); + + // Views + ImageView infoIcon = findViewById(R.id.info_icon); + ImageView bugIcon = findViewById(R.id.bug_icon); + ImageView settingsIcon = findViewById(R.id.setting_icon); FrameLayout switchButtonFrame = findViewById(R.id.switch_button_frame); switchButton = findViewById(R.id.switch_button); stateText = findViewById(R.id.state_text); publicIP = findViewById(R.id.publicIP); - ipProgressBar = (ProgressBar) findViewById(R.id.ipProgressBar); + ipProgressBar = findViewById(R.id.ipProgressBar); + // Set listeners infoIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, InfoActivity.class))); bugIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, BugActivity.class))); settingsIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, SettingsActivity.class))); switchButtonFrame.setOnClickListener(v -> switchButton.toggle()); - if (!fileManager.getBoolean("isFirstValueInit")) { - fileManager.set("USERSETTING_endpoint", "engage.cloudflareclient.com:2408"); - fileManager.set("USERSETTING_port", "8086"); - fileManager.set("USERSETTING_gool", false); - fileManager.set("USERSETTING_psiphon", false); - fileManager.set("USERSETTING_lan", false); - fileManager.set("isFirstValueInit", true); - } + // Request for VPN creation + ActivityResultLauncher vpnPermissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() != RESULT_OK) { + Toast.makeText(this, "Really!?", Toast.LENGTH_LONG).show(); + } + switchButton.setChecked(false); + }); + // Listener for toggle switch switchButton.setOnCheckedChangeListener((view, isChecked) -> { if (!isChecked) { if (!lastKnownConnectionState.isDisconnected()) { @@ -108,6 +82,62 @@ public void handleOnBackPressed() { OblivionVpnService.startVpnService(this); } }); + + // Request permission to create push notifications + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityResultLauncher pushNotificationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (!isGranted) { + Toast.makeText(this, "Permission denied", Toast.LENGTH_LONG).show(); + } + }); + pushNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + } + + // Set the behaviour of the back button + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + // Custom back pressed logic here + if (backPressedTime + 2000 > System.currentTimeMillis()) { + if (backToast != null) backToast.cancel(); + finish(); // or super.handleOnBackPressed() if you want to keep default behavior alongside + } else { + if (backToast != null) + backToast.cancel(); // Cancel the existing toast to avoid stacking + backToast = Toast.makeText(MainActivity.this, "برای خروج، دوباره بازگشت را فشار دهید.", Toast.LENGTH_SHORT); + backToast.show(); + } + backPressedTime = System.currentTimeMillis(); + } + }); + } + + protected void cleanOrMigrateSettings() { + // Get the global FileManager instance + FileManager fileManager = FileManager.getInstance(getApplicationContext()); + + if (!fileManager.getBoolean("isFirstValueInit")) { + fileManager.set("USERSETTING_endpoint", "engage.cloudflareclient.com:2408"); + fileManager.set("USERSETTING_port", "8086"); + fileManager.set("USERSETTING_gool", false); + fileManager.set("USERSETTING_psiphon", false); + fileManager.set("USERSETTING_lan", false); + fileManager.set("isFirstValueInit", true); + } + + // Check which split mode apps have been uninstalled and remove them from the list in settings + Set splitApps = fileManager.getStringSet("splitTunnelApps", new HashSet<>()); + Set shouldKeep = new HashSet<>(); + final PackageManager pm = getApplicationContext().getPackageManager(); + for (String packageName : splitApps) { + try { + pm.getPackageInfo(packageName, PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException ignored) { + continue; + } + shouldKeep.add(packageName); + } + fileManager.set("splitTunnelApps", shouldKeep); } @NonNull diff --git a/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java b/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java index 23bc7e15..f0721190 100644 --- a/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java +++ b/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java @@ -16,9 +16,11 @@ import android.os.Message; import android.os.Messenger; import android.os.ParcelFileDescriptor; +import android.os.PowerManager; import android.os.RemoteException; import android.util.Log; +import androidx.annotation.NonNull; import androidx.core.app.NotificationChannelCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -35,7 +37,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -72,22 +73,23 @@ public void run() { handler.postDelayed(this, 2000); // Poll every 2 seconds } }; -// ExecutorService executorService = Executors.newFixedThreadPool(2); - private Executor executorService = Executors.newSingleThreadExecutor(); + + private final Executor executorService = Executors.newSingleThreadExecutor(); private Notification notification; private ParcelFileDescriptor mInterface; private String bindAddress; private FileManager fileManager; + private static PowerManager.WakeLock wLock; private ConnectionState lastKnownState = ConnectionState.DISCONNECTED; - public static void startVpnService(Context context) { + public static synchronized void startVpnService(Context context) { Intent intent = new Intent(context, OblivionVpnService.class); intent.setAction(OblivionVpnService.FLAG_VPN_START); ContextCompat.startForegroundService(context, intent); } - public static void stopVpnService(Context context) { + public static synchronized void stopVpnService(Context context) { Intent intent = new Intent(context, OblivionVpnService.class); intent.setAction(OblivionVpnService.FLAG_VPN_STOP); ContextCompat.startForegroundService(context, intent); @@ -220,8 +222,8 @@ public static String isLocalPortInUse(String bindAddress) { } } - private static Set getSplitTunnelApps(FileManager fm) { - return fm.getStringSet("splitTunnelApps", new HashSet<>()); + private Set getSplitTunnelApps() { + return fileManager.getStringSet("splitTunnelApps", new HashSet<>()); } private void performConnectionTest(String bindAddress, ConnectionStateChangeListener changeListener) { @@ -285,46 +287,60 @@ private void clearLogFile() { private void start() { fileManager = FileManager.getInstance(this); - bindAddress = getBindAddress(); - - executorService.execute(new Runnable() { - @Override - public void run() { - setLastKnownState(ConnectionState.CONNECTING); - Log.i(TAG, "Clearing Logs"); - clearLogFile(); - Log.i(TAG, "Create Notification"); - createNotification(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - startForeground(1, notification); - } else { - startForeground(1, notification, FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED); - } - Log.i(TAG, "Configuring VPN service"); - configure(); - performConnectionTest(bindAddress, (state) -> { - if (state == ConnectionState.DISCONNECTED) { - onRevoke(); - } - setLastKnownState(state); - }); + setLastKnownState(ConnectionState.CONNECTING); + Log.i(TAG, "Clearing Logs"); + clearLogFile(); + Log.i(TAG, "Create Notification"); + createNotification(); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { + startForeground(1, notification); + } else { + startForeground(1, notification, FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED); + } + + if (wLock == null) { + wLock = ((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "oblivion:vpn"); + wLock.setReferenceCounted(false); + } + + executorService.execute(() -> { + bindAddress = getBindAddress(); + Log.i(TAG, "Configuring VPN service"); + try { + configure(); + } catch (Exception e) { + onRevoke(); + e.printStackTrace(); + return; } + + performConnectionTest(bindAddress, (state) -> { + if (state == ConnectionState.DISCONNECTED) { + onRevoke(); + } + setLastKnownState(state); + }); }); } @Override - public int onStartCommand(Intent intent, int flags, int startId) { + public synchronized int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { return START_NOT_STICKY; } - if (intent.getAction().equals(FLAG_VPN_START)) { + String action = intent.getAction(); + if (action == null) { + return START_NOT_STICKY; + } + + if (action.equals(FLAG_VPN_START)) { start(); return START_STICKY; } - if (intent.getAction().equals(FLAG_VPN_STOP)) { + if (action.equals(FLAG_VPN_STOP)) { onRevoke(); return START_NOT_STICKY; } @@ -359,6 +375,11 @@ public void onRevoke() { e.printStackTrace(); } + if (wLock != null) { + wLock.release(); + wLock = null; + } + if (mInterface != null) { try { mInterface.close(); @@ -367,16 +388,7 @@ public void onRevoke() { } } - executorService.execute(new Runnable() { - @Override - public void run() { - try { - Tun2socks.stop(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); + executorService.execute(Tun2socks::stop); } private void publishConnectionState(ConnectionState state) { @@ -401,13 +413,12 @@ private void publishConnectionStateTo(String observerKey, ConnectionState state) } } - public void setLastKnownState(ConnectionState lastKnownState) { + private void setLastKnownState(ConnectionState lastKnownState) { this.lastKnownState = lastKnownState; publishConnectionState(lastKnownState); } private String getNotificationText() { - boolean usePsiphon = fileManager.getBoolean("USERSETTING_psiphon"); boolean useWarp = fileManager.getBoolean("USERSETTING_gool"); @@ -417,7 +428,6 @@ private String getNotificationText() { return "Warp in Warp"; } return "Warp"; - } private void createNotification() { @@ -456,31 +466,23 @@ public void removeConnectionStateObserver(String key) { connectionStateObservers.remove(key); } - private void configure() { + private void configure() throws Exception { VpnService.Builder builder = new VpnService.Builder(); - try { - builder.setSession("oblivion") - .setMtu(1500) - .addAddress(PRIVATE_VLAN4_CLIENT, 30) - .addAddress(PRIVATE_VLAN6_CLIENT, 126) - .addDnsServer("8.8.8.8") - .addDnsServer("8.8.4.4") - .addDnsServer("1.1.1.1") - .addDnsServer("1.0.0.1") - .addDnsServer("2001:4860:4860::8888") - .addDnsServer("2001:4860:4860::8844") - .addDisallowedApplication(getPackageName()) - .addRoute("0.0.0.0", 0) - .addRoute("::", 0); - } catch (Exception e) { - e.printStackTrace(); - } + builder.setSession("oblivion") + .setMtu(1500) + .addAddress(PRIVATE_VLAN4_CLIENT, 30) + .addAddress(PRIVATE_VLAN6_CLIENT, 126) + .addDnsServer("1.1.1.1") + .addDnsServer("1.0.0.1") + .addDisallowedApplication(getPackageName()) + .addRoute("0.0.0.0", 0) + .addRoute("::", 0); fileManager.getStringSet("splitTunnelApps", new HashSet<>()); SplitTunnelMode splitTunnelMode = SplitTunnelMode.getSplitTunnelMode(fileManager); if (splitTunnelMode == SplitTunnelMode.BLACKLIST) { - for (String packageName : getSplitTunnelApps(fileManager)) { + for (String packageName : getSplitTunnelApps()) { try { builder.addDisallowedApplication(packageName); } catch (PackageManager.NameNotFoundException e) { @@ -490,6 +492,7 @@ private void configure() { } mInterface = builder.establish(); + if (mInterface == null) throw new RuntimeException("failed to establish VPN interface"); Log.i(TAG, "Interface created"); String endpoint = fileManager.getString("USERSETTING_endpoint", "engage.cloudflareclient.com:2408").trim(); @@ -520,11 +523,7 @@ private void configure() { so.setTunFd(mInterface.getFd()); - try { - Tun2socks.start(so); - } catch (Exception e) { - e.printStackTrace(); - } + Tun2socks.start(so); } private static class IncomingHandler extends Handler { @@ -535,7 +534,7 @@ private static class IncomingHandler extends Handler { } @Override - public void handleMessage(Message msg) { + public void handleMessage(@NonNull Message msg) { final Message message = new Message(); message.copyFrom(msg); OblivionVpnService service = serviceRef.get(); diff --git a/app/src/main/java/org/bepass/oblivion/PublicIPUtils.java b/app/src/main/java/org/bepass/oblivion/PublicIPUtils.java index 682b39eb..f2698bce 100644 --- a/app/src/main/java/org/bepass/oblivion/PublicIPUtils.java +++ b/app/src/main/java/org/bepass/oblivion/PublicIPUtils.java @@ -7,12 +7,9 @@ import org.json.JSONObject; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.net.Proxy; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -40,67 +37,44 @@ public static synchronized PublicIPUtils getInstance(Context context) { return instance; } - public static String convertStreamToString(InputStream is) { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - - String line; - try { - while ((line = reader.readLine()) != null) { - sb.append(line).append('\n'); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return sb.toString(); - } public void getIPDetails(IPDetailsCallback callback) { Handler handler = new Handler(); IPDetails details = new IPDetails(); - executorService.execute(new Runnable() { - @Override - public void run() { - long startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < 30 * 1000) { // 30 seconds - try { - int socksPort = Integer.parseInt(fm.getString("USERSETTING_port")); - Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("localhost", socksPort)); - OkHttpClient client = new OkHttpClient.Builder() - .proxy(proxy) - .connectTimeout(5, TimeUnit.SECONDS) // 5 seconds connection timeout - .readTimeout(5, TimeUnit.SECONDS) // 5 seconds read timeout - .build(); - Request request = new Request.Builder() - .url("https://api.country.is/") - .build(); - Response response = client.newCall(request).execute(); - - JSONObject jsonData = new JSONObject(response.body().string()); - details.ip = jsonData.getString("ip"); - details.country = jsonData.getString("country"); - details.flag = EmojiManager.getForAlias(jsonData.getString("country").toLowerCase()).getUnicode(); - handler.post(() -> callback.onDetailsReceived(details)); - return; - } catch (Exception e) { - e.printStackTrace(); - } - - try { - Thread.sleep(1000); // Sleep before retrying - } catch (InterruptedException e) { - e.printStackTrace(); - return; + executorService.execute(() -> { + long startTime = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTime < 30 * 1000) { // 30 seconds + try { + int socksPort = Integer.parseInt(fm.getString("USERSETTING_port")); + Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("localhost", socksPort)); + OkHttpClient client = new OkHttpClient.Builder() + .proxy(proxy) + .connectTimeout(5, TimeUnit.SECONDS) // 5 seconds connection timeout + .readTimeout(5, TimeUnit.SECONDS) // 5 seconds read timeout + .build(); + Request request = new Request.Builder() + .url("https://api.country.is/") + .build(); + JSONObject jsonData; + try (Response response = client.newCall(request).execute()) { + jsonData = new JSONObject(Objects.requireNonNull(response.body()).string()); } + details.ip = jsonData.getString("ip"); + details.country = jsonData.getString("country"); + details.flag = EmojiManager.getForAlias(jsonData.getString("country").toLowerCase()).getUnicode(); handler.post(() -> callback.onDetailsReceived(details)); + return; + } catch (Exception e) { + e.printStackTrace(); + } + + try { + Thread.sleep(1000); // Sleep before retrying + } catch (InterruptedException e) { + e.printStackTrace(); + return; } + handler.post(() -> callback.onDetailsReceived(details)); } }); } diff --git a/app/src/main/java/org/bepass/oblivion/QuickStartService.java b/app/src/main/java/org/bepass/oblivion/QuickStartService.java index f7903887..48d61dc1 100644 --- a/app/src/main/java/org/bepass/oblivion/QuickStartService.java +++ b/app/src/main/java/org/bepass/oblivion/QuickStartService.java @@ -79,32 +79,29 @@ public void onClick() { private void subscribe() { if (!isBound) return; - OblivionVpnService.registerConnectionStateObserver(CONNECTION_OBSERVER_KEY, serviceMessenger, new ConnectionStateChangeListener() { - @Override - public void onChange(ConnectionState state) { - Tile tile = getQsTile(); - if (tile == null) { - return; //Quick setting tile was not registered by system. Return to prevent crash - } - switch (state) { - case DISCONNECTED: - tile.setState(Tile.STATE_INACTIVE); - tile.setLabel("Oblivion"); - tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.vpn_off)); - tile.updateTile(); - break; - case CONNECTING: - tile.setState(Tile.STATE_ACTIVE); - tile.setLabel("Connecting"); - tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.vpn_off)); - tile.updateTile(); - break; - case CONNECTED: - tile.setState(Tile.STATE_ACTIVE); - tile.setLabel("Connected"); - tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.vpn_on)); - tile.updateTile(); - } + OblivionVpnService.registerConnectionStateObserver(CONNECTION_OBSERVER_KEY, serviceMessenger, state -> { + Tile tile = getQsTile(); + if (tile == null) { + return; //Quick setting tile was not registered by system. Return to prevent crash + } + switch (state) { + case DISCONNECTED: + tile.setState(Tile.STATE_INACTIVE); + tile.setLabel("Oblivion"); + tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.vpn_off)); + tile.updateTile(); + break; + case CONNECTING: + tile.setState(Tile.STATE_ACTIVE); + tile.setLabel("Connecting"); + tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.vpn_off)); + tile.updateTile(); + break; + case CONNECTED: + tile.setState(Tile.STATE_ACTIVE); + tile.setLabel("Connected"); + tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.vpn_on)); + tile.updateTile(); } }); } diff --git a/app/src/main/java/org/bepass/oblivion/SettingsActivity.java b/app/src/main/java/org/bepass/oblivion/SettingsActivity.java index 137332b9..359b4524 100644 --- a/app/src/main/java/org/bepass/oblivion/SettingsActivity.java +++ b/app/src/main/java/org/bepass/oblivion/SettingsActivity.java @@ -11,20 +11,14 @@ import android.widget.Spinner; import android.widget.TextView; -import androidx.appcompat.app.AppCompatActivity; - -public class SettingsActivity extends AppCompatActivity { - - FileManager fileManager; - ImageView back; - - LinearLayout endpointLayout, portLayout, lanLayout, psiphonLayout, countryLayout, licenseLayout, goolLayout, splitTunnelLayout; - - TextView endpoint, port, license; - CheckBox psiphon, lan, gool; - Spinner country; - ArrayAdapter adapter; - +import androidx.activity.OnBackPressedCallback; + +public class SettingsActivity extends StateAwareBaseActivity { + private FileManager fileManager; + private LinearLayout countryLayout; + private TextView endpoint, port, license; + private CheckBox psiphon, lan, gool; + private Spinner country; private CheckBox.OnCheckedChangeListener psiphonListener; private CheckBox.OnCheckedChangeListener goolListener; @@ -39,7 +33,42 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); - init(); + fileManager = FileManager.getInstance(getApplicationContext()); + + LinearLayout endpointLayout = findViewById(R.id.endpoint_layout); + LinearLayout portLayout = findViewById(R.id.port_layout); + LinearLayout splitTunnelLayout = findViewById(R.id.split_tunnel_layout); + LinearLayout lanLayout = findViewById(R.id.lan_layout); + LinearLayout psiphonLayout = findViewById(R.id.psiphon_layout); + countryLayout = findViewById(R.id.country_layout); + LinearLayout licenseLayout = findViewById(R.id.license_layout); + LinearLayout goolLayout = findViewById(R.id.gool_layout); + + endpoint = findViewById(R.id.endpoint); + port = findViewById(R.id.port); + country = findViewById(R.id.country); + license = findViewById(R.id.license); + + psiphon = findViewById(R.id.psiphon); + lan = findViewById(R.id.lan); + gool = findViewById(R.id.gool); + + ImageView back = findViewById(R.id.back); + back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); + + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (StateAwareBaseActivity.getRequireRestartVpnService()) { + StateAwareBaseActivity.setRequireRestartVpnService(false); + if (!lastKnownConnectionState.isDisconnected()) { + OblivionVpnService.stopVpnService(SettingsActivity.this); + OblivionVpnService.startVpnService(SettingsActivity.this); + } + } + finish(); + } + }); SheetsCallBack sheetsCallBack = this::settingBasicValuesFromSPF; // Listen to Changes @@ -47,7 +76,7 @@ protected void onCreate(Bundle savedInstanceState) { portLayout.setOnClickListener(v -> (new EditSheet(this, "پورت", "port", sheetsCallBack)).start()); licenseLayout.setOnClickListener(v -> (new EditSheet(this, "لایسنس", "license", sheetsCallBack)).start()); - adapter = ArrayAdapter.createFromResource(this, R.array.countries, R.layout.country_item_layout); + ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.countries, R.layout.country_item_layout); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); country.setAdapter(adapter); @@ -60,9 +89,7 @@ public void onItemSelected(AdapterView parent, View view, int position, long id) } @Override - public void onNothingSelected(AdapterView parent) { - - } + public void onNothingSelected(AdapterView parent) {} }); splitTunnelLayout.setOnClickListener(v -> startActivity(new Intent(this, SplitTunnelActivity.class))); @@ -102,7 +129,6 @@ public void onNothingSelected(AdapterView parent) { } private int getIndexFromName(Spinner spinner, String name) { - for (int i = 0; i < spinner.getCount(); i++) { if (spinner.getItemAtPosition(i).toString().equals(name)) { return i; @@ -119,7 +145,7 @@ private void settingBasicValuesFromSPF() { String countryCode = fileManager.getString("USERSETTING_country"); int index = 0; - if (!countryCode.equals("")) { + if (!countryCode.isEmpty()) { String countryName = CountryUtils.getCountryName(countryCode); index = getIndexFromName(country, countryName); } @@ -139,29 +165,11 @@ private void settingBasicValuesFromSPF() { } } - private void init() { - - fileManager = FileManager.getInstance(getApplicationContext()); - - endpointLayout = findViewById(R.id.endpoint_layout); - portLayout = findViewById(R.id.port_layout); - splitTunnelLayout = findViewById(R.id.split_tunnel_layout); - lanLayout = findViewById(R.id.lan_layout); - psiphonLayout = findViewById(R.id.psiphon_layout); - countryLayout = findViewById(R.id.country_layout); - licenseLayout = findViewById(R.id.license_layout); - goolLayout = findViewById(R.id.gool_layout); - - back = findViewById(R.id.back); - endpoint = findViewById(R.id.endpoint); - port = findViewById(R.id.port); - country = findViewById(R.id.country); - license = findViewById(R.id.license); - - psiphon = findViewById(R.id.psiphon); - lan = findViewById(R.id.lan); - gool = findViewById(R.id.gool); - - back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); + @Override + String getKey() { + return "settingsActivity"; } + + @Override + void onConnectionStateChange(ConnectionState state) {} } diff --git a/app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java b/app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java index fa0d7e75..e222491c 100644 --- a/app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java +++ b/app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java @@ -12,14 +12,11 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash_screen); final int SPLASH_DISPLAY_LENGTH = 2750; // 2.75s - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - // Create an Intent that will start the Main Activity. - Intent mainIntent = new Intent(SplashScreenActivity.this, MainActivity.class); - SplashScreenActivity.this.startActivity(mainIntent); - SplashScreenActivity.this.finish(); - } + new Handler().postDelayed(() -> { + // Create an Intent that will start the Main Activity. + Intent mainIntent = new Intent(SplashScreenActivity.this, MainActivity.class); + SplashScreenActivity.this.startActivity(mainIntent); + SplashScreenActivity.this.finish(); }, SPLASH_DISPLAY_LENGTH); } } \ No newline at end of file diff --git a/app/src/main/java/org/bepass/oblivion/SplitTunnelActivity.java b/app/src/main/java/org/bepass/oblivion/SplitTunnelActivity.java index 149a0ef1..add17f9a 100644 --- a/app/src/main/java/org/bepass/oblivion/SplitTunnelActivity.java +++ b/app/src/main/java/org/bepass/oblivion/SplitTunnelActivity.java @@ -12,53 +12,42 @@ import com.google.android.material.progressindicator.CircularProgressIndicator; -public class SplitTunnelActivity extends ConnectionAwareBaseActivity { - - private ImageView back; +public class SplitTunnelActivity extends StateAwareBaseActivity { private RecyclerView appsRecycler; private CircularProgressIndicator progress; - private boolean settingsChanged = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // Sets the contents from the layout setContentView(R.layout.activity_split_tunnel); - back = findViewById(R.id.back); + + // Find UI elements and assign them to vars + ImageView back = findViewById(R.id.back); appsRecycler = findViewById(R.id.appsRecycler); progress = findViewById(R.id.progress); + // Handles the back button behaviour back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); - getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - if (settingsChanged) { - settingsChanged = false; - if (!lastKnownConnectionState.isDisconnected()) { - OblivionVpnService.stopVpnService(SplitTunnelActivity.this); - OblivionVpnService.startVpnService(SplitTunnelActivity.this); - } - } - finish(); - } - }); - BypassListAppsAdapter bypassListAppsAdapter = new BypassListAppsAdapter(this, new BypassListAppsAdapter.LoadListener() { - @Override - public void onLoad(boolean loading) { + // Set up the app list + BypassListAppsAdapter bypassListAppsAdapter = new BypassListAppsAdapter(this, loading -> { appsRecycler.setVisibility(loading ? View.INVISIBLE : View.VISIBLE); if (loading) progress.show(); else progress.hide(); - } }); + // Signal the need to restart the VPN service on app selection change bypassListAppsAdapter.setOnAppSelectListener((packageName, selected) -> { - settingsChanged = true; + StateAwareBaseActivity.setRequireRestartVpnService(true); }); - SplitTunnelOptionsAdapter optionsAdapter = new SplitTunnelOptionsAdapter(this, new SplitTunnelOptionsAdapter.OnSettingsChanged() { + // Set behaviour for Split tunnel options + SplitTunnelOptionsAdapter optionsAdapter = new SplitTunnelOptionsAdapter(this, new SplitTunnelOptionsAdapter.OnSettingsChanged() { @Override public void splitTunnelMode(SplitTunnelMode mode) { - settingsChanged = true; + StateAwareBaseActivity.setRequireRestartVpnService(true); FileManager.getInstance(SplitTunnelActivity.this).set("splitTunnelMode", mode.toString()); } @@ -72,13 +61,9 @@ public void shouldShowSystemApps(boolean show) { } @Override - void onConnectionStateChange(ConnectionState state) { - - } + void onConnectionStateChange(ConnectionState state) { } @NonNull @Override - String getKey() { - return "splitTunnelActivity"; - } + String getKey() { return "splitTunnelActivity"; } } diff --git a/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java b/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java index daf5ad96..aa445f51 100644 --- a/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java +++ b/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java @@ -15,7 +15,7 @@ public class SplitTunnelOptionsAdapter extends RecyclerView.Adapter + app:layout_constraintTop_toBottomOf="@+id/textView" /> + app:layout_constraintTop_toBottomOf="@id/linearLayout2" /> diff --git a/app/src/main/res/mipmap-xhdpi/tv_banner.png b/app/src/main/res/mipmap-xhdpi/tv_banner.png index bcc88ffa..cb23490b 100644 Binary files a/app/src/main/res/mipmap-xhdpi/tv_banner.png and b/app/src/main/res/mipmap-xhdpi/tv_banner.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c8e8ff99..59031af5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Brazil Bulgaria Canada + Croatia Denmark Estonia Finland @@ -20,6 +21,7 @@ Netherlands Norway Poland + Portugal Romania Serbia Singapore diff --git a/build.gradle b/build.gradle index d4203545..7fb6e340 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.3.0' apply false + id 'com.android.application' version '8.4.0' apply false id 'org.jetbrains.kotlin.android' version '1.8.10' apply false } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fbeeace0..195898cb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Feb 07 16:52:07 IRST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists