use new proxy_url list API (#3292)

This commit is contained in:
Asiel Díaz Benítez
2024-09-26 14:30:54 +02:00
committed by GitHub
parent 8aa615157a
commit bc6d676fcd
15 changed files with 544 additions and 299 deletions
@@ -56,6 +56,7 @@ public class DcContext {
public final static int DC_QR_BACKUP = 251;
public final static int DC_QR_BACKUP2 = 252;
public final static int DC_QR_WEBRTC = 260;
public final static int DC_QR_PROXY = 271;
public final static int DC_QR_ADDR = 320;
public final static int DC_QR_TEXT = 330;
public final static int DC_QR_URL = 332;
@@ -20,7 +20,7 @@ import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA;
import static org.thoughtcrime.securesms.ConversationActivity.STARTING_POSITION_EXTRA;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_ADDRESS;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SERVER_FLAGS;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_ENABLED;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_URL;
import static org.thoughtcrime.securesms.util.RelayUtil.acquireRelayMessageContent;
import static org.thoughtcrime.securesms.util.RelayUtil.getDirectSharingChatId;
import static org.thoughtcrime.securesms.util.RelayUtil.getSharedTitle;
@@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.connect.DirectShareUtil;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
import org.thoughtcrime.securesms.qr.QrActivity;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -361,7 +362,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
if (!isRelayingMessageContent(this)) {
inflater.inflate(R.menu.text_secure_normal, menu);
menu.findItem(R.id.menu_global_map).setVisible(Prefs.isLocationStreamingEnabled(this));
menu.findItem(R.id.menu_proxy_settings).setVisible(DcHelper.getInt(this, CONFIG_SOCKS5_ENABLED) == 1);
menu.findItem(R.id.menu_proxy_settings).setVisible(!TextUtils.isEmpty(DcHelper.get(this, CONFIG_PROXY_URL)));
}
super.onPrepareOptionsMenu(menu);
@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints;
import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
import org.thoughtcrime.securesms.qr.RegistrationQrActivity;
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
import org.thoughtcrime.securesms.util.Prefs;
@@ -1,160 +0,0 @@
package org.thoughtcrime.securesms;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_ENABLED;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_HOST;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_PASSWORD;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_PORT;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_USER;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Patterns;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.Group;
import com.b44t.messenger.DcContext;
import com.google.android.material.textfield.TextInputEditText;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class ProxySettingsActivity extends BaseActionBarActivity {
private enum VerificationType {
SERVER,
PORT,
}
private final DynamicTheme dynamicTheme = new DynamicTheme();
private SwitchCompat proxySwitch;
private Group proxyGroup;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
dynamicTheme.onCreate(this);
setContentView(R.layout.proxy_settings_activity);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.proxy_settings);
actionBar.setDisplayHomeAsUpEnabled(true);
}
proxyGroup = findViewById(R.id.proxy_group);
proxySwitch = findViewById(R.id.proxy_switch);
proxySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
proxyGroup.setVisibility(isChecked? View.VISIBLE : View.GONE);
});
proxySwitch.setChecked(DcHelper.getInt(this, CONFIG_SOCKS5_ENABLED) == 1);
TextInputEditText proxyHostInput = findViewById(R.id.proxy_host_text);
proxyHostInput.setOnFocusChangeListener((view, focused) -> focusListener(view, focused, VerificationType.SERVER));
proxyHostInput.setText(DcHelper.get(this, CONFIG_SOCKS5_HOST));
TextInputEditText proxyPortInput = findViewById(R.id.proxy_port_text);
proxyPortInput.setOnFocusChangeListener((view, focused) -> focusListener(view, focused, VerificationType.PORT));
proxyPortInput.setText(DcHelper.get(this, CONFIG_SOCKS5_PORT));
TextInputEditText proxyUserInput = findViewById(R.id.proxy_user_text);
proxyUserInput.setText(DcHelper.get(this, CONFIG_SOCKS5_USER));
TextInputEditText proxyPasswordInput = findViewById(R.id.proxy_password_text);
proxyPasswordInput.setText(DcHelper.get(this, CONFIG_SOCKS5_PASSWORD));
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onDestroy() {
super.onDestroy();
saveConfig();
}
@Override
public void onPause() {
super.onPause();
saveConfig();
}
private void focusListener(View view, boolean focused, VerificationType type) {
if (!focused) {
TextInputEditText inputEditText = (TextInputEditText) view;
switch (type) {
case SERVER:
verifyServer(inputEditText);
break;
case PORT:
verifyPort(inputEditText);
break;
}
}
}
private void verifyServer(TextInputEditText view) {
String server = view.getText().toString();
if (!TextUtils.isEmpty(server) && !Patterns.DOMAIN_NAME.matcher(server).matches()
&& !Patterns.IP_ADDRESS.matcher(server).matches()
&& !Patterns.WEB_URL.matcher(server).matches()
&& !"localhost".equals(server)) {
view.setError(getString(R.string.login_error_server));
}
}
private void verifyPort(TextInputEditText view) {
String portString = view.getText().toString();
if (!portString.isEmpty()) {
String error = getString(R.string.login_error_port);
try {
int port = Integer.valueOf(portString);
if (port < 1 || port > 65535) {
view.setError(error);
}
} catch (NumberFormatException exception) {
view.setError(error);
}
}
}
private void saveConfig() {
DcContext dcContext = DcHelper.getContext(this);
dcContext.setConfigInt(CONFIG_SOCKS5_ENABLED, proxySwitch.isChecked()? 1 : 0);
setConfig(R.id.proxy_host_text, CONFIG_SOCKS5_HOST, true);
setConfig(R.id.proxy_port_text, CONFIG_SOCKS5_PORT, true);
setConfig(R.id.proxy_user_text, CONFIG_SOCKS5_USER, true);
setConfig(R.id.proxy_password_text, CONFIG_SOCKS5_PASSWORD, false);
dcContext.stopIo();
dcContext.startIo();
}
private void setConfig(@IdRes int viewId, String configTarget, boolean doTrim) {
TextInputEditText view = findViewById(viewId);
String value = view.getText().toString();
if(doTrim) {
value = value.trim();
}
DcHelper.getContext(this).setConfig(configTarget, value.isEmpty()? null : value);
}
}
@@ -12,11 +12,7 @@ import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SEND_SECURITY;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SEND_SERVER;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SEND_USER;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SERVER_FLAGS;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_ENABLED;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_HOST;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_PASSWORD;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_PORT;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SOCKS5_USER;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_ENABLED;
import static org.thoughtcrime.securesms.connect.DcHelper.getContext;
import static org.thoughtcrime.securesms.service.IPCAddAccountsService.ACCOUNT_DATA;
@@ -59,6 +55,7 @@ import com.google.android.material.textfield.TextInputEditText;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.Util;
@@ -162,7 +159,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements DcEve
String strVal;
int intVal;
intVal = DcHelper.getInt(this, CONFIG_SOCKS5_ENABLED);
intVal = DcHelper.getInt(this, CONFIG_PROXY_ENABLED);
proxySwitch.setChecked(intVal == 1);
expandAdvanced = expandAdvanced || intVal == 1;
@@ -260,7 +257,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements DcEve
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
proxySwitch.setChecked(DcHelper.getInt(this, CONFIG_SOCKS5_ENABLED) == 1);
proxySwitch.setChecked(DcHelper.getInt(this, CONFIG_PROXY_ENABLED) == 1);
}
private void showLog() {
@@ -72,11 +72,8 @@ public class DcHelper {
public static final String CONFIG_SHOW_EMAILS = "show_emails";
public static final String CONFIG_MEDIA_QUALITY = "media_quality";
public static final String CONFIG_WEBRTC_INSTANCE = "webrtc_instance";
public static final String CONFIG_SOCKS5_ENABLED = "socks5_enabled";
public static final String CONFIG_SOCKS5_HOST = "socks5_host";
public static final String CONFIG_SOCKS5_PORT = "socks5_port";
public static final String CONFIG_SOCKS5_USER = "socks5_user";
public static final String CONFIG_SOCKS5_PASSWORD = "socks5_password";
public static final String CONFIG_PROXY_ENABLED = "proxy_enabled";
public static final String CONFIG_PROXY_URL = "proxy_url";
public static final String CONFIG_VERIFIED_ONE_ON_ONE_CHATS = "verified_one_on_one_chats";
public static final String CONFIG_WEBXDC_REALTIME_ENABLED = "webxdc_realtime_enabled";
@@ -37,7 +37,7 @@ import com.b44t.messenger.rpc.RpcException;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.LogViewActivity;
import org.thoughtcrime.securesms.ProxySettingsActivity;
import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.RegistrationActivity;
import org.thoughtcrime.securesms.connect.DcEventCenter;
@@ -0,0 +1,172 @@
package org.thoughtcrime.securesms.proxy;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_ENABLED;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcLot;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.DcHelper;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class ProxyListAdapter extends BaseAdapter {
private enum ProxyState {
CONNECTED,
CONNECTING,
NOT_CONNECTED,
}
@NonNull private final Context context;
@NonNull private final DcContext dcContext;
@NonNull private final List<String> proxies = new LinkedList<>();
@Nullable private ItemClickListener itemClickListener;
@Nullable private ProxyState proxyState;
@Nullable private String selectedProxy;
public ProxyListAdapter(@NonNull Context context)
{
this.context = context;
this.dcContext = DcHelper.getContext(context);
}
@Override
public int getCount() {
return proxies.size();
}
@Override
public Object getItem(int position) {
return proxies.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View v, final ViewGroup parent) {
if (v == null) {
v = LayoutInflater.from(context).inflate(R.layout.proxy_list_item, parent, false);
}
TextView host = v.findViewById(R.id.host);
TextView protocol = v.findViewById(R.id.protocol);
ImageView checkmark = v.findViewById(R.id.checkmark);
TextView status = v.findViewById(R.id.status);
final String proxyUrl = (String)getItem(position);
final DcLot qrParsed = dcContext.checkQr(proxyUrl);
if (qrParsed.getState() == DcContext.DC_QR_PROXY) {
host.setText(qrParsed.getText1());
protocol.setText(proxyUrl.split(":", 2)[0]);
} else {
host.setText(proxyUrl);
protocol.setText(R.string.unknown);
}
if (proxyUrl.equals(selectedProxy)) {
checkmark.setVisibility(View.VISIBLE);
status.setVisibility(View.VISIBLE);
status.setText(getConnectivityString());
} else {
checkmark.setVisibility(View.GONE);
status.setVisibility(View.GONE);
}
v.setOnClickListener(view -> {
if (itemClickListener != null) {
itemClickListener.onItemClick(proxyUrl);
}
});
v.findViewById(R.id.share).setOnClickListener(view -> {
if (itemClickListener != null) {
itemClickListener.onItemShare(proxyUrl);
}
});
v.findViewById(R.id.delete).setOnClickListener(view -> {
if (itemClickListener != null) {
itemClickListener.onItemDelete(proxyUrl);
}
});
return v;
}
public void changeData(String newProxies) {
proxies.clear();
if (!TextUtils.isEmpty(newProxies)) {
Collections.addAll(proxies, newProxies.split("\n"));
}
selectedProxy = proxies.isEmpty()? null : proxies.get(0);
proxyState = null; // to force notifyDataSetChanged() in refreshConnectivity()
refreshConnectivity();
}
public void setSelectedProxy(String proxyUrl) {
selectedProxy = proxyUrl;
notifyDataSetChanged();
}
private String getConnectivityString() {
if (proxyState == ProxyState.CONNECTED) {
return context.getString(R.string.connectivity_connected);
}
if (proxyState == ProxyState.CONNECTING) {
return context.getString(R.string.connectivity_connecting);
}
return context.getString(R.string.connectivity_not_connected);
}
public void refreshConnectivity() {
if (DcHelper.getInt(context, CONFIG_PROXY_ENABLED) != 1) {
if (proxyState != ProxyState.NOT_CONNECTED) {
proxyState = ProxyState.NOT_CONNECTED;
notifyDataSetChanged();
}
return;
}
int connectivity = dcContext.getConnectivity();
if (connectivity >= DcContext.DC_CONNECTIVITY_WORKING) {
if (proxyState != ProxyState.CONNECTED) {
proxyState = ProxyState.CONNECTED;
notifyDataSetChanged();
}
} else if (connectivity >= DcContext.DC_CONNECTIVITY_CONNECTING) {
if (proxyState != ProxyState.CONNECTING) {
proxyState = ProxyState.CONNECTING;
notifyDataSetChanged();
}
} else if (proxyState != ProxyState.NOT_CONNECTED) {
proxyState = ProxyState.NOT_CONNECTED;
notifyDataSetChanged();
}
}
public void setItemClickListener(@Nullable ItemClickListener listener) {
itemClickListener = listener;
}
public interface ItemClickListener {
void onItemClick(String proxyUrl);
void onItemShare(String proxyUrl);
void onItemDelete(String proxyUrl);
}
}
@@ -0,0 +1,189 @@
package org.thoughtcrime.securesms.proxy;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_ENABLED;
import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_URL;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcLot;
import org.thoughtcrime.securesms.BaseActionBarActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Util;
import java.util.LinkedList;
public class ProxySettingsActivity extends BaseActionBarActivity
implements ProxyListAdapter.ItemClickListener, DcEventCenter.DcEventDelegate {
private final DynamicTheme dynamicTheme = new DynamicTheme();
private SwitchCompat proxySwitch;
private ProxyListAdapter adapter;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
dynamicTheme.onCreate(this);
setContentView(R.layout.proxy_settings_activity);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.proxy_settings);
actionBar.setDisplayHomeAsUpEnabled(true);
}
adapter = new ProxyListAdapter(this);
adapter.setItemClickListener(this);
proxySwitch = findViewById(R.id.proxy_switch);
proxySwitch.setChecked(DcHelper.getInt(this, CONFIG_PROXY_ENABLED) == 1);
proxySwitch.setOnClickListener(l -> {
if (proxySwitch.isChecked() && adapter.getCount() == 0) {
showAddProxyDialog();
} else {
DcHelper.set(this, CONFIG_PROXY_ENABLED, proxySwitch.isChecked()? "1" : "0");
restartIO();
}
});
ListView proxyList = findViewById(R.id.proxy_list);
proxyList.setAdapter(adapter);
proxyList.addHeaderView(View.inflate(this, R.layout.proxy_list_header, null), null, false);
View footer = View.inflate(this, R.layout.proxy_list_footer, null);
footer.setOnClickListener(l -> showAddProxyDialog());
proxyList.addFooterView(footer);
adapter.changeData(DcHelper.get(this, CONFIG_PROXY_URL));
DcHelper.getEventCenter(this).addObserver(DcContext.DC_EVENT_CONNECTIVITY_CHANGED, this);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public void onDestroy() {
super.onDestroy();
DcHelper.getEventCenter(this).removeObservers(this);
}
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onItemClick(String proxyUrl) {
if (DcHelper.getContext(this).setConfigFromQr(proxyUrl)) {
restartIO();
adapter.setSelectedProxy(proxyUrl);
proxySwitch.setChecked(DcHelper.getInt(this, CONFIG_PROXY_ENABLED) == 1);
} else {
Toast.makeText(this, R.string.proxy_invalid, Toast.LENGTH_LONG).show();
}
}
@Override
public void onItemShare(String proxyUrl) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, proxyUrl);
startActivity(Intent.createChooser(intent, getString(R.string.chat_share_with_title)));
}
@Override
public void onItemDelete(String proxyUrl) {
String host = DcHelper.getContext(this).checkQr(proxyUrl).getText1();
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(R.string.proxy_delete)
.setMessage(getString(R.string.proxy_delete_explain, host))
.setPositiveButton(R.string.delete, (dlg, btn) -> deleteProxy(proxyUrl))
.setNegativeButton(android.R.string.cancel, null)
.show();
Util.redPositiveButton(dialog);
}
private void deleteProxy(String proxyUrl) {
final LinkedList<String> proxies = new LinkedList<>();
for (String proxy: DcHelper.get(this, CONFIG_PROXY_URL).split("\n")) {
if (!proxy.equals(proxyUrl)) {
proxies.add(proxy);
}
}
if (proxies.isEmpty()) {
DcHelper.set(this, CONFIG_PROXY_ENABLED, "0");
proxySwitch.setChecked(false);
}
String proxyUrls = String.join("\n", proxies);
DcHelper.set(this, CONFIG_PROXY_URL, proxyUrls);
restartIO();
adapter.changeData(proxyUrls);
}
private void showAddProxyDialog() {
View view = View.inflate(this, R.layout.single_line_input, null);
EditText inputField = view.findViewById(R.id.input_field);
inputField.setHint(R.string.proxy_add_url_hint);
new AlertDialog.Builder(this)
.setTitle(R.string.proxy_add)
.setMessage(R.string.proxy_add_explain)
.setView(view)
.setPositiveButton(R.string.proxy_use_proxy, (dialog, whichButton) -> {
String newProxy = inputField.getText().toString().trim();
DcContext dcContext = DcHelper.getContext(this);
final DcLot qrParsed = dcContext.checkQr(newProxy);
if (qrParsed.getState() == DcContext.DC_QR_PROXY) {
dcContext.setConfigFromQr(newProxy);
restartIO();
adapter.changeData(DcHelper.get(this, CONFIG_PROXY_URL));
} else {
Toast.makeText(this, R.string.proxy_invalid, Toast.LENGTH_LONG).show();
}
proxySwitch.setChecked(DcHelper.getInt(this, CONFIG_PROXY_ENABLED) == 1);
})
.setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> {
if (proxySwitch.isChecked() && adapter.getCount() == 0) {
// user enabled switch without having proxies yet, revert
proxySwitch.setChecked(false);
}
})
.setCancelable(false)
.show();
}
private void restartIO() {
DcContext dcContext = DcHelper.getContext(this);
dcContext.stopIo();
dcContext.startIo();
}
@Override
public void handleEvent(@NonNull DcEvent event) {
if (event.getId() == DcContext.DC_EVENT_CONNECTIVITY_CHANGED) {
adapter.refreshConnectivity();
}
}
}