diff --git a/app/libs/ca.psiphon.aar b/app/libs/ca.psiphon.aar
index 162712512..f8ba1d64a 100644
--- a/app/libs/ca.psiphon.aar
+++ b/app/libs/ca.psiphon.aar
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4618570cc5a8bb59b3fa3b58332f49c28edc003860f851d41ac9dbe7d0e622b9
-size 37715892
+oid sha256:c2706a9dd1d090aaf38e313e654e0e68ae455ee0e120a86c6d20c8076171afef
+size 37730522
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 434fb2f73..bf2f571e9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -64,6 +64,15 @@
android:scheme="psiphon"
android:host="settings" />
+
+
+
+
+
+
+
+
+
+
@@ -159,41 +176,41 @@
android:process=":LoggingContentProvider"
android:authorities="com.psiphon3.LoggingContentProvider" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/psiphon3/HomeTabFragment.java b/app/src/main/java/com/psiphon3/HomeTabFragment.java
index 48578efa5..df4576f24 100644
--- a/app/src/main/java/com/psiphon3/HomeTabFragment.java
+++ b/app/src/main/java/com/psiphon3/HomeTabFragment.java
@@ -35,6 +35,7 @@
import android.widget.TextView;
import android.widget.ViewFlipper;
+import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@@ -164,16 +165,20 @@ public void onDestroy() {
}
private void updateStatusUI(TunnelState tunnelState) {
+ @DrawableRes int statusIconResId;
if (tunnelState.isRunning()) {
if (tunnelState.connectionData().isConnected()) {
- statusViewImage.setImageResource(R.drawable.status_icon_connected);
+ statusIconResId = tunnelState.connectionData().personalPairingEnabled() ?
+ R.drawable.status_icon_connected_pp : R.drawable.status_icon_connected;
} else {
- statusViewImage.setImageResource(R.drawable.status_icon_connecting);
+ statusIconResId = tunnelState.connectionData().personalPairingEnabled() ?
+ R.drawable.status_icon_connecting_pp : R.drawable.status_icon_connecting;
}
} else {
// the tunnel state is either unknown or not running
- statusViewImage.setImageResource(R.drawable.status_icon_disconnected);
+ statusIconResId = R.drawable.status_icon_disconnected;
}
+ statusViewImage.setImageResource(statusIconResId);
}
private void loadEmbeddedWebView(String url) {
diff --git a/app/src/main/java/com/psiphon3/MainActivity.java b/app/src/main/java/com/psiphon3/MainActivity.java
index 802db2462..2e4b7ffe4 100644
--- a/app/src/main/java/com/psiphon3/MainActivity.java
+++ b/app/src/main/java/com/psiphon3/MainActivity.java
@@ -34,6 +34,9 @@
import android.nfc.cardemulation.CardEmulation;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
import android.provider.Settings;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -41,6 +44,7 @@
import android.text.TextUtils;
import android.text.style.BulletSpan;
import android.text.util.Linkify;
+import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -55,7 +59,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.SwitchCompat;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.PermissionChecker;
@@ -69,8 +75,10 @@
import com.google.android.material.tabs.TabLayout;
import com.psiphon3.VpnRulesHelper;
import com.psiphon3.log.LogsMaintenanceWorker;
+import com.psiphon3.log.MyLog;
import com.psiphon3.psiphonlibrary.EmbeddedValues;
import com.psiphon3.psiphonlibrary.LocalizedActivities;
+import com.psiphon3.psiphonlibrary.PersonalPairingHelper;
import com.psiphon3.psiphonlibrary.TunnelManager;
import com.psiphon3.psiphonlibrary.Utils;
import com.psiphon3.psiphonlibrary.VpnAppsUtils;
@@ -90,11 +98,14 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import io.reactivex.Completable;
+import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
public class MainActivity extends LocalizedActivities.AppCompatActivity {
@@ -128,6 +139,17 @@ public MainActivity() {
// Keeps track of the Psiphon Bump help state
private PsiphonBumpHelpState psiphonBumpHelpState = PsiphonBumpHelpState.DISABLED;
+ private View personalPairingToggleContainer;
+ private SwitchCompat personalPairingToggle;
+ private TextView personalPairingLabel;
+ private Button personalPairingTurnOffButton;
+ private boolean personalPairingEnabled;
+ private TunnelState latestTunnelState;
+ private long personalPairingConnectingSinceMs = -1;
+ private static final long PERSONAL_PAIRING_TURN_OFF_PROMPT_DELAY_MS = TimeUnit.MINUTES.toMillis(2);
+ private final Handler personalPairingPromptHandler = new Handler(Looper.getMainLooper());
+ private final Runnable personalPairingPromptRunnable = this::updatePersonalPairingTurnOffPrompt;
+
enum PsiphonBumpHelpState {
DISABLED,
NEED_SYSTEM_NFC,
@@ -208,6 +230,22 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
helpConnectFab = findViewById(R.id.help_connect_fab);
+ personalPairingToggleContainer = findViewById(R.id.personalPairingToggleContainer);
+ personalPairingToggle = findViewById(R.id.personalPairingToggle);
+ personalPairingToggle.setOnCheckedChangeListener((buttonView, isChecked) ->
+ viewModel.setPersonalParingEnabled(isChecked));
+
+ personalPairingLabel = findViewById(R.id.personalPairingLabel);
+ personalPairingTurnOffButton = findViewById(R.id.personalPairingTurnOffButton);
+ personalPairingTurnOffButton.setOnClickListener(v -> {
+ if (personalPairingToggle.isChecked()) {
+ personalPairingToggle.setChecked(false);
+ } else {
+ viewModel.setPersonalParingEnabled(false);
+ }
+ });
+
+
EmbeddedValues.initialize(getApplicationContext());
// Load VPN exclusion rules from storage for main app process
@@ -301,6 +339,7 @@ public void onTabReselected(TabLayout.Tab tab) {
@Override
public void onDestroy() {
+ personalPairingPromptHandler.removeCallbacks(personalPairingPromptRunnable);
compositeDisposable.dispose();
super.onDestroy();
}
@@ -309,6 +348,7 @@ public void onDestroy() {
protected void onPause() {
super.onPause();
cancelInvalidProxySettingsToast();
+ personalPairingPromptHandler.removeCallbacks(personalPairingPromptRunnable);
compositeDisposable.clear();
}
@@ -346,19 +386,8 @@ protected void onResume() {
.doOnNext(this::updateServiceStateUI)
.subscribe());
- // If device supports Psiphon Bump observe tunnel state and update NFC UI and HCE state accordingly
- if (Utils.supportsPsiphonBump(this)) {
- compositeDisposable.add(getTunnelServiceInteractor().tunnelStateFlowable()
- .observeOn(AndroidSchedulers.mainThread())
- .doOnNext(this::updatePsiphonBumpState)
- // disable Psiphon Bump HCE and hide help connect FAB when this subscription is
- // disposed.
- .doOnCancel(() -> {
- updatePsiphonBumpHceState(false);
- helpConnectFab.setVisibility(View.GONE);
- })
- .subscribe());
- }
+ // Set up Psiphon Bump state handling
+ setupPsiphonBumpHandling();
// Observe custom proxy validation results to show a toast for invalid ones
compositeDisposable.add(viewModel.customProxyValidationResultFlowable()
@@ -397,6 +426,91 @@ protected void onResume() {
.andThen(autoStartMaybe())
.doOnSuccess(__ -> startTunnel())
.subscribe());
+
+ // Observe personal pairing state changes and restart the tunnel if needed
+// Observe personal pairing state changes and restart the tunnel if needed
+ compositeDisposable.add(
+ viewModel.pairingStateRestartTunnelFlowable()
+ .observeOn(AndroidSchedulers.mainThread())
+ .switchMap(__ -> getTunnelServiceInteractor().tunnelStateFlowable()
+ .filter(tunnelState -> !tunnelState.isUnknown())
+ .take(1)
+ .doOnNext(tunnelState -> {
+ if (tunnelState.isRunning()) {
+ getTunnelServiceInteractor().commandTunnelRestart();
+ }
+ })
+ )
+ .subscribe());
+
+ // Observe personal paring state and update the UI
+ compositeDisposable.add(
+ viewModel.personalPairingStateFlowable()
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnNext(state -> {
+ personalPairingEnabled = state.enabled;
+
+ if (state.data == null || state.data.compartmentId == null || state.data.compartmentId.isEmpty()) {
+ // Hide the personal pairing toggle layout if there is no data
+ personalPairingToggleContainer.setVisibility(View.GONE);
+ } else {
+ // Show the personal pairing toggle layout if there is data
+ personalPairingToggleContainer.setVisibility(View.VISIBLE);
+ }
+
+ if (state.enabled && state.data != null && state.data.compartmentId != null && !state.data.compartmentId.isEmpty()) {
+ String alias = state.data.alias;
+ personalPairingToggle.setChecked(true);
+ if (alias != null && !alias.isEmpty()) {
+ personalPairingLabel.setText(getString(R.string.preference_summary_personal_pairing_enabled_with_alias, alias));
+ } else {
+ personalPairingLabel.setText(getString(R.string.preference_summary_personal_pairing_enabled));
+ }
+ personalPairingLabel.setVisibility(View.VISIBLE);
+ } else {
+ personalPairingToggle.setChecked(false);
+ personalPairingLabel.setVisibility(View.INVISIBLE); // Not GONE, we want to keep the space
+ }
+
+ updatePersonalPairingTurnOffPrompt();
+ })
+ .subscribe());
+ }
+
+ private void setupPsiphonBumpHandling() {
+ if (!Utils.supportsPsiphonBump(this)) {
+ updatePsiphonBumpHceState(false);
+ helpConnectFab.setVisibility(View.GONE);
+ helpConnectFab.setOnClickListener(null);
+ return;
+ }
+
+ compositeDisposable.add(
+ Flowable.combineLatest(
+ getTunnelServiceInteractor().tunnelStateFlowable(),
+ viewModel.personalPairingStateFlowable(),
+ Pair::new)
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnNext(statePair -> {
+ TunnelState tunnelState = statePair.first;
+ PersonalPairingHelper.PersonalPairingState personalPairingState = statePair.second;
+
+ // If personal pairing is enabled, override everything else.
+ if (personalPairingState.enabled) {
+ updatePsiphonBumpHceState(false);
+ helpConnectFab.setVisibility(View.GONE);
+ helpConnectFab.setOnClickListener(null);
+ } else {
+ // Otherwise process normal tunnel state.
+ updatePsiphonBumpState(tunnelState);
+ }
+ })
+ .doOnCancel(() -> {
+ updatePsiphonBumpHceState(false);
+ helpConnectFab.setVisibility(View.GONE);
+ })
+ .subscribe()
+ );
}
// Check runtime permissions and show rationales if needed.
@@ -551,7 +665,47 @@ protected void onNewIntent(Intent intent) {
HandleCurrentIntent(intent);
}
+ private boolean shouldShowPersonalPairingTurnOffPrompt() {
+ if (!personalPairingEnabled || latestTunnelState == null || !latestTunnelState.isRunning()) {
+ return false;
+ }
+
+ TunnelState.ConnectionData connectionData = latestTunnelState.connectionData();
+ return connectionData != null
+ && connectionData.networkConnectionState() == TunnelState.ConnectionData.NetworkConnectionState.CONNECTING;
+ }
+
+ private void hidePersonalPairingTurnOffPrompt() {
+ personalPairingConnectingSinceMs = -1;
+ personalPairingPromptHandler.removeCallbacks(personalPairingPromptRunnable);
+ personalPairingTurnOffButton.setVisibility(View.GONE);
+ }
+
+ private void updatePersonalPairingTurnOffPrompt() {
+ if (!shouldShowPersonalPairingTurnOffPrompt()) {
+ hidePersonalPairingTurnOffPrompt();
+ return;
+ }
+
+ if (personalPairingConnectingSinceMs < 0) {
+ personalPairingConnectingSinceMs = SystemClock.elapsedRealtime();
+ }
+
+ long elapsed = SystemClock.elapsedRealtime() - personalPairingConnectingSinceMs;
+ long remaining = PERSONAL_PAIRING_TURN_OFF_PROMPT_DELAY_MS - elapsed;
+
+ personalPairingPromptHandler.removeCallbacks(personalPairingPromptRunnable);
+ if (remaining <= 0) {
+ personalPairingTurnOffButton.setVisibility(View.VISIBLE);
+ } else {
+ personalPairingTurnOffButton.setVisibility(View.GONE);
+ personalPairingPromptHandler.postDelayed(personalPairingPromptRunnable, remaining);
+ }
+ }
+
private void updateServiceStateUI(final TunnelState tunnelState) {
+ latestTunnelState = tunnelState;
+
if (tunnelState.isUnknown()) {
openBrowserButton.setEnabled(false);
toggleButton.setEnabled(false);
@@ -595,6 +749,8 @@ private void updateServiceStateUI(final TunnelState tunnelState) {
connectionProgressBar.setVisibility(View.INVISIBLE);
connectionWaitingNetworkIndicator.setVisibility(View.INVISIBLE);
}
+
+ updatePersonalPairingTurnOffPrompt();
}
// update NFC UI
@@ -848,6 +1004,7 @@ private void HandleCurrentIntent(Intent intent) {
}
}
+ // Handles deep links
private boolean handleDeepLinkIntent(@NonNull Intent intent) {
final String FWD_SLASH = "/";
@@ -857,40 +1014,165 @@ private boolean handleDeepLinkIntent(@NonNull Intent intent) {
final String SETTINGS_PATH_VPN = "/vpn";
final String SETTINGS_PATH_PROXY = "/proxy";
final String SETTINGS_PATH_MORE_OPTIONS = "/more-options";
+ final String PAIR_HOST = "pair";
Uri intentUri = intent.getData();
- // Check if this is a deep link intent we can handle
- if (!Intent.ACTION_VIEW.equals(intent.getAction()) ||
- intentUri == null ||
- !PSIPHON_SCHEME.equals(intentUri.getScheme())) {
- // Intent not handled
+ // Check if the intent is a view action and has a valid URI
+ if (!Intent.ACTION_VIEW.equals(intent.getAction()) || intentUri == null) {
+ // Intent was not handled
return false;
}
+ String scheme = intentUri.getScheme();
+ String host = intentUri.getHost();
String path = intentUri.getPath();
- switch (intentUri.getHost()) {
- case SETTINGS_HOST:
- selectTabByTag("settings");
- if (path != null) {
- // If uri path is "/vpn" or "/vpn/.*" then signal to navigate to VPN settings screen.
- // If the path is "/proxy" or "/proxy/.*" then signal to navigate to Proxy settings screen.
- // If the path is "/more-options" or "/more-options/.*" then signal to navigate to More Options screen.
- if (path.equals(SETTINGS_PATH_VPN) || path.startsWith(SETTINGS_PATH_VPN + FWD_SLASH)) {
- viewModel.signalOpenVpnSettings();
- } else if (path.equals(SETTINGS_PATH_PROXY) || path.startsWith(SETTINGS_PATH_PROXY + FWD_SLASH)) {
- viewModel.signalOpenProxySettings();
- } else if (path.equals(SETTINGS_PATH_MORE_OPTIONS) || path.startsWith(SETTINGS_PATH_MORE_OPTIONS)) {
- viewModel.signalOpenMoreOptions();
- }
- }
- // intent handled
+ // Handle Personal Pairing deep links (psiphon://pair/)
+ if (PSIPHON_SCHEME.equals(scheme) && PAIR_HOST.equals(host)) {
+ // Return false if path is missing or invalid
+ if (path == null || path.length() <= 1) {
+ // Intent was not handled
+ return false;
+ }
+
+ if (!intentUri.getPathSegments().isEmpty()) {
+ handlePersonalPairingData(intentUri.toString());
+ // Intent was handled
return true;
+ }
+ MyLog.w("MainActivity::handleDeepLinkIntent: empty pairing data in deep link");
+ // Intent was not handled
+ return false;
+ }
+
+ // Finally, handle psiphon://settings/... deep links
+ if (SETTINGS_HOST.equals(host)) {
+ selectTabByTag("settings");
+ // If uri path is "/vpn" or "/vpn/.*" then signal to navigate to VPN settings screen.
+ // If the path is "/proxy" or "/proxy/.*" then signal to navigate to Proxy settings screen.
+ // If the path is "/more-options" or "/more-options/.*" then signal to navigate to More Options screen.
+ if (path != null) {
+ if (path.equals(SETTINGS_PATH_VPN) || path.startsWith(SETTINGS_PATH_VPN + FWD_SLASH)) {
+ viewModel.signalOpenVpnSettings();
+ } else if (path.equals(SETTINGS_PATH_PROXY) || path.startsWith(SETTINGS_PATH_PROXY + FWD_SLASH)) {
+ viewModel.signalOpenProxySettings();
+ } else if (path.equals(SETTINGS_PATH_MORE_OPTIONS) || path.startsWith(SETTINGS_PATH_MORE_OPTIONS + FWD_SLASH)) {
+ viewModel.signalOpenMoreOptions();
+ }
+ }
+ // Intent was handled
+ return true;
}
- // intent not handled
+ // Intent was not handled
return false;
}
+ // Handles personal pairing data import from deep links
+ private void handlePersonalPairingData(String input) {
+ Flowable tunnelStateFlowable = getTunnelServiceInteractor()
+ .tunnelStateFlowable()
+ .filter(state -> !state.isUnknown());
+
+ compositeDisposable.add(
+ viewModel.handlePersonalPairingData(input, tunnelStateFlowable)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(result -> {
+ switch (result.action) {
+ case SHOW_SUCCESS:
+ showToast(R.string.personal_pairing_data_import_success);
+ break;
+ case SHOW_ALREADY_EXISTS:
+ showToast(R.string.personal_pairing_data_already_exists);
+ break;
+ case SHOW_ERROR:
+ showToast(getPairingImportErrorString(result.validationError));
+ break;
+ case PROMPT_ENABLE:
+ showEnableConfirmationDialog(result.data);
+ break;
+ case PROMPT_UPDATE:
+ showUpdateConfirmationDialog(result.data, result.existingCompartmentId, result.existingEnabled);
+ break;
+ }
+ }, error -> showToast(getPairingImportErrorString(
+ PersonalPairingHelper.validationErrorFromException(error))))
+ );
+ }
+
+ @StringRes
+ private int getPairingImportErrorString(PersonalPairingHelper.ImportValidationError validationError) {
+ if (validationError == PersonalPairingHelper.ImportValidationError.UNSUPPORTED_VERSION) {
+ return R.string.personal_pairing_unsupported_version;
+ }
+ if (validationError == PersonalPairingHelper.ImportValidationError.INVALID_INPUT_FORMAT) {
+ return R.string.personal_pairing_invalid_url;
+ }
+ return R.string.personal_pairing_invalid_data;
+ }
+
+ // Keeps track of any import pairing data toast to cancel if we need to show a new one
+ Toast importPairingDataToast;
+
+ // Shows a toast while cancelling any current import pairing data toast
+ private void showToast(@StringRes int messageId) {
+ if (importPairingDataToast != null) {
+ importPairingDataToast.cancel();
+ }
+ importPairingDataToast = Toast.makeText(MainActivity.this, messageId, Toast.LENGTH_LONG);
+ importPairingDataToast.show();
+ }
+
+ // Keep track of the update confirmation dialog to dismiss if we need to show a new one
+ AlertDialog updateConfirmationDialog;
+ // Confirms updating existing personal pairing data if the compartment ID already present in the settings
+ private void showUpdateConfirmationDialog(PersonalPairingHelper.PersonalPairingData newData, String existingId, boolean enabled) {
+ if (updateConfirmationDialog != null && updateConfirmationDialog.isShowing()) {
+ updateConfirmationDialog.dismiss();
+ }
+ View dialogView = getLayoutInflater().inflate(R.layout.dialog_pairing_update, null);
+ TextView oldIdView = dialogView.findViewById(R.id.old_compartment_id);
+ TextView newIdView = dialogView.findViewById(R.id.new_compartment_id);
+ oldIdView.setText(existingId);
+ newIdView.setText(newData.compartmentId);
+
+ updateConfirmationDialog = new AlertDialog.Builder(this)
+ .setIcon(R.drawable.ic_psiphon_alert_notification)
+ .setTitle(R.string.personal_pairing_update_title)
+ .setView(dialogView)
+ .setPositiveButton(R.string.personal_pairing_update_positive_button,
+ (dialog, which) -> {
+ viewModel.confirmPersonalPairingImport(newData, enabled);
+ showToast(R.string.personal_pairing_data_update_success);
+ })
+ .setNegativeButton(R.string.personal_pairing_update_negative_button, null)
+ .show();
+ }
+
+ // Keep track of the enable confirmation dialog to dismiss if we need to show a new one
+ AlertDialog enableConfirmationDialog;
+ // Confirms enabling personal pairing feature while importing personal pairing data
+ private void showEnableConfirmationDialog(PersonalPairingHelper.PersonalPairingData data) {
+ if (enableConfirmationDialog != null && enableConfirmationDialog.isShowing()) {
+ enableConfirmationDialog.dismiss();
+ }
+ View dialogView = getLayoutInflater().inflate(R.layout.dialog_pairing_enable, null);
+
+ enableConfirmationDialog = new AlertDialog.Builder(this)
+ .setIcon(R.drawable.ic_psiphon_alert_notification)
+ .setTitle(R.string.personal_pairing_enable_confirmation_dialog_title)
+ .setView(dialogView)
+ .setPositiveButton(R.string.lbl_yes, (dialog, which) -> {
+ viewModel.confirmPersonalPairingImport(data, true);
+ showToast(R.string.personal_pairing_data_import_success);
+ })
+ .setNegativeButton(R.string.lbl_no, (dialog, which) -> {
+ viewModel.confirmPersonalPairingImport(data, false);
+ showToast(R.string.personal_pairing_data_import_success);
+ })
+ .show();
+ }
+
@Override
public void startTunnel() {
// Don't start if custom proxy settings is selected and values are invalid
@@ -916,8 +1198,15 @@ private void preventAutoStart() {
}
private boolean shouldAutoStart() {
+ Intent intent = getIntent();
+
+ // Check if launched from a deep link or app link and prevent auto-start if so
+ boolean isDeepLink = Intent.ACTION_VIEW.equals(intent.getAction()) &&
+ (intent.getData() != null);
+
return isFirstRun &&
- !getIntent().getBooleanExtra(INTENT_EXTRA_PREVENT_AUTO_START, false);
+ !intent.getBooleanExtra(INTENT_EXTRA_PREVENT_AUTO_START, false) &&
+ !isDeepLink;
}
// Returns an object only if tunnel should be auto-started,
diff --git a/app/src/main/java/com/psiphon3/MainActivityViewModel.java b/app/src/main/java/com/psiphon3/MainActivityViewModel.java
index d8cc5ad4b..f5b7f87a3 100644
--- a/app/src/main/java/com/psiphon3/MainActivityViewModel.java
+++ b/app/src/main/java/com/psiphon3/MainActivityViewModel.java
@@ -34,10 +34,15 @@
import com.psiphon3.log.LogsDataSourceFactory;
import com.psiphon3.log.LogsLastEntryHelper;
import com.psiphon3.log.MyLog;
+import com.psiphon3.psiphonlibrary.PersonalPairingHelper;
import com.psiphon3.psiphonlibrary.UpstreamProxySettings;
+import java.util.Objects;
+
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
+import io.reactivex.Single;
+import io.reactivex.disposables.CompositeDisposable;
public class MainActivityViewModel extends AndroidViewModel implements DefaultLifecycleObserver {
private final PublishRelay customProxyValidationResultRelay = PublishRelay.create();
@@ -49,9 +54,12 @@ public class MainActivityViewModel extends AndroidViewModel implements DefaultLi
private final Flowable lastLogEntryFlowable;
private final Flowable> logsPagedListFlowable;
private final ContentObserver loggingObserver;
+ private final PersonalPairingHelper personalPairingHelper;
+ private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public MainActivityViewModel(@NonNull Application application) {
super(application);
+ personalPairingHelper = new PersonalPairingHelper(application);
LogsLastEntryHelper logsLastEntryHelper = new LogsLastEntryHelper(application.getContentResolver());
LogsDataSourceFactory logsDataSourceFactory = new LogsDataSourceFactory(application.getContentResolver());
@@ -92,6 +100,7 @@ public void onChange(boolean selfChange) {
@Override
protected void onCleared() {
super.onCleared();
+ compositeDisposable.clear();
getApplication().getContentResolver().unregisterContentObserver(loggingObserver);
}
@@ -166,4 +175,58 @@ public Flowable lastLogEntryFlowable() {
return lastLogEntryFlowable
.map(logEntry -> MyLog.getStatusLogMessageForDisplay(logEntry.getLogJson(), getApplication()));
}
+
+ public Flowable personalPairingStateFlowable() {
+ return personalPairingHelper.observePersonalPairingState()
+ .distinctUntilChanged();
+ }
+
+ public Single handlePersonalPairingData(
+ String input,
+ Flowable tunnelState) {
+ return personalPairingHelper.handleImport(input, tunnelState);
+ }
+
+ public void confirmPersonalPairingImport(PersonalPairingHelper.PersonalPairingData data, boolean enableSetting) {
+ personalPairingHelper.confirmImport(data, enableSetting);
+ }
+
+ public void setPersonalPairingState(boolean isEnabled, @NonNull PersonalPairingHelper.PersonalPairingData data) {
+ personalPairingHelper.setPersonalPairingState(isEnabled, data);
+ }
+
+ public void setPersonalParingEnabled(boolean isEnabled) {
+ personalPairingHelper.setPersonalPairingEnabled(isEnabled);
+ }
+
+ // Keep track of the last known pairing state to determine when tunnel restart is needed
+ private PersonalPairingHelper.PersonalPairingState lastKnownPersonalPairingState;
+
+
+ // Observes pairing state changes and triggers tunnel restart when necessary. Restart occurs when:
+ // - Pairing is enabled/ disabled
+ // - Compartment ID changes while pairing is enabled
+ // returns a flowable that emits true when a restart is needed
+ public Flowable pairingStateRestartTunnelFlowable() {
+ return personalPairingStateFlowable()
+ .map(currentState -> {
+ boolean shouldRestart = false;
+
+ if (lastKnownPersonalPairingState != null) {
+ boolean enableChanged = lastKnownPersonalPairingState.enabled != currentState.enabled;
+ String previousCompartmentId =
+ lastKnownPersonalPairingState.data == null ? null : lastKnownPersonalPairingState.data.compartmentId;
+ String currentCompartmentId =
+ currentState.data == null ? null : currentState.data.compartmentId;
+ boolean compartmentChanged = lastKnownPersonalPairingState.enabled && currentState.enabled &&
+ !Objects.equals(previousCompartmentId, currentCompartmentId);
+
+ shouldRestart = enableChanged || compartmentChanged;
+ }
+
+ lastKnownPersonalPairingState = currentState;
+ return shouldRestart;
+ })
+ .switchMap(shouldRestart -> shouldRestart ? Flowable.just(true) : Flowable.empty());
+ }
}
diff --git a/app/src/main/java/com/psiphon3/OptionsTabFragment.java b/app/src/main/java/com/psiphon3/OptionsTabFragment.java
index 37d7e18c7..d7c3285e3 100644
--- a/app/src/main/java/com/psiphon3/OptionsTabFragment.java
+++ b/app/src/main/java/com/psiphon3/OptionsTabFragment.java
@@ -17,6 +17,8 @@
import com.psiphon3.psiphonlibrary.LocalizedActivities;
import com.psiphon3.psiphonlibrary.MoreOptionsPreferenceActivity;
+import com.psiphon3.psiphonlibrary.PersonalPairingHelper;
+import com.psiphon3.psiphonlibrary.PersonalPairingPreferenceActivity;
import com.psiphon3.psiphonlibrary.ProxyOptionsPreferenceActivity;
import com.psiphon3.psiphonlibrary.PsiphonConstants;
import com.psiphon3.psiphonlibrary.PsiphonPreferenceFragmentCompat;
@@ -42,10 +44,12 @@ private enum RestartMode {
private static final int REQUEST_CODE_VPN_PREFERENCES = 100;
private static final int REQUEST_CODE_PROXY_PREFERENCES = 101;
private static final int REQUEST_CODE_MORE_PREFERENCES = 102;
+ private static final int REQUEST_CODE_PERSONAL_PAIRING = 103;
private RegionListPreference regionListPreference;
private Preference vpnOptionsPreference;
private Preference proxyOptionsPreference;
+ private Preference personalPairingPreference;
private AppPreferences multiProcessPreferences;
private MainActivityViewModel viewModel;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@@ -105,6 +109,15 @@ public void onCreatePreferences(Bundle bundle, String s) {
}
return true;
});
+ personalPairingPreference = findPreference(getContext().getString(R.string.personalPairingPreferenceKey));
+ personalPairingPreference.setOnPreferenceClickListener(__ -> {
+ final FragmentActivity activity = getActivity();
+ if (activity != null && !activity.isFinishing()) {
+ startActivityForResult(new Intent(getActivity(),
+ PersonalPairingPreferenceActivity.class), REQUEST_CODE_PERSONAL_PAIRING);
+ }
+ return true;
+ });
}
@Override
@@ -167,6 +180,13 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
}
})
.subscribe());
+
+ // Observe 'Personal Pairing` signal from deep link intent handler
+ // and update the preference summary
+ compositeDisposable.add(viewModel.personalPairingStateFlowable()
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnNext(this::setPersonalPairingSummary)
+ .subscribe());
}
private void onRegionSelected(String selectedRegionCode) {
@@ -218,6 +238,21 @@ private void setSummaryFromPreferences() {
}
}
+ private void setPersonalPairingSummary(PersonalPairingHelper.PersonalPairingState personalPairingState) {
+ if (personalPairingState.enabled) {
+ String alias = personalPairingState.data == null ? null : personalPairingState.data.alias;
+ if (alias == null || alias.isEmpty()) {
+ // If no alias show just Enabled
+ personalPairingPreference.setSummary(R.string.preference_summary_personal_pairing_enabled);
+ } else {
+ // If alias then show the alias
+ personalPairingPreference.setSummary(getString(R.string.preference_summary_personal_pairing_enabled_with_alias, alias));
+ }
+ } else {
+ personalPairingPreference.setSummary(R.string.preference_summary_personal_pairing_disabled);
+ }
+ }
+
@Override
public void onActivityResult(int request, int result, Intent data) {
final @NonNull RestartMode restartMode;
@@ -237,6 +272,14 @@ public void onActivityResult(int request, int result, Intent data) {
updateMoreSettingsFromPreferences();
break;
+ case REQUEST_CODE_PERSONAL_PAIRING:
+ // Note: we are not checking for restart requirements here
+ // because the personal pairing settings may be changed from the main screen or by importing data from a deep link as well as from the personal pairing settings screen
+ // and we will be taking care of the restart requirements centrally
+ restartMode = RestartMode.NONE;
+ updatePersonalPairingFromPreferences();
+ break;
+
default:
restartMode = RestartMode.NONE;
super.onActivityResult(request, result, data);
@@ -450,4 +493,17 @@ private void updateMoreSettingsFromPreferences() {
new SharedPreferencesImport(requireContext(), prefName, getString(R.string.nfcBumpPreference), getString(R.string.nfcBumpPreference))
);
}
+
+ private void updatePersonalPairingFromPreferences() {
+ String prefName = getString(R.string.moreOptionsPreferencesName);
+ boolean isEnabled = requireContext().getSharedPreferences(prefName, MODE_PRIVATE)
+ .getBoolean(getString(R.string.personalPairingEnabledPreference), false);
+ String compartmentId = requireContext().getSharedPreferences(prefName, MODE_PRIVATE)
+ .getString(getString(R.string.personalPairingCompartmentIdPreference), "");
+ String alias = requireContext().getSharedPreferences(prefName, MODE_PRIVATE)
+ .getString(getString(R.string.personalPairingAliasPreference), "");
+
+ PersonalPairingHelper.PersonalPairingData data = new PersonalPairingHelper.PersonalPairingData(compartmentId, alias);
+ viewModel.setPersonalPairingState(isEnabled, data);
+ }
}
diff --git a/app/src/main/java/com/psiphon3/TunnelState.java b/app/src/main/java/com/psiphon3/TunnelState.java
index 841e48d14..f3867f4c5 100644
--- a/app/src/main/java/com/psiphon3/TunnelState.java
+++ b/app/src/main/java/com/psiphon3/TunnelState.java
@@ -62,6 +62,8 @@ public enum NetworkConnectionState {
@Nullable
public abstract ArrayList vpnApps();
+ public abstract boolean personalPairingEnabled();
+
public static Builder builder() {
return new AutoValue_TunnelState_ConnectionData.Builder()
.setNetworkConnectionState(NetworkConnectionState.CONNECTING)
@@ -72,7 +74,8 @@ public static Builder builder() {
.setHttpPort(0)
.setHomePages(null)
.setVpnMode(com.psiphon3.psiphonlibrary.VpnAppsUtils.VpnAppsExclusionSetting.ALL_APPS)
- .setVpnApps(null);
+ .setVpnApps(null)
+ .setPersonalPairingEnabled(false);
}
@AutoValue.Builder
@@ -95,6 +98,8 @@ public static abstract class Builder {
public abstract Builder setVpnApps(@Nullable ArrayList vpnApps);
+ public abstract Builder setPersonalPairingEnabled(boolean value);
+
public abstract ConnectionData build();
}
diff --git a/app/src/main/java/com/psiphon3/psiphonlibrary/PersonalPairingHelper.java b/app/src/main/java/com/psiphon3/psiphonlibrary/PersonalPairingHelper.java
new file mode 100644
index 000000000..6e5a8f104
--- /dev/null
+++ b/app/src/main/java/com/psiphon3/psiphonlibrary/PersonalPairingHelper.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2024, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package com.psiphon3.psiphonlibrary;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.jakewharton.rxrelay2.BehaviorRelay;
+import com.psiphon3.R;
+import com.psiphon3.TunnelState;
+import com.psiphon3.log.MyLog;
+
+import net.grandcentrix.tray.AppPreferences;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+
+/**
+ * Helper class to manage the state and configuration of the personal pairing feature.
+ * Provides utilities to observe, validate, and update personal pairing settings,
+ * handle user imports, and manage storage and relay mechanisms for state changes.
+ */
+public class PersonalPairingHelper {
+ private static final String PSIPHON_SCHEME = "psiphon";
+ private static final String PSIPHON_PAIR_HOST = "pair";
+ private static final String HTTP_SCHEME = "http";
+ private static final String HTTPS_SCHEME = "https";
+ private static final String PAIR_PATH_SEGMENT = "pair";
+ private static final String SUPPORTED_VERSION = "1";
+ private static final String VERSION_KEY = "v";
+ private static final String DATA_KEY = "data";
+ private static final String ID_KEY = "id";
+ private static final String NAME_KEY = "name";
+ private static final Pattern BASE64URL_PATTERN = Pattern.compile("^[A-Za-z0-9_-]+$");
+ private static final Pattern BASE64_PATTERN = Pattern.compile("^[A-Za-z0-9+/]+={0,2}$");
+ private static final Pattern COMPARTMENT_ID_STANDARD_PATTERN = Pattern.compile("^[A-Za-z0-9+/]{43}$");
+ private static final JsonFactory JSON_FACTORY = new JsonFactory();
+
+ public enum ImportValidationError {
+ INVALID_INPUT_FORMAT,
+ MALFORMED_TOKEN,
+ UNSUPPORTED_VERSION
+ }
+
+ public static class PersonalPairingImportException extends IllegalArgumentException {
+ public final ImportValidationError validationError;
+
+ public PersonalPairingImportException(ImportValidationError validationError) {
+ super(validationError.name());
+ this.validationError = validationError;
+ }
+
+ public PersonalPairingImportException(ImportValidationError validationError, Throwable cause) {
+ super(validationError.name(), cause);
+ this.validationError = validationError;
+ }
+ }
+
+ public static class PersonalPairingState {
+ public final boolean enabled;
+ public final PersonalPairingData data;
+
+ public PersonalPairingState(boolean enabled, PersonalPairingData data) {
+ this.enabled = enabled;
+ this.data = data;
+ }
+
+ public static PersonalPairingState create(boolean enabled, PersonalPairingData data) {
+ return new PersonalPairingState(enabled, data);
+ }
+
+ public PersonalPairingState withEnabled(boolean enabled) {
+ return new PersonalPairingState(enabled, this.data);
+ }
+
+ public PersonalPairingState withData(PersonalPairingData data) {
+ return new PersonalPairingState(this.enabled, data);
+ }
+ }
+
+ public static class PersonalPairingData {
+ public final String compartmentId;
+ public final String alias;
+
+ public PersonalPairingData(String compartmentId, String alias) {
+ this.compartmentId = compartmentId;
+ this.alias = alias;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PersonalPairingData that = (PersonalPairingData) o;
+ return Objects.equals(compartmentId, that.compartmentId) &&
+ Objects.equals(alias, that.alias);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(compartmentId, alias);
+ }
+ }
+
+ private final BehaviorRelay personalPairingStateRelay;
+ private final AppPreferences prefs;
+ private final Context context;
+
+ public static String toStandardBase64CompartmentId(String compartmentId) {
+ if (compartmentId == null) {
+ return null;
+ }
+ return compartmentId
+ .replace('-', '+')
+ .replace('_', '/');
+ }
+
+ public PersonalPairingHelper(Context context) {
+ this.context = context;
+ this.prefs = new AppPreferences(context);
+ this.personalPairingStateRelay = BehaviorRelay.createDefault(loadInitialState());
+ }
+
+ // Load initial state from multi-process shared preferences
+ private PersonalPairingState loadInitialState() {
+ boolean enabled = prefs.getBoolean(context.getString(R.string.personalPairingEnabledPreference), false);
+ String compartmentId = prefs.getString(
+ context.getString(R.string.personalPairingCompartmentIdPreference), "");
+ String alias = prefs.getString(
+ context.getString(R.string.personalPairingAliasPreference), "");
+
+ PersonalPairingData data = null;
+ if (compartmentId != null && !compartmentId.isEmpty()) {
+ data = new PersonalPairingData(toStandardBase64CompartmentId(compartmentId), alias != null ? alias : "");
+ }
+
+ return new PersonalPairingState(enabled, data);
+ }
+
+ // Observe personal pairing state changes
+ public Flowable observePersonalPairingState() {
+ return personalPairingStateRelay.hide()
+ .toFlowable(BackpressureStrategy.LATEST);
+ }
+
+ // Update personal pairing state enabled flag
+ public void setPersonalPairingEnabled(boolean enabled) {
+ PersonalPairingState currentState = personalPairingStateRelay.getValue();
+ if (currentState != null && currentState.enabled != enabled) {
+ prefs.put(context.getString(R.string.personalPairingEnabledPreference), enabled);
+ personalPairingStateRelay.accept(currentState.withEnabled(enabled));
+ }
+ }
+
+ // Update personal pairing state data, i.e. compartment ID and alias values and enabled flag
+ public void setPersonalPairingState(boolean enabled, PersonalPairingData data) {
+ if (data == null) {
+ return;
+ }
+
+ PersonalPairingData normalizedData = new PersonalPairingData(
+ toStandardBase64CompartmentId(data.compartmentId),
+ data.alias);
+
+ PersonalPairingState currentState = personalPairingStateRelay.getValue();
+ if (currentState != null && (currentState.enabled != enabled ||
+ !Objects.equals(currentState.data, normalizedData))) {
+ prefs.put(context.getString(R.string.personalPairingEnabledPreference), enabled);
+ prefs.put(context.getString(R.string.personalPairingCompartmentIdPreference), normalizedData.compartmentId);
+ prefs.put(context.getString(R.string.personalPairingAliasPreference), normalizedData.alias);
+ personalPairingStateRelay.accept(PersonalPairingState.create(enabled, normalizedData));
+ }
+ }
+
+ // Container class for import result and data
+ public static class ImportResult {
+ public enum Action {
+ // Data imported successfully
+ SHOW_SUCCESS,
+ // Data already exists (same compartment ID)
+ SHOW_ALREADY_EXISTS,
+ // Data import failed
+ SHOW_ERROR,
+ // Prompt user to enable the feature
+ PROMPT_ENABLE,
+ // Prompt user to update existing data
+ PROMPT_UPDATE
+ }
+
+ public final Action action;
+ public final PersonalPairingData data;
+ public final String existingCompartmentId;
+ public final Boolean existingEnabled;
+ public final ImportValidationError validationError;
+
+ private ImportResult(Action action,
+ PersonalPairingData data,
+ String existingCompartmentId,
+ Boolean existingEnabled,
+ ImportValidationError validationError) {
+ this.action = action;
+ this.data = data;
+ this.existingCompartmentId = existingCompartmentId;
+ this.existingEnabled = existingEnabled;
+ this.validationError = validationError;
+ }
+
+ public static ImportResult success(PersonalPairingData data) {
+ return new ImportResult(Action.SHOW_SUCCESS, data, null, null, null);
+ }
+
+ public static ImportResult alreadyExists(PersonalPairingData data) {
+ return new ImportResult(Action.SHOW_ALREADY_EXISTS, data, null, null, null);
+ }
+
+ public static ImportResult error(ImportValidationError validationError) {
+ return new ImportResult(Action.SHOW_ERROR, null, null, null, validationError);
+ }
+
+ public static ImportResult promptEnable(PersonalPairingData data) {
+ return new ImportResult(Action.PROMPT_ENABLE, data, null, null, null);
+ }
+
+ public static ImportResult needsUpdate(PersonalPairingData data, String existingId, Boolean existingEnabled) {
+ return new ImportResult(Action.PROMPT_UPDATE, data, existingId, existingEnabled, null);
+ }
+ }
+
+ public static ImportValidationError validationErrorFromException(Throwable throwable) {
+ if (throwable instanceof PersonalPairingImportException) {
+ return ((PersonalPairingImportException) throwable).validationError;
+ }
+ return ImportValidationError.MALFORMED_TOKEN;
+ }
+
+ private static PersonalPairingImportException invalidInputFormat() {
+ return new PersonalPairingImportException(ImportValidationError.INVALID_INPUT_FORMAT);
+ }
+
+ private static PersonalPairingImportException malformedToken() {
+ return new PersonalPairingImportException(ImportValidationError.MALFORMED_TOKEN);
+ }
+
+ private static PersonalPairingImportException malformedToken(Throwable cause) {
+ return new PersonalPairingImportException(ImportValidationError.MALFORMED_TOKEN, cause);
+ }
+
+ private static PersonalPairingImportException unsupportedVersion() {
+ return new PersonalPairingImportException(ImportValidationError.UNSUPPORTED_VERSION);
+ }
+
+ private static String normalizeTokenInput(String input) {
+ if (input == null) {
+ throw invalidInputFormat();
+ }
+
+ String trimmedInput = input.trim();
+ if (trimmedInput.isEmpty()) {
+ throw invalidInputFormat();
+ }
+
+ if (!trimmedInput.contains("://")) {
+ return trimmedInput;
+ }
+
+ URI uri;
+ try {
+ uri = new URI(trimmedInput);
+ } catch (URISyntaxException e) {
+ throw invalidInputFormat();
+ }
+
+ String scheme = uri.getScheme();
+ if (scheme == null || scheme.isEmpty()) {
+ throw invalidInputFormat();
+ }
+
+ String[] segments = getRawPathSegments(uri.getRawPath());
+
+ if (PSIPHON_SCHEME.equals(scheme)) {
+ if (!PSIPHON_PAIR_HOST.equals(uri.getHost())) {
+ throw invalidInputFormat();
+ }
+ if (segments.length != 1) {
+ throw invalidInputFormat();
+ }
+ String token = segments[0];
+ if (token == null || token.isEmpty()) {
+ throw invalidInputFormat();
+ }
+ return token;
+ }
+
+ if (HTTP_SCHEME.equals(scheme) || HTTPS_SCHEME.equals(scheme)) {
+ return extractTokenFromPairPath(segments);
+ }
+
+ throw invalidInputFormat();
+ }
+
+ private static String extractTokenFromPairPath(String[] segments) {
+ for (int i = segments.length - 2; i >= 0; i--) {
+ if (PAIR_PATH_SEGMENT.equals(segments[i])) {
+ if (i + 2 != segments.length) {
+ throw invalidInputFormat();
+ }
+ String token = segments[i + 1];
+ if (token == null || token.isEmpty()) {
+ throw invalidInputFormat();
+ }
+ return token;
+ }
+ }
+ throw invalidInputFormat();
+ }
+
+ private static String[] getRawPathSegments(String rawPath) {
+ if (rawPath == null || rawPath.isEmpty() || "/".equals(rawPath)) {
+ return new String[0];
+ }
+ if (!rawPath.startsWith("/")) {
+ throw invalidInputFormat();
+ }
+ String[] segments = rawPath.substring(1).split("/", -1);
+ for (String segment : segments) {
+ if (segment.isEmpty()) {
+ throw invalidInputFormat();
+ }
+ }
+ return segments;
+ }
+
+ private static byte[] decodeToken(String token) {
+ if (token == null || token.isEmpty()) {
+ throw malformedToken();
+ }
+
+ if (BASE64URL_PATTERN.matcher(token).matches()) {
+ try {
+ return decodeUrlSafeBase64(token);
+ } catch (IllegalArgumentException e) {
+ throw malformedToken(e);
+ }
+ }
+
+ if (BASE64_PATTERN.matcher(token).matches() && token.length() % 4 == 0) {
+ try {
+ return Utils.Base64.decode(token);
+ } catch (IllegalArgumentException e) {
+ throw malformedToken(e);
+ }
+ }
+
+ throw malformedToken();
+ }
+
+ private static String requireNonEmptyString(JsonParser parser) throws IOException {
+ if (parser.getCurrentToken() != JsonToken.VALUE_STRING) {
+ throw malformedToken();
+ }
+ String stringValue = parser.getValueAsString();
+ if (stringValue.isEmpty()) {
+ throw malformedToken();
+ }
+ return stringValue;
+ }
+
+ private static PersonalPairingData parsePayload(byte[] decodedToken) {
+ try (JsonParser parser = JSON_FACTORY.createParser(decodedToken)) {
+ if (parser.nextToken() != JsonToken.START_OBJECT) {
+ throw malformedToken();
+ }
+
+ String version = null;
+ String compartmentId = null;
+ String alias = null;
+ int topLevelFieldCount = 0;
+
+ while (parser.nextToken() != JsonToken.END_OBJECT) {
+ if (parser.getCurrentToken() != JsonToken.FIELD_NAME) {
+ throw malformedToken();
+ }
+
+ String fieldName = parser.getCurrentName();
+ topLevelFieldCount++;
+ parser.nextToken();
+
+ if (VERSION_KEY.equals(fieldName)) {
+ version = requireNonEmptyString(parser);
+ } else if (DATA_KEY.equals(fieldName)) {
+ if (parser.getCurrentToken() != JsonToken.START_OBJECT) {
+ throw malformedToken();
+ }
+
+ int dataFieldCount = 0;
+ while (parser.nextToken() != JsonToken.END_OBJECT) {
+ if (parser.getCurrentToken() != JsonToken.FIELD_NAME) {
+ throw malformedToken();
+ }
+
+ String dataFieldName = parser.getCurrentName();
+ dataFieldCount++;
+ parser.nextToken();
+
+ if (ID_KEY.equals(dataFieldName)) {
+ compartmentId = requireNonEmptyString(parser);
+ } else if (NAME_KEY.equals(dataFieldName)) {
+ alias = requireNonEmptyString(parser);
+ } else {
+ throw malformedToken();
+ }
+ }
+
+ if (dataFieldCount != 2 || compartmentId == null || alias == null) {
+ throw malformedToken();
+ }
+ } else {
+ throw malformedToken();
+ }
+ }
+
+ if (topLevelFieldCount != 2 || version == null) {
+ throw malformedToken();
+ }
+
+ if (!SUPPORTED_VERSION.equals(version)) {
+ throw unsupportedVersion();
+ }
+
+ String normalizedCompartmentId = validateAndNormalizeCompartmentId(compartmentId);
+ return new PersonalPairingData(normalizedCompartmentId, alias);
+ } catch (PersonalPairingImportException e) {
+ throw e;
+ } catch (IOException | RuntimeException e) {
+ throw malformedToken(e);
+ }
+ }
+
+ private static String validateAndNormalizeCompartmentId(String compartmentId) {
+ if (!COMPARTMENT_ID_STANDARD_PATTERN.matcher(compartmentId).matches()) {
+ throw malformedToken();
+ }
+
+ String normalizedCompartmentId = toStandardBase64CompartmentId(compartmentId);
+
+ try {
+ byte[] decoded = decodeUrlSafeBase64(normalizedCompartmentId);
+ if (decoded.length != 32) {
+ throw malformedToken();
+ }
+ } catch (IllegalArgumentException e) {
+ throw malformedToken(e);
+ }
+
+ return normalizedCompartmentId;
+ }
+
+ private static byte[] decodeUrlSafeBase64(String token) {
+ int padLength = (4 - (token.length() % 4)) % 4;
+ String paddedToken = token + "====".substring(0, padLength);
+ String normalized = paddedToken
+ .replace('-', '+')
+ .replace('_', '/');
+ return Utils.Base64.decode(normalized);
+ }
+
+ // Extract personal pairing data from a token string, deep link, or wrapper URL
+ public static PersonalPairingData extractPersonalPairingData(String input) throws IllegalArgumentException {
+ String token = normalizeTokenInput(input);
+ try {
+ byte[] decodedToken = decodeToken(token);
+ return parsePayload(decodedToken);
+ } catch (PersonalPairingImportException e) {
+ throw e;
+ } catch (Exception e) {
+ throw malformedToken(e);
+ }
+ }
+
+ // Validate personal pairing data and determine the appropriate action
+ private ImportResult validatePersonalPairingData(String input) {
+ try {
+ PersonalPairingData personalPairingData = extractPersonalPairingData(input);
+ String storedCompartmentId = prefs.getString(context.getString(R.string.personalPairingCompartmentIdPreference), "");
+ Boolean storedEnabled = prefs.getBoolean(context.getString(R.string.personalPairingEnabledPreference), false);
+ if (storedCompartmentId == null || storedCompartmentId.isEmpty()) {
+ return ImportResult.promptEnable(personalPairingData);
+ } else if (storedCompartmentId.equals(personalPairingData.compartmentId)) {
+ return ImportResult.alreadyExists(personalPairingData);
+ } else {
+ return ImportResult.needsUpdate(personalPairingData, storedCompartmentId, storedEnabled);
+ }
+ } catch (IllegalArgumentException e) {
+ ImportValidationError validationError = validationErrorFromException(e);
+ MyLog.e("PersonalPairingHelper::validatePersonalPairingData error: " + validationError.name());
+ return ImportResult.error(validationError);
+ }
+ }
+
+ // Handle the import of personal pairing data import and determine the appropriate action
+ public Single handleImport(@NonNull String input, Flowable tunnelState) {
+ return Single.fromCallable(() -> validatePersonalPairingData(input))
+ .flatMap(result -> {
+ if (result.action == ImportResult.Action.PROMPT_ENABLE) {
+ // If importing a new pairing while the tunnel is running, prompt the user to enable
+ // the feature because enabling the feature will restart the tunnel
+ // Otherwise, enable the feature automatically without prompting
+ return tunnelState
+ .firstOrError()
+ .map(state -> {
+ if (state.isRunning()) {
+ // Pass through the PROMPT_ENABLE result to trigger the prompt UI
+ return result;
+ } else {
+ // Enable the feature automatically, pass SHOW_SUCCESS result to trigger import success UI
+ setPersonalPairingState(true, result.data);
+ return ImportResult.success(result.data);
+ }
+ });
+ }
+ return Single.just(result);
+ });
+ }
+
+ // Save the personal pairing data after user confirmation and sets the feature enabled flag
+ public void confirmImport(PersonalPairingData data, boolean enableSetting) {
+ setPersonalPairingState(enableSetting, data);
+ }
+
+ // Reset all personal pairing preferences
+ public static void resetPersonalPairingPreferences(Context context) {
+ AppPreferences prefs = new AppPreferences(context);
+ prefs.remove(context.getString(R.string.personalPairingEnabledPreference));
+ prefs.remove(context.getString(R.string.personalPairingCompartmentIdPreference));
+ prefs.remove(context.getString(R.string.personalPairingAliasPreference));
+ }
+}
diff --git a/app/src/main/java/com/psiphon3/psiphonlibrary/PersonalPairingPreferenceActivity.java b/app/src/main/java/com/psiphon3/psiphonlibrary/PersonalPairingPreferenceActivity.java
new file mode 100644
index 000000000..8ba57aca9
--- /dev/null
+++ b/app/src/main/java/com/psiphon3/psiphonlibrary/PersonalPairingPreferenceActivity.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2020, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package com.psiphon3.psiphonlibrary;
+
+import android.app.AlertDialog;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.EditTextPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.psiphon3.MainActivityViewModel;
+import com.psiphon3.R;
+
+public class PersonalPairingPreferenceActivity extends LocalizedActivities.AppCompatActivity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ getSupportFragmentManager().beginTransaction()
+ .add(android.R.id.content, new PersonalPairingPreferenceFragment())
+ .commit();
+ }
+
+ MainActivityViewModel viewModel = new ViewModelProvider(this,
+ new ViewModelProvider.AndroidViewModelFactory(getApplication()))
+ .get(MainActivityViewModel.class);
+ getLifecycle().addObserver(viewModel);
+ }
+
+ public static class PersonalPairingPreferenceFragment extends PsiphonPreferenceFragmentCompat
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private CheckBoxPreference enabledPref;
+ private Preference importPref;
+ private EditTextPreference compartmentIdPref;
+ private EditTextPreference aliasPref;
+ private Preference resetPref;
+ private Toast currentToast;
+ private PersonalPairingHelper personalPairingHelper;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
+ addPreferencesFromResource(R.xml.personal_pairing_preferences);
+ final PreferenceScreen preferences = getPreferenceScreen();
+
+ // Initialize preferences
+ enabledPref = preferences.findPreference(getString(R.string.personalPairingEnabledPreference));
+ importPref = preferences.findPreference(getString(R.string.personalPairingImportPreference));
+ compartmentIdPref = preferences.findPreference(getString(R.string.personalPairingCompartmentIdPreference));
+ aliasPref = preferences.findPreference(getString(R.string.personalPairingAliasPreference));
+ resetPref = preferences.findPreference(getString(R.string.personalPairingResetPreference));
+ personalPairingHelper = new PersonalPairingHelper(requireContext());
+
+ // Set initial values from current preferences
+ final PreferenceGetter preferenceGetter = getPreferenceGetter();
+ enabledPref.setChecked(preferenceGetter.getBoolean(getString(R.string.personalPairingEnabledPreference), false));
+ compartmentIdPref.setText(preferenceGetter.getString(getString(R.string.personalPairingCompartmentIdPreference), ""));
+ aliasPref.setText(preferenceGetter.getString(getString(R.string.personalPairingAliasPreference), ""));
+
+ // Set up import button click listener
+ importPref.setOnPreferenceClickListener(preference -> {
+ showImportDialog();
+ return true;
+ });
+
+ // Set up name preference change listener
+ aliasPref.setOnPreferenceChangeListener((preference, newValue) -> {
+ if (TextUtils.isEmpty(compartmentIdPref.getText())) {
+ showToast(R.string.personal_pairing_need_compartment_id, Toast.LENGTH_SHORT);
+ return false;
+ }
+ return true;
+ });
+
+ // Set up enabled preference change listener
+ enabledPref.setOnPreferenceChangeListener((preference, newValue) -> {
+ boolean newEnabled = (Boolean) newValue;
+ if (newEnabled && TextUtils.isEmpty(compartmentIdPref.getText())) {
+ showToast(R.string.personal_pairing_need_compartment_id, Toast.LENGTH_SHORT);
+ return false;
+ }
+ return true;
+ });
+
+ // Set up reset click listener
+ resetPref.setOnPreferenceClickListener(preference -> {
+ showResetDialog();
+ return true;
+ });
+
+ updatePersonalPairingPreferencesUI();
+ }
+
+ private void showImportDialog() {
+ View dialogView = LayoutInflater.from(getContext())
+ .inflate(R.layout.dialog_import_pairing, null);
+ EditText urlInput = dialogView.findViewById(R.id.url_input);
+
+ new AlertDialog.Builder(requireContext())
+ .setTitle(R.string.personal_pairing_import_dialog_title)
+ .setView(dialogView)
+ .setPositiveButton(R.string.import_button, (dialog, which) -> {
+ String url = urlInput.getText().toString();
+ try {
+ PersonalPairingHelper.PersonalPairingData data = PersonalPairingHelper.extractPersonalPairingData(url);
+ updatePairingData(data);
+ // Also enable the feature automatically
+ enabledPref.setChecked(true);
+ } catch (IllegalArgumentException e) {
+ showToast(getPairingImportErrorString(
+ PersonalPairingHelper.validationErrorFromException(e)), Toast.LENGTH_LONG);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private void showResetDialog() {
+ String compartmentId = compartmentIdPref.getText();
+
+ // Guard against empty ID
+ if (compartmentId == null || compartmentId.isEmpty()) {
+ showToast(R.string.personal_pairing_reset_error, Toast.LENGTH_SHORT);
+ return;
+ }
+
+ // Determine how many characters to request
+ int charsToRequest = Math.min(compartmentId.length(), 4);
+ String lastChars = compartmentId.substring(compartmentId.length() - charsToRequest);
+
+ View dialogView = LayoutInflater.from(getContext())
+ .inflate(R.layout.dialog_reset_pairing, null);
+
+ TextView messageText = dialogView.findViewById(R.id.delete_message);
+ TextView symbolsText = dialogView.findViewById(R.id.delete_symbols);
+
+ messageText.setText(getString(R.string.personal_pairing_delete_message, charsToRequest));
+ symbolsText.setText(lastChars);
+
+ EditText deleteInput = dialogView.findViewById(R.id.delete_input);
+ deleteInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(charsToRequest) });
+
+ AlertDialog dialog = new AlertDialog.Builder(requireContext())
+ .setTitle(R.string.personal_pairing_reset_dialog_title)
+ .setView(dialogView)
+ .setPositiveButton(R.string.reset_button, null) // Set listener later to prevent auto-dismiss
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+
+ // Allow pressing enter to submit
+ deleteInput.setOnEditorActionListener((v, actionId, event) -> {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveButton != null) {
+ positiveButton.performClick();
+ }
+ return true;
+ }
+ return false;
+ });
+
+ dialog.setOnShowListener(dialogInterface -> {
+ Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ button.setOnClickListener(view -> {
+ String input = deleteInput.getText().toString();
+ if (lastChars.equals(input)) {
+ resetPairingPreferences();
+ showToast(R.string.personal_pairing_reset_success, Toast.LENGTH_SHORT);
+ dialog.dismiss();
+ } else {
+ showToast(R.string.personal_pairing_reset_invalid, Toast.LENGTH_SHORT);
+ }
+ });
+ });
+
+ dialog.show();
+ }
+
+ private void resetPairingPreferences() {
+ compartmentIdPref.setText("");
+ compartmentIdPref.setSummary(R.string.personal_pairing_compartment_id_summary);
+ aliasPref.setText("");
+ aliasPref.setSummary(R.string.personal_pairing_alias_summary);
+ enabledPref.setChecked(false);
+ PersonalPairingHelper.resetPersonalPairingPreferences(getContext());
+ }
+
+ private void updatePairingData(PersonalPairingHelper.PersonalPairingData data) {
+ compartmentIdPref.setText(data.compartmentId);
+ aliasPref.setText(data.alias);
+ updatePersonalPairingPreferencesUI();
+ persistPersonalPairingState();
+ }
+
+ private void persistPersonalPairingState() {
+ String compartmentId = compartmentIdPref.getText();
+ String alias = aliasPref.getText();
+ boolean enabled = enabledPref.isChecked();
+
+ if (compartmentId == null) {
+ compartmentId = "";
+ }
+ if (alias == null) {
+ alias = "";
+ }
+
+ personalPairingHelper.setPersonalPairingState(
+ enabled,
+ new PersonalPairingHelper.PersonalPairingData(compartmentId, alias));
+ }
+
+ private void updatePersonalPairingPreferencesUI() {
+ boolean hasCompartmentId = !TextUtils.isEmpty(compartmentIdPref.getText());
+ boolean isEnabled = enabledPref.isChecked();
+
+ // Update preference states
+ aliasPref.setEnabled(hasCompartmentId); // Keep editable if compartment ID is set even if the feature is disabled
+ resetPref.setEnabled(hasCompartmentId);
+
+ compartmentIdPref.setEnabled(isEnabled && hasCompartmentId);
+
+ // If no compartment ID, ensure feature is disabled
+ if (!hasCompartmentId && isEnabled) {
+ enabledPref.setChecked(false);
+ }
+
+ // Update compartment ID and alias summaries if the compartment ID is set, otherwise show the default summary
+ if (hasCompartmentId) {
+ String compartmentId = compartmentIdPref.getText();
+ compartmentIdPref.setSummary(!TextUtils.isEmpty(compartmentId) ? compartmentId : null);
+
+ String name = aliasPref.getText();
+ aliasPref.setSummary(!TextUtils.isEmpty(name) ? name.replace("\n", " ") : null);
+ }
+ }
+
+ private void showToast(@StringRes int messageId, int toastLength) {
+ if (currentToast != null) {
+ currentToast.cancel();
+ }
+ currentToast = Toast.makeText(getContext(), messageId, toastLength);
+ currentToast.show();
+ }
+
+ @StringRes
+ private int getPairingImportErrorString(PersonalPairingHelper.ImportValidationError validationError) {
+ if (validationError == PersonalPairingHelper.ImportValidationError.UNSUPPORTED_VERSION) {
+ return R.string.personal_pairing_unsupported_version;
+ }
+ if (validationError == PersonalPairingHelper.ImportValidationError.INVALID_INPUT_FORMAT) {
+ return R.string.personal_pairing_invalid_url;
+ }
+ return R.string.personal_pairing_invalid_data;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceScreen().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceScreen().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ updatePersonalPairingPreferencesUI();
+ persistPersonalPairingState();
+ }
+ }
+}
diff --git a/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelManager.java b/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelManager.java
index 36037956f..9c8b8d44a 100644
--- a/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelManager.java
+++ b/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelManager.java
@@ -130,8 +130,9 @@ enum ServiceToClientMessage {
static final String DATA_TUNNEL_STATE_CLIENT_REGION = "clientRegion";
static final String DATA_TUNNEL_STATE_SPONSOR_ID = "sponsorId";
public static final String DATA_TUNNEL_STATE_HOME_PAGES = "homePages";
- public static final String DATA_TUNNEL_STATE_VPN_MODE = "vpnMode";
- public static final String DATA_TUNNEL_STATE_VPN_APPS = "vpnApps";
+ public static final String DATA_TUNNEL_STATE_VPN_MODE = "vpnMode";
+ public static final String DATA_TUNNEL_STATE_VPN_APPS = "vpnApps";
+ public static final String DATA_TUNNEL_STATE_IS_PERSONAL_PAIRING_MODE = "isPersonalPairingMode";
static final String DATA_TRANSFER_STATS_CONNECTED_TIME = "dataTransferStatsConnectedTime";
static final String DATA_TRANSFER_STATS_TOTAL_BYTES_SENT = "dataTransferStatsTotalBytesSent";
static final String DATA_TRANSFER_STATS_TOTAL_BYTES_RECEIVED = "dataTransferStatsTotalBytesReceived";
@@ -153,6 +154,7 @@ static class Config {
boolean disableTimeouts = false;
String sponsorId = EmbeddedValues.SPONSOR_ID;
String deviceLocation = "";
+ String personalPairingCompartmentId = "";
}
private Config m_tunnelConfig;
@@ -172,8 +174,9 @@ public static class State {
String clientRegion = "";
String sponsorId = "";
ArrayList homePages = new ArrayList<>();
- VpnAppsUtils.VpnAppsExclusionSetting vpnMode = VpnAppsUtils.VpnAppsExclusionSetting.ALL_APPS;
- ArrayList vpnApps = new ArrayList<>();
+ VpnAppsUtils.VpnAppsExclusionSetting vpnMode = VpnAppsUtils.VpnAppsExclusionSetting.ALL_APPS;
+ ArrayList vpnApps = new ArrayList<>();
+ public boolean isPersonalPairingMode;
boolean isConnected() {
return networkConnectionState == TunnelState.ConnectionData.NetworkConnectionState.CONNECTED;
@@ -317,7 +320,7 @@ IBinder onBind(Intent intent) {
// also updates service notification.
private Disposable connectionStatusUpdaterDisposable() {
return connectionObservable()
- .switchMap(pair -> {
+ .switchMap(pair -> {
TunnelState.ConnectionData.NetworkConnectionState networkConnectionState = pair.first;
boolean isRoutingThroughTunnel = pair.second;
@@ -342,11 +345,11 @@ private Disposable connectionStatusUpdaterDisposable() {
m_isRoutingThroughTunnelPublishRelay.accept(Boolean.TRUE);
// Do not emit downstream if we are just started routing.
return Observable.empty();
- }
- return Observable.just(networkConnectionState);
- })
- .distinctUntilChanged()
- .doOnNext(networkConnectionState -> {
+ }
+ return Observable.just(networkConnectionState);
+ })
+ .distinctUntilChanged()
+ .doOnNext(networkConnectionState -> {
m_tunnelState.networkConnectionState = networkConnectionState;
sendClientMessage(ServiceToClientMessage.TUNNEL_CONNECTION_STATE.ordinal(), getTunnelStateBundle());
// Don't update notification to CONNECTING, etc., when a stop was commanded.
@@ -536,6 +539,19 @@ private Single getTunnelConfigSingle() {
tunnelConfig.disableTimeouts = multiProcessPreferences
.getBoolean(getContext().getString(R.string.disableTimeoutsPreference),
false);
+ boolean personalPairingModePreference = multiProcessPreferences.getBoolean(
+ getContext().getString(R.string.personalPairingEnabledPreference), false);
+
+ // If personal pairing is enabled, get the compartment ID from the preferences
+ String compartmentId = "";
+ if (personalPairingModePreference) {
+ compartmentId = multiProcessPreferences.getString(getContext().getString(R.string.personalPairingCompartmentIdPreference), "");
+ compartmentId = PersonalPairingHelper.toStandardBase64CompartmentId(compartmentId);
+ if (TextUtils.isEmpty(compartmentId)) {
+ MyLog.w("TunnelManager::getTunnelConfigSingle: personal pairing is enabled but the compartment ID is empty.");
+ }
+ }
+ tunnelConfig.personalPairingCompartmentId = compartmentId;
return tunnelConfig;
});
@@ -565,7 +581,7 @@ private Notification createNotification(
int defaults = 0;
if (networkConnectionState == TunnelState.ConnectionData.NetworkConnectionState.CONNECTED) {
- iconID = R.drawable.notification_icon_connected;
+ iconID = isPersonalPairingMode() ? R.drawable.notification_icon_connected_pp : R.drawable.notification_icon_connected;
switch (vpnAppsExclusionSetting) {
case INCLUDE_APPS:
contentText = getContext().getResources()
@@ -587,7 +603,7 @@ private Notification createNotification(
contentText = getContext().getString(R.string.waiting_for_network_connectivity);
ticker = getContext().getText(R.string.waiting_for_network_connectivity);
} else {
- iconID = R.drawable.notification_icon_connecting_animation;
+ iconID = isPersonalPairingMode() ? R.drawable.notification_icon_connecting_animation_pp : R.drawable.notification_icon_connecting_animation;
contentText = getContext().getString(R.string.psiphon_service_notification_message_connecting);
ticker = getContext().getText(R.string.psiphon_service_notification_message_connecting);
}
@@ -636,6 +652,10 @@ private Notification createNotification(
.build();
}
+ private boolean isPersonalPairingMode() {
+ return m_tunnelConfig != null && !TextUtils.isEmpty(m_tunnelConfig.personalPairingCompartmentId);
+ }
+
/**
* Update the context used to get resources with the passed context
*
@@ -822,18 +842,18 @@ public void handleMessage(Message msg) {
}
}
- private static void setLocale(TunnelManager manager) {
+ private static void setLocale(TunnelManager manager) {
LocaleManager localeManager = LocaleManager.getInstance(manager.m_parentService);
String languageCode = localeManager.getLanguage();
if (localeManager.isSystemLocale(languageCode)) {
manager.m_context = localeManager.resetToSystemLocale(manager.m_parentService);
} else {
manager.m_context = localeManager.setNewLocale(manager.m_parentService, languageCode);
- }
- manager.updateNotifications();
- // Also update upgrade notifications
- UpgradeManager.UpgradeInstaller.updateNotification(manager.getContext());
- }
+ }
+ manager.updateNotifications();
+ // Also update upgrade notifications
+ UpgradeManager.UpgradeInstaller.updateNotification(manager.getContext());
+ }
private Message composeClientMessage(int what, Bundle data) {
Message msg = Message.obtain(null, what);
@@ -893,10 +913,11 @@ private Bundle getTunnelStateBundle() {
data.putString(DATA_TUNNEL_STATE_CLIENT_REGION, m_tunnelState.clientRegion);
data.putString(DATA_TUNNEL_STATE_SPONSOR_ID, m_tunnelState.sponsorId);
data.putStringArrayList(DATA_TUNNEL_STATE_HOME_PAGES, m_tunnelState.homePages);
- data.putSerializable(DATA_TUNNEL_STATE_VPN_MODE, m_tunnelState.vpnMode);
- data.putStringArrayList(DATA_TUNNEL_STATE_VPN_APPS, m_tunnelState.vpnApps);
- return data;
- }
+ data.putSerializable(DATA_TUNNEL_STATE_VPN_MODE, m_tunnelState.vpnMode);
+ data.putStringArrayList(DATA_TUNNEL_STATE_VPN_APPS, m_tunnelState.vpnApps);
+ data.putBoolean(DATA_TUNNEL_STATE_IS_PERSONAL_PAIRING_MODE, isPersonalPairingMode());
+ return data;
+ }
private Bundle getDataTransferStatsBundle() {
Bundle data = new Bundle();
@@ -944,12 +965,12 @@ private void runTunnel() {
final String stdErrRedirectPath = PsiphonCrashService.getStdRedirectPath(m_parentService);
NDCrash.nativeInitializeStdErrRedirect(stdErrRedirectPath);
- m_isStopping.set(false);
- m_networkConnectionStatePublishRelay.accept(TunnelState.ConnectionData.NetworkConnectionState.CONNECTING);
- m_isRoutingThroughTunnelPublishRelay.accept(Boolean.FALSE);
-
- // Notify if an upgrade has already been downloaded and is waiting for install
- UpgradeManager.UpgradeInstaller.notifyUpgrade(getContext(), PsiphonTunnel.getDefaultUpgradeDownloadFilePath(getContext()));
+ m_isStopping.set(false);
+ m_networkConnectionStatePublishRelay.accept(TunnelState.ConnectionData.NetworkConnectionState.CONNECTING);
+ m_isRoutingThroughTunnelPublishRelay.accept(Boolean.FALSE);
+
+ // Notify if an upgrade has already been downloaded and is waiting for install
+ UpgradeManager.UpgradeInstaller.notifyUpgrade(getContext(), PsiphonTunnel.getDefaultUpgradeDownloadFilePath(getContext()));
MyLog.i(R.string.starting_tunnel, MyLog.Sensitivity.NOT_SENSITIVE);
@@ -1251,19 +1272,19 @@ public static String buildTunnelCoreConfig(
try {
- json.put("ClientVersion", EmbeddedValues.CLIENT_VERSION);
-
- if (UpgradeChecker.upgradeCheckNeeded(context)) {
-
- json.put("UpgradeDownloadURLs", new JSONArray(EmbeddedValues.UPGRADE_URLS_JSON));
-
- json.put("UpgradeDownloadClientVersionHeader", "x-amz-meta-psiphon-client-version");
-
- json.put("EnableUpgradeDownload", true);
- }
-
- json.put("MigrateUpgradeDownloadFilename",
- new UpgradeManager.OldDownloadedUpgradeFile(context).getFullPath());
+ json.put("ClientVersion", EmbeddedValues.CLIENT_VERSION);
+
+ if (UpgradeChecker.upgradeCheckNeeded(context)) {
+
+ json.put("UpgradeDownloadURLs", new JSONArray(EmbeddedValues.UPGRADE_URLS_JSON));
+
+ json.put("UpgradeDownloadClientVersionHeader", "x-amz-meta-psiphon-client-version");
+
+ json.put("EnableUpgradeDownload", true);
+ }
+
+ json.put("MigrateUpgradeDownloadFilename",
+ new UpgradeManager.OldDownloadedUpgradeFile(context).getFullPath());
json.put("PropagationChannelId", EmbeddedValues.PROPAGATION_CHANNEL_ID);
@@ -1369,6 +1390,11 @@ public static String buildTunnelCoreConfig(
json.put("EmitBytesTransferred", true);
+ // Set the personal pairing config if config has a non-empty personal pairing compartment ID
+ if (!TextUtils.isEmpty(tunnelConfig.personalPairingCompartmentId)) {
+ json.put("InproxyClientPersonalCompartmentID", tunnelConfig.personalPairingCompartmentId);
+ }
+
return json.toString();
} catch (JSONException e) {
return null;
@@ -1378,15 +1404,15 @@ public static String buildTunnelCoreConfig(
// This observable emits a pair consisting of the latest NetworkConnectionState state and a
// Boolean representing whether we are routing the traffic via tunnel.
// Emits a new pair every time when either of the sources emits a new value.
- private Observable> connectionObservable() {
- return Observable.combineLatest(m_networkConnectionStatePublishRelay,
- m_isRoutingThroughTunnelPublishRelay,
- ((BiFunction>) Pair::new))
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .distinctUntilChanged();
- }
+ private Observable> connectionObservable() {
+ return Observable.combineLatest(m_networkConnectionStatePublishRelay,
+ m_isRoutingThroughTunnelPublishRelay,
+ ((BiFunction>) Pair::new))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .distinctUntilChanged();
+ }
/**
* Configure tunnel with appropriate client platform affixes (i.e., the main Psiphon app
@@ -1660,27 +1686,27 @@ public void run() {
}
@Override
- public void onClientRegion(final String region) {
- m_Handler.post(new Runnable() {
- @Override
- public void run() {
- m_tunnelState.clientRegion = region;
- }
- });
- }
-
- @Override
- public void onClientUpgradeDownloaded(String filename) {
- m_Handler.post(new Runnable() {
- @Override
- public void run() {
- UpgradeManager.UpgradeInstaller.notifyUpgrade(getContext(), filename);
- }
- });
- }
-
- @Override
- public void onUntunneledAddress(final String address) {
+ public void onClientRegion(final String region) {
+ m_Handler.post(new Runnable() {
+ @Override
+ public void run() {
+ m_tunnelState.clientRegion = region;
+ }
+ });
+ }
+
+ @Override
+ public void onClientUpgradeDownloaded(String filename) {
+ m_Handler.post(new Runnable() {
+ @Override
+ public void run() {
+ UpgradeManager.UpgradeInstaller.notifyUpgrade(getContext(), filename);
+ }
+ });
+ }
+
+ @Override
+ public void onUntunneledAddress(final String address) {
m_Handler.post(new Runnable() {
@Override
public void run() {
diff --git a/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelServiceInteractor.java b/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelServiceInteractor.java
index 4053f936e..d64a01e1b 100644
--- a/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelServiceInteractor.java
+++ b/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelServiceInteractor.java
@@ -298,6 +298,7 @@ private static TunnelManager.State getTunnelStateFromBundle(Bundle data) {
if (vpnApps != null) {
tunnelState.vpnApps = vpnApps;
}
+ tunnelState.isPersonalPairingMode = data.getBoolean(TunnelManager.DATA_TUNNEL_STATE_IS_PERSONAL_PAIRING_MODE);
return tunnelState;
}
@@ -352,6 +353,7 @@ public void handleMessage(Message msg) {
.setHomePages(state.homePages)
.setVpnMode(state.vpnMode)
.setVpnApps(state.vpnApps)
+ .setPersonalPairingEnabled(state.isPersonalPairingMode)
.build();
tunnelState = TunnelState.running(connectionData);
} else {
diff --git a/app/src/main/res/drawable-hdpi/notification_icon_connected_pp.png b/app/src/main/res/drawable-hdpi/notification_icon_connected_pp.png
new file mode 100644
index 000000000..2a9b05d48
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon_connected_pp.png differ
diff --git a/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_01.png b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_01.png
new file mode 100644
index 000000000..cb266a8db
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_01.png differ
diff --git a/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_02.png b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_02.png
new file mode 100644
index 000000000..468c5d9e6
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_02.png differ
diff --git a/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_03.png b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_03.png
new file mode 100644
index 000000000..1334ca516
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_03.png differ
diff --git a/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_04.png b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_04.png
new file mode 100644
index 000000000..06293783f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_04.png differ
diff --git a/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_05.png b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_05.png
new file mode 100644
index 000000000..106a79226
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_05.png differ
diff --git a/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_06.png b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_06.png
new file mode 100644
index 000000000..aee1a443a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon_connecting_pp_06.png differ
diff --git a/app/src/main/res/drawable-hdpi/status_icon_connected.png b/app/src/main/res/drawable-hdpi/status_icon_connected.png
deleted file mode 100644
index 406eb6fd3..000000000
Binary files a/app/src/main/res/drawable-hdpi/status_icon_connected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/status_icon_connecting.png b/app/src/main/res/drawable-hdpi/status_icon_connecting.png
deleted file mode 100644
index 06c3cec4d..000000000
Binary files a/app/src/main/res/drawable-hdpi/status_icon_connecting.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/status_icon_disconnected.png b/app/src/main/res/drawable-hdpi/status_icon_disconnected.png
deleted file mode 100644
index 5bfb44ec7..000000000
Binary files a/app/src/main/res/drawable-hdpi/status_icon_disconnected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/notification_icon_connected_pp.png b/app/src/main/res/drawable-mdpi/notification_icon_connected_pp.png
new file mode 100644
index 000000000..d81ad3f62
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon_connected_pp.png differ
diff --git a/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_01.png b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_01.png
new file mode 100644
index 000000000..750c924e2
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_01.png differ
diff --git a/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_02.png b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_02.png
new file mode 100644
index 000000000..2ab44faed
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_02.png differ
diff --git a/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_03.png b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_03.png
new file mode 100644
index 000000000..46966f9c5
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_03.png differ
diff --git a/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_04.png b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_04.png
new file mode 100644
index 000000000..46f8639c3
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_04.png differ
diff --git a/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_05.png b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_05.png
new file mode 100644
index 000000000..d03bfca1b
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_05.png differ
diff --git a/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_06.png b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_06.png
new file mode 100644
index 000000000..a645e642a
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon_connecting_pp_06.png differ
diff --git a/app/src/main/res/drawable-mdpi/status_icon_connected.png b/app/src/main/res/drawable-mdpi/status_icon_connected.png
deleted file mode 100644
index 558982e8b..000000000
Binary files a/app/src/main/res/drawable-mdpi/status_icon_connected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/status_icon_connecting.png b/app/src/main/res/drawable-mdpi/status_icon_connecting.png
deleted file mode 100644
index 6c7904bf6..000000000
Binary files a/app/src/main/res/drawable-mdpi/status_icon_connecting.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/status_icon_disconnected.png b/app/src/main/res/drawable-mdpi/status_icon_disconnected.png
deleted file mode 100644
index 6400d7995..000000000
Binary files a/app/src/main/res/drawable-mdpi/status_icon_disconnected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/notification_icon_connected_pp.png b/app/src/main/res/drawable-xhdpi/notification_icon_connected_pp.png
new file mode 100644
index 000000000..0085eb837
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon_connected_pp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_01.png b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_01.png
new file mode 100644
index 000000000..1f2991481
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_01.png differ
diff --git a/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_02.png b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_02.png
new file mode 100644
index 000000000..0585325e8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_02.png differ
diff --git a/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_03.png b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_03.png
new file mode 100644
index 000000000..d5ddfdb28
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_03.png differ
diff --git a/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_04.png b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_04.png
new file mode 100644
index 000000000..57aa8b084
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_04.png differ
diff --git a/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_05.png b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_05.png
new file mode 100644
index 000000000..8719e3e5f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_05.png differ
diff --git a/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_06.png b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_06.png
new file mode 100644
index 000000000..dcc4a7137
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon_connecting_pp_06.png differ
diff --git a/app/src/main/res/drawable-xhdpi/status_icon_connected.png b/app/src/main/res/drawable-xhdpi/status_icon_connected.png
deleted file mode 100644
index f33a5e0dd..000000000
Binary files a/app/src/main/res/drawable-xhdpi/status_icon_connected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/status_icon_connecting.png b/app/src/main/res/drawable-xhdpi/status_icon_connecting.png
deleted file mode 100644
index d974f3582..000000000
Binary files a/app/src/main/res/drawable-xhdpi/status_icon_connecting.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/status_icon_disconnected.png b/app/src/main/res/drawable-xhdpi/status_icon_disconnected.png
deleted file mode 100644
index 0ff4839fa..000000000
Binary files a/app/src/main/res/drawable-xhdpi/status_icon_disconnected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon_connected_pp.png b/app/src/main/res/drawable-xxhdpi/notification_icon_connected_pp.png
new file mode 100644
index 000000000..34c0e1f54
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon_connected_pp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_01.png b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_01.png
new file mode 100644
index 000000000..1f71f4a17
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_01.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_02.png b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_02.png
new file mode 100644
index 000000000..5e47f8a8d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_02.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_03.png b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_03.png
new file mode 100644
index 000000000..8a1287698
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_03.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_04.png b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_04.png
new file mode 100644
index 000000000..ee6f9bcee
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_04.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_05.png b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_05.png
new file mode 100644
index 000000000..45d2a06dc
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_05.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_06.png b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_06.png
new file mode 100644
index 000000000..71e0f2805
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon_connecting_pp_06.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/status_icon_connected.png b/app/src/main/res/drawable-xxhdpi/status_icon_connected.png
deleted file mode 100644
index a23e3c034..000000000
Binary files a/app/src/main/res/drawable-xxhdpi/status_icon_connected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/status_icon_connecting.png b/app/src/main/res/drawable-xxhdpi/status_icon_connecting.png
deleted file mode 100644
index 007edb0de..000000000
Binary files a/app/src/main/res/drawable-xxhdpi/status_icon_connecting.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/status_icon_disconnected.png b/app/src/main/res/drawable-xxhdpi/status_icon_disconnected.png
deleted file mode 100644
index 501f38dae..000000000
Binary files a/app/src/main/res/drawable-xxhdpi/status_icon_disconnected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon_connected_pp.png b/app/src/main/res/drawable-xxxhdpi/notification_icon_connected_pp.png
new file mode 100644
index 000000000..33c51c036
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon_connected_pp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_01.png b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_01.png
new file mode 100644
index 000000000..eccad30f9
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_01.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_02.png b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_02.png
new file mode 100644
index 000000000..6289ffb83
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_02.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_03.png b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_03.png
new file mode 100644
index 000000000..292fc34e8
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_03.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_04.png b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_04.png
new file mode 100644
index 000000000..c7fad8874
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_04.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_05.png b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_05.png
new file mode 100644
index 000000000..b3f5394c3
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_05.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_06.png b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_06.png
new file mode 100644
index 000000000..39f78133c
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon_connecting_pp_06.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/status_icon_connected.png b/app/src/main/res/drawable-xxxhdpi/status_icon_connected.png
deleted file mode 100644
index 47e928102..000000000
Binary files a/app/src/main/res/drawable-xxxhdpi/status_icon_connected.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/status_icon_connecting.png b/app/src/main/res/drawable-xxxhdpi/status_icon_connecting.png
deleted file mode 100644
index 2e5bc4303..000000000
Binary files a/app/src/main/res/drawable-xxxhdpi/status_icon_connecting.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/status_icon_disconnected.png b/app/src/main/res/drawable-xxxhdpi/status_icon_disconnected.png
deleted file mode 100644
index d2df3daeb..000000000
Binary files a/app/src/main/res/drawable-xxxhdpi/status_icon_disconnected.png and /dev/null differ
diff --git a/app/src/main/res/drawable/notification_icon_connecting_animation_pp.xml b/app/src/main/res/drawable/notification_icon_connecting_animation_pp.xml
new file mode 100644
index 000000000..1350c56eb
--- /dev/null
+++ b/app/src/main/res/drawable/notification_icon_connecting_animation_pp.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/p2p_24px.xml b/app/src/main/res/drawable/p2p_24px.xml
new file mode 100644
index 000000000..47fa2da86
--- /dev/null
+++ b/app/src/main/res/drawable/p2p_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/status_icon_connected.xml b/app/src/main/res/drawable/status_icon_connected.xml
new file mode 100644
index 000000000..2b68223a3
--- /dev/null
+++ b/app/src/main/res/drawable/status_icon_connected.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/status_icon_connected_pp.xml b/app/src/main/res/drawable/status_icon_connected_pp.xml
new file mode 100644
index 000000000..c8c995709
--- /dev/null
+++ b/app/src/main/res/drawable/status_icon_connected_pp.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/status_icon_connecting.xml b/app/src/main/res/drawable/status_icon_connecting.xml
new file mode 100644
index 000000000..788f53f44
--- /dev/null
+++ b/app/src/main/res/drawable/status_icon_connecting.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/status_icon_connecting_pp.xml b/app/src/main/res/drawable/status_icon_connecting_pp.xml
new file mode 100644
index 000000000..9f9c21d71
--- /dev/null
+++ b/app/src/main/res/drawable/status_icon_connecting_pp.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/status_icon_disconnected.xml b/app/src/main/res/drawable/status_icon_disconnected.xml
new file mode 100644
index 000000000..49f05c6a8
--- /dev/null
+++ b/app/src/main/res/drawable/status_icon_disconnected.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_import_pairing.xml b/app/src/main/res/layout/dialog_import_pairing.xml
new file mode 100644
index 000000000..d8aa1f850
--- /dev/null
+++ b/app/src/main/res/layout/dialog_import_pairing.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_pairing_enable.xml b/app/src/main/res/layout/dialog_pairing_enable.xml
new file mode 100644
index 000000000..1a6760173
--- /dev/null
+++ b/app/src/main/res/layout/dialog_pairing_enable.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_pairing_update.xml b/app/src/main/res/layout/dialog_pairing_update.xml
new file mode 100644
index 000000000..80b21fe93
--- /dev/null
+++ b/app/src/main/res/layout/dialog_pairing_update.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_reset_pairing.xml b/app/src/main/res/layout/dialog_reset_pairing.xml
new file mode 100644
index 000000000..44ee06912
--- /dev/null
+++ b/app/src/main/res/layout/dialog_reset_pairing.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
index 62f9701b0..1a15a4ff2 100644
--- a/app/src/main/res/layout/main_activity.xml
+++ b/app/src/main/res/layout/main_activity.xml
@@ -84,25 +84,65 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
+
+
+
+
+
+
+
+
+
+
@@ -116,6 +156,7 @@
android:background="@drawable/connection_waiting_network_animation_drawable"
android:orientation="horizontal"
android:visibility="invisible" />
+
+
diff --git a/app/src/main/res/layout/preference_button_layout.xml b/app/src/main/res/layout/preference_button_layout.xml
new file mode 100644
index 000000000..01c8bcf18
--- /dev/null
+++ b/app/src/main/res/layout/preference_button_layout.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/preference_category_title.xml b/app/src/main/res/layout/preference_category_title.xml
new file mode 100644
index 000000000..641eff05e
--- /dev/null
+++ b/app/src/main/res/layout/preference_category_title.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-am/strings.xml b/app/src/main/res/values-am/strings.xml
index 104cd2949..dfc0a0546 100644
--- a/app/src/main/res/values-am/strings.xml
+++ b/app/src/main/res/values-am/strings.xml
@@ -163,4 +163,44 @@
ይጫኑማዘመን አልተሳካም
+
+ ነቅቷል
+ በ%1$s በኩል
+ ተሰናክሏል።
+ ለተሻሻለ ግላዊነት እና ተደራሽነት በታመነ የአቻ መተላለፊያ ጣቢያ በኩል ይገናኙ። ለመጀመር የማጣመሪያ አገናኝ ያስፈልግዎታል።
+ የግል ማጣመርን አንቃ
+ በኮንዱይት ጣቢያ በኩል ይገናኙ
+ ማጣመር URL አስመጣ
+ ዩአርኤል ማጣመር
+ የማጣመሪያ URL እዚህ ለጥፍ
+ ልክ ያልሆነ ማጣመር URL ቅርጸት
+ የተሳሳተ የማጣመር የውሂብ ቅርጸት
+ የማይደገፍ የማጣመሪያ ማስመሰያ ሥሪት
+ እባክዎ መጀመሪያ የማጣመጃ ዩአርኤል ያስመጡ
+ የማጣመሪያ መታወቂያ
+ የእርስዎ ልዩ የማጣመሪያ መለያ
+ ተለዋጭ ስም ማጣመር
+ ለዚህ የማጣመሪያ ውቅር ተስማሚ ስም
+ አስመጣ
+ የግል ማጣመር
+ መገናኘት ላይ ችግር አለ? Turn off Personal Pairing
+ የግል ማጣመሪያ ውቅር ተቀምጧል
+ የግል ማጣመሪያ ውቅር ተዘምኗል
+ የግል ማጣመሪያ ውቅር አስቀድሞ አለ።
+ የግል ማጣመሪያ ውሂብን ያዘምኑ
+ የአሁኑ የግል ማጣመሪያ መታወቂያ፡-
+ አዲስ የግል ማጣመሪያ መታወቂያ፡-
+ አዘምን
+ ሰርዝ
+ የግል ማጣመርን አንቃ
+ አሁን የግል ማጣመር ይንቃ? ይህ ንቁ ዋሻውን እንደገና ያስጀምረዋል. ያለበለዚያ፣ በመተግበሪያው ቅንብሮች ውስጥ በእጅ እስኪነቃ ድረስ ቅንጅቶች ከውጭ ይመጣሉ እና እንደተሰናከሉ ይቆያሉ።
+ የግል ማጣመርን ዳግም ያስጀምሩ
+ ይህንን የግል ማጣመሪያ ውቅር እስከመጨረሻው ያስወግዱት።
+ የግል ማጣመርን ዳግም ያስጀምሩ
+ ለማረጋገጥ ምልክቶችን አስገባ
+ ይህንን የግል ማጣመሪያ ውቅር ለማስወገድ የማጣመሪያ መታወቂያ የመጨረሻዎቹን %d ምልክቶች ያስገቡ፡-
+ ውቅረትን ዳግም ማስጀመር አልተቻለም
+ እባክዎ ትክክለኛ የማረጋገጫ ምልክቶችን ያስገቡ
+ የማዋቀር ዳግም ማስጀመር
+ ዳግም አስጀምር
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index b13820210..32cece519 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -167,4 +167,44 @@
تثبيتفشل التحديث
+
+ مفعّل
+ عبر %1$s
+ معطّل
+ اتصل عبر محطة Conduit لنظير موثوق لتعزيز الخصوصية وسهولة الوصول. ستحتاج إلى رابط إقران للبدء.
+ تفعيل الاقتران الشخصي
+ الاتصال عبر محطة Conduit
+ استيراد رابط الإقران
+ رابط الإقران
+ الصق رابط الإقران هنا
+ تنسيق رابط الإقران غير صالح
+ تنسيق بيانات الإقران غير صالح
+ إصدار رمز الإقران غير مدعوم
+ يرجى استيراد رابط إقران أولاً
+ معرّف الإقران
+ معرّف الإقران الفريد الخاص بك
+ اسم مستعار للإقران
+ اسم سهل لهذه الإعدادات
+ استيراد
+ الاقتران الشخصي
+ تواجه مشكلة في الاتصال؟ عطّل الاقتران الشخصي
+ تم حفظ إعدادات الاقتران الشخصي
+ تم تحديث إعدادات الاقتران الشخصي
+ إعدادات الاقتران الشخصي موجودة بالفعل
+ تحديث بيانات الاقتران الشخصي
+ معرّف الاقتران الشخصي الحالي:
+ معرّف الاقتران الشخصي الجديد:
+ تحديث
+ إلغاء
+ تفعيل الاقتران الشخصي
+ هل تريد تفعيل الاقتران الشخصي الآن؟ سيؤدي ذلك إلى إعادة تشغيل النفق النشط. بخلاف ذلك، سيتم استيراد الإعدادات وستبقى معطّلة حتى تفعّلها يدويًا في إعدادات التطبيق.
+ إعادة تعيين الاقتران الشخصي
+ إزالة إعدادات الاقتران الشخصي هذه نهائيًا
+ إعادة تعيين الاقتران الشخصي
+ أدخل الرموز للتأكيد
+ لإزالة إعدادات الاقتران الشخصي هذه، أدخل آخر %d رموز من معرّف الإقران:
+ تعذّرت إعادة تعيين الإعدادات
+ يرجى إدخال رموز التأكيد الصحيحة
+ تمت إعادة تعيين الإعدادات
+ إعادة تعيين
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index 5fc4e0bc0..6a7c02054 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -163,4 +163,44 @@
QuraşdırYeniləmə uğursuz oldu
+
+ Aktivdir
+ %1$s vasitəsilə
+ Əlil
+ Təkmil məxfilik və giriş üçün etibarlı həmyaşıdların Conduit stansiyası vasitəsilə qoşulun. Başlamaq üçün cütləşmə linkinə ehtiyacınız olacaq.
+ Şəxsi Qoşulmanı aktivləşdirin
+ Conduit stansiyası vasitəsilə qoşulun
+ Cütləşmə URL-sini idxal edin
+ URL cütlənir
+ Cütləşmə URL-sini bura yerləşdirin
+ Yanlış qoşulma URL formatı
+ Yanlış cütləşmə data formatı
+ Dəstəklənməyən cütləşmə nişanı versiyası
+ Lütfən, əvvəlcə qoşalaşma URL-ni import edin
+ Cütləşmə ID
+ Unikal cütləşmə identifikatorunuz
+ Cütləşmə ləqəbi
+ Bu cütləşdirmə konfiqurasiyası üçün dost ad
+ İdxal
+ Şəxsi cütləşmə
+ Qoşulmaqda problem var? Şəxsi Qoşulmanı söndürün
+ Şəxsi Qoşulma konfiqurasiyası yadda saxlanıldı
+ Şəxsi Qoşulma konfiqurasiyası yeniləndi
+ Şəxsi Qoşulma konfiqurasiyası artıq mövcuddur
+ Şəxsi Qoşulma məlumatlarını yeniləyin
+ Cari Şəxsi Qoşulma ID:
+ Yeni Şəxsi Qoşulma ID:
+ Yeniləyin
+ Ləğv et
+ Şəxsi Qoşulmanı aktivləşdirin
+ Şəxsi cütləşmə indi aktiv edilsin? Bu, aktiv tuneli yenidən işə salacaq. Əks halda, parametrlər idxal ediləcək və proqram parametrlərində əl ilə aktivləşdirilənə qədər qeyri-aktiv olaraq qalacaq.
+ Şəxsi Qoşulmanı Sıfırlayın
+ Bu Şəxsi Qoşulma konfiqurasiyasını həmişəlik silin
+ Şəxsi Qoşulmanı Sıfırlayın
+ Təsdiq etmək üçün simvolları daxil edin
+ Bu Şəxsi Qoşulma konfiqurasiyasını silmək üçün Qoşulma ID-sinin son %d simvollarını daxil edin:
+ Konfiqurasiyanı sıfırlamaq mümkün deyil
+ Zəhmət olmasa düzgün təsdiq simvollarını daxil edin
+ Konfiqurasiya sıfırlanır
+ Sıfırlayın
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index 8b508abcc..cba9ff7f4 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -165,4 +165,44 @@
УсталявацьАбнаўленне не ўдалося
+
+ Уключана
+ Праз %1$s
+ Інваліды
+ Падключайцеся праз станцыю Conduit даверанага піра для павышэння прыватнасці і доступу. Каб пачаць, вам спатрэбіцца спасылка для спалучэння.
+ Уключыць асабістае спалучэнне
+ Падключыцеся праз станцыю Conduit
+ Імпарт спалучэння URL
+ Спалучэнне URL
+ Устаўце сюды URL для спалучэння
+ Няправільны фармат URL-адраса спалучэння
+ Няправільны фармат даных спалучэння
+ Версія токена спалучэння не падтрымліваецца
+ Спачатку імпартуйце URL для спалучэння
+ Ідэнтыфікатар спалучэння
+ Ваш унікальны ідэнтыфікатар спалучэння
+ Спалучэнне псеўданіма
+ Зручная назва для гэтай канфігурацыі спалучэння
+ Імпарт
+ Персанальнае спалучэнне
+ Праблемы з падключэннем? Выключыце персанальнае спалучэнне
+ Асабістая канфігурацыя спалучэння захавана
+ Канфігурацыя персанальнага спалучэння абноўлена
+ Асабістая канфігурацыя спалучэння ўжо існуе
+ Абнавіце асабістыя даныя спалучэння
+ Бягучы асабісты ідэнтыфікатар спалучэння:
+ Новы асабісты ідэнтыфікатар спалучэння:
+ Абнаўленне
+ Адмяніць
+ Уключыць асабістае спалучэнне
+ Уключыць асабістае спалучэнне? Гэта перазапусціць актыўны тунэль. У адваротным выпадку налады будуць імпартаваны і застануцца адключанымі, пакуль не будуць уключаны ўручную ў наладах прыкладання.
+ Скінуць персанальнае спалучэнне
+ Выдаліце гэту канфігурацыю персанальнага спалучэння назаўжды
+ Скінуць персанальнае спалучэнне
+ Увядзіце сімвалы для пацверджання
+ Каб выдаліць гэту персанальную канфігурацыю спалучэння, увядзіце апошнія %d сімвалы ідэнтыфікатара спалучэння:
+ Немагчыма скінуць канфігурацыю
+ Увядзіце правільныя сімвалы пацверджання
+ Скід канфігурацыі
+ Скінуць
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index dacc0a6c9..809cf3335 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -159,4 +159,44 @@
ইনস্টলআপডেট ব্যর্থ
+
+ সক্রিয়
+ %1$s এর মাধ্যমে
+ অক্ষম
+ বর্ধিত গোপনীয়তা এবং অ্যাক্সেসের জন্য একটি বিশ্বস্ত পিয়ারের কন্ডুইট স্টেশনের মাধ্যমে সংযোগ করুন৷ শুরু করার জন্য আপনার একটি পেয়ারিং লিঙ্কের প্রয়োজন হবে।
+ ব্যক্তিগত পেয়ারিং সক্ষম করুন৷
+ কন্ডুইট স্টেশনের মাধ্যমে সংযোগ করুন
+ পেয়ারিং URL আমদানি করুন৷
+ পেয়ারিং ইউআরএল
+ পেয়ারিং ইউআরএল এখানে পেস্ট করুন
+ অবৈধ পেয়ারিং URL বিন্যাস৷
+ অবৈধ পেয়ারিং ডেটা ফর্ম্যাট৷
+ অসমর্থিত পেয়ারিং টোকেন সংস্করণ
+ অনুগ্রহ করে প্রথমে একটি পেয়ারিং URL আমদানি করুন৷
+ পেয়ারিং আইডি
+ আপনার অনন্য জোড়া শনাক্তকারী
+ পেয়ারিং উপনাম
+ এই পেয়ারিং কনফিগারেশনের জন্য একটি বন্ধুত্বপূর্ণ নাম
+ আমদানি
+ ব্যক্তিগত জুটি
+ সংযোগ করতে সমস্যা হচ্ছে? ব্যক্তিগত পেয়ারিং বন্ধ করুন
+ ব্যক্তিগত জোড়া কনফিগারেশন সংরক্ষিত
+ ব্যক্তিগত পেয়ারিং কনফিগারেশন আপডেট করা হয়েছে
+ ব্যক্তিগত পেয়ারিং কনফিগারেশন ইতিমধ্যেই বিদ্যমান
+ ব্যক্তিগত পেয়ারিং ডেটা আপডেট করুন
+ বর্তমান ব্যক্তিগত পেয়ারিং আইডি:
+ নতুন ব্যক্তিগত পেয়ারিং আইডি:
+ আপডেট
+ বাতিল করুন
+ ব্যক্তিগত পেয়ারিং সক্ষম করুন৷
+ এখন ব্যক্তিগত পেয়ারিং সক্ষম করবেন? এটি সক্রিয় টানেলটি পুনরায় চালু করবে। অন্যথায়, সেটিংস আমদানি করা হবে এবং অ্যাপ্লিকেশন সেটিংসে ম্যানুয়ালি সক্ষম না হওয়া পর্যন্ত অক্ষম থাকবে৷
+ ব্যক্তিগত পেয়ারিং রিসেট করুন
+ এই ব্যক্তিগত জোড়া কনফিগারেশন স্থায়ীভাবে সরান
+ ব্যক্তিগত পেয়ারিং রিসেট করুন
+ নিশ্চিত করতে প্রতীক লিখুন
+ এই ব্যক্তিগত পেয়ারিং কনফিগারেশনটি সরাতে, পেয়ারিং আইডির শেষ %d চিহ্নগুলি লিখুন:
+ কনফিগারেশন রিসেট করতে অক্ষম
+ সঠিক নিশ্চিতকরণ চিহ্ন লিখুন
+ কনফিগারেশন রিসেট
+ রিসেট করুন
diff --git a/app/src/main/res/values-bo/strings.xml b/app/src/main/res/values-bo/strings.xml
index 7b1ca9e5e..c8745ac60 100644
--- a/app/src/main/res/values-bo/strings.xml
+++ b/app/src/main/res/values-bo/strings.xml
@@ -159,4 +159,44 @@
སྒྲིག་སྦྱོརགསར་བཟོ་ཕམ
+
+ སྤྱོད་བཞིན་ཡོད།
+ %1$s བརྒྱུད་ནས།
+ སྤྱོད་མེད།
+ སྒེར་གྱི་གཉིས་སྦྲེལ་བརྒྱུད་ནས་གསང་བ་དང་འཛུལ་སྤྱོད་ལ་ལེགས་བཅོས་བྱེད། འགོ་འཛུགས་ཀྱི་ཆེད pairing link ཞིག་དགོས།
+ སྒེར་གྱི་གཉིས་སྦྲེལ་ཁ་ཕྱེས།
+ Conduit station བརྒྱུད་ནས་འབྲེལ་མཐུད།
+ pairing URL ནང་འཇུག
+ Pairing URL
+ འདིར pairing URL སྦྱོར།
+ pairing URL རྣམ་པ་ནོར་འདུག
+ pairing གཞི་གྲངས་རྣམ་པ་ནོར་འདུག
+ རྒྱབ་སྐྱོར་མེད་པའི pairing token པར་གཞི།
+ སྔོན་ལ pairing URL ཞིག་ནང་འཇུག་གནང་།
+ Pairing ID
+ ཁྱེད་རང་གི་མི་འདྲ་བའི pairing ངོས་འཛིན།
+ Pairing alias
+ pairing སྒྲིག་བཀོད་འདིའི་མཐུན་པའི་མིང་།
+ ནང་འཇུག
+ སྒེར་གྱི་གཉིས་སྦྲེལ།
+ འབྲེལ་མཐུད་དཀའ་ངལ་ཡོད་དམ། སྒེར་གྱི་གཉིས་སྦྲེལ་སྒོ་རྒྱག
+ སྒེར་གྱི་གཉིས་སྦྲེལ་སྒྲིག་བཀོད་ཉར་ཚགས་བྱས་ཡོད།
+ སྒེར་གྱི་གཉིས་སྦྲེལ་སྒྲིག་བཀོད་གསར་བཅོས་བྱས།
+ སྒེར་གྱི་གཉིས་སྦྲེལ་སྒྲིག་བཀོད་ཡོད་ཟིན།
+ སྒེར་གྱི་གཉིས་སྦྲེལ་གཞི་གྲངས་གསར་བཅོས།
+ ད་ལྟའི Personal Pairing ID:
+ གསར་པའི Personal Pairing ID:
+ གསར་བཅོས
+ ཕྱིར་བཤོལ
+ སྒེར་གྱི་གཉིས་སྦྲེལ་ཁ་ཕྱེས།
+ ད་ལྟ་སྒེར་གྱི་གཉིས་སྦྲེལ་ཁ་ཕྱེ་རམ། འདིས tunnel འགྲོ་བཞིན་པ་སླར་འགོ་འཛུགས་བྱེད། དེ་མིན་སྒྲིག་བཀོད་ནང་འཇུག་བྱས་ནས་མཉམ་སྤྱོད་སྒྲིག་བཀོད་ནང་ལག་ཐོག་ཁ་ཕྱེ་མ་བྱས་བར་སྤྱོད་མེད་བཞག་ནས་གནས་ཀྱི་རེད།
+ སྒེར་གྱི་གཉིས་སྦྲེལ་བསྐྱར་སྒྲིག
+ སྒེར་གྱི་གཉིས་སྦྲེལ་སྒྲིག་བཀོད་འདི་རྟག་པར་བསུབས།
+ སྒེར་གྱི་གཉིས་སྦྲེལ་བསྐྱར་སྒྲིག
+ ངེས་གཏན་བྱེད་པར་རྟགས་འདི་འཇུག
+ སྒེར་གྱི་གཉིས་སྦྲེལ་སྒྲིག་བཀོད་འདི་བསུབ་པར Pairing ID ཡི་མཐའ་མའི %d རྟགས་འཇུག་རོགས།
+ སྒྲིག་བཀོད་བསྐྱར་སྒྲིག་མ་ཐུབ།
+ དག་མཐུན་རྟགས་འགྲིག་པ་འཇུག་རོགས།
+ སྒྲིག་བཀོད་བསྐྱར་སྒྲིག་བྱས་ཡོད།
+ བསྐྱར་སྒྲིག
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index c762b2449..dcd63fd7e 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -166,4 +166,44 @@
InstallierenUpdate fehlgeschlagen
+
+ Ermöglicht
+ Über %1$s
+ Deaktiviert
+ Stellen Sie eine Verbindung über die Conduit-Station eines vertrauenswürdigen Peers her, um die Privatsphäre und den Zugriff zu verbessern. Um loszulegen, benötigen Sie einen Kopplungslink.
+ Aktivieren Sie die persönliche Kopplung
+ Verbindung über Conduit-Station
+ Kopplungs-URL importieren
+ Kopplungs-URL
+ Fügen Sie hier die Kopplungs-URL ein
+ Ungültiges Pairing-URL-Format
+ Ungültiges Paarungsdatenformat
+ Nicht unterstützte Pairing-Token-Version
+ Bitte importieren Sie zuerst eine Kopplungs-URL
+ Pairing-ID
+ Ihre eindeutige Kopplungskennung
+ Pairing-Alias
+ Ein benutzerfreundlicher Name für diese Paarungskonfiguration
+ Import
+ Persönliches Pairing
+ Probleme beim Verbinden? Deaktivieren Sie die persönliche Kopplung
+ Persönliche Kopplungskonfiguration gespeichert
+ Persönliche Kopplungskonfiguration aktualisiert
+ Die persönliche Kopplungskonfiguration ist bereits vorhanden
+ Persönliche Kopplungsdaten aktualisieren
+ Aktuelle persönliche Pairing-ID:
+ Neue persönliche Pairing-ID:
+ Aktualisieren
+ Stornieren
+ Aktivieren Sie die persönliche Kopplung
+ Persönliches Pairing jetzt aktivieren? Dadurch wird der aktive Tunnel neu gestartet. Andernfalls werden die Einstellungen importiert und bleiben deaktiviert, bis sie manuell in den Anwendungseinstellungen aktiviert werden.
+ Persönliche Kopplung zurücksetzen
+ Entfernen Sie diese persönliche Kopplungskonfiguration dauerhaft
+ Persönliche Kopplung zurücksetzen
+ Geben Sie zur Bestätigung Symbole ein
+ Um diese persönliche Kopplungskonfiguration zu entfernen, geben Sie die letzten %d-Symbole der Kopplungs-ID ein:
+ Konfiguration kann nicht zurückgesetzt werden
+ Bitte geben Sie die korrekten Bestätigungssymbole ein
+ Konfiguration zurückgesetzt
+ Zurücksetzen
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index e424ba1dc..1355f2291 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -166,4 +166,44 @@
ΕγκατάστασηΗ ενημέρωση απέτυχε
+
+ Ενεργοποιημένο
+ Μέσω %1$s
+ Ανάπηρος
+ Συνδεθείτε μέσω ενός αξιόπιστου σταθμού αγωγού για βελτιωμένο απόρρητο και πρόσβαση. Θα χρειαστείτε έναν σύνδεσμο σύζευξης για να ξεκινήσετε.
+ Ενεργοποίηση Personal Pairing
+ Σύνδεση μέσω σταθμού Conduit
+ Εισαγωγή διεύθυνσης URL σύζευξης
+ Διεύθυνση URL σύζευξης
+ Επικολλήστε εδώ τη διεύθυνση URL σύζευξης
+ Μη έγκυρη μορφή URL σύζευξης
+ Μη έγκυρη μορφή δεδομένων σύζευξης
+ Μη υποστηριζόμενη έκδοση διακριτικού σύζευξης
+ Εισαγάγετε πρώτα μια διεύθυνση URL σύζευξης
+ Αναγνωριστικό ζεύξης
+ Το μοναδικό σας αναγνωριστικό σύζευξης
+ Ψευδώνυμο ζεύξης
+ Ένα φιλικό όνομα για αυτήν τη διαμόρφωση σύζευξης
+ Εισαγωγή
+ Προσωπικό Pairing
+ Πρόβλημα σύνδεσης; Απενεργοποιήστε το Personal Pairing
+ Η διαμόρφωση Personal Pairing αποθηκεύτηκε
+ Η διαμόρφωση Personal Pairing ενημερώθηκε
+ Υπάρχει ήδη διαμόρφωση Personal Pairing
+ Ενημερώστε τα προσωπικά δεδομένα σύζευξης
+ Τρέχον αναγνωριστικό προσωπικής σύζευξης:
+ Νέο προσωπικό αναγνωριστικό σύζευξης:
+ Εκσυγχρονίζω
+ Ματαίωση
+ Ενεργοποίηση Personal Pairing
+ Ενεργοποίηση προσωπικής σύζευξης τώρα; Αυτό θα επανεκκινήσει το ενεργό τούνελ. Διαφορετικά, οι ρυθμίσεις θα εισαχθούν και θα παραμείνουν απενεργοποιημένες μέχρι να ενεργοποιηθούν μη αυτόματα στις ρυθμίσεις της εφαρμογής.
+ Επαναφορά προσωπικής σύζευξης
+ Καταργήστε οριστικά αυτήν τη διαμόρφωση Personal Pairing
+ Επαναφορά προσωπικής σύζευξης
+ Εισαγάγετε σύμβολα για επιβεβαίωση
+ Για να καταργήσετε αυτήν τη διαμόρφωση Personal Pairing, εισαγάγετε τα τελευταία %d σύμβολα του αναγνωριστικού ζεύξης:
+ Δεν είναι δυνατή η επαναφορά της διαμόρφωσης
+ Εισαγάγετε τα σωστά σύμβολα επιβεβαίωσης
+ Επαναφορά διαμόρφωσης
+ Επαναφορά
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 77080934d..a70472dde 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -166,4 +166,44 @@
InstalarLa actualización falló
+
+ Activado
+ Vía %1$s
+ Desactivado
+ Conéctese a través de una estación Conduit de un par confiable para mejorar la privacidad y el acceso. Necesitará un enlace de emparejamiento para comenzar.
+ Habilitar emparejamiento personal
+ Conexión a través de la estación Conduit
+ Importar URL de emparejamiento
+ URL de emparejamiento
+ Pegue la URL de emparejamiento aquí
+ Formato de URL de emparejamiento no válido
+ Formato de datos de emparejamiento no válido
+ Versión del token de emparejamiento no compatible
+ Primero importe una URL de emparejamiento
+ ID de emparejamiento
+ Su identificador de emparejamiento único
+ Alias de emparejamiento
+ Un nombre descriptivo para esta configuración de emparejamiento
+ Importar
+ Emparejamiento personal
+ ¿Problemas para conectarse? Desactivar el emparejamiento personal
+ Configuración de emparejamiento personal guardada
+ Configuración de emparejamiento personal actualizada
+ La configuración de emparejamiento personal ya existe
+ Actualizar datos de emparejamiento personal
+ ID de emparejamiento personal actual:
+ Nueva ID de emparejamiento personal:
+ Actualizar
+ Cancelar
+ Habilitar emparejamiento personal
+ ¿Habilitar el emparejamiento personal ahora? Esto reiniciará el túnel activo. De lo contrario, la configuración se importará y permanecerá deshabilitada hasta que se habilite manualmente en la configuración de la aplicación.
+ Restablecer emparejamiento personal
+ Eliminar esta configuración de emparejamiento personal de forma permanente
+ Restablecer emparejamiento personal
+ Ingrese símbolos para confirmar
+ Para eliminar esta configuración de emparejamiento personal, ingrese los últimos símbolos %d del ID de emparejamiento:
+ No se puede restablecer la configuración
+ Por favor ingrese los símbolos de confirmación correctos
+ Restablecer configuración
+ Reiniciar
diff --git a/app/src/main/res/values-fa-rAF/strings.xml b/app/src/main/res/values-fa-rAF/strings.xml
index 311240c46..87be13f1e 100644
--- a/app/src/main/res/values-fa-rAF/strings.xml
+++ b/app/src/main/res/values-fa-rAF/strings.xml
@@ -160,4 +160,44 @@
نصببهروزرسانی ناموفق
+
+ فعال
+ از طریق %1$s
+ غیرفعال
+ برای افزایش حریم خصوصی و دسترسی، از طریق ایستگاه Conduit یک همتای قابل اعتماد متصل شوید. برای شروع به یک لینک جفتسازی نیاز دارید.
+ فعال کردن جفتسازی شخصی
+ اتصال از طریق ایستگاه Conduit
+ وارد کردن لینک جفتسازی
+ لینک جفتسازی
+ لینک جفتسازی را اینجا وارد کنید
+ قالب لینک جفتسازی نامعتبر است
+ قالب داده جفتسازی نامعتبر است
+ نسخه توکن جفتسازی پشتیبانی نمیشود
+ لطفاً ابتدا یک لینک جفتسازی وارد کنید
+ شناسه جفتسازی
+ شناسه یکتای جفتسازی شما
+ نام مستعار جفتسازی
+ یک نام ساده برای این پیکربندی جفتسازی
+ وارد کردن
+ جفتسازی شخصی
+ در اتصال مشکل دارید؟ جفتسازی شخصی را خاموش کنید
+ پیکربندی جفتسازی شخصی ذخیره شد
+ پیکربندی جفتسازی شخصی بهروزرسانی شد
+ پیکربندی جفتسازی شخصی از قبل وجود دارد
+ بهروزرسانی دادههای جفتسازی شخصی
+ شناسه فعلی جفتسازی شخصی:
+ شناسه جدید جفتسازی شخصی:
+ بهروزرسانی
+ لغو
+ فعال کردن جفتسازی شخصی
+ اکنون جفتسازی شخصی فعال شود؟ این کار تونل فعال را بازراهاندازی میکند. در غیر این صورت، تنظیمات وارد میشوند و تا زمانی که در تنظیمات برنامه بهصورت دستی فعال شوند، غیرفعال میمانند.
+ بازنشانی جفتسازی شخصی
+ این پیکربندی جفتسازی شخصی را برای همیشه حذف کنید
+ بازنشانی جفتسازی شخصی
+ برای تأیید، کاراکترها را وارد کنید
+ برای حذف این پیکربندی جفتسازی شخصی، آخرین %d کاراکتر از شناسه جفتسازی را وارد کنید:
+ بازنشانی پیکربندی ممکن نیست
+ لطفاً کاراکترهای تأیید درست را وارد کنید
+ پیکربندی بازنشانی شد
+ بازنشانی
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 04f6e41ad..f6bb29c49 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -172,4 +172,44 @@
نصببهروزرسانی ناموفق
+
+ فعال
+ از طریق %1$s
+ غیرفعال
+ برای افزایش حریم خصوصی و دسترسی، از طریق ایستگاه Conduit یک همتای قابل اعتماد متصل شوید. برای شروع به یک لینک جفتسازی نیاز دارید.
+ فعال کردن جفتسازی شخصی
+ اتصال از طریق ایستگاه Conduit
+ وارد کردن لینک جفتسازی
+ لینک جفتسازی
+ لینک جفتسازی را اینجا وارد کنید
+ قالب لینک جفتسازی نامعتبر است
+ قالب داده جفتسازی نامعتبر است
+ نسخه توکن جفتسازی پشتیبانی نمیشود
+ لطفاً ابتدا یک لینک جفتسازی وارد کنید
+ شناسه جفتسازی
+ شناسه یکتای جفتسازی شما
+ نام مستعار جفتسازی
+ یک نام ساده برای این پیکربندی جفتسازی
+ وارد کردن
+ جفتسازی شخصی
+ در اتصال مشکل دارید؟ جفتسازی شخصی را خاموش کنید
+ پیکربندی جفتسازی شخصی ذخیره شد
+ پیکربندی جفتسازی شخصی بهروزرسانی شد
+ پیکربندی جفتسازی شخصی از قبل وجود دارد
+ بهروزرسانی دادههای جفتسازی شخصی
+ شناسه فعلی جفتسازی شخصی:
+ شناسه جدید جفتسازی شخصی:
+ بهروزرسانی
+ لغو
+ فعال کردن جفتسازی شخصی
+ اکنون جفتسازی شخصی فعال شود؟ این کار تونل فعال را بازراهاندازی میکند. در غیر این صورت، تنظیمات وارد میشوند و تا زمانی که در تنظیمات برنامه بهصورت دستی فعال شوند، غیرفعال میمانند.
+ بازنشانی جفتسازی شخصی
+ این پیکربندی جفتسازی شخصی را برای همیشه حذف کنید
+ بازنشانی جفتسازی شخصی
+ برای تأیید، کاراکترها را وارد کنید
+ برای حذف این پیکربندی جفتسازی شخصی، آخرین %d کاراکتر از شناسه جفتسازی را وارد کنید:
+ بازنشانی پیکربندی ممکن نیست
+ لطفاً کاراکترهای تأیید درست را وارد کنید
+ پیکربندی بازنشانی شد
+ بازنشانی
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 056e88e20..ca290a4a1 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -167,4 +167,44 @@ Katso lokeista teknisiä tietoja.AsennaPäivitys epäonnistui
+
+ Käytössä
+ %1$s:n kautta
+ Pois käytöstä
+ Yhdistä luotettavan vertaiskanavan kautta parantaaksesi yksityisyyttä ja pääsyä. Tarvitset pariliitoslinkin aloittaaksesi.
+ Ota käyttöön henkilökohtainen pariliitos
+ Yhdistä Conduit-aseman kautta
+ Tuo laiteparin URL-osoite
+ Pariliitoksen URL-osoite
+ Liitä pariliitoksen URL-osoite tähän
+ Virheellinen pariliitoksen URL-muoto
+ Virheellinen pariliitostietojen muoto
+ Pariliitostunnuksen versiota ei tueta
+ Tuo pariliitoksen URL-osoite ensin
+ Pariliitostunnus
+ Ainutlaatuinen pariliitostunnuksesi
+ Parinmuodostus alias
+ Ystävällinen nimi tälle pariliitoskokoonpanolle
+ Tuoda
+ Henkilökohtainen pariliitos
+ Onko yhteyden muodostamisessa ongelmia? Poista henkilökohtainen pariliitos käytöstä
+ Henkilökohtaisen pariliitoksen määritys tallennettu
+ Henkilökohtaisen pariliitoksen määritys päivitetty
+ Henkilökohtainen pariliitosmääritys on jo olemassa
+ Päivitä henkilökohtaiset pariliitostiedot
+ Nykyinen henkilökohtainen pariliitostunnus:
+ Uusi henkilökohtainen pariliitostunnus:
+ Päivittää
+ Peruuttaa
+ Ota käyttöön henkilökohtainen pariliitos
+ Otetaanko henkilökohtainen pariliitos käyttöön nyt? Tämä käynnistää aktiivisen tunnelin uudelleen. Muussa tapauksessa asetukset tuodaan ja pysyvät poissa käytöstä, kunnes ne otetaan manuaalisesti käyttöön sovelluksen asetuksissa.
+ Nollaa henkilökohtainen pariliitos
+ Poista tämä henkilökohtaisen pariliitoksen määritys pysyvästi
+ Nollaa henkilökohtainen pariliitos
+ Vahvista syöttämällä symbolit
+ Voit poistaa tämän henkilökohtaisen pariliitoksen määrityksen kirjoittamalla pariliitostunnuksen viimeiset %d-symbolit:
+ Määritystä ei voi nollata
+ Anna oikeat vahvistussymbolit
+ Kokoonpanon nollaus
+ Nollaa
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 049c2e5b6..bae2a5a53 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -166,4 +166,44 @@
InstallerÉchec de la mise à jour
+
+ Activé
+ Via %1$s
+ Désactivé
+ Connectez-vous via la station Conduit d\'un homologue de confiance pour une confidentialité et un accès améliorés. Vous aurez besoin d\'un lien d\'appairage pour commencer.
+ Activer le couplage personnel
+ Se connecter via la station Conduit
+ Importer l\'URL de couplage
+ URL de couplage
+ Collez l\'URL d\'association ici
+ Format d\'URL de couplage non valide
+ Format de données de couplage invalide
+ Version du jeton d\'association non prise en charge
+ Veuillez d\'abord importer une URL de couplage
+ ID d\'appariement
+ Votre identifiant d\'appariement unique
+ Alias d’appariement
+ Un nom convivial pour cette configuration d\'appairage
+ Importer
+ Jumelage personnel
+ Un problème de connexion ? Désactiver le couplage personnel
+ Configuration du couplage personnel enregistrée
+ Configuration du couplage personnel mise à jour
+ La configuration du couplage personnel existe déjà
+ Mettre à jour les données de couplage personnel
+ ID d\'association personnel actuel :
+ Nouvel identifiant de jumelage personnel :
+ Mise à jour
+ Annuler
+ Activer le couplage personnel
+ Activer l\'appairage personnel maintenant ? Cela redémarrera le tunnel actif. Sinon, les paramètres seront importés et resteront désactivés jusqu\'à leur activation manuelle dans les paramètres de l\'application.
+ Réinitialiser l\'appairage personnel
+ Supprimer définitivement cette configuration de couplage personnel
+ Réinitialiser l\'appairage personnel
+ Entrez les symboles pour confirmer
+ Pour supprimer cette configuration de couplage personnel, saisissez les derniers symboles %d de l\'ID de couplage :
+ Impossible de réinitialiser la configuration
+ Veuillez saisir les symboles de confirmation corrects
+ Réinitialisation de la configuration
+ Réinitialiser
diff --git a/app/src/main/res/values-ha/strings.xml b/app/src/main/res/values-ha/strings.xml
index ba6faa4de..86e9aac15 100644
--- a/app/src/main/res/values-ha/strings.xml
+++ b/app/src/main/res/values-ha/strings.xml
@@ -161,4 +161,44 @@
ShigarSabuntawa ta gaza
+
+ An kunna
+ Ta hanyar %1$s
+ An kashe
+ Haɗa ta hanyar amintaccen tashar Conduit ta abokin aiki don haɓaka keɓaɓɓu da samun dama. Kuna buƙatar hanyar haɗi don farawa.
+ Kunna Haɗin Kan Keɓaɓɓu
+ Haɗa ta tashar Conduit
+ Shigo URL ɗin haɗin kai
+ Haɗin URL
+ Manna URL ɗin haɗin kai anan
+ Tsarin URL mara inganci
+ Tsarin bayanai mara inganci
+ Sigar alamar haɗin kai mara tallafi
+ Da fatan za a fara shigo da URL ɗin haɗin gwiwa
+ ID ɗin haɗin kai
+ Mai gano haɗin haɗin ku na musamman
+ Sunan mai haɗawa da juna
+ Sunan abokantaka don wannan daidaitawar haɗin kai
+ Shigo da
+ Haɗin kai na sirri
+ Matsalar haɗawa? Kashe Haɗin Kai
+ Ajiye saitin Haɗin kai na sirri
+ An sabunta saitin Haɗin kai na sirri
+ Tsarin Haɗin kai na sirri ya riga ya wanzu
+ Sabunta bayanan Haɗin kai na sirri
+ ID na Haɗin Haɗin Kai na Yanzu:
+ Sabuwar ID Haɗin Haɗin Kai:
+ Sabuntawa
+ Soke
+ Kunna Haɗin Kan Keɓaɓɓu
+ Kunna haɗin kai na sirri yanzu? Wannan zai sake kunna rami mai aiki. In ba haka ba, za a shigo da saituna kuma su kasance a kashe har sai an kunna su da hannu a cikin saitunan aikace-aikacen.
+ Sake saita Haɗin Kan Keɓaɓɓu
+ Cire wannan saitin Haɗin Haɗin kai na dindindin
+ Sake saita Haɗin Kan Keɓaɓɓu
+ Shigar da alamomi don tabbatarwa
+ Don cire wannan saitin Haɗin Haɗin Kai, shigar da alamomin %d na ƙarshe na ID ɗin Haɗawa:
+ An kasa sake saita saiti
+ Da fatan za a shigar da daidai alamun tabbatarwa
+ Sake saitin saitin
+ Sake saitin
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index 03e6decae..1ce9bfa1a 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -165,4 +165,44 @@
इंस्टॉल करेंअपडेट विफल
+
+ सक्रिय
+ %1$s के माध्यम से
+ अक्षम
+ बेहतर गोपनीयता और पहुंच के लिए किसी विश्वसनीय सहकर्मी के कंड्यूट स्टेशन से जुड़ें। आरंभ करने के लिए आपको एक युग्मन लिंक की आवश्यकता होगी।
+ व्यक्तिगत युग्मन सक्षम करें
+ कंड्यूट स्टेशन के माध्यम से कनेक्ट करें
+ युग्मन URL आयात करें
+ यूआरएल जोड़ना
+ यहां पेयरिंग यूआरएल चिपकाएं
+ अमान्य युग्मन URL प्रारूप
+ अमान्य युग्मन डेटा प्रारूप
+ असमर्थित युग्मन टोकन संस्करण
+ कृपया पहले एक युग्मन URL आयात करें
+ युग्मन आईडी
+ आपका अद्वितीय युग्मन पहचानकर्ता
+ युग्म उपनाम
+ इस युग्मन विन्यास के लिए एक अनुकूल नाम
+ आयात
+ व्यक्तिगत जोड़ी
+ कनेक्ट करने में समस्या आ रही है? व्यक्तिगत युग्मन बंद करें
+ व्यक्तिगत युग्मन कॉन्फ़िगरेशन सहेजा गया
+ व्यक्तिगत युग्मन कॉन्फ़िगरेशन अद्यतन किया गया
+ व्यक्तिगत युग्मन कॉन्फ़िगरेशन पहले से मौजूद है
+ व्यक्तिगत युग्मन डेटा अपडेट करें
+ वर्तमान व्यक्तिगत जोड़ी आईडी:
+ नई व्यक्तिगत जोड़ी आईडी:
+ अद्यतन
+ रद्द करना
+ व्यक्तिगत युग्मन सक्षम करें
+ अब व्यक्तिगत युग्मन सक्षम करें? यह सक्रिय सुरंग को पुनः आरंभ करेगा। अन्यथा, सेटिंग्स आयात की जाएंगी और एप्लिकेशन सेटिंग्स में मैन्युअल रूप से सक्षम होने तक अक्षम रहेंगी।
+ व्यक्तिगत युग्मन रीसेट करें
+ इस व्यक्तिगत युग्मन कॉन्फ़िगरेशन को स्थायी रूप से हटा दें
+ व्यक्तिगत युग्मन रीसेट करें
+ पुष्टि करने के लिए प्रतीक दर्ज करें
+ इस व्यक्तिगत युग्मन कॉन्फ़िगरेशन को हटाने के लिए, युग्मन आईडी के अंतिम %d प्रतीक दर्ज करें:
+ कॉन्फ़िगरेशन रीसेट करने में असमर्थ
+ कृपया सही पुष्टि चिह्न दर्ज करें
+ कॉन्फ़िगरेशन रीसेट
+ रीसेट करें
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index d5670342e..a6f298dba 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -163,4 +163,44 @@
InstalirajAžuriranje nije uspjelo
+
+ Omogućeno
+ Preko %1$s
+ Onesposobljeno
+ Povežite se putem Conduit stanice pouzdanog peer-a za poboljšanu privatnost i pristup. Trebat će vam veza za uparivanje da biste započeli.
+ Omogući osobno uparivanje
+ Povežite se preko stanice Conduit
+ Uvezi URL za uparivanje
+ URL za uparivanje
+ Ovdje zalijepite URL za uparivanje
+ Nevažeći format URL-a za uparivanje
+ Nevažeći format podataka za uparivanje
+ Nepodržana verzija tokena za uparivanje
+ Prvo uvezite URL za uparivanje
+ ID uparivanja
+ Vaš jedinstveni identifikator uparivanja
+ Pseudonim za uparivanje
+ Prijateljski naziv za ovu konfiguraciju uparivanja
+ Uvoz
+ Osobno uparivanje
+ Problemi s povezivanjem? Isključite osobno uparivanje
+ Osobna konfiguracija uparivanja je spremljena
+ Konfiguracija osobnog uparivanja ažurirana
+ Osobna konfiguracija uparivanja već postoji
+ Ažurirajte podatke o osobnom uparivanju
+ Trenutačni osobni ID uparivanja:
+ Novi osobni ID uparivanja:
+ Ažurirati
+ Otkazati
+ Omogući osobno uparivanje
+ Omogućiti osobno uparivanje sada? Ovo će ponovno pokrenuti aktivni tunel. U suprotnom, postavke će se uvesti i ostati onemogućene dok se ručno ne omoguće u postavkama aplikacije.
+ Poništi osobno uparivanje
+ Trajno uklonite ovu konfiguraciju osobnog uparivanja
+ Poništi osobno uparivanje
+ Unesite simbole za potvrdu
+ Za uklanjanje ove konfiguracije osobnog uparivanja unesite zadnjih %d simbola ID-a uparivanja:
+ Nije moguće poništiti konfiguraciju
+ Unesite ispravne simbole za potvrdu
+ Poništavanje konfiguracije
+ Resetiraj
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 58f086e9b..eb2e9852a 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -165,4 +165,44 @@
InstalUpdate gagal
+
+ Diaktifkan
+ Melalui %1$s
+ Dengan disabilitas
+ Terhubung melalui stasiun Conduit rekan tepercaya untuk meningkatkan privasi dan akses. Anda memerlukan tautan penyandingan untuk memulai.
+ Aktifkan Pemasangan Pribadi
+ Terhubung melalui stasiun Conduit
+ Impor URL penyandingan
+ URL penyandingan
+ Tempelkan URL penyandingan di sini
+ Format URL penyandingan tidak valid
+ Format data penyandingan tidak valid
+ Versi token penyandingan tidak didukung
+ Harap impor URL penyandingan terlebih dahulu
+ ID penyandingan
+ Pengidentifikasi pasangan unik Anda
+ Alias berpasangan
+ Nama yang bersahabat untuk konfigurasi pemasangan ini
+ Impor
+ Pasangan Pribadi
+ Ada masalah saat menyambung? Matikan Pemasangan Pribadi
+ Konfigurasi Pemasangan Pribadi disimpan
+ Konfigurasi Pemasangan Pribadi diperbarui
+ Konfigurasi Personal Pairing sudah ada
+ Perbarui data Pemasangan Pribadi
+ ID Pasangan Pribadi Saat Ini:
+ ID Pasangan Pribadi Baru:
+ Memperbarui
+ Membatalkan
+ Aktifkan Pemasangan Pribadi
+ Aktifkan penyandingan pribadi sekarang? Ini akan memulai ulang terowongan aktif. Jika tidak, pengaturan akan diimpor dan tetap dinonaktifkan hingga diaktifkan secara manual di pengaturan aplikasi.
+ Setel Ulang Pemasangan Pribadi
+ Hapus konfigurasi Personal Pairing ini secara permanen
+ Setel Ulang Pemasangan Pribadi
+ Masukkan simbol untuk mengonfirmasi
+ Untuk menghapus konfigurasi Personal Pairing ini, masukkan simbol %d terakhir dari ID Pairing:
+ Tidak dapat mengatur ulang konfigurasi
+ Silakan masukkan simbol konfirmasi yang benar
+ Penyetelan ulang konfigurasi
+ Mengatur ulang
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index bc21e9216..3377fa86d 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -165,4 +165,44 @@
InstallaAggiornamento fallito
+
+ Abilitato
+ Tramite %1$s
+ Disabilitato
+ Connettiti tramite la stazione Conduit di un peer affidabile per una maggiore privacy e accesso. Avrai bisogno di un collegamento di accoppiamento per iniziare.
+ Abilita abbinamento personale
+ Connettersi tramite la stazione Conduit
+ Importa l\'URL di abbinamento
+ URL di abbinamento
+ Incolla qui l\'URL di abbinamento
+ Formato URL di abbinamento non valido
+ Formato dati di abbinamento non valido
+ Versione del token di accoppiamento non supportata
+ Importa prima un URL di accoppiamento
+ ID di accoppiamento
+ Il tuo identificatore di abbinamento univoco
+ Alias di abbinamento
+ Un nome descrittivo per questa configurazione di accoppiamento
+ Importare
+ Abbinamento personale
+ Problemi di connessione? Disattiva l\'abbinamento personale
+ Configurazione dell\'abbinamento personale salvata
+ Configurazione dell\'abbinamento personale aggiornata
+ La configurazione di abbinamento personale esiste già
+ Aggiorna i dati di abbinamento personale
+ ID di abbinamento personale attuale:
+ Nuovo ID di abbinamento personale:
+ Aggiornamento
+ Cancellare
+ Abilita abbinamento personale
+ Vuoi abilitare l\'abbinamento personale adesso? Ciò riavvierà il tunnel attivo. In caso contrario, le impostazioni verranno importate e rimarranno disabilitate finché non verranno abilitate manualmente nelle impostazioni dell\'applicazione.
+ Reimposta l\'abbinamento personale
+ Rimuovi permanentemente questa configurazione di abbinamento personale
+ Reimposta l\'abbinamento personale
+ Inserisci i simboli per confermare
+ Per rimuovere questa configurazione di abbinamento personale, inserisci gli ultimi simboli %d dell\'ID di abbinamento:
+ Impossibile reimpostare la configurazione
+ Inserisci i simboli di conferma corretti
+ Ripristino della configurazione
+ Reset
diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml
index bc5a1f833..e9a9a5cc4 100644
--- a/app/src/main/res/values-kk/strings.xml
+++ b/app/src/main/res/values-kk/strings.xml
@@ -160,4 +160,44 @@
ОрнатуЖаңарту сәтсіз
+
+ Қосылған
+ %1$s арқылы
+ Өшірілген
+ Жетілдірілген құпиялылық пен қолжетімділік үшін сенімді құрдастың Conduit станциясы арқылы қосылыңыз. Жұмысты бастау үшін сізге жұптастыру сілтемесі қажет.
+ Жеке жұптастыруды қосыңыз
+ Conduit станциясы арқылы қосылыңыз
+ Жұптау URL мекенжайын импорттау
+ Жұпталуда URL
+ Жұптау URL мекенжайын осы жерге қойыңыз
+ Жарамсыз жұптастыру URL форматы
+ Жұптау деректерінің пішімі жарамсыз
+ Жұптастыру белгісінің нұсқасына қолдау көрсетілмейді
+ Алдымен жұптастыру URL мекенжайын импорттаңыз
+ Жұптау идентификаторы
+ Бірегей жұптау идентификаторыңыз
+ Жұптау бүркеншік ат
+ Бұл жұптау конфигурациясына ыңғайлы атау
+ Импорттау
+ Жеке жұптастыру
+ Қосылу мүмкін емес пе? Жеке жұптастыруды өшіріңіз
+ Жеке жұптау конфигурациясы сақталды
+ Жеке жұптау конфигурациясы жаңартылды
+ Жеке жұптау конфигурациясы бұрыннан бар
+ Жеке жұптау деректерін жаңартыңыз
+ Ағымдағы жеке жұптау идентификаторы:
+ Жаңа жеке жұптау идентификаторы:
+ Жаңарту
+ Бас тарту
+ Жеке жұптастыруды қосыңыз
+ Жеке жұптастыруды қазір қосу керек пе? Бұл белсенді туннельді қайта іске қосады. Әйтпесе, параметрлер импортталады және қолданба параметрлерінде қолмен қосылғанша өшірілген күйде қалады.
+ Жеке жұптастыруды қалпына келтіріңіз
+ Осы Жеке жұптау конфигурациясын біржола жойыңыз
+ Жеке жұптастыруды қалпына келтіріңіз
+ Растау үшін таңбаларды енгізіңіз
+ Осы жеке жұптау конфигурациясын жою үшін жұптау идентификаторының соңғы %d таңбаларын енгізіңіз:
+ Конфигурацияны қалпына келтіру мүмкін емес
+ Растау таңбаларын дұрыс енгізіңіз
+ Конфигурацияны қалпына келтіру
+ Қалпына келтіру
diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml
index 35d279652..366cb7f2c 100644
--- a/app/src/main/res/values-km/strings.xml
+++ b/app/src/main/res/values-km/strings.xml
@@ -162,4 +162,44 @@
ដំឡើងកំណែថ្មីបរាជ័យ
+
+ បានបើក
+ តាមរយៈ %1$s
+ ពិការ
+ ភ្ជាប់តាមរយៈស្ថានីយ៍ Conduit របស់មិត្តភ័ក្តិដែលជឿទុកចិត្តសម្រាប់ភាពឯកជន និងការចូលប្រើប្រាស់កាន់តែប្រសើរឡើង។ អ្នកនឹងត្រូវការតំណផ្គូផ្គងដើម្បីចាប់ផ្តើម។
+ បើកការផ្គូផ្គងផ្ទាល់ខ្លួន
+ ភ្ជាប់តាមរយៈស្ថានីយ៍បំពង់
+ នាំចូល URL ផ្គូផ្គង
+ ការផ្គូផ្គង URL
+ បិទភ្ជាប់ URL ដែលផ្គូផ្គងនៅទីនេះ
+ ទម្រង់ URL ការផ្គូផ្គងមិនត្រឹមត្រូវ
+ ទម្រង់ទិន្នន័យផ្គូផ្គងមិនត្រឹមត្រូវ
+ កំណែនិមិត្តសញ្ញាផ្គូផ្គងមិនគាំទ្រ
+ សូមនាំចូល URL ដែលផ្គូផ្គងជាមុនសិន
+ លេខសម្គាល់ការផ្គូផ្គង
+ ឧបករណ៍សម្គាល់ការផ្គូផ្គងតែមួយគត់របស់អ្នក។
+ ការផ្គូផ្គងឈ្មោះក្លែងក្លាយ
+ ឈ្មោះដែលងាយស្រួលសម្រាប់ការកំណត់រចនាសម្ព័ន្ធផ្គូផ្គងនេះ។
+ នាំចូល
+ ការផ្គូផ្គងផ្ទាល់ខ្លួន
+ មានបញ្ហាក្នុងការតភ្ជាប់? បិទការផ្គូផ្គងផ្ទាល់ខ្លួន
+ បានរក្សាទុកការកំណត់ការផ្គូផ្គងផ្ទាល់ខ្លួន
+ បានធ្វើបច្ចុប្បន្នភាពការកំណត់រចនាសម្ព័ន្ធការផ្គូផ្គងផ្ទាល់ខ្លួន
+ ការកំណត់រចនាសម្ព័ន្ធការផ្គូផ្គងផ្ទាល់ខ្លួនមានរួចហើយ
+ ធ្វើបច្ចុប្បន្នភាពទិន្នន័យការផ្គូផ្គងផ្ទាល់ខ្លួន
+ លេខសម្គាល់ការផ្គូផ្គងផ្ទាល់ខ្លួនបច្ចុប្បន្ន៖
+ លេខសម្គាល់ការផ្គូផ្គងផ្ទាល់ខ្លួនថ្មី៖
+ ធ្វើបច្ចុប្បន្នភាព
+ បោះបង់
+ បើកការផ្គូផ្គងផ្ទាល់ខ្លួន
+ បើកការផ្គូផ្គងផ្ទាល់ខ្លួនឥឡូវនេះ? វានឹងចាប់ផ្តើមផ្លូវរូងក្រោមដីសកម្មឡើងវិញ។ បើមិនដូច្នោះទេ ការកំណត់នឹងត្រូវបាននាំចូល ហើយនៅតែបិទរហូតដល់បើកដំណើរការដោយដៃនៅក្នុងការកំណត់កម្មវិធី។
+ កំណត់ការផ្គូផ្គងផ្ទាល់ខ្លួនឡើងវិញ
+ លុបការកំណត់ការផ្គូផ្គងផ្ទាល់ខ្លួននេះចេញជាអចិន្ត្រៃយ៍
+ កំណត់ការផ្គូផ្គងផ្ទាល់ខ្លួនឡើងវិញ
+ បញ្ចូលនិមិត្តសញ្ញាដើម្បីបញ្ជាក់
+ ដើម្បីលុបការកំណត់ការផ្គូផ្គងផ្ទាល់ខ្លួននេះ បញ្ចូលនិមិត្តសញ្ញា %d ចុងក្រោយនៃលេខសម្គាល់ការផ្គូផ្គង៖
+ មិនអាចកំណត់ការកំណត់ឡើងវិញបានទេ។
+ សូមបញ្ចូលនិមិត្តសញ្ញាបញ្ជាក់ត្រឹមត្រូវ។
+ កំណត់រចនាសម្ព័ន្ធឡើងវិញ
+ កំណត់ឡើងវិញ
diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml
index 77de602d3..15756f924 100644
--- a/app/src/main/res/values-kn/strings.xml
+++ b/app/src/main/res/values-kn/strings.xml
@@ -162,4 +162,44 @@
ಸ್ಥಾಪಿಸಿಅಪ್ಡೇಟ್ ವಿಫಲ
+
+ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ
+ %1$s ಮೂಲಕ
+ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ
+ ವರ್ಧಿತ ಗೌಪ್ಯತೆ ಮತ್ತು ಪ್ರವೇಶಕ್ಕಾಗಿ ವಿಶ್ವಾಸಾರ್ಹ ಪೀರ್ನ ವಾಹಿನಿ ನಿಲ್ದಾಣದ ಮೂಲಕ ಸಂಪರ್ಕಿಸಿ. ಪ್ರಾರಂಭಿಸಲು ನಿಮಗೆ ಜೋಡಿಸುವ ಲಿಂಕ್ ಅಗತ್ಯವಿದೆ.
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ
+ ವಾಹಕ ನಿಲ್ದಾಣದ ಮೂಲಕ ಸಂಪರ್ಕಿಸಿ
+ ಜೋಡಿಸುವ URL ಅನ್ನು ಆಮದು ಮಾಡಿ
+ URL ಅನ್ನು ಜೋಡಿಸಲಾಗುತ್ತಿದೆ
+ ಜೋಡಿಸುವ URL ಅನ್ನು ಇಲ್ಲಿ ಅಂಟಿಸಿ
+ ಅಮಾನ್ಯವಾದ ಜೋಡಣೆ URL ಫಾರ್ಮ್ಯಾಟ್
+ ಅಮಾನ್ಯವಾದ ಪೇರಿಂಗ್ ಡೇಟಾ ಫಾರ್ಮ್ಯಾಟ್
+ ಬೆಂಬಲಿತವಲ್ಲದ ಜೋಡಣೆ ಟೋಕನ್ ಆವೃತ್ತಿ
+ ದಯವಿಟ್ಟು ಮೊದಲು ಜೋಡಿಸುವ URL ಅನ್ನು ಆಮದು ಮಾಡಿ
+ ಜೋಡಣೆ ID
+ ನಿಮ್ಮ ಅನನ್ಯ ಜೋಡಣೆ ಗುರುತಿಸುವಿಕೆ
+ ಜೋಡಣೆ ಅಲಿಯಾಸ್
+ ಈ ಜೋಡಣೆಯ ಕಾನ್ಫಿಗರೇಶನ್ಗೆ ಸ್ನೇಹಿ ಹೆಸರು
+ ಆಮದು ಮಾಡಿಕೊಳ್ಳಿ
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆ
+ ಸಂಪರ್ಕಿಸುವಲ್ಲಿ ಸಮಸ್ಯೆ ಇದೆಯೇ? ವೈಯಕ್ತಿಕ ಜೋಡಣೆಯನ್ನು ಆಫ್ ಮಾಡಿ
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆಯ ಸಂರಚನೆಯನ್ನು ಉಳಿಸಲಾಗಿದೆ
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆ ಕಾನ್ಫಿಗರೇಶನ್ ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿದೆ
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆ ಡೇಟಾವನ್ನು ನವೀಕರಿಸಿ
+ ಪ್ರಸ್ತುತ ವೈಯಕ್ತಿಕ ಜೋಡಣೆ ID:
+ ಹೊಸ ವೈಯಕ್ತಿಕ ಜೋಡಣೆ ಐಡಿ:
+ ನವೀಕರಿಸಿ
+ ರದ್ದುಮಾಡು
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ
+ ಈಗ ವೈಯಕ್ತಿಕ ಜೋಡಣೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದೇ? ಇದು ಸಕ್ರಿಯ ಸುರಂಗವನ್ನು ಮರುಪ್ರಾರಂಭಿಸುತ್ತದೆ. ಇಲ್ಲದಿದ್ದರೆ, ಅಪ್ಲಿಕೇಶನ್ ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಹಸ್ತಚಾಲಿತವಾಗಿ ಸಕ್ರಿಯಗೊಳಿಸುವವರೆಗೆ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಆಮದು ಮಾಡಿಕೊಳ್ಳಲಾಗುತ್ತದೆ ಮತ್ತು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗುತ್ತದೆ.
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆಯನ್ನು ಮರುಹೊಂದಿಸಿ
+ ಈ ವೈಯಕ್ತಿಕ ಜೋಡಣೆ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ಶಾಶ್ವತವಾಗಿ ತೆಗೆದುಹಾಕಿ
+ ವೈಯಕ್ತಿಕ ಜೋಡಣೆಯನ್ನು ಮರುಹೊಂದಿಸಿ
+ ದೃಢೀಕರಿಸಲು ಚಿಹ್ನೆಗಳನ್ನು ನಮೂದಿಸಿ
+ ಈ ವೈಯಕ್ತಿಕ ಜೋಡಿಸುವಿಕೆಯ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲು, ಜೋಡಿಸುವ ID ಯ ಕೊನೆಯ %d ಚಿಹ್ನೆಗಳನ್ನು ನಮೂದಿಸಿ:
+ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ಮರುಹೊಂದಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ
+ ದಯವಿಟ್ಟು ಸರಿಯಾದ ದೃಢೀಕರಣ ಚಿಹ್ನೆಗಳನ್ನು ನಮೂದಿಸಿ
+ ಕಾನ್ಫಿಗರೇಶನ್ ರೀಸೆಟ್
+ ಮರುಹೊಂದಿಸಿ
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index f8023ea1d..a9e88f977 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -162,4 +162,44 @@
설치업데이트 실패
+
+ 활성화됨
+ %1$s을(를) 통해
+ 장애가 있는
+ 향상된 개인 정보 보호 및 액세스를 위해 신뢰할 수 있는 피어의 Conduit 스테이션을 통해 연결하세요. 시작하려면 페어링 링크가 필요합니다.
+ 개인 페어링 활성화
+ Conduit 스테이션을 통해 연결
+ 페어링 URL 가져오기
+ 페어링 URL
+ 여기에 페어링 URL을 붙여넣으세요.
+ 잘못된 페어링 URL 형식
+ 잘못된 페어링 데이터 형식
+ 지원되지 않는 페어링 토큰 버전
+ 먼저 페어링 URL을 가져오세요.
+ 페어링 ID
+ 귀하의 고유한 페어링 식별자
+ 페어링 별칭
+ 이 페어링 구성에 대한 표시 이름
+ 수입
+ 개인 페어링
+ 연결하는 데 문제가 있나요? 개인 페어링 끄기
+ 개인 페어링 구성이 저장되었습니다.
+ 개인 페어링 구성이 업데이트되었습니다.
+ 개인 페어링 구성이 이미 존재합니다.
+ 개인 페어링 데이터 업데이트
+ 현재 개인 페어링 ID:
+ 새 개인 페어링 ID:
+ 업데이트
+ 취소
+ 개인 페어링 활성화
+ 지금 개인 페어링을 활성화하시겠습니까? 그러면 활성 터널이 다시 시작됩니다. 그렇지 않으면 설정을 가져오고 애플리케이션 설정에서 수동으로 활성화할 때까지 비활성화된 상태로 유지됩니다.
+ 개인 페어링 재설정
+ 이 개인 페어링 구성을 영구적으로 제거
+ 개인 페어링 재설정
+ 확인하려면 기호를 입력하세요.
+ 이 개인 페어링 구성을 제거하려면 페어링 ID의 마지막 %d 기호를 입력하세요.
+ 구성을 재설정할 수 없습니다.
+ 올바른 확인 기호를 입력하세요.
+ 구성 재설정
+ 다시 놓기
diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml
index 98807409f..ef37980b9 100644
--- a/app/src/main/res/values-ky/strings.xml
+++ b/app/src/main/res/values-ky/strings.xml
@@ -159,4 +159,44 @@
ОрнотууЖаңылоо ишке ашпады
+
+ Иштетилди
+ %1$s аркылуу
+ Өчүрүлгөн
+ Жакшыртылган купуялуулук жана мүмкүнчүлүк алуу үчүн ишенимдүү теңдештин Conduit станциясы аркылуу туташыңыз. Баштоо үчүн сизге жупташтыруу шилтемеси керек болот.
+ Жеке жупташтыруу
+ Conduit станциясы аркылуу туташуу
+ Жупташтыруу URL\'ин импорттоо
+ Жупташуу URL
+ Жупташтыруу URL\'ин бул жерге чаптаңыз
+ Жупташтыруу URL форматы жараксыз
+ Жупташтыруу маалымат форматы жараксыз
+ Колдоого алынбаган жупташтыруучу токен версиясы
+ Сураныч, адегенде жупташтыруу URL дарегин импорттоо
+ Жупташуу ID
+ Сиздин уникалдуу жупташуу идентификаторуңуз
+ Жупташуу лакап ат
+ Бул жупташтыруу конфигурациясынын ыңгайлуу аталышы
+ Импорттоо
+ Жеке жупташуу
+ Туташууда ката кеттиби? Жеке жупташтырууну өчүрүңүз
+ Жеке жупташтыруу конфигурациясы сакталды
+ Жеке жупташтыруу конфигурациясы жаңырды
+ Жеке жупташуу конфигурациясы мурунтан эле бар
+ Жеке жупташтыруу дайындарын жаңыртыңыз
+ Учурдагы жеке жупташтыруу ID:
+ Жаңы жеке жупташтыруу ID:
+ Жаңыртуу
+ Жокко чыгаруу
+ Жеке жупташтыруу
+ Жеке жупташтыруу азыр иштетилсинби? Бул активдүү туннелди кайра иштетет. Болбосо, орнотуулар импорттолуп, колдонмо жөндөөлөрүндө кол менен иштетилгенге чейин өчүрүлгөн бойдон калат.
+ Жеке жупташтыруу
+ Бул Жеке жупташуу конфигурациясын биротоло алып салыңыз
+ Жеке жупташтыруу
+ Ырастоо үчүн символдорду киргизиңиз
+ Бул Жеке жупташуу конфигурациясын алып салуу үчүн Жупташтыруу IDнин акыркы %d символдорун киргизиңиз:
+ Конфигурацияны баштапкы абалга келтирүү мүмкүн эмес
+ Сураныч, туура ырастоо белгилерин киргизиңиз
+ Конфигурацияны баштапкы абалга келтирүү
+ Калыбына келтирүү
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 7d275b0a9..b949aca13 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -162,4 +162,44 @@
ഇൻസ്റ്റാൾ ചെയ്യുകഅപ്ഡേറ്റ് പരാജയപ്പെട്ടു
+
+ പ്രവർത്തനക്ഷമമാക്കി
+ %1$s വഴി
+ അപ്രാപ്തമാക്കി
+ മെച്ചപ്പെടുത്തിയ സ്വകാര്യതയ്ക്കും ആക്സസ്സിനുമായി ഒരു വിശ്വസ്ത പിയേഴ്സ് കൺഡ്യൂറ്റ് സ്റ്റേഷനിലൂടെ കണക്റ്റുചെയ്യുക. ആരംഭിക്കുന്നതിന് നിങ്ങൾക്ക് ഒരു ജോടിയാക്കൽ ലിങ്ക് ആവശ്യമാണ്.
+ വ്യക്തിഗത ജോടിയാക്കൽ പ്രവർത്തനക്ഷമമാക്കുക
+ Conduit സ്റ്റേഷൻ വഴി ബന്ധിപ്പിക്കുക
+ ജോടിയാക്കൽ URL ഇറക്കുമതി ചെയ്യുക
+ ജോടിയാക്കൽ URL
+ ജോടിയാക്കൽ URL ഇവിടെ ഒട്ടിക്കുക
+ ജോടിയാക്കൽ URL ഫോർമാറ്റ് അസാധുവാണ്
+ ജോടിയാക്കൽ ഡാറ്റ ഫോർമാറ്റ് അസാധുവാണ്
+ പിന്തുണയ്ക്കാത്ത ജോടിയാക്കൽ ടോക്കൺ പതിപ്പ്
+ ആദ്യം ഒരു ജോടിയാക്കൽ URL ഇറക്കുമതി ചെയ്യുക
+ ജോടിയാക്കൽ ഐഡി
+ നിങ്ങളുടെ അദ്വിതീയ ജോടിയാക്കൽ ഐഡൻ്റിഫയർ
+ ജോടിയാക്കൽ അപരനാമം
+ ഈ ജോടിയാക്കൽ കോൺഫിഗറേഷനുള്ള ഒരു സൗഹൃദ നാമം
+ ഇറക്കുമതി ചെയ്യുക
+ വ്യക്തിഗത ജോടിയാക്കൽ
+ ബന്ധിപ്പിക്കുന്നതിൽ പ്രശ്നമുണ്ടോ? വ്യക്തിഗത ജോടിയാക്കൽ ഓഫാക്കുക
+ വ്യക്തിഗത ജോടിയാക്കൽ കോൺഫിഗറേഷൻ സംരക്ഷിച്ചു
+ വ്യക്തിഗത ജോടിയാക്കൽ കോൺഫിഗറേഷൻ അപ്ഡേറ്റ് ചെയ്തു
+ വ്യക്തിഗത ജോടിയാക്കൽ കോൺഫിഗറേഷൻ ഇതിനകം നിലവിലുണ്ട്
+ വ്യക്തിഗത ജോടിയാക്കൽ ഡാറ്റ അപ്ഡേറ്റ് ചെയ്യുക
+ നിലവിലെ വ്യക്തിഗത ജോടിയാക്കൽ ഐഡി:
+ പുതിയ വ്യക്തിഗത ജോടിയാക്കൽ ഐഡി:
+ അപ്ഡേറ്റ്
+ റദ്ദാക്കുക
+ വ്യക്തിഗത ജോടിയാക്കൽ പ്രവർത്തനക്ഷമമാക്കുക
+ വ്യക്തിഗത ജോടിയാക്കൽ ഇപ്പോൾ പ്രവർത്തനക്ഷമമാക്കണോ? ഇത് സജീവമായ തുരങ്കം പുനരാരംഭിക്കും. അല്ലെങ്കിൽ, ആപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിൽ സ്വമേധയാ പ്രവർത്തനക്ഷമമാകുന്നതുവരെ ക്രമീകരണങ്ങൾ ഇറക്കുമതി ചെയ്യുകയും പ്രവർത്തനരഹിതമായി തുടരുകയും ചെയ്യും.
+ വ്യക്തിഗത ജോടിയാക്കൽ പുനഃസജ്ജമാക്കുക
+ ഈ വ്യക്തിഗത ജോടിയാക്കൽ കോൺഫിഗറേഷൻ ശാശ്വതമായി നീക്കം ചെയ്യുക
+ വ്യക്തിഗത ജോടിയാക്കൽ പുനഃസജ്ജമാക്കുക
+ സ്ഥിരീകരിക്കാൻ ചിഹ്നങ്ങൾ നൽകുക
+ ഈ വ്യക്തിഗത ജോടിയാക്കൽ കോൺഫിഗറേഷൻ നീക്കം ചെയ്യാൻ, ജോടിയാക്കൽ ഐഡിയുടെ അവസാനത്തെ %d ചിഹ്നങ്ങൾ നൽകുക:
+ കോൺഫിഗറേഷൻ പുനഃസജ്ജമാക്കാനായില്ല
+ ശരിയായ സ്ഥിരീകരണ ചിഹ്നങ്ങൾ നൽകുക
+ കോൺഫിഗറേഷൻ റീസെറ്റ്
+ പുനഃസജ്ജമാക്കുക
diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml
index 4ba000e18..fdef2fbfc 100644
--- a/app/src/main/res/values-mr/strings.xml
+++ b/app/src/main/res/values-mr/strings.xml
@@ -162,4 +162,44 @@
स्थापित कराअपडेट अयशस्वी
+
+ सक्षम केले
+ %1$s द्वारे
+ अक्षम
+ वर्धित गोपनीयता आणि प्रवेशासाठी विश्वासू समवयस्कांच्या कंड्युट स्टेशनद्वारे कनेक्ट व्हा. सुरू करण्यासाठी तुम्हाला जोडणी दुव्याची आवश्यकता असेल.
+ वैयक्तिक जोडणी सक्षम करा
+ कंड्युट स्टेशनद्वारे कनेक्ट करा
+ पेअरिंग URL इंपोर्ट करा
+ पेअरिंग URL
+ पेअरिंग URL येथे पेस्ट करा
+ अवैध जोडणी URL स्वरूप
+ अवैध जोडणी डेटा स्वरूप
+ असमर्थित पेअरिंग टोकन आवृत्ती
+ कृपया प्रथम एक जोडणी URL आयात करा
+ पेअरिंग आयडी
+ तुमचा युनिक पेअरिंग आयडेंटिफायर
+ पेअरिंग उपनाव
+ या जोडणी कॉन्फिगरेशनसाठी अनुकूल नाव
+ आयात करा
+ वैयक्तिक जोडणी
+ कनेक्ट करण्यात अडचण येत आहे? वैयक्तिक जोडणी बंद करा
+ वैयक्तिक पेअरिंग कॉन्फिगरेशन जतन केले
+ वैयक्तिक पेअरिंग कॉन्फिगरेशन अपडेट केले
+ वैयक्तिक पेअरिंग कॉन्फिगरेशन आधीपासून अस्तित्वात आहे
+ वैयक्तिक जोडणी डेटा अद्यतनित करा
+ सध्याचा वैयक्तिक पेअरिंग आयडी:
+ नवीन वैयक्तिक पेअरिंग आयडी:
+ अपडेट करा
+ रद्द करा
+ वैयक्तिक जोडणी सक्षम करा
+ आता वैयक्तिक जोडणी सक्षम करायची? हे सक्रिय बोगदा रीस्टार्ट करेल. अन्यथा, सेटिंग्ज आयात केल्या जातील आणि ऍप्लिकेशन सेटिंग्जमध्ये व्यक्तिचलितपणे सक्षम होईपर्यंत अक्षम राहतील.
+ वैयक्तिक जोडणी रीसेट करा
+ हे वैयक्तिक पेअरिंग कॉन्फिगरेशन कायमचे काढून टाका
+ वैयक्तिक जोडणी रीसेट करा
+ पुष्टी करण्यासाठी चिन्हे प्रविष्ट करा
+ हे वैयक्तिक पेअरिंग कॉन्फिगरेशन काढण्यासाठी, पेअरिंग आयडीचे शेवटचे %d चिन्ह प्रविष्ट करा:
+ कॉन्फिगरेशन रीसेट करण्यात अक्षम
+ कृपया योग्य पुष्टीकरण चिन्हे प्रविष्ट करा
+ कॉन्फिगरेशन रीसेट
+ रीसेट करा
diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml
index a8e382bbd..7318a8675 100644
--- a/app/src/main/res/values-ms/strings.xml
+++ b/app/src/main/res/values-ms/strings.xml
@@ -158,4 +158,44 @@
PasangKemas kini gagal
+
+ Didayakan
+ Melalui %1$s
+ Dilumpuhkan
+ Berhubung melalui stesen Conduit rakan sebaya yang dipercayai untuk privasi dan akses yang dipertingkatkan. Anda memerlukan pautan berpasangan untuk bermula.
+ Dayakan Gandingan Peribadi
+ Sambung melalui stesen Conduit
+ Import URL gandingan
+ URL berpasangan
+ Tampalkan URL gandingan di sini
+ Format URL gandingan tidak sah
+ Format data gandingan tidak sah
+ Versi token gandingan tidak disokong
+ Sila import URL gandingan dahulu
+ ID berpasangan
+ Pengecam gandingan unik anda
+ Menggandingkan alias
+ Nama mesra untuk konfigurasi gandingan ini
+ Import
+ Pasangan Peribadi
+ Masalah menyambung? Matikan Gandingan Peribadi
+ Konfigurasi Gandingan Peribadi disimpan
+ Konfigurasi Gandingan Peribadi dikemas kini
+ Konfigurasi Gandingan Peribadi sudah wujud
+ Kemas kini data Gandingan Peribadi
+ ID Pasangan Peribadi Semasa:
+ ID Gandingan Peribadi Baharu:
+ Kemas kini
+ Batal
+ Dayakan Gandingan Peribadi
+ Dayakan gandingan peribadi sekarang? Ini akan memulakan semula terowong aktif. Jika tidak, tetapan akan diimport dan kekal dilumpuhkan sehingga didayakan secara manual dalam tetapan aplikasi.
+ Tetapkan semula Gandingan Peribadi
+ Alih keluar konfigurasi Pasangan Peribadi ini secara kekal
+ Tetapkan semula Gandingan Peribadi
+ Masukkan simbol untuk mengesahkan
+ Untuk mengalih keluar konfigurasi Pasangan Peribadi ini, masukkan %d simbol terakhir ID Berpasangan:
+ Tidak dapat menetapkan semula konfigurasi
+ Sila masukkan simbol pengesahan yang betul
+ Tetapan semula konfigurasi
+ Tetapkan semula
diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml
index 72447a32b..46ff2dfe2 100644
--- a/app/src/main/res/values-my/strings.xml
+++ b/app/src/main/res/values-my/strings.xml
@@ -166,4 +166,44 @@
ထည့်သွင်းမည်အပ်ဒိတ် မအောင်မြင်ပါ
+
+ ဖွင့်ထားသည်။
+ %1$s မှတဆင့်
+ မသန်စွမ်း
+ ပိုမိုကောင်းမွန်သော ကိုယ်ရေးကိုယ်တာနှင့် ဝင်ရောက်နိုင်မှုအတွက် ယုံကြည်စိတ်ချရသောရွယ်တူချင်း၏ Conduit ဘူတာရုံမှတဆင့် ချိတ်ဆက်ပါ။ စတင်ရန် တွဲချိတ်လင့်ခ်တစ်ခု လိုအပ်ပါမည်။
+ Personal Pairing ကိုဖွင့်ပါ။
+ Conduit station မှတဆင့် ချိတ်ဆက်ပါ။
+ တွဲချိတ်ခြင်း URL ကို တင်သွင်းပါ။
+ URL တွဲချိတ်ခြင်း။
+ တွဲချိတ်ခြင်း URL ကို ဤနေရာတွင် ကူးထည့်ပါ။
+ တွဲချိတ်ခြင်း URL ဖော်မတ် မမှန်ကန်ပါ။
+ မမှန်ကန်သောတွဲချိတ်ဒေတာဖော်မတ်
+ တွဲချိတ်ခြင်း တိုကင်ဗားရှင်းကို ပံ့ပိုးမထားပါ။
+ ကျေးဇူးပြု၍ တွဲချိတ်ခြင်း URL ကို ဦးစွာ ထည့်သွင်းပါ။
+ တွဲချိတ်ခြင်း ID
+ သင်၏ထူးခြားသောတွဲချိတ်မှုအမှတ်အသား
+ နာမည်တူတွဲ
+ ဤတွဲချိတ်မှုဖွဲ့စည်းပုံအတွက် အဆင်ပြေသောအမည်
+ သွင်းကုန်
+ Personal Pairing
+ ချိတ်ဆက်ရာတွင် ပြဿနာရှိပါသလား။ Personal Pairing ကို ပိတ်ပါ။
+ Personal Pairing configuration ကို သိမ်းပြီးပါပြီ။
+ ပုဂ္ဂိုလ်ရေးဆိုင်ရာ တွဲချိတ်မှုပုံစံကို အပ်ဒိတ်လုပ်ထားသည်။
+ Personal Pairing ဖွဲ့စည်းမှု ရှိနှင့်ပြီးဖြစ်သည်။
+ Personal Pairing Data ကို အပ်ဒိတ်လုပ်ပါ။
+ လက်ရှိ ကိုယ်ရေးကိုယ်တာ တွဲချိတ်ခြင်း ID-
+ ကိုယ်ပိုင်တွဲချိတ်ခြင်း ID အသစ်-
+ အပ်ဒိတ်
+ မလုပ်တော့
+ Personal Pairing ကိုဖွင့်ပါ။
+ ကိုယ်ရေးကိုယ်တာတွဲချိတ်ခြင်းကို ယခုဖွင့်မလား။ ၎င်းသည် အသုံးပြုနေသော ဥမင်ကို ပြန်လည်စတင်မည်ဖြစ်သည်။ မဟုတ်ပါက ဆက်တင်များကို ထည့်သွင်းပြီး အပလီကေးရှင်းဆက်တင်များတွင် ကိုယ်တိုင်မဖွင့်မချင်း ဆက်လက်ပိတ်ထားပါမည်။
+ Personal Pairing ကို ပြန်လည်သတ်မှတ်ပါ။
+ ဤကိုယ်ရေးကိုယ်တာတွဲချိတ်မှုပုံစံကို အပြီးတိုင်ဖယ်ရှားပါ။
+ Personal Pairing ကို ပြန်လည်သတ်မှတ်ပါ။
+ အတည်ပြုရန် သင်္ကေတများထည့်ပါ။
+ ဤကိုယ်ရေးကိုယ်တာ တွဲချိတ်မှုပုံစံကို ဖယ်ရှားရန်၊ တွဲချိတ်ခြင်း ID ၏ နောက်ဆုံး %d သင်္ကေတများကို ထည့်ပါ-
+ ဖွဲ့စည်းမှုပုံစံကို ပြန်လည်သတ်မှတ်၍မရပါ။
+ ကျေးဇူးပြု၍ မှန်ကန်သော အတည်ပြုသင်္ကေတများကို ထည့်သွင်းပါ။
+ ပြင်ဆင်သတ်မှတ်မှု ပြန်လည်သတ်မှတ်ခြင်း။
+ ပြန်လည်သတ်မှတ်ပါ။
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index ffbe31074..60f5574b8 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -162,4 +162,44 @@
InstallerOppdatering mislyktes
+
+ Aktivert
+ Via %1$s
+ Funksjonshemmet
+ Koble til via en pålitelig peers Conduit-stasjon for forbedret personvern og tilgang. Du trenger en koblingskobling for å komme i gang.
+ Aktiver personlig sammenkobling
+ Koble til via Conduit-stasjon
+ Importer sammenkoblings-URL
+ Koblings-URL
+ Lim inn sammenkoblings-URL her
+ Ugyldig nettadresseformat for sammenkobling
+ Ugyldig sammenkoblingsdataformat
+ Ustøttet paringstokenversjon
+ Importer en sammenkoblings-URL først
+ Parings-ID
+ Din unike sammenkoblingsidentifikator
+ Paringsalias
+ Et vennlig navn for denne sammenkoblingskonfigurasjonen
+ Import
+ Personlig sammenkobling
+ Problemer med å koble til? Slå av personlig sammenkobling
+ Personlig sammenkoblingskonfigurasjon lagret
+ Personlig sammenkoblingskonfigurasjon oppdatert
+ Personlig sammenkoblingskonfigurasjon eksisterer allerede
+ Oppdater personlige sammenkoblingsdata
+ Gjeldende personlig sammenkoblings-ID:
+ Ny personlig sammenkoblings-ID:
+ Oppdater
+ Kansellere
+ Aktiver personlig sammenkobling
+ Vil du aktivere personlig sammenkobling nå? Dette vil starte den aktive tunnelen på nytt. Ellers vil innstillingene importeres og forbli deaktivert til de aktiveres manuelt i applikasjonsinnstillingene.
+ Tilbakestill personlig sammenkobling
+ Fjern denne konfigurasjonen for personlig sammenkobling permanent
+ Tilbakestill personlig sammenkobling
+ Skriv inn symboler for å bekrefte
+ For å fjerne denne konfigurasjonen for personlig sammenkobling, skriv inn de siste %d-symbolene for sammenkoblings-IDen:
+ Kan ikke tilbakestille konfigurasjonen
+ Vennligst skriv inn korrekte bekreftelsessymboler
+ Konfigurasjon tilbakestilt
+ Tilbakestill
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 56ad229f9..ed55ddb2e 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -165,4 +165,44 @@
InstallerenUpdate mislukt
+
+ Ingeschakeld
+ Via %1$s
+ Gehandicapt
+ Maak verbinding via een vertrouwd Conduit-station van een peer voor verbeterde privacy en toegang. Je hebt een koppelingslink nodig om aan de slag te gaan.
+ Schakel persoonlijk koppelen in
+ Verbinding maken via Conduit-station
+ Koppelings-URL importeren
+ Koppelings-URL
+ Plak de koppelings-URL hier
+ Ongeldige koppelings-URL-indeling
+ Ongeldig indeling van koppelingsgegevens
+ Niet-ondersteunde koppelingstokenversie
+ Importeer eerst een koppelings-URL
+ Koppelings-ID
+ Uw unieke koppelings-ID
+ Koppelalias
+ Een beschrijvende naam voor deze koppelingsconfiguratie
+ Importeren
+ Persoonlijk koppelen
+ Problemen met verbinden? Schakel Persoonlijk koppelen uit
+ Persoonlijke koppelingsconfiguratie opgeslagen
+ Persoonlijke koppelingsconfiguratie bijgewerkt
+ Er bestaat al een persoonlijke koppelingsconfiguratie
+ Update persoonlijke koppelingsgegevens
+ Huidige persoonlijke koppelings-ID:
+ Nieuwe persoonlijke koppelings-ID:
+ Update
+ Annuleren
+ Schakel persoonlijk koppelen in
+ Persoonlijk koppelen nu inschakelen? Hierdoor wordt de actieve tunnel opnieuw gestart. Anders worden de instellingen geïmporteerd en blijven ze uitgeschakeld totdat ze handmatig worden ingeschakeld in de applicatie-instellingen.
+ Persoonlijke koppeling resetten
+ Verwijder deze persoonlijke koppelingsconfiguratie permanent
+ Persoonlijke koppeling resetten
+ Voer symbolen in om te bevestigen
+ Om deze persoonlijke koppelingsconfiguratie te verwijderen, voert u de laatste %d-symbolen van de koppelings-ID in:
+ Kan de configuratie niet resetten
+ Voer de juiste bevestigingssymbolen in
+ Configuratie opnieuw ingesteld
+ Opnieuw instellen
diff --git a/app/src/main/res/values-om/strings.xml b/app/src/main/res/values-om/strings.xml
index e9074ad2d..9346c8dcc 100644
--- a/app/src/main/res/values-om/strings.xml
+++ b/app/src/main/res/values-om/strings.xml
@@ -157,4 +157,44 @@
KuusiHaaromsi hin milkoofne
+
+ Dandeessifameera
+ Karaa %1$s
+ Miidhamaa
+ Iccitii fi qaqqabummaa fooyya\'aa argachuuf karaa buufata Conduit hiriyaa amanamaa ta\'een wal qunnamsiisaa. Jalqabuuf hidhaa wal-fuudhii (pairing link) si barbaachisa.
+ Wal-qunnamtii Dhuunfaa dandeessisi
+ Karaa buufata Conduit tiin wal qunnamsiisa
+ URL wal-faana galchuu galchi
+ URL wal-qabsiisuu
+ URL wal-qabsiisuu asitti maxxansi
+ Akkaataa URL wal-fuudhii sirrii hin taane
+ Akkaataa deetaa wal-fuudhii sirrii hin taane
+ Vershinii mallattoo wal-fuudhii hin deeggaramne
+ Maaloo dursa URL wal-fuudhii galchi
+ ID wal-fuudhii
+ Adda baastuu wal-fuudhii addaa kee
+ Maqaa biraa wal-qabsiisuu
+ Maqaa michuu qindeessaa wal-fuudhii kanaaf
+ Biyya alaatii galchuu
+ Wal-fuudhii Dhuunfaa
+ Rakkoon wal qunnamsiisuu? Wal-qunnamtii Dhuunfaa (Personal Pairing) cufi
+ Qindaa\'inni Wal-qabsiisummaa Dhuunfaa qusatameera
+ Qindaa\'inni Wal-qabsiisummaa Dhuunfaa haaromfameera
+ Qindaa\'inni Wal-qabsiisuu Dhuunfaa duraan jira
+ Daataa Wal-qabsiisummaa Dhuunfaa haaromsi
+ ID Wal-fuudhii Dhuunfaa Ammaa:
+ ID Wal-fuudhii Dhuunfaa Haaraa:
+ Odeeffannoo dhiyeenyaa
+ Haquu
+ Wal-qunnamtii Dhuunfaa dandeessisi
+ Amma wal-fuudhii dhuunfaa dandeessisi? Kunis tuneela socho\'aa deebisee jalqaba. Yoo kana hin taane, qindaa\'inoota ni galfama, hanga qindaa\'inoota aplikeeshinii keessatti harkaan dandeessifamutti ni cufama.
+ Wal-qunnamtii Dhuunfaa Irra Deebi\'i
+ Qindaa\'ina Wal-qunnamtii Dhuunfaa kana dhaabbataadhaan haqi
+ Wal-qunnamtii Dhuunfaa Irra Deebi\'i
+ Mallattoolee mirkaneessuuf galchi
+ Qindaa\'ina Wal-qunnamtii Dhuunfaa kana haquuf, mallattoolee %d dhumaa ID Wal-qabsiisuu galchi:
+ Qindaa\'ina deebisanii saaguu hin dandeenye
+ Maaloo mallattoolee mirkaneessaa sirrii galchi
+ Qindeessituu irra deebi\'ii saagi
+ Reset gochuu
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 15676f2ac..b55eaddf3 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -160,4 +160,44 @@
InstalarAtualização falhou
+
+ Habilitado
+ Via %1$s
+ Desabilitado
+ Conecte-se através de uma estação de conduíte de ponto confiável para maior privacidade e acesso. Você precisará de um link de pareamento para começar.
+ Ativar emparelhamento pessoal
+ Conecte-se via estação de conduíte
+ URL de pareamento de importação
+ URL de pareamento
+ Cole o URL de pareamento aqui
+ Formato de URL de pareamento inválido
+ Formato de dados de pareamento inválido
+ Versão do token de pareamento incompatível
+ Importe primeiro um URL de pareamento
+ ID de pareamento
+ Seu identificador de pareamento exclusivo
+ Alias de emparelhamento
+ Um nome amigável para esta configuração de emparelhamento
+ Importar
+ Emparelhamento Pessoal
+ Problemas para conectar? Desative o emparelhamento pessoal
+ Configuração de emparelhamento pessoal salva
+ Configuração de emparelhamento pessoal atualizada
+ A configuração de emparelhamento pessoal já existe
+ Atualizar dados de emparelhamento pessoal
+ ID de emparelhamento pessoal atual:
+ Novo ID de emparelhamento pessoal:
+ Atualizar
+ Cancelar
+ Ativar emparelhamento pessoal
+ Ativar o emparelhamento pessoal agora? Isto irá reiniciar o túnel ativo. Caso contrário, as configurações serão importadas e permanecerão desativadas até serem ativadas manualmente nas configurações do aplicativo.
+ Redefinir emparelhamento pessoal
+ Remova esta configuração de emparelhamento pessoal permanentemente
+ Redefinir emparelhamento pessoal
+ Insira símbolos para confirmar
+ Para remover esta configuração de emparelhamento pessoal, insira os últimos símbolos %d do ID de emparelhamento:
+ Não foi possível redefinir a configuração
+ Insira os símbolos de confirmação corretos
+ Redefinição de configuração
+ Reiniciar
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 0a8408243..b7b7ab0ce 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -163,4 +163,44 @@ Com a caixa de diálogo do Psiphon Bump aberta no seu dispositivo e a aplicaçã
InstalarAtualização falhou
+
+ Habilitado
+ Via %1$s
+ Desabilitado
+ Conecte-se através de uma estação de conduíte de ponto confiável para maior privacidade e acesso. Você precisará de um link de pareamento para começar.
+ Ativar emparelhamento pessoal
+ Conecte-se via estação de conduíte
+ URL de pareamento de importação
+ URL de pareamento
+ Cole o URL de pareamento aqui
+ Formato de URL de pareamento inválido
+ Formato de dados de pareamento inválido
+ Versão do token de pareamento incompatível
+ Importe primeiro um URL de pareamento
+ ID de pareamento
+ Seu identificador de pareamento exclusivo
+ Alias de emparelhamento
+ Um nome amigável para esta configuração de emparelhamento
+ Importar
+ Emparelhamento Pessoal
+ Problemas para conectar? Desative o emparelhamento pessoal
+ Configuração de emparelhamento pessoal salva
+ Configuração de emparelhamento pessoal atualizada
+ A configuração de emparelhamento pessoal já existe
+ Atualizar dados de emparelhamento pessoal
+ ID de emparelhamento pessoal atual:
+ Novo ID de emparelhamento pessoal:
+ Atualizar
+ Cancelar
+ Ativar emparelhamento pessoal
+ Ativar o emparelhamento pessoal agora? Isto irá reiniciar o túnel ativo. Caso contrário, as configurações serão importadas e permanecerão desativadas até serem ativadas manualmente nas configurações do aplicativo.
+ Redefinir emparelhamento pessoal
+ Remova esta configuração de emparelhamento pessoal permanentemente
+ Redefinir emparelhamento pessoal
+ Insira símbolos para confirmar
+ Para remover esta configuração de emparelhamento pessoal, insira os últimos símbolos %d do ID de emparelhamento:
+ Não foi possível redefinir a configuração
+ Insira os símbolos de confirmação corretos
+ Redefinição de configuração
+ Reiniciar
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 08bc2f8f8..76b6504be 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -166,4 +166,44 @@
УстановитьОбновление не удалось
+
+ Включено
+ Через %1$s
+ Отключено
+ Подключайтесь через станцию Conduit доверенного узла для повышения конфиденциальности и улучшения доступа. Чтобы начать, вам понадобится ссылка для сопряжения.
+ Включить личное сопряжение
+ Подключение через станцию Conduit
+ Импортировать ссылку сопряжения
+ Ссылка сопряжения
+ Вставьте ссылку сопряжения сюда
+ Неверный формат ссылки сопряжения
+ Неверный формат данных сопряжения
+ Неподдерживаемая версия токена сопряжения
+ Сначала импортируйте ссылку сопряжения
+ ID сопряжения
+ Ваш уникальный идентификатор сопряжения
+ Псевдоним сопряжения
+ Удобное имя для этой конфигурации сопряжения
+ Импортировать
+ Личное сопряжение
+ Проблемы с подключением? Отключите личное сопряжение
+ Конфигурация личного сопряжения сохранена
+ Конфигурация личного сопряжения обновлена
+ Конфигурация личного сопряжения уже существует
+ Обновить данные личного сопряжения
+ Текущий ID личного сопряжения:
+ Новый ID личного сопряжения:
+ Обновить
+ Отмена
+ Включить личное сопряжение
+ Включить личное сопряжение сейчас? Это перезапустит активный туннель. В противном случае настройки будут импортированы и останутся отключёнными, пока вы не включите их вручную в настройках приложения.
+ Сбросить личное сопряжение
+ Удалить эту конфигурацию личного сопряжения навсегда
+ Сбросить личное сопряжение
+ Введите символы для подтверждения
+ Чтобы удалить эту конфигурацию личного сопряжения, введите последние %d символов ID сопряжения:
+ Не удалось сбросить конфигурацию
+ Введите правильные символы подтверждения
+ Конфигурация сброшена
+ Сбросить
diff --git a/app/src/main/res/values-sn/strings.xml b/app/src/main/res/values-sn/strings.xml
index b03b2917a..988f49605 100644
--- a/app/src/main/res/values-sn/strings.xml
+++ b/app/src/main/res/values-sn/strings.xml
@@ -162,4 +162,44 @@
IsaKuvandudzwa kwatadza
+
+ Yagoneswa
+ Via %1$s
+ Yakaremara
+ Batanidza kuburikidza neakavimbika peer\'s Conduit chiteshi kuti uwedzere kuvanzika uye kuwana. Unozoda chinongedzo chekubatanidza kuti utange.
+ Gonesa Personal Pairing
+ Batanidza neConduit station
+ Pinza maURL ekubatanidza
+ Kubatanidza URL
+ Namata URL yekubatanidza pano
+ Mafomati ekubatanidza URL haasiriyo
+ Fomati yedata yekubatanidza haikodzeri
+ Haisi kutsigirwa pairing token version
+ Tapota unzai URL yepeyi kutanga
+ Pairing ID
+ Chiziviso chako chekubatanidza
+ Pairing alias
+ Zita rehushamwari rezvigadziriso zvekubatanidza izvi
+ Import
+ Personal Pairing
+ Dambudziko rekubatanidza? Dzima Personal Pairing
+ Personal Pairing gadziriro yachengetwa
+ Personal Pairing gadziriso yakagadziridzwa
+ Personal Pairing gadziriso yatovepo
+ Gadziridza Personal Pairing data
+ Iyezvino Personal Pairing ID:
+ New Personal Pairing ID:
+ Update
+ Cancel
+ Gonesa Personal Pairing
+ Bvisa pairing yako izvozvi? Izvi zvichatangazve mugero unoshanda. Zvikasadaro, marongero anozounzwa kunze uye oramba akaremara kusvika agoneswa nemaoko muzvirongwa zvekushandisa.
+ Reset Personal Pairing
+ Bvisa iyi Personal Pairing configuration zvachose
+ Reset Personal Pairing
+ Isa zviratidzo kuti usimbise
+ Kuti ubvise iyi Yemunhu Pairing gadziriso, isa yekupedzisira %d zviratidzo zvePairing ID:
+ Hatina kukwanisa kuseta patsva
+ Ndokumbira uise zviratidzo zvekusimbisa
+ Configuration reset
+ Reset
diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml
index 7521ee6d1..9342da2cf 100644
--- a/app/src/main/res/values-sw/strings.xml
+++ b/app/src/main/res/values-sw/strings.xml
@@ -166,4 +166,44 @@
SakinishaSasisho limeshindwa
+
+ Imewashwa
+ Kupitia %1$s
+ Imezimwa
+ Unganisha kupitia kituo cha mfereji wa rika unaoaminika kwa faragha na ufikiaji ulioimarishwa. Utahitaji kiungo cha kuoanisha ili kuanza.
+ Washa Uoanishaji wa Kibinafsi
+ Unganisha kupitia kituo cha Mfereji
+ Ingiza URL ya kuoanisha
+ URL ya kuoanisha
+ Bandika URL ya kuoanisha hapa
+ Umbizo batili la URL ya kuoanisha
+ Umbizo batili la data ya kuoanisha
+ Toleo la tokeni la kuoanisha halitumiki
+ Tafadhali ingiza URL ya kuoanisha kwanza
+ Kitambulisho cha kuoanisha
+ Kitambulisho chako cha kipekee cha kuoanisha
+ Lakabu za kuoanisha
+ Jina la kirafiki kwa usanidi huu wa kuoanisha
+ Ingiza
+ Uoanishaji wa Kibinafsi
+ Je, una tatizo la kuunganisha? Zima Uoanishaji wa Kibinafsi
+ Usanidi wa Kuoanisha Kibinafsi umehifadhiwa
+ Usanidi wa Kuoanisha Kibinafsi umesasishwa
+ Tayari usanidi wa Kuoanisha Kibinafsi upo
+ Sasisha data ya Kuoanisha Kibinafsi
+ Kitambulisho cha Sasa cha Kuoanisha Kibinafsi:
+ Kitambulisho Kipya cha Kuoanisha Kibinafsi:
+ Sasisha
+ Ghairi
+ Washa Uoanishaji wa Kibinafsi
+ Ungependa kuwasha uoanishaji wa kibinafsi sasa? Hii itaanzisha upya handaki inayotumika. Vinginevyo, mipangilio italetwa na kubaki imezimwa hadi iwezeshwe wewe mwenyewe katika mipangilio ya programu.
+ Weka upya Uoanishaji wa Kibinafsi
+ Ondoa usanidi huu wa Kuoanisha Kibinafsi kabisa
+ Weka upya Uoanishaji wa Kibinafsi
+ Weka alama ili kuthibitisha
+ Ili kuondoa usanidi huu wa Kuoanisha Kibinafsi, weka alama za %d za mwisho za Kitambulisho cha Kuoanisha:
+ Imeshindwa kuweka upya usanidi
+ Tafadhali weka alama sahihi za uthibitishaji
+ Kuweka upya mipangilio
+ Weka upya
diff --git a/app/src/main/res/values-tg/strings.xml b/app/src/main/res/values-tg/strings.xml
index 97bb6d091..d2dc96121 100644
--- a/app/src/main/res/values-tg/strings.xml
+++ b/app/src/main/res/values-tg/strings.xml
@@ -160,4 +160,44 @@
Насб карданНавсозӣ бо нокомӣ анҷом ёфт
+
+ Фаъол
+ Тавассути %1$s
+ Маъюб
+ Барои махфият ва дастрасии мукаммал тавассути истгоҳи Conduit-и боэътимод пайваст шавед. Барои оғоз кардан ба шумо пайванди ҷуфткунӣ лозим аст.
+ Пайвасткунии шахсиро фаъол созед
+ Ба воситаи истгоҳи Conduit пайваст шавед
+ URL-и пайвасткуниро ворид кунед
+ URL пайваст
+ URL-и ҷуфтро дар ин ҷо гузоред
+ Формати URL пайвасткунии нодуруст
+ Формати ҷуфткунии маълумот нодуруст
+ Версияи аломати ҷуфткунӣ дастгирӣнашаванда
+ Лутфан аввал URL-и пайвасткуниро ворид кунед
+ ID-и ҷуфтшавӣ
+ Муайянкунандаи ҷуфткунии беназири шумо
+ Ҷуфт кардани тахаллус
+ Номи дӯстона барои ин конфигуратсияи ҷуфтшавӣ
+ Воридот
+ Ҷуфти шахсӣ
+ Мушкилоти пайвастшавӣ? Пайвасткунии шахсиро хомӯш кунед
+ Конфигуратсияи ҷуфткунии шахсӣ захира карда шуд
+ Конфигуратсияи ҷуфткунии шахсӣ нав карда шуд
+ Конфигуратсияи ҷуфткунии шахсӣ аллакай вуҷуд дорад
+ Навсозии маълумоти ҷуфткунии шахсӣ
+ ID-и ҳозираи ҷуфткунии шахсӣ:
+ ID-и нави ҷуфткунии шахсӣ:
+ Навсозӣ
+ Бекор кардан
+ Пайвасткунии шахсиро фаъол созед
+ Ҷуфти шахсиро ҳоло фаъол созед? Ин нақби фаъолро аз нав оғоз мекунад. Дар акси ҳол, танзимот ворид карда мешаванд ва то он даме, ки дар танзимоти барнома дастӣ фаъол карда шавад, ғайрифаъол мемонанд.
+ Аз нав танзимкунии ҷуфткунии шахсӣ
+ Ин конфигуратсияи ҷуфткунии шахсиро ба таври доимӣ нест кунед
+ Аз нав танзимкунии ҷуфткунии шахсӣ
+ Барои тасдиқ рамзҳоро ворид кунед
+ Барои нест кардани ин конфигуратсияи Ҷуфти шахсӣ, рамзҳои охирини %d-и ID-и ҷуфтро ворид кунед:
+ Конфигуратсияро аз нав барқарор кардан ғайриимкон аст
+ Лутфан аломатҳои тасдиқи дурустро ворид кунед
+ Аз нав танзимкунии конфигуратсия
+ Бозсозӣ
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index 643c43330..43272c800 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -158,4 +158,44 @@
ติดตั้งอัปเดตล้มเหลว
+
+ เปิดใช้งานแล้ว
+ ผ่านทาง %1$s
+ พิการ
+ เชื่อมต่อผ่านสถานี Conduit ของเพื่อนที่เชื่อถือได้เพื่อเพิ่มความเป็นส่วนตัวและการเข้าถึง คุณจะต้องมีลิงก์การจับคู่เพื่อเริ่มต้น
+ เปิดใช้งานการจับคู่ส่วนบุคคล
+ เชื่อมต่อผ่านสถานีท่อร้อยสาย
+ นำเข้า URL การจับคู่
+ URL การจับคู่
+ วาง URL การจับคู่ที่นี่
+ รูปแบบ URL การจับคู่ไม่ถูกต้อง
+ รูปแบบข้อมูลการจับคู่ไม่ถูกต้อง
+ ไม่รองรับเวอร์ชันโทเค็นการจับคู่
+ โปรดนำเข้า URL การจับคู่ก่อน
+ รหัสการจับคู่
+ ตัวระบุการจับคู่เฉพาะของคุณ
+ การจับคู่นามแฝง
+ ชื่อที่จำง่ายสำหรับการกำหนดค่าการจับคู่นี้
+ นำเข้า
+ การจับคู่ส่วนบุคคล
+ มีปัญหาในการเชื่อมต่อใช่ไหม? ปิดการจับคู่ส่วนตัว
+ บันทึกการกำหนดค่าการจับคู่ส่วนตัวแล้ว
+ อัปเดตการกำหนดค่าการจับคู่ส่วนตัวแล้ว
+ มีการกำหนดค่าการจับคู่ส่วนบุคคลอยู่แล้ว
+ อัปเดตข้อมูลการจับคู่ส่วนบุคคล
+ ID การจับคู่ส่วนตัวปัจจุบัน:
+ ID การจับคู่ส่วนตัวใหม่:
+ อัปเดต
+ ยกเลิก
+ เปิดใช้งานการจับคู่ส่วนบุคคล
+ เปิดใช้งานการจับคู่ส่วนตัวตอนนี้หรือไม่ นี่จะรีสตาร์ททันเนลที่ใช้งานอยู่ มิฉะนั้น การตั้งค่าจะถูกนำเข้าและยังคงปิดใช้งานอยู่จนกว่าจะเปิดใช้งานด้วยตนเองในการตั้งค่าแอปพลิเคชัน
+ รีเซ็ตการจับคู่ส่วนตัว
+ ลบการกำหนดค่าการจับคู่ส่วนบุคคลนี้อย่างถาวร
+ รีเซ็ตการจับคู่ส่วนตัว
+ ป้อนสัญลักษณ์เพื่อยืนยัน
+ หากต้องการลบการกำหนดค่าการจับคู่ส่วนบุคคลนี้ ให้ป้อนสัญลักษณ์ %d สุดท้ายของรหัสการจับคู่:
+ ไม่สามารถรีเซ็ตการกำหนดค่าได้
+ กรุณากรอกสัญลักษณ์ยืนยันที่ถูกต้อง
+ รีเซ็ตการกำหนดค่า
+ รีเซ็ต
diff --git a/app/src/main/res/values-ti/strings.xml b/app/src/main/res/values-ti/strings.xml
index 6ec4a654a..774b0f197 100644
--- a/app/src/main/res/values-ti/strings.xml
+++ b/app/src/main/res/values-ti/strings.xml
@@ -164,4 +164,44 @@
ኣትክልምምሕያሽ ኣይሰለጠን
+
+ ተኸፊቱ
+ ብመንገዲ %1$s
+ ስንኩል
+ ንዝተዓጻጸፈ ብሕትውናን ተበጻሕነትን ብመንገዲ ናይ ሓደ እሙን መዛኑ ኮንዱይት ጣብያ ምትእስሳር። ንኽትጅምር ናይ ምጽምባር መላግቦ ከድልየካ እዩ።
+ ውልቃዊ ምጽምባር ኣንቅሕ
+ ብመንገዲ ጣብያ ኮንዱይት ምትእስሳር
+ ናይ ምጽምባር URL ኣእቱ
+ ምጽምባር URL
+ ናይ ምጽምባር URL ኣብዚ ለጥፍ
+ ዘይሕጋዊ ናይ ምጽምባር URL ቅርጺ
+ ዘይሕጋዊ ናይ ምጽምባር ዳታ ቅርጺ
+ ዘይተደገፈ ናይ ምጽምባር ቶከን ስሪት።
+ በጃኹም መጀመርታ ናይ ምጽምባር URL ኣእትዉ
+ ናይ ምጽምባር መለለዪ
+ ፍሉይ ናይ ምጽምባር መለለዪኻ
+ ምጽምባር ቅጽል ስም
+ ነዚ ናይ ምጽምባር ውቅር ምሕዝነታዊ ስም
+ ናብ ዓዲ ውሽጢ ኣቕሓ ምእታው
+ ውልቃዊ ምጽምባር
+ ጸገም ምትእስሳር? ውልቃዊ ምጽምባር ኣጥፍእዎ።
+ ውልቃዊ ምጽምባር ውቅር ተዓቂቡ።
+ ውልቃዊ ምጽምባር ውቅር ተሓዲሱ
+ ውልቃዊ ምጽምባር ውቅር ድሮ ኣሎ።
+ ውልቃዊ ምጽምባር ዳታ ምዕራፍ
+ ናይ ሕጂ ውልቃዊ ምጽምባር መለለዪ፤
+ ሓድሽ ውልቃዊ ምጽምባር መለለዪ:
+ ኣዘምን
+ ሰርዝ
+ ውልቃዊ ምጽምባር ኣንቅሕ
+ ሕጂ ውልቃዊ ምጽምባር ኣኽእሎ? እዚ ድማ ነቲ ንጡፍ ታንከር ዳግማይ ክጅምሮ እዩ። እንተዘይኮይኑ ቅጥዕታት ካብ ወጻኢ ክኣትዉን ኣብቲ ናይ መተግበሪ ቅጥዕታት ብኢድ ክሳብ ዝኽፈቱ ስንኩላን ኮይኖም ክጸንሑ እዮም።
+ ውልቃዊ ምጽምባር ዳግማይ ምትካል
+ ነዚ ናይ ውልቃዊ ምጽምባር ውቅር ንሓዋሩ ኣውጽእዎ።
+ ውልቃዊ ምጽምባር ዳግማይ ምትካል
+ ንምርግጋጽ ምልክታት ኣእቱ
+ ነዚ ውልቃዊ ምጽምባር ውቅር ንምእላይ፡ ናይ መወዳእታ %d ምልክታት ናይ ምጽምባር መለለዪ ኣእቱ፤
+ ውቅር ዳግማይ ምትካል ኣይከኣለን
+ በጃኹም ቅኑዕ ናይ ምርግጋጽ ምልክታት ኣእትዉ
+ ናይ ውቅር ዳግመ ምትዕርራይ
+ ዳግማይ ምትካል
diff --git a/app/src/main/res/values-tk/strings.xml b/app/src/main/res/values-tk/strings.xml
index 0d23abbe8..6fd4b26cd 100644
--- a/app/src/main/res/values-tk/strings.xml
+++ b/app/src/main/res/values-tk/strings.xml
@@ -159,4 +159,44 @@
GurnamakTäzelenme şowsuz boldy
+
+ Işledilen
+ %1$s arkaly
+ Maýyp
+ Giňeldilen şahsy durmuşyň eldegrilmesizligi we elýeterliligi üçin ynamdar deň-duşlaryň geçiriji stansiýasy arkaly birikdiriň. Başlamak üçin jübüt baglanyşyk gerek bolar.
+ Şahsy jübütlemegi işjeňleşdiriň
+ Geçiriji stansiýasy arkaly birikdiriň
+ Jübütleme URL-ni import ediň
+ Jübütlemek URL
+ Jübütlemek URL-ni şu ýere goýuň
+ Nädogry jübütlenen URL formaty
+ Nädogry jübüt maglumat formaty
+ Goldaw berilmeýän jübütlik token wersiýasy
+ Ilki bilen jübütlenen URL import ediň
+ Jübüt ID
+ Özboluşly jübüt kesgitleýjiňiz
+ Jübüt lakamy
+ Bu jübüt konfigurasiýa üçin dostlukly at
+ Import
+ Şahsy jübütlik
+ Birikdirmekde kynçylyk çekýärsiňizmi? Şahsy jübütligi öçüriň
+ Şahsy jübütleme konfigurasiýasy ýatda saklandy
+ Şahsy jübütleme konfigurasiýasy täzelendi
+ Şahsy jübütleme konfigurasiýasy eýýäm bar
+ Şahsy jübüt maglumatlary täzeläň
+ Häzirki şahsy jübütleme belgisi:
+ Täze şahsy jübütleme belgisi:
+ Täzelen
+ Elatyr
+ Şahsy jübütlemegi işjeňleşdiriň
+ Şahsy jübütlemäni indi açyp bilersiňizmi? Bu işjeň tuneli täzeden başlar. Otherwiseogsam, sazlamalar import ediler we programma sazlamalarynda el bilen işleýänçä ýapyk bolar.
+ Şahsy jübütlemäni täzeden düzmek
+ Bu Şahsy jübütleme konfigurasiýasyny hemişelik aýyryň
+ Şahsy jübütlemäni täzeden düzmek
+ Tassyklamak üçin nyşanlary giriziň
+ Bu Şahsy jübütleme konfigurasiýasyny aýyrmak üçin Jübüt ID-iň soňky %d nyşanlaryny giriziň:
+ Sazlamany täzeden düzüp bolmaýar
+ Dogry tassyklama nyşanlaryny giriziň
+ Sazlama täzeden düzmek
+ Täzeden düzmek
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index fa3f456f2..78de5a9ad 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -166,4 +166,44 @@
KurGüncellenemedi
+
+ Etkinleştirilmiş
+ %1$s aracılığıyla
+ Engelli
+ Gelişmiş gizlilik ve erişim için güvenilir bir eşin Conduit istasyonu aracılığıyla bağlanın. Başlamak için bir eşleştirme bağlantısına ihtiyacınız olacak.
+ Kişisel Eşleştirmeyi Etkinleştir
+ Conduit istasyonu aracılığıyla bağlanın
+ Eşleştirme URL\'sini içe aktar
+ Eşleştirme URL\'si
+ Eşleştirme URL\'sini buraya yapıştırın
+ Geçersiz eşleştirme URL\'si biçimi
+ Geçersiz eşleştirme veri biçimi
+ Desteklenmeyen eşleştirme jetonu sürümü
+ Lütfen önce bir eşleştirme URL\'sini içe aktarın
+ Eşleştirme Kimliği
+ Benzersiz eşleştirme tanımlayıcınız
+ Takma ad eşleniyor
+ Bu eşleştirme yapılandırması için kolay bir ad
+ İçe aktarmak
+ Kişisel Eşleştirme
+ Bağlantıda sorun mu yaşıyorsunuz? Kişisel Eşleştirmeyi kapat
+ Kişisel Eşleştirme yapılandırması kaydedildi
+ Kişisel Eşleştirme yapılandırması güncellendi
+ Kişisel Eşleştirme yapılandırması zaten mevcut
+ Kişisel Eşleştirme verilerini güncelleyin
+ Mevcut Kişisel Eşleştirme Kimliği:
+ Yeni Kişisel Eşleştirme Kimliği:
+ Güncelleme
+ İptal etmek
+ Kişisel Eşleştirmeyi Etkinleştir
+ Kişisel eşleştirme şimdi etkinleştirilsin mi? Bu, aktif tüneli yeniden başlatacaktır. Aksi takdirde ayarlar içe aktarılacak ve uygulama ayarlarında manuel olarak etkinleştirilene kadar devre dışı kalacaktır.
+ Kişisel Eşleştirmeyi Sıfırla
+ Bu Kişisel Eşleştirme yapılandırmasını kalıcı olarak kaldır
+ Kişisel Eşleştirmeyi Sıfırla
+ Onaylamak için sembolleri girin
+ Bu Kişisel Eşleştirme yapılandırmasını kaldırmak için Eşleştirme Kimliğinin son %d sembollerini girin:
+ Yapılandırma sıfırlanamıyor
+ Lütfen doğru onay sembollerini girin
+ Yapılandırma sıfırlama
+ Sıfırla
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index b1bd7b7c7..ac92b9691 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -166,4 +166,44 @@
ВстановитиОновлення не вдалося
+
+ Увімкнено
+ Через %1$s
+ Вимкнено
+ Підключайтеся через станцію Conduit надійного однорангового пристрою для покращеної конфіденційності та доступу. Щоб почати, вам знадобиться посилання для створення пари.
+ Увімкнути персональне підключення
+ Підключіться через станцію Conduit
+ Імпортувати URL-адресу підключення
+ URL-адреса сполучення
+ Вставте тут URL-адресу підключення
+ Недійсний формат URL-адреси підключення
+ Недійсний формат даних сполучення
+ Непідтримувана версія маркера сполучення
+ Спочатку імпортуйте URL-адресу для створення пари
+ Ідентифікатор з’єднання
+ Ваш унікальний ідентифікатор пари
+ Псевдонім створення пари
+ Зручна назва для цієї конфігурації сполучення
+ Імпорт
+ Персональне сполучення
+ Проблеми з підключенням? Вимкніть Personal Pairing
+ Персональну конфігурацію сполучення збережено
+ Оновлено конфігурацію особистої пари
+ Особиста конфігурація створення пари вже існує
+ Оновіть персональні дані сполучення
+ Поточний особистий ідентифікатор пари:
+ Новий особистий ідентифікатор пари:
+ оновлення
+ Скасувати
+ Увімкнути персональне підключення
+ Увімкнути особисте підключення зараз? Це перезапустить активний тунель. В іншому випадку налаштування буде імпортовано та залишатиметься вимкненим, доки не буде ввімкнено вручну в налаштуваннях програми.
+ Скинути особисте сполучення
+ Видаліть цю персональну конфігурацію з’єднання назавжди
+ Скинути особисте сполучення
+ Введіть символи для підтвердження
+ Щоб видалити цю особисту конфігурацію з’єднання, введіть останні символи %d ідентифікатора з’єднання:
+ Не вдалося скинути конфігурацію
+ Введіть правильні символи підтвердження
+ Скидання конфігурації
+ Скинути
diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml
index 23bd3b8f6..9da328491 100644
--- a/app/src/main/res/values-ur/strings.xml
+++ b/app/src/main/res/values-ur/strings.xml
@@ -165,4 +165,44 @@
انسٹالاپڈیٹ ناکام
+
+ فعال
+ %1$s کے ذریعے
+ معذور
+ بہتر رازداری اور رسائی کے لیے ایک بھروسہ مند ساتھی کے کنڈیوٹ اسٹیشن کے ذریعے جڑیں۔ شروع کرنے کے لیے آپ کو جوڑا بنانے کے لنک کی ضرورت ہوگی۔
+ ذاتی جوڑی کو فعال کریں۔
+ نالی اسٹیشن کے ذریعے جڑیں۔
+ جوڑا بنانے کا URL درآمد کریں۔
+ جوڑا یو آر ایل
+ جوڑا بنانے کا URL یہاں چسپاں کریں۔
+ جوڑا بنانے کا URL غلط فارمیٹ
+ غلط جوڑی ڈیٹا فارمیٹ
+ غیر تعاون یافتہ پیئرنگ ٹوکن ورژن
+ براہ کرم پہلے جوڑا بنانے والا URL درآمد کریں۔
+ جوڑا بنانے والی ID
+ آپ کا منفرد جوڑا شناخت کنندہ
+ جوڑا بنانے کا عرف
+ اس جوڑا بنانے کی ترتیب کے لیے ایک دوستانہ نام
+ درآمد کریں۔
+ ذاتی جوڑا
+ منسلک ہونے میں دشواری؟ ذاتی جوڑا بند کریں۔
+ ذاتی جوڑا بنانے کی ترتیب محفوظ ہو گئی۔
+ ذاتی جوڑا بنانے کی ترتیب کو اپ ڈیٹ کر دیا گیا۔
+ ذاتی جوڑا بنانے کی ترتیب پہلے سے موجود ہے۔
+ ذاتی جوڑا بنانے کا ڈیٹا اپ ڈیٹ کریں۔
+ موجودہ ذاتی جوڑا ID:
+ نئی ذاتی جوڑی ID:
+ اپڈیٹ کریں۔
+ منسوخ کریں۔
+ ذاتی جوڑی کو فعال کریں۔
+ ذاتی جوڑا بنانے کو ابھی فعال کریں؟ اس سے فعال سرنگ دوبارہ شروع ہو جائے گی۔ بصورت دیگر، ترتیبات درآمد کی جائیں گی اور ایپلیکیشن کی ترتیبات میں دستی طور پر فعال ہونے تک غیر فعال رہیں گی۔
+ ذاتی جوڑا دوبارہ ترتیب دیں۔
+ اس ذاتی جوڑی کی ترتیب کو مستقل طور پر ہٹا دیں۔
+ ذاتی جوڑا دوبارہ ترتیب دیں۔
+ تصدیق کے لیے علامتیں درج کریں۔
+ اس ذاتی جوڑی کی ترتیب کو ہٹانے کے لیے، جوڑا بنانے والے ID کے آخری %d علامتیں درج کریں:
+ کنفیگریشن ری سیٹ کرنے سے قاصر
+ براہ کرم درست تصدیقی علامتیں درج کریں۔
+ کنفیگریشن ری سیٹ
+ دوبارہ ترتیب دیں۔
diff --git a/app/src/main/res/values-uz/strings.xml b/app/src/main/res/values-uz/strings.xml
index 55842aacd..8c18329c8 100644
--- a/app/src/main/res/values-uz/strings.xml
+++ b/app/src/main/res/values-uz/strings.xml
@@ -158,4 +158,44 @@
O\'rnatishYangilanish muvaffaqiyatsiz
+
+ Yoqilgan
+ %1$s orqali
+ Oʻchirilgan
+ Kengaytirilgan maxfiylik va kirish uchun ishonchli peer\'s Conduit stantsiyasi orqali ulaning. Boshlash uchun sizga ulash havolasi kerak bo\'ladi.
+ Shaxsiy ulanishni yoqing
+ Conduit stantsiyasi orqali ulang
+ Ulanish URL manzilini import qiling
+ Ulanish URL
+ Ulanish URL manzilini shu yerga qo‘ying
+ Ulanish URL formati noto‘g‘ri
+ Ulanish maʼlumotlar formati notoʻgʻri
+ Ulanish tokeni versiyasi qo‘llab-quvvatlanmaydi
+ Iltimos, avval ulanish URL manzilini import qiling
+ Ulanish identifikatori
+ Sizning noyob juftlik identifikatoringiz
+ Ulanish taxallus
+ Ushbu ulanish konfiguratsiyasi uchun qulay nom
+ Import
+ Shaxsiy juftlik
+ Ulanishda muammo bormi? Shaxsiy ulanishni o\'chiring
+ Shaxsiy ulanish konfiguratsiyasi saqlandi
+ Shaxsiy ulanish konfiguratsiyasi yangilandi
+ Shaxsiy juftlik konfiguratsiyasi allaqachon mavjud
+ Shaxsiy ulanish maʼlumotlarini yangilang
+ Joriy shaxsiy juftlik identifikatori:
+ Yangi shaxsiy juftlik identifikatori:
+ Yangilash
+ Bekor qilish
+ Shaxsiy ulanishni yoqing
+ Shaxsiy ulanish hozir yoqilsinmi? Bu faol tunnelni qayta ishga tushiradi. Aks holda, sozlamalar import qilinadi va ilova sozlamalarida qo\'lda yoqilmaguncha o\'chirilgan bo\'lib qoladi.
+ Shaxsiy ulanishni tiklash
+ Ushbu shaxsiy juftlik konfiguratsiyasini butunlay olib tashlang
+ Shaxsiy ulanishni tiklash
+ Tasdiqlash uchun belgilarni kiriting
+ Ushbu Shaxsiy Ulanish konfiguratsiyasini olib tashlash uchun Ulanish identifikatorining oxirgi %d belgilarini kiriting:
+ Konfiguratsiyani qayta o‘rnatib bo‘lmadi
+ Iltimos, tasdiqlovchi belgilarni toʻgʻri kiriting
+ Konfiguratsiyani tiklash
+ Qayta tiklash
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index d87cd478c..7b91b1d8e 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -165,4 +165,44 @@
Cài đặtCập nhật thất bại
+
+ Đã bật
+ Qua %1$s
+ Tàn tật
+ Kết nối thông qua trạm Conduit của một đối tác đáng tin cậy để tăng cường quyền riêng tư và quyền truy cập. Bạn sẽ cần một liên kết ghép nối để bắt đầu.
+ Bật ghép nối cá nhân
+ Kết nối qua trạm Conduit
+ Nhập URL ghép nối
+ URL ghép nối
+ Dán URL ghép nối vào đây
+ Định dạng URL ghép nối không hợp lệ
+ Định dạng dữ liệu ghép nối không hợp lệ
+ Phiên bản mã thông báo ghép nối không được hỗ trợ
+ Trước tiên hãy nhập URL ghép nối
+ ID ghép nối
+ Mã nhận dạng ghép nối duy nhất của bạn
+ Bí danh ghép nối
+ Một cái tên thân thiện cho cấu hình ghép nối này
+ Nhập khẩu
+ Ghép đôi cá nhân
+ Sự cố kết nối? Tắt ghép nối cá nhân
+ Đã lưu cấu hình Ghép nối cá nhân
+ Đã cập nhật cấu hình Ghép nối cá nhân
+ Cấu hình Ghép nối cá nhân đã tồn tại
+ Cập nhật dữ liệu Ghép đôi cá nhân
+ ID ghép nối cá nhân hiện tại:
+ ID ghép nối cá nhân mới:
+ Cập nhật
+ Hủy bỏ
+ Bật ghép nối cá nhân
+ Bật ghép nối cá nhân ngay bây giờ? Điều này sẽ khởi động lại đường hầm đang hoạt động. Nếu không, cài đặt sẽ được nhập và vẫn bị tắt cho đến khi được bật thủ công trong cài đặt ứng dụng.
+ Đặt lại ghép nối cá nhân
+ Xóa vĩnh viễn cấu hình Ghép nối cá nhân này
+ Đặt lại ghép nối cá nhân
+ Nhập ký hiệu để xác nhận
+ Để xóa cấu hình Ghép nối cá nhân này, hãy nhập ký hiệu %d cuối cùng của ID ghép nối:
+ Không thể thiết lập lại cấu hình
+ Vui lòng nhập chính xác các ký hiệu xác nhận
+ Đặt lại cấu hình
+ Cài lại
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index d4e74cbe4..5c4d9fba7 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -166,4 +166,44 @@
安裝更新失敗
+
+ 已啟用
+ 透過 %1$s
+ 已停用
+ 透過可信任對等方的 Conduit 節點連線,以提升隱私與存取能力。您需要配對連結才能開始。
+ 啟用個人配對
+ 透過 Conduit 節點連線
+ 匯入配對連結
+ 配對連結
+ 在此貼上配對連結
+ 配對連結格式無效
+ 配對資料格式無效
+ 不支援的配對令牌版本
+ 請先匯入配對連結
+ 配對 ID
+ 您的唯一配對識別符
+ 配對別名
+ 此配對配置的友善名稱
+ 匯入
+ 個人配對
+ 連線有問題嗎?關閉個人配對
+ 個人配對設定已儲存
+ 個人配對設定已更新
+ 個人配對設定已存在
+ 更新個人配對資料
+ 目前個人配對 ID:
+ 新的個人配對 ID:
+ 更新
+ 取消
+ 啟用個人配對
+ 現在啟用個人配對嗎?這會重新啟動目前的通道。否則,設定會被匯入,並保持停用,直到您在應用程式設定中手動啟用。
+ 重設個人配對
+ 永久移除此個人配對設定
+ 重設個人配對
+ 輸入字元以確認
+ 若要刪除此個人配對設定,請輸入配對 ID 的後 %d 個字元:
+ 無法重設設定
+ 請輸入正確的確認字元
+ 設定已重設
+ 重設
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 6665655d2..83ca7b94e 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -166,4 +166,44 @@
安装更新失败
+
+ 已启用
+ 通过 %1$s
+ 已禁用
+ 通过可信对等方的 Conduit 节点连接,以增强隐私与访问能力。您需要配对链接才能开始。
+ 启用个人配对
+ 通过 Conduit 节点连接
+ 导入配对链接
+ 配对链接
+ 在此粘贴配对链接
+ 配对链接格式无效
+ 配对数据格式无效
+ 不支持的配对令牌版本
+ 请先导入配对链接
+ 配对 ID
+ 您的唯一配对标识
+ 配对别名
+ 此配对配置的易记名称
+ 导入
+ 个人配对
+ 连接遇到问题?关闭个人配对
+ 个人配对配置已保存
+ 个人配对配置已更新
+ 个人配对配置已存在
+ 更新个人配对数据
+ 当前个人配对 ID:
+ 新的个人配对 ID:
+ 更新
+ 取消
+ 启用个人配对
+ 现在启用个人配对吗?这将重启当前隧道。否则,设置会被导入,并保持禁用状态,直到您在应用设置中手动启用。
+ 重置个人配对
+ 永久删除此个人配对配置
+ 重置个人配对
+ 输入字符以确认
+ 要删除此个人配对配置,请输入配对 ID 的后 %d 个字符:
+ 无法重置配置
+ 请输入正确的确认字符
+ 配置已重置
+ 重置
diff --git a/app/src/main/res/values/psiphon_android_library_untranslated_strings.xml b/app/src/main/res/values/psiphon_android_library_untranslated_strings.xml
index 928ba2f02..cd2a77ed8 100644
--- a/app/src/main/res/values/psiphon_android_library_untranslated_strings.xml
+++ b/app/src/main/res/values/psiphon_android_library_untranslated_strings.xml
@@ -50,6 +50,12 @@
vpnServiceDataCollectionDisclosureAccepteddeviceLocationPrecisionserviceRunningPreference
+ personalPairingPreferenceKey
+ personalPairingEnabledPreference
+ personalPairingImportPreference
+ personalPairingCompartmentIdPreference
+ personalPairingAliasPreference
+ personalPairingResetPreferenceEnglish,en
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ac18c718a..b24b999dd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -51,7 +51,7 @@
You need to have at least one app unselected in this mode!Requires Android 5.0 or greaterRequires Android 7.0 or greater
- Select only one browser for classic browser-only mode
+ Select only one browser for classic browser-only modePsiphon MalAware
@@ -91,7 +91,7 @@
%s uses the Android VPN service to provide you with uncensored access to Internet content. We collect, aggregate, and store the following information:
- Your city, country, ISP, and optionally, approximate location. The city, country, and ISP are derived from the IP address that you connect to our servers from, and the IP address itself is never logged. This data is collected for usage and performance statistics and service health purposes.>
+ Your city, country, ISP, and optionally, approximate location. The city, country, and ISP are derived from the IP address that you connect to our servers from, and the IP address itself is never logged. This data is collected for usage and performance statistics and service health purposes.Your email, when you send us a diagnostics or crash log feedback. Providing email in the feedback is optional.
@@ -192,4 +192,120 @@
Please allow %s to access your device\'s approximate location. Your location data estimate is used to optimize the Psiphon network, and is accurate to within about 3 square kilometers.
+
+ Enabled
+
+
+ Via %1$s
+
+
+ Disabled
+
+
+ Connect through a trusted peer\'s Conduit station for enhanced privacy and access. You\'ll need a pairing link to get started.
+
+
+ Enable Personal Pairing
+
+
+ Connect via Conduit station
+
+
+ Import pairing URL
+
+
+ Pairing URL
+
+
+ Paste pairing URL here
+
+
+ Invalid pairing URL format
+
+
+ Invalid pairing data format
+
+
+ Unsupported pairing token version
+
+
+ Please import a pairing URL first
+
+
+ Pairing ID
+
+
+ Your unique pairing identifier
+
+
+ Pairing alias
+
+
+ A friendly name for this pairing configuration
+
+
+ Import
+
+
+ Personal Pairing
+
+
+ Trouble connecting? Turn off Personal Pairing
+
+
+ Personal Pairing configuration saved
+
+
+ Personal Pairing configuration updated
+
+
+ Personal Pairing configuration already exists
+
+
+ Update Personal Pairing data
+
+
+ Current Personal Pairing ID:
+
+
+ New Personal Pairing ID:
+
+
+ Update
+
+
+ Cancel
+
+
+ Enable Personal Pairing
+
+
+ Enable personal pairing now? This will restart the active tunnel. Otherwise, settings will be imported and remain disabled until manually enabled in the application settings.
+
+
+ Reset Personal Pairing
+
+
+ Remove this Personal Pairing configuration permanently
+
+
+ Reset Personal Pairing
+
+
+ Enter symbols to confirm
+
+
+ To remove this Personal Pairing configuration, enter the last %d symbols of the Pairing ID:
+
+
+ Unable to reset configuration
+
+
+ Please enter correct confirmation symbols
+
+
+ Configuration reset
+
+
+ Reset
diff --git a/app/src/main/res/xml/personal_pairing_preferences.xml b/app/src/main/res/xml/personal_pairing_preferences.xml
new file mode 100644
index 000000000..ae889abda
--- /dev/null
+++ b/app/src/main/res/xml/personal_pairing_preferences.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/settings_preferences_screen.xml b/app/src/main/res/xml/settings_preferences_screen.xml
index d9c39c60b..72f5ec0e5 100644
--- a/app/src/main/res/xml/settings_preferences_screen.xml
+++ b/app/src/main/res/xml/settings_preferences_screen.xml
@@ -13,6 +13,10 @@
android:icon="@drawable/baseline_device_hub_white_24"
android:key="@string/proxyOptionsPreferenceKey"
android:title="@string/label_proxy_settings" />
+