Skip to content
Open
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
11 changes: 9 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
android:noHistory="true"
tools:ignore="LockedOrientationActivity">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.INTERNET" />
Expand All @@ -13,7 +14,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />

<uses-permission
Expand Down Expand Up @@ -75,6 +77,11 @@
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="org.fptn.vpn.views.experimentalsettings.ExperimentalSettingsActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait" />

<service
android:name="org.fptn.vpn.services.vpn.FptnService"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ private void disconnect(PVNClientException exception) {
unregisterNetworkCallback();

if (SharedPrefUtils.getResetSelectedServerEnabled(this)
|| (!SharedPrefUtils.getResetSelectedServerEnabled(this) && exception != null)) {
|| (SharedPrefUtils.getResetSelectedServerOnExceptionEnabled(this) && exception != null)) {
executorService.submit(() -> {
try {
resetSelectedServer();
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/org/fptn/vpn/utils/SharedPrefUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ public static void saveResetSelectedServerEnabled(Context context, boolean enabl
sharedPreferences.edit().putBoolean(Constants.RESET_SELECTED_SERVER_PREF_KEY, enabled).apply();
}

public static boolean getResetSelectedServerOnExceptionEnabled(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.APPLICATION_SHARED_PREFERENCES, Context.MODE_PRIVATE);
return sharedPreferences.getBoolean(Constants.RESET_SELECTED_SERVER_ON_EXCEPTION_PREF_KEY, false);
}

public static void saveResetSelectedServerOnExceptionEnabled(Context context, boolean enabled) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.APPLICATION_SHARED_PREFERENCES, Context.MODE_PRIVATE);
sharedPreferences.edit().putBoolean(Constants.RESET_SELECTED_SERVER_ON_EXCEPTION_PREF_KEY, enabled).apply();
}


public static BypassCensorshipMethod getBypassCensorshipMethod(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.APPLICATION_SHARED_PREFERENCES, Context.MODE_PRIVATE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package org.fptn.vpn.views.experimentalsettings;

import android.annotation.SuppressLint;
import android.app.StatusBarManager;
import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;

import org.fptn.vpn.R;
import org.fptn.vpn.services.tile.FptnTileService;
import org.fptn.vpn.utils.SharedPrefUtils;

public class ExperimentalSettingsActivity extends AppCompatActivity {
private final String TAG = this.getClass().getSimpleName();

private SwitchCompat switchNetworkType;
private SwitchCompat switchIPAddress;
private SeekBar seekBarAttemptsCount;
private SeekBar seekBarDelayBetween;
private SwitchCompat resetServerAfterDisconnectSwitch;
private SwitchCompat resetServerAfterDisconnectOnException;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.experimental_settings_layout);

initializeVariable();
}

@SuppressLint("InlinedApi")
private void initializeVariable() {
switchNetworkType = findViewById(R.id.reconnect_on_change_network_type_switch);
switchNetworkType.setChecked(SharedPrefUtils.getReconnectOnChangeNetworkTypeEnabled(this));

switchIPAddress = findViewById(R.id.reconnect_on_change_ip_address_switch);
switchIPAddress.setChecked(SharedPrefUtils.getReconnectOnChangeIPEnabled(this));

// Reconnects attempts count
seekBarAttemptsCount = findViewById(R.id.seekBarAttemptsCount);
TextView textViewAttemptsCount = findViewById(R.id.textViewAttemptsCount);
seekBarAttemptsCount.setOnSeekBarChangeListener(new SimpleSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (progress == 3) {
textViewAttemptsCount.setText("∞");
} else {
String format = getString(R.string.reconnect_attempts_text);
textViewAttemptsCount.setText(String.format(format, progress * 5));
}
}
});

seekBarAttemptsCount.setProgress(0);
int reconnectAttemptsCount = SharedPrefUtils.getReconnectAttemptsCount(this);
if (reconnectAttemptsCount == Integer.MAX_VALUE) {
seekBarAttemptsCount.setProgress(3);
} else {
seekBarAttemptsCount.setProgress(reconnectAttemptsCount / 5);
}

// Reconnects delay between in seconds
seekBarDelayBetween = findViewById(R.id.seekBarDelayBetween);
TextView textViewDelayBetween = findViewById(R.id.textViewDelayBetween);
seekBarDelayBetween.setOnSeekBarChangeListener(new SimpleSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
String format = getString(R.string.delay_between_attempts_seconds);
textViewDelayBetween.setText(String.format(format, progress + 1));
}
});

int delayBetweenReconnect = SharedPrefUtils.getDelayBetweenReconnect(this);
seekBarDelayBetween.setProgress(0);
seekBarDelayBetween.setProgress(delayBetweenReconnect - 1);

