global: supply backend asynchronously
We can't block for IO, so move everything to async workers or to callbacks. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
0f128f99a1
commit
e8891d775b
@ -24,6 +24,8 @@ import com.wireguard.android.util.ToolsInstaller;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
public class Application extends android.app.Application {
|
public class Application extends android.app.Application {
|
||||||
@ -34,6 +36,10 @@ public class Application extends android.app.Application {
|
|||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
private ToolsInstaller toolsInstaller;
|
private ToolsInstaller toolsInstaller;
|
||||||
private TunnelManager tunnelManager;
|
private TunnelManager tunnelManager;
|
||||||
|
private Handler handler;
|
||||||
|
private List<BackendCallback> haveBackendCallbacks = new ArrayList<>();
|
||||||
|
private Object haveBackendCallbacksLock = new Object();
|
||||||
|
|
||||||
public Application() {
|
public Application() {
|
||||||
weakSelf = new WeakReference<>(this);
|
weakSelf = new WeakReference<>(this);
|
||||||
}
|
}
|
||||||
@ -47,11 +53,40 @@ public class Application extends android.app.Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Backend getBackend() {
|
public static Backend getBackend() {
|
||||||
return get().backend;
|
final Application app = get();
|
||||||
|
synchronized(app) {
|
||||||
|
if (app.backend == null) {
|
||||||
|
if (new File("/sys/module/wireguard").exists()) {
|
||||||
|
try {
|
||||||
|
app.rootShell.start();
|
||||||
|
app.backend = new WgQuickBackend(app.getApplicationContext());
|
||||||
|
} catch (final Exception ignored) { }
|
||||||
|
}
|
||||||
|
if (app.backend == null)
|
||||||
|
app.backend = new GoBackend(app.getApplicationContext());
|
||||||
|
synchronized (app.haveBackendCallbacksLock) {
|
||||||
|
for (final BackendCallback callback : app.haveBackendCallbacks)
|
||||||
|
app.handler.post(() -> callback.callback(app.backend));
|
||||||
|
app.haveBackendCallbacks = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return app.backend;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Class getBackendType() {
|
@FunctionalInterface
|
||||||
return get().backend.getClass();
|
public interface BackendCallback {
|
||||||
|
void callback(final Backend backend);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onHaveBackend(final BackendCallback callback) {
|
||||||
|
final Application app = get();
|
||||||
|
synchronized (app.haveBackendCallbacksLock) {
|
||||||
|
if (app.haveBackendCallbacks == null)
|
||||||
|
callback.callback(app.backend);
|
||||||
|
else
|
||||||
|
app.haveBackendCallbacks.add(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RootShell getRootShell() {
|
public static RootShell getRootShell() {
|
||||||
@ -74,8 +109,8 @@ public class Application extends android.app.Application {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
|
handler = new Handler(Looper.getMainLooper());
|
||||||
final Executor executor = AsyncTask.SERIAL_EXECUTOR;
|
final Executor executor = AsyncTask.SERIAL_EXECUTOR;
|
||||||
final Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
final ConfigStore configStore = new FileConfigStore(getApplicationContext());
|
final ConfigStore configStore = new FileConfigStore(getApplicationContext());
|
||||||
|
|
||||||
asyncWorker = new AsyncWorker(executor, handler);
|
asyncWorker = new AsyncWorker(executor, handler);
|
||||||
@ -87,16 +122,8 @@ public class Application extends android.app.Application {
|
|||||||
sharedPreferences.getBoolean("dark_theme", false) ?
|
sharedPreferences.getBoolean("dark_theme", false) ?
|
||||||
AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
|
AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
|
||||||
|
|
||||||
if (new File("/sys/module/wireguard").exists()) {
|
tunnelManager = new TunnelManager(configStore);
|
||||||
try {
|
asyncWorker.runAsync(Application::getBackend);
|
||||||
rootShell.start();
|
|
||||||
backend = new WgQuickBackend(getApplicationContext());
|
|
||||||
} catch (final Exception ignored) { }
|
|
||||||
}
|
|
||||||
if (backend == null)
|
|
||||||
backend = new GoBackend(getApplicationContext());
|
|
||||||
|
|
||||||
tunnelManager = new TunnelManager(backend, configStore);
|
|
||||||
tunnelManager.onCreate();
|
tunnelManager.onCreate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,18 +20,20 @@ public class BootShutdownReceiver extends BroadcastReceiver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(final Context context, final Intent intent) {
|
public void onReceive(final Context context, final Intent intent) {
|
||||||
if (Application.getBackendType() != WgQuickBackend.class)
|
Application.onHaveBackend(backend -> {
|
||||||
return;
|
if (backend.getClass() != WgQuickBackend.class)
|
||||||
final String action = intent.getAction();
|
return;
|
||||||
if (action == null)
|
final String action = intent.getAction();
|
||||||
return;
|
if (action == null)
|
||||||
final TunnelManager tunnelManager = Application.getTunnelManager();
|
return;
|
||||||
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
|
final TunnelManager tunnelManager = Application.getTunnelManager();
|
||||||
Log.i(TAG, "Broadcast receiver restoring state (boot)");
|
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
|
||||||
tunnelManager.restoreState(false).whenComplete(ExceptionLoggers.D);
|
Log.i(TAG, "Broadcast receiver restoring state (boot)");
|
||||||
} else if (Intent.ACTION_SHUTDOWN.equals(action)) {
|
tunnelManager.restoreState(false).whenComplete(ExceptionLoggers.D);
|
||||||
Log.i(TAG, "Broadcast receiver saving state (shutdown)");
|
} else if (Intent.ACTION_SHUTDOWN.equals(action)) {
|
||||||
tunnelManager.saveState();
|
Log.i(TAG, "Broadcast receiver saving state (shutdown)");
|
||||||
}
|
tunnelManager.saveState();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,11 +54,13 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity {
|
|||||||
// The selected tunnel must be set before the superclass method recreates fragments.
|
// The selected tunnel must be set before the superclass method recreates fragments.
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (Application.getBackendType() == GoBackend.class) {
|
Application.onHaveBackend(backend -> {
|
||||||
final Intent intent = GoBackend.VpnService.prepare(this);
|
if (backend.getClass() == GoBackend.class) {
|
||||||
if (intent != null)
|
final Intent intent = GoBackend.VpnService.prepare(this);
|
||||||
startActivityForResult(intent, 0);
|
if (intent != null)
|
||||||
}
|
startActivityForResult(intent, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,6 +16,7 @@ import android.support.v7.app.AppCompatActivity;
|
|||||||
import android.support.v7.app.AppCompatDelegate;
|
import android.support.v7.app.AppCompatDelegate;
|
||||||
import android.support.v7.preference.Preference;
|
import android.support.v7.preference.Preference;
|
||||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||||
|
import android.support.v7.preference.PreferenceScreen;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import com.wireguard.android.Application;
|
import com.wireguard.android.Application;
|
||||||
@ -95,12 +96,21 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String key) {
|
public void onCreatePreferences(final Bundle savedInstanceState, final String key) {
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
if (Application.getBackendType() != WgQuickBackend.class) {
|
final Preference wgQuickOnlyPrefs[] = {
|
||||||
Preference pref = getPreferenceManager().findPreference("tools_installer");
|
getPreferenceManager().findPreference("tools_installer"),
|
||||||
getPreferenceScreen().removePreference(pref);
|
getPreferenceManager().findPreference("restore_on_boot")
|
||||||
pref = getPreferenceManager().findPreference("restore_on_boot");
|
};
|
||||||
getPreferenceScreen().removePreference(pref);
|
for (final Preference pref : wgQuickOnlyPrefs)
|
||||||
}
|
pref.setVisible(false);
|
||||||
|
final PreferenceScreen screen = getPreferenceScreen();
|
||||||
|
Application.onHaveBackend(backend -> {
|
||||||
|
for (final Preference pref : wgQuickOnlyPrefs) {
|
||||||
|
if (backend.getClass() == WgQuickBackend.class)
|
||||||
|
pref.setVisible(true);
|
||||||
|
else
|
||||||
|
screen.removePreference(pref);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import android.support.annotation.NonNull;
|
|||||||
|
|
||||||
import com.wireguard.android.Application;
|
import com.wireguard.android.Application;
|
||||||
import com.wireguard.android.BR;
|
import com.wireguard.android.BR;
|
||||||
import com.wireguard.android.backend.Backend;
|
|
||||||
import com.wireguard.android.configStore.ConfigStore;
|
import com.wireguard.android.configStore.ConfigStore;
|
||||||
import com.wireguard.android.model.Tunnel.State;
|
import com.wireguard.android.model.Tunnel.State;
|
||||||
import com.wireguard.android.model.Tunnel.Statistics;
|
import com.wireguard.android.model.Tunnel.Statistics;
|
||||||
@ -47,7 +46,6 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
|
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
|
||||||
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
||||||
|
|
||||||
private final Backend backend;
|
|
||||||
private final ConfigStore configStore;
|
private final ConfigStore configStore;
|
||||||
private final ObservableSortedKeyedList<String, Tunnel> tunnels =
|
private final ObservableSortedKeyedList<String, Tunnel> tunnels =
|
||||||
new ObservableSortedKeyedArrayList<>(COMPARATOR);
|
new ObservableSortedKeyedArrayList<>(COMPARATOR);
|
||||||
@ -55,8 +53,7 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
private boolean haveLoaded;
|
private boolean haveLoaded;
|
||||||
private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>();
|
private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>();
|
||||||
|
|
||||||
public TunnelManager(final Backend backend, final ConfigStore configStore) {
|
public TunnelManager(final ConfigStore configStore) {
|
||||||
this.backend = backend;
|
|
||||||
this.configStore = configStore;
|
this.configStore = configStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,12 +83,12 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
tunnels.remove(tunnel);
|
tunnels.remove(tunnel);
|
||||||
return Application.getAsyncWorker().runAsync(() -> {
|
return Application.getAsyncWorker().runAsync(() -> {
|
||||||
if (originalState == State.UP)
|
if (originalState == State.UP)
|
||||||
backend.setState(tunnel, State.DOWN);
|
Application.getBackend().setState(tunnel, State.DOWN);
|
||||||
try {
|
try {
|
||||||
configStore.delete(tunnel.getName());
|
configStore.delete(tunnel.getName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
if (originalState == State.UP)
|
if (originalState == State.UP)
|
||||||
backend.setState(tunnel, State.UP);
|
Application.getBackend().setState(tunnel, State.UP);
|
||||||
// Re-throw the exception to fail the completion.
|
// Re-throw the exception to fail the completion.
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -116,12 +113,12 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CompletionStage<State> getTunnelState(final Tunnel tunnel) {
|
CompletionStage<State> getTunnelState(final Tunnel tunnel) {
|
||||||
return Application.getAsyncWorker().supplyAsync(() -> backend.getState(tunnel))
|
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel))
|
||||||
.thenApply(tunnel::onStateChanged);
|
.thenApply(tunnel::onStateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
|
CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
|
||||||
return Application.getAsyncWorker().supplyAsync(() -> backend.getStatistics(tunnel))
|
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel))
|
||||||
.thenApply(tunnel::onStatisticsChanged);
|
.thenApply(tunnel::onStatisticsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +128,7 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
|
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
Application.getAsyncWorker().supplyAsync(configStore::enumerate)
|
Application.getAsyncWorker().supplyAsync(configStore::enumerate)
|
||||||
.thenAcceptBoth(Application.getAsyncWorker().supplyAsync(backend::enumerate), this::onTunnelsLoaded)
|
.thenAcceptBoth(Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().enumerate()), this::onTunnelsLoaded)
|
||||||
.whenComplete(ExceptionLoggers.E);
|
.whenComplete(ExceptionLoggers.E);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +156,7 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void refreshTunnelStates() {
|
public void refreshTunnelStates() {
|
||||||
Application.getAsyncWorker().supplyAsync(backend::enumerate)
|
Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().enumerate())
|
||||||
.thenAccept(running -> {
|
.thenAccept(running -> {
|
||||||
for (final Tunnel tunnel : tunnels)
|
for (final Tunnel tunnel : tunnels)
|
||||||
tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN);
|
tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN);
|
||||||
@ -207,7 +204,7 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
|
|
||||||
CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
|
CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
|
||||||
return Application.getAsyncWorker().supplyAsync(() -> {
|
return Application.getAsyncWorker().supplyAsync(() -> {
|
||||||
final Config appliedConfig = backend.applyConfig(tunnel, config);
|
final Config appliedConfig = Application.getBackend().applyConfig(tunnel, config);
|
||||||
return configStore.save(tunnel.getName(), appliedConfig);
|
return configStore.save(tunnel.getName(), appliedConfig);
|
||||||
}).thenApply(tunnel::onConfigChanged);
|
}).thenApply(tunnel::onConfigChanged);
|
||||||
}
|
}
|
||||||
@ -227,11 +224,11 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
tunnels.remove(tunnel);
|
tunnels.remove(tunnel);
|
||||||
return Application.getAsyncWorker().supplyAsync(() -> {
|
return Application.getAsyncWorker().supplyAsync(() -> {
|
||||||
if (originalState == State.UP)
|
if (originalState == State.UP)
|
||||||
backend.setState(tunnel, State.DOWN);
|
Application.getBackend().setState(tunnel, State.DOWN);
|
||||||
configStore.rename(tunnel.getName(), name);
|
configStore.rename(tunnel.getName(), name);
|
||||||
final String newName = tunnel.onNameChanged(name);
|
final String newName = tunnel.onNameChanged(name);
|
||||||
if (originalState == State.UP)
|
if (originalState == State.UP)
|
||||||
backend.setState(tunnel, State.UP);
|
Application.getBackend().setState(tunnel, State.UP);
|
||||||
return newName;
|
return newName;
|
||||||
}).whenComplete((newName, e) -> {
|
}).whenComplete((newName, e) -> {
|
||||||
// On failure, we don't know what state the tunnel might be in. Fix that.
|
// On failure, we don't know what state the tunnel might be in. Fix that.
|
||||||
@ -247,7 +244,7 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
CompletionStage<State> setTunnelState(final Tunnel tunnel, final State state) {
|
CompletionStage<State> setTunnelState(final Tunnel tunnel, final State state) {
|
||||||
// Ensure the configuration is loaded before trying to use it.
|
// Ensure the configuration is loaded before trying to use it.
|
||||||
return tunnel.getConfigAsync().thenCompose(x ->
|
return tunnel.getConfigAsync().thenCompose(x ->
|
||||||
Application.getAsyncWorker().supplyAsync(() -> backend.setState(tunnel, state))
|
Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().setState(tunnel, state))
|
||||||
).whenComplete((newState, e) -> {
|
).whenComplete((newState, e) -> {
|
||||||
// Ensure onStateChanged is always called (failure or not), and with the correct state.
|
// Ensure onStateChanged is always called (failure or not), and with the correct state.
|
||||||
tunnel.onStateChanged(e == null ? newState : tunnel.getState());
|
tunnel.onStateChanged(e == null ? newState : tunnel.getState());
|
||||||
|
@ -27,13 +27,14 @@ public class VersionPreference extends Preference {
|
|||||||
public VersionPreference(final Context context, final AttributeSet attrs) {
|
public VersionPreference(final Context context, final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
|
|
||||||
final Backend backend = Application.getBackend();
|
Application.onHaveBackend(backend -> {
|
||||||
versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypeName().toLowerCase());
|
versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypeName().toLowerCase());
|
||||||
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> {
|
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> {
|
||||||
versionSummary = exception == null
|
versionSummary = exception == null
|
||||||
? getContext().getString(R.string.version_summary, backend.getTypeName(), version)
|
? getContext().getString(R.string.version_summary, backend.getTypeName(), version)
|
||||||
: getContext().getString(R.string.version_summary_unknown, backend.getTypeName().toLowerCase());
|
: getContext().getString(R.string.version_summary_unknown, backend.getTypeName().toLowerCase());
|
||||||
notifyChanged();
|
notifyChanged();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user