implement support for HTTP proxy (#489)
This change allows the user to manually enter an unauthenticated proxy configuration to be used for all API connections. This is mainly intended for using Tusky with Tor (via Orbot or a local proxy).
This commit is contained in:
parent
73342d38cf
commit
7c83e0f87d
9 changed files with 149 additions and 8 deletions
|
@ -137,8 +137,10 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
OkHttpClient.Builder okBuilder =
|
OkHttpClient.Builder okBuilder =
|
||||||
OkHttpUtils.getCompatibleClientBuilder()
|
OkHttpUtils.getCompatibleClientBuilder(preferences)
|
||||||
.addInterceptor(new AuthInterceptor(this))
|
.addInterceptor(new AuthInterceptor(this))
|
||||||
.dispatcher(mastodonApiDispatcher);
|
.dispatcher(mastodonApiDispatcher);
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ public class LoginActivity extends AppCompatActivity {
|
||||||
private MastodonApi getApiFor(String domain) {
|
private MastodonApi getApiFor(String domain) {
|
||||||
Retrofit retrofit = new Retrofit.Builder()
|
Retrofit retrofit = new Retrofit.Builder()
|
||||||
.baseUrl("https://" + domain)
|
.baseUrl("https://" + domain)
|
||||||
.client(OkHttpUtils.getCompatibleClient())
|
.client(OkHttpUtils.getCompatibleClient(preferences))
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
|
@ -76,8 +76,10 @@ public final class NotificationPullJobCreator implements JobCreator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MastodonApi createMastodonApi(String domain, Context context) {
|
private static MastodonApi createMastodonApi(String domain, Context context) {
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(
|
||||||
|
context.getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
|
||||||
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder(preferences)
|
||||||
.addInterceptor(new AuthInterceptor(context))
|
.addInterceptor(new AuthInterceptor(context))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.arch.persistence.room.Room;
|
import android.arch.persistence.room.Room;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v7.app.AppCompatDelegate;
|
import android.support.v7.app.AppCompatDelegate;
|
||||||
|
|
||||||
import com.evernote.android.job.JobManager;
|
import com.evernote.android.job.JobManager;
|
||||||
|
@ -37,7 +39,8 @@ public class TuskyApplication extends Application {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
// Initialize Picasso configuration
|
// Initialize Picasso configuration
|
||||||
Picasso.Builder builder = new Picasso.Builder(this);
|
Picasso.Builder builder = new Picasso.Builder(this);
|
||||||
builder.downloader(new OkHttp3Downloader(OkHttpUtils.getCompatibleClient()));
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
builder.downloader(new OkHttp3Downloader(OkHttpUtils.getCompatibleClient(preferences)));
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
builder.listener((picasso, uri, exception) -> exception.printStackTrace());
|
builder.listener((picasso, uri, exception) -> exception.printStackTrace());
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
package com.keylesspalace.tusky.fragment;
|
package com.keylesspalace.tusky.fragment;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.support.annotation.XmlRes;
|
import android.support.annotation.XmlRes;
|
||||||
|
@ -27,7 +29,10 @@ import com.keylesspalace.tusky.PreferencesActivity;
|
||||||
import com.keylesspalace.tusky.R;
|
import com.keylesspalace.tusky.R;
|
||||||
import com.keylesspalace.tusky.util.NotificationManager;
|
import com.keylesspalace.tusky.util.NotificationManager;
|
||||||
|
|
||||||
public class PreferencesFragment extends PreferenceFragment {
|
public class PreferencesFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
SharedPreferences sharedPreferences;
|
||||||
|
static boolean httpProxyChanged = false;
|
||||||
|
static boolean pendingRestart = false;
|
||||||
|
|
||||||
public static PreferencesFragment newInstance(@XmlRes int preference) {
|
public static PreferencesFragment newInstance(@XmlRes int preference) {
|
||||||
PreferencesFragment fragment = new PreferencesFragment();
|
PreferencesFragment fragment = new PreferencesFragment();
|
||||||
|
@ -101,6 +106,91 @@ public class PreferencesFragment extends PreferenceFragment {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Preference httpProxyPreferences = findPreference("httpProxyPreferences");
|
||||||
|
if(httpProxyPreferences != null) {
|
||||||
|
httpProxyPreferences.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
PreferencesActivity activity = (PreferencesActivity) getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
pendingRestart = false;
|
||||||
|
activity.showFragment(R.xml.http_proxy_preferences, R.string.pref_title_http_proxy_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
sharedPreferences = getPreferenceManager().getSharedPreferences();
|
||||||
|
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
|
||||||
|
updateSummary("httpProxyServer");
|
||||||
|
updateSummary("httpProxyPort");
|
||||||
|
updateHttpProxySummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||||
|
super.onPause();
|
||||||
|
if (pendingRestart) {
|
||||||
|
pendingRestart = false;
|
||||||
|
httpProxyChanged = false;
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||||
|
String key) {
|
||||||
|
switch (key) {
|
||||||
|
case "httpProxyServer":
|
||||||
|
case "httpProxyPort":
|
||||||
|
updateSummary(key);
|
||||||
|
case "httpProxyEnabled":
|
||||||
|
httpProxyChanged = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSummary(String key) {
|
||||||
|
switch (key) {
|
||||||
|
case "httpProxyServer":
|
||||||
|
case "httpProxyPort":
|
||||||
|
EditTextPreference editTextPreference = (EditTextPreference) findPreference(key);
|
||||||
|
if (editTextPreference != null) {
|
||||||
|
editTextPreference.setSummary(editTextPreference.getText());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHttpProxySummary() {
|
||||||
|
Preference httpProxyPref = findPreference("httpProxyPreferences");
|
||||||
|
if (httpProxyPref != null) {
|
||||||
|
if (httpProxyChanged) {
|
||||||
|
pendingRestart = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean httpProxyEnabled = sharedPreferences.getBoolean("httpProxyEnabled", false);
|
||||||
|
|
||||||
|
String httpServer = sharedPreferences.getString("httpProxyServer", "");
|
||||||
|
int httpPort = Integer.parseInt(sharedPreferences.getString("httpProxyPort", "-1"));
|
||||||
|
|
||||||
|
if (httpProxyEnabled && !httpServer.isEmpty() && (httpPort > 0 && httpPort < 65535)) {
|
||||||
|
httpProxyPref.setSummary(httpServer + ":" + httpPort);
|
||||||
|
} else {
|
||||||
|
httpProxyPref.setSummary("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.util;
|
package com.keylesspalace.tusky.util;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
@ -23,6 +25,8 @@ import com.keylesspalace.tusky.BuildConfig;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
@ -62,7 +66,11 @@ public class OkHttpUtils {
|
||||||
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
|
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static OkHttpClient.Builder getCompatibleClientBuilder() {
|
public static OkHttpClient.Builder getCompatibleClientBuilder(SharedPreferences preferences) {
|
||||||
|
boolean httpProxyEnabled = preferences.getBoolean("httpProxyEnabled", false);
|
||||||
|
String httpServer = preferences.getString("httpProxyServer", "");
|
||||||
|
int httpPort = Integer.parseInt(preferences.getString("httpProxyPort", "-1"));
|
||||||
|
|
||||||
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
.allEnabledCipherSuites()
|
.allEnabledCipherSuites()
|
||||||
.supportsTlsExtensions(true)
|
.supportsTlsExtensions(true)
|
||||||
|
@ -80,12 +88,17 @@ public class OkHttpUtils {
|
||||||
.writeTimeout(30, TimeUnit.SECONDS)
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
.connectionSpecs(specList);
|
.connectionSpecs(specList);
|
||||||
|
|
||||||
|
if (httpProxyEnabled && !httpServer.isEmpty() && (httpPort > 0) && (httpPort < 65535)) {
|
||||||
|
InetSocketAddress address = InetSocketAddress.createUnresolved(httpServer, httpPort);
|
||||||
|
builder.proxy(new Proxy(Proxy.Type.HTTP, address));
|
||||||
|
}
|
||||||
|
|
||||||
return enableHigherTlsOnPreLollipop(builder);
|
return enableHigherTlsOnPreLollipop(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static OkHttpClient getCompatibleClient() {
|
public static OkHttpClient getCompatibleClient(SharedPreferences preferences) {
|
||||||
return getCompatibleClientBuilder().build();
|
return getCompatibleClientBuilder(preferences).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -177,6 +177,11 @@
|
||||||
<string name="pref_title_show_boosts">Show boosts</string>
|
<string name="pref_title_show_boosts">Show boosts</string>
|
||||||
<string name="pref_title_show_replies">Show replies</string>
|
<string name="pref_title_show_replies">Show replies</string>
|
||||||
<string name="pref_title_show_media_preview">Show media previews</string>
|
<string name="pref_title_show_media_preview">Show media previews</string>
|
||||||
|
<string name="pref_title_proxy_settings">Proxy</string>
|
||||||
|
<string name="pref_title_http_proxy_settings">HTTP proxy</string>
|
||||||
|
<string name="pref_title_http_proxy_enable">Enable HTTP proxy</string>
|
||||||
|
<string name="pref_title_http_proxy_server">HTTP proxy server</string>
|
||||||
|
<string name="pref_title_http_proxy_port">HTTP proxy port</string>
|
||||||
|
|
||||||
<string-array name="pull_notification_check_interval_names">
|
<string-array name="pull_notification_check_interval_names">
|
||||||
<item>15 minutes</item>
|
<item>15 minutes</item>
|
||||||
|
|
18
app/src/main/res/xml/http_proxy_preferences.xml
Normal file
18
app/src/main/res/xml/http_proxy_preferences.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen android:title="@string/pref_title_http_proxy_settings"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:key="httpProxyEnabled"
|
||||||
|
android:title="@string/pref_title_http_proxy_enable" />
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="httpProxyServer"
|
||||||
|
android:title="@string/pref_title_http_proxy_server"
|
||||||
|
android:summary="%s" />
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="httpProxyPort"
|
||||||
|
android:title="@string/pref_title_http_proxy_port"
|
||||||
|
android:summary="%s" />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
|
@ -74,4 +74,12 @@
|
||||||
android:title="@string/pref_title_edit_notification_settings" />
|
android:title="@string/pref_title_edit_notification_settings" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="@string/pref_title_proxy_settings">
|
||||||
|
<Preference
|
||||||
|
android:key="httpProxyPreferences"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/pref_title_http_proxy_settings" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
Loading…
Reference in a new issue