// Quick tile request
View tileButtonLayout = findViewById(R.id.tile_layout);
Button buttonRequestTile = findViewById(R.id.quick_settings_tile_button);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
buttonRequestTile.setVisibility(View.VISIBLE);
buttonRequestTile.setEnabled(true);
buttonRequestTile.setOnClickListener(v -> requestQuickSettingsTile(buttonRequestTile));

tileButtonLayout.setVisibility(View.VISIBLE);
} else {
buttonRequestTile.setVisibility(View.GONE); // Use GONE to free up layout space

tileButtonLayout.setVisibility(View.GONE);
}

// Reset selected server on disconnect
resetServerAfterDisconnectSwitch = findViewById(R.id.reset_selected_server_after_disconnect_switch);
resetServerAfterDisconnectSwitch.setChecked(SharedPrefUtils.getResetSelectedServerEnabled(this));

// Reset selected server on disconnect with exception
resetServerAfterDisconnectOnException = findViewById(R.id.reset_selected_server_after_disconnect_with_exception);
resetServerAfterDisconnectOnException.setChecked(SharedPrefUtils.getResetSelectedServerEnabled(this));

resetServerAfterDisconnectOnException.setChecked(SharedPrefUtils.getResetSelectedServerOnExceptionEnabled(this));
updateExceptionVisibility(resetServerAfterDisconnectSwitch.isChecked());

// Toggle visibility on change
resetServerAfterDisconnectSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) -> updateExceptionVisibility(isChecked));


// Save and Cancel buttons
Button cancelButton = findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(v -> {
Log.d(TAG, "Cancel button clicked");
finish();
});

Button saveButton = findViewById(R.id.save_button);
saveButton.setOnClickListener(v -> saveAndFinish());
}

private void updateExceptionVisibility(boolean isVisible) {
resetServerAfterDisconnectOnException.setVisibility(isVisible ? View.GONE : View.VISIBLE);
}

@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
private void requestQuickSettingsTile(Button buttonRequestTile) {
StatusBarManager statusBarManager = getSystemService(StatusBarManager.class);
if (statusBarManager == null) return;

// Disable button to prevent double-clicks
buttonRequestTile.setEnabled(false);

try {
ComponentName componentName = new ComponentName(this, FptnTileService.class);
String label = getString(R.string.app_name); // Or specific tile label
Icon icon = Icon.createWithResource(this, R.drawable.ic_logo);
statusBarManager.requestAddTileService(
componentName,
label,
icon,
getMainExecutor(),
resultCode -> handleTileRequestResult(resultCode, buttonRequestTile)
);
} catch (Exception e) {
Log.e(TAG, "Failed to request tile addition", e);
Toast.makeText(this, R.string.tile_addition_failed, Toast.LENGTH_SHORT).show();
buttonRequestTile.setEnabled(true);
}
}

private void handleTileRequestResult(int resultCode, Button button) {
// Re-enable button unless the tile was successfully added/exists
boolean shouldKeepDisabled = false;

switch (resultCode) {
case StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED:
Log.d(TAG, "Tile already added successfully.");
Toast.makeText(this, R.string.tile_already_added, Toast.LENGTH_SHORT).show();
button.setBackgroundResource(R.drawable.round_back_secondary_cancel_100);
shouldKeepDisabled = true;
break;

case StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED:
Log.d(TAG, "Tile added successfully.");
Toast.makeText(this, R.string.tile_added_successfully, Toast.LENGTH_SHORT).show();
shouldKeepDisabled = true;
break;

case StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED:
Log.d(TAG, "User cancelled the request or it failed.");
button.setEnabled(true);
break;

default:
Log.d(TAG, "Unknown result code: " + resultCode);
button.setEnabled(true);
break;
}

if (shouldKeepDisabled) {
button.setEnabled(false);
button.setAlpha(0.5f); // Visual cue that it's no longer needed
}
}

private void saveAndFinish() {
Log.d(TAG, "Save button clicked");

SharedPrefUtils.saveReconnectOnChangeNetworkTypeEnabled(this, switchNetworkType.isChecked());
SharedPrefUtils.saveReconnectOnChangeIPEnabled(this, switchIPAddress.isChecked());
SharedPrefUtils.saveResetSelectedServerEnabled(this, resetServerAfterDisconnectSwitch.isChecked());
SharedPrefUtils.saveResetSelectedServerOnExceptionEnabled(this, resetServerAfterDisconnectOnException.isChecked());

int attemptsCountProgress = seekBarAttemptsCount.getProgress();
if (attemptsCountProgress == 3) {
SharedPrefUtils.saveReconnectAttemptsCount(this, Integer.MAX_VALUE);
} else {
SharedPrefUtils.saveReconnectAttemptsCount(this, attemptsCountProgress * 5);
}

int delayBetweenProgress = seekBarDelayBetween.getProgress();
SharedPrefUtils.saveDelayBetweenReconnect(this, delayBetweenProgress + 1);

finish();
}

// Helper to reduce boilerplate for SeekBars
private abstract static class SimpleSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
}

Loading