backend: do not depend on anything except config
This is likely broken but should make for a good starting point. It also should hopefully handle stopping tunnels before starting new ones, in the case of the GoBackend. Again, untested. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
		
							parent
							
								
									0990430513
								
							
						
					
					
						commit
						4905185e61
					
				| @ -5,6 +5,7 @@ | |||||||
| 
 | 
 | ||||||
| package com.wireguard.android; | package com.wireguard.android; | ||||||
| 
 | 
 | ||||||
|  | import android.app.PendingIntent; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| @ -19,12 +20,14 @@ import androidx.preference.PreferenceManager; | |||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.appcompat.app.AppCompatDelegate; | import androidx.appcompat.app.AppCompatDelegate; | ||||||
| 
 | 
 | ||||||
|  | import com.wireguard.android.activity.MainActivity; | ||||||
| import com.wireguard.android.backend.Backend; | import com.wireguard.android.backend.Backend; | ||||||
| import com.wireguard.android.backend.GoBackend; | import com.wireguard.android.backend.GoBackend; | ||||||
| import com.wireguard.android.backend.WgQuickBackend; | import com.wireguard.android.backend.WgQuickBackend; | ||||||
| import com.wireguard.android.configStore.FileConfigStore; | import com.wireguard.android.configStore.FileConfigStore; | ||||||
| import com.wireguard.android.model.TunnelManager; | import com.wireguard.android.model.TunnelManager; | ||||||
| import com.wireguard.android.util.AsyncWorker; | import com.wireguard.android.util.AsyncWorker; | ||||||
|  | import com.wireguard.android.util.ExceptionLoggers; | ||||||
| import com.wireguard.android.util.ModuleLoader; | import com.wireguard.android.util.ModuleLoader; | ||||||
| import com.wireguard.android.util.RootShell; | import com.wireguard.android.util.RootShell; | ||||||
| import com.wireguard.android.util.ToolsInstaller; | import com.wireguard.android.util.ToolsInstaller; | ||||||
| @ -89,8 +92,16 @@ public class Application extends android.app.Application { | |||||||
|                     } catch (final Exception ignored) { |                     } catch (final Exception ignored) { | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 if (backend == null) |                 if (backend == null) { | ||||||
|                     backend = new GoBackend(app.getApplicationContext()); |                     final Context context = app.getApplicationContext(); | ||||||
|  |                     final Intent configureIntent = new Intent(context, MainActivity.class); | ||||||
|  |                     configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||||
|  |                     final PendingIntent pendingConfigureIntent = PendingIntent.getActivity(context, 0, configureIntent, 0); | ||||||
|  |                     backend = new GoBackend(context, pendingConfigureIntent); | ||||||
|  |                     GoBackend.setAlwaysOnCallback(() -> { | ||||||
|  |                         get().tunnelManager.restoreState(true).whenComplete(ExceptionLoggers.D); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|                 app.backend = backend; |                 app.backend = backend; | ||||||
|             } |             } | ||||||
|             return app.backend; |             return app.backend; | ||||||
|  | |||||||
| @ -21,8 +21,8 @@ import android.util.Log; | |||||||
| 
 | 
 | ||||||
| import com.wireguard.android.activity.MainActivity; | import com.wireguard.android.activity.MainActivity; | ||||||
| import com.wireguard.android.activity.TunnelToggleActivity; | import com.wireguard.android.activity.TunnelToggleActivity; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.backend.Tunnel.State; | ||||||
| import com.wireguard.android.model.Tunnel.State; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| import com.wireguard.android.widget.SlashDrawable; | import com.wireguard.android.widget.SlashDrawable; | ||||||
| 
 | 
 | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| @ -41,7 +41,7 @@ public class QuickTileService extends TileService { | |||||||
|     private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback(); |     private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback(); | ||||||
|     @Nullable private Icon iconOff; |     @Nullable private Icon iconOff; | ||||||
|     @Nullable private Icon iconOn; |     @Nullable private Icon iconOn; | ||||||
|     @Nullable private Tunnel tunnel; |     @Nullable private ObservableTunnel tunnel; | ||||||
| 
 | 
 | ||||||
|     /* This works around an annoying unsolved frameworks bug some people are hitting. */ |     /* This works around an annoying unsolved frameworks bug some people are hitting. */ | ||||||
|     @Override |     @Override | ||||||
| @ -121,7 +121,7 @@ public class QuickTileService extends TileService { | |||||||
| 
 | 
 | ||||||
|     private void updateTile() { |     private void updateTile() { | ||||||
|         // Update the tunnel. |         // Update the tunnel. | ||||||
|         final Tunnel newTunnel = Application.getTunnelManager().getLastUsedTunnel(); |         final ObservableTunnel newTunnel = Application.getTunnelManager().getLastUsedTunnel(); | ||||||
|         if (newTunnel != tunnel) { |         if (newTunnel != tunnel) { | ||||||
|             if (tunnel != null) |             if (tunnel != null) | ||||||
|                 tunnel.removeOnPropertyChangedCallback(onStateChangedCallback); |                 tunnel.removeOnPropertyChangedCallback(onStateChangedCallback); | ||||||
| @ -135,7 +135,7 @@ public class QuickTileService extends TileService { | |||||||
|         final Tile tile = getQsTile(); |         final Tile tile = getQsTile(); | ||||||
|         if (tunnel != null) { |         if (tunnel != null) { | ||||||
|             label = tunnel.getName(); |             label = tunnel.getName(); | ||||||
|             state = tunnel.getState() == Tunnel.State.UP ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; |             state = tunnel.getState() == State.UP ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; | ||||||
|         } else { |         } else { | ||||||
|             label = getString(R.string.app_name); |             label = getString(R.string.app_name); | ||||||
|             state = Tile.STATE_INACTIVE; |             state = Tile.STATE_INACTIVE; | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ import android.os.Bundle; | |||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| 
 | 
 | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| 
 | 
 | ||||||
| @ -23,14 +23,14 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity { | |||||||
|     private static final String KEY_SELECTED_TUNNEL = "selected_tunnel"; |     private static final String KEY_SELECTED_TUNNEL = "selected_tunnel"; | ||||||
| 
 | 
 | ||||||
|     private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry(); |     private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry(); | ||||||
|     @Nullable private Tunnel selectedTunnel; |     @Nullable private ObservableTunnel selectedTunnel; | ||||||
| 
 | 
 | ||||||
|     public void addOnSelectedTunnelChangedListener(final OnSelectedTunnelChangedListener listener) { |     public void addOnSelectedTunnelChangedListener(final OnSelectedTunnelChangedListener listener) { | ||||||
|         selectionChangeRegistry.add(listener); |         selectionChangeRegistry.add(listener); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |     @Nullable | ||||||
|     public Tunnel getSelectedTunnel() { |     public ObservableTunnel getSelectedTunnel() { | ||||||
|         return selectedTunnel; |         return selectedTunnel; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -60,15 +60,15 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity { | |||||||
|         super.onSaveInstanceState(outState); |         super.onSaveInstanceState(outState); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected abstract void onSelectedTunnelChanged(@Nullable Tunnel oldTunnel, @Nullable Tunnel newTunnel); |     protected abstract void onSelectedTunnelChanged(@Nullable ObservableTunnel oldTunnel, @Nullable ObservableTunnel newTunnel); | ||||||
| 
 | 
 | ||||||
|     public void removeOnSelectedTunnelChangedListener( |     public void removeOnSelectedTunnelChangedListener( | ||||||
|             final OnSelectedTunnelChangedListener listener) { |             final OnSelectedTunnelChangedListener listener) { | ||||||
|         selectionChangeRegistry.remove(listener); |         selectionChangeRegistry.remove(listener); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setSelectedTunnel(@Nullable final Tunnel tunnel) { |     public void setSelectedTunnel(@Nullable final ObservableTunnel tunnel) { | ||||||
|         final Tunnel oldTunnel = selectedTunnel; |         final ObservableTunnel oldTunnel = selectedTunnel; | ||||||
|         if (Objects.equals(oldTunnel, tunnel)) |         if (Objects.equals(oldTunnel, tunnel)) | ||||||
|             return; |             return; | ||||||
|         selectedTunnel = tunnel; |         selectedTunnel = tunnel; | ||||||
| @ -77,21 +77,21 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public interface OnSelectedTunnelChangedListener { |     public interface OnSelectedTunnelChangedListener { | ||||||
|         void onSelectedTunnelChanged(@Nullable Tunnel oldTunnel, @Nullable Tunnel newTunnel); |         void onSelectedTunnelChanged(@Nullable ObservableTunnel oldTunnel, @Nullable ObservableTunnel newTunnel); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static final class SelectionChangeNotifier |     private static final class SelectionChangeNotifier | ||||||
|             extends NotifierCallback<OnSelectedTunnelChangedListener, Tunnel, Tunnel> { |             extends NotifierCallback<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel> { | ||||||
|         @Override |         @Override | ||||||
|         public void onNotifyCallback(final OnSelectedTunnelChangedListener listener, |         public void onNotifyCallback(final OnSelectedTunnelChangedListener listener, | ||||||
|                                      final Tunnel oldTunnel, final int ignored, |                                      final ObservableTunnel oldTunnel, final int ignored, | ||||||
|                                      final Tunnel newTunnel) { |                                      final ObservableTunnel newTunnel) { | ||||||
|             listener.onSelectedTunnelChanged(oldTunnel, newTunnel); |             listener.onSelectedTunnelChanged(oldTunnel, newTunnel); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static final class SelectionChangeRegistry |     private static final class SelectionChangeRegistry | ||||||
|             extends CallbackRegistry<OnSelectedTunnelChangedListener, Tunnel, Tunnel> { |             extends CallbackRegistry<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel> { | ||||||
|         private SelectionChangeRegistry() { |         private SelectionChangeRegistry() { | ||||||
|             super(new SelectionChangeNotifier()); |             super(new SelectionChangeNotifier()); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ import android.widget.LinearLayout; | |||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
| import com.wireguard.android.fragment.TunnelDetailFragment; | import com.wireguard.android.fragment.TunnelDetailFragment; | ||||||
| import com.wireguard.android.fragment.TunnelEditorFragment; | import com.wireguard.android.fragment.TunnelEditorFragment; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @ -117,8 +117,8 @@ public class MainActivity extends BaseActivity | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, |     protected void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, | ||||||
|                                            @Nullable final Tunnel newTunnel) { |                                            @Nullable final ObservableTunnel newTunnel) { | ||||||
|         final FragmentManager fragmentManager = getSupportFragmentManager(); |         final FragmentManager fragmentManager = getSupportFragmentManager(); | ||||||
|         final int backStackEntries = fragmentManager.getBackStackEntryCount(); |         final int backStackEntries = fragmentManager.getBackStackEntryCount(); | ||||||
|         if (newTunnel == null) { |         if (newTunnel == null) { | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ import android.os.Bundle; | |||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| import com.wireguard.android.fragment.TunnelEditorFragment; | import com.wireguard.android.fragment.TunnelEditorFragment; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Standalone activity for creating tunnels. |  * Standalone activity for creating tunnels. | ||||||
| @ -28,7 +28,7 @@ public class TunnelCreatorActivity extends BaseActivity { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) { |     protected void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) { | ||||||
|         finish(); |         finish(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,8 +19,8 @@ import android.widget.Toast; | |||||||
| import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||||
| import com.wireguard.android.QuickTileService; | import com.wireguard.android.QuickTileService; | ||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| import com.wireguard.android.model.Tunnel.State; | import com.wireguard.android.backend.Tunnel.State; | ||||||
| import com.wireguard.android.util.ErrorMessages; | import com.wireguard.android.util.ErrorMessages; | ||||||
| 
 | 
 | ||||||
| @RequiresApi(Build.VERSION_CODES.N) | @RequiresApi(Build.VERSION_CODES.N) | ||||||
| @ -30,7 +30,7 @@ public class TunnelToggleActivity extends AppCompatActivity { | |||||||
|     @Override |     @Override | ||||||
|     protected void onCreate(@Nullable final Bundle savedInstanceState) { |     protected void onCreate(@Nullable final Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         final Tunnel tunnel = Application.getTunnelManager().getLastUsedTunnel(); |         final ObservableTunnel tunnel = Application.getTunnelManager().getLastUsedTunnel(); | ||||||
|         if (tunnel == null) |         if (tunnel == null) | ||||||
|             return; |             return; | ||||||
|         tunnel.setState(State.TOGGLE).whenComplete((v, t) -> { |         tunnel.setState(State.TOGGLE).whenComplete((v, t) -> { | ||||||
|  | |||||||
| @ -5,43 +5,32 @@ | |||||||
| 
 | 
 | ||||||
| package com.wireguard.android.backend; | package com.wireguard.android.backend; | ||||||
| 
 | 
 | ||||||
| import com.wireguard.android.model.Tunnel; |  | ||||||
| import com.wireguard.android.model.Tunnel.State; |  | ||||||
| import com.wireguard.android.model.Tunnel.Statistics; |  | ||||||
| import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||||
| 
 | 
 | ||||||
|  | import java.util.Collection; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| 
 | 
 | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Interface for implementations of the WireGuard secure network tunnel. |  * Interface for implementations of the WireGuard secure network tunnel. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| public interface Backend { | public interface Backend { | ||||||
|     /** |     /** | ||||||
|      * Update the volatile configuration of a running tunnel and return the resulting configuration. |      * Enumerate names of currently-running tunnels. | ||||||
|      * If the tunnel is not up, return the configuration that would result (if known), or else |  | ||||||
|      * simply return the given configuration. |  | ||||||
|      * |  | ||||||
|      * @param tunnel The tunnel to apply the configuration to. |  | ||||||
|      * @param config The new configuration for this tunnel. |  | ||||||
|      * @return The updated configuration of the tunnel. |  | ||||||
|      */ |  | ||||||
|     Config applyConfig(Tunnel tunnel, Config config) throws Exception; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Enumerate the names of currently-running tunnels. |  | ||||||
|      * |      * | ||||||
|      * @return The set of running tunnel names. |      * @return The set of running tunnel names. | ||||||
|      */ |      */ | ||||||
|     Set<String> enumerate(); |     Set<String> getRunningTunnelNames(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get the actual state of a tunnel. |      * Get the state of a tunnel. | ||||||
|      * |      * | ||||||
|      * @param tunnel The tunnel to examine the state of. |      * @param tunnel The tunnel to examine the state of. | ||||||
|      * @return The state of the tunnel. |      * @return The state of the tunnel. | ||||||
|      */ |      */ | ||||||
|     State getState(Tunnel tunnel) throws Exception; |     Tunnel.State getState(Tunnel tunnel) throws Exception; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get statistics about traffic and errors on this tunnel. If the tunnel is not running, the |      * Get statistics about traffic and errors on this tunnel. If the tunnel is not running, the | ||||||
| @ -68,12 +57,32 @@ public interface Backend { | |||||||
|     String getVersion() throws Exception; |     String getVersion() throws Exception; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Set the state of a tunnel. |      * Set the state of a tunnel, updating it's configuration. If the tunnel is already up, config | ||||||
|  |      * may update the running configuration; config may be null when setting the tunnel down. | ||||||
|      * |      * | ||||||
|      * @param tunnel The tunnel to control the state of. |      * @param tunnel The tunnel to control the state of. | ||||||
|      * @param state  The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or |      * @param state  The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or | ||||||
|      *               {@code TOGGLE}. |      *               {@code TOGGLE}. | ||||||
|  |      * @param config The configuration for this tunnel, may be null if state is {@code DOWN}. | ||||||
|      * @return The updated state of the tunnel. |      * @return The updated state of the tunnel. | ||||||
|      */ |      */ | ||||||
|     State setState(Tunnel tunnel, State state) throws Exception; |     Tunnel.State setState(Tunnel tunnel, Tunnel.State state, @Nullable Config config) throws Exception; | ||||||
|  | 
 | ||||||
|  |     interface TunnelStateChangeNotificationReceiver { | ||||||
|  |         void tunnelStateChange(Tunnel tunnel, Tunnel.State state); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Register a state change notification callback. | ||||||
|  |      * | ||||||
|  |      * @param receiver The receiver object to receive the notification. | ||||||
|  |      */ | ||||||
|  |     void registerStateChangeNotification(TunnelStateChangeNotificationReceiver receiver); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Unregister a state change notification callback. | ||||||
|  |      * | ||||||
|  |      * @param receiver The receiver object to no longer receive the notification. | ||||||
|  |      */ | ||||||
|  |     void unregisterStateChangeNotification(TunnelStateChangeNotificationReceiver receiver); | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,10 +16,7 @@ import android.util.Log; | |||||||
| 
 | 
 | ||||||
| import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
| import com.wireguard.android.activity.MainActivity; | import com.wireguard.android.backend.Tunnel.State; | ||||||
| import com.wireguard.android.model.Tunnel; |  | ||||||
| import com.wireguard.android.model.Tunnel.State; |  | ||||||
| import com.wireguard.android.model.Tunnel.Statistics; |  | ||||||
| import com.wireguard.android.util.ExceptionLoggers; | import com.wireguard.android.util.ExceptionLoggers; | ||||||
| import com.wireguard.android.util.SharedLibraryLoader; | import com.wireguard.android.util.SharedLibraryLoader; | ||||||
| import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||||
| @ -30,6 +27,7 @@ import com.wireguard.crypto.KeyFormatException; | |||||||
| 
 | 
 | ||||||
| import java.net.InetAddress; | import java.net.InetAddress; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.HashSet; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
| @ -40,14 +38,26 @@ import java9.util.concurrent.CompletableFuture; | |||||||
| public final class GoBackend implements Backend { | public final class GoBackend implements Backend { | ||||||
|     private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName(); |     private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName(); | ||||||
|     private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>(); |     private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>(); | ||||||
|  |     public interface AlwaysOnCallback { | ||||||
|  |         void alwaysOnTriggered(); | ||||||
|  |     } | ||||||
|  |     @Nullable private static AlwaysOnCallback alwaysOnCallback; | ||||||
|  |     public static void setAlwaysOnCallback(AlwaysOnCallback cb) { | ||||||
|  |         alwaysOnCallback = cb; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     private final Context context; |     private final Context context; | ||||||
|  |     private final PendingIntent configurationIntent; | ||||||
|     @Nullable private Tunnel currentTunnel; |     @Nullable private Tunnel currentTunnel; | ||||||
|  |     @Nullable private Config currentConfig; | ||||||
|     private int currentTunnelHandle = -1; |     private int currentTunnelHandle = -1; | ||||||
| 
 | 
 | ||||||
|     public GoBackend(final Context context) { |     private final Set<TunnelStateChangeNotificationReceiver> notifiers = new HashSet<>(); | ||||||
|  | 
 | ||||||
|  |     public GoBackend(final Context context, final PendingIntent configurationIntent) { | ||||||
|         SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); |         SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); | ||||||
|         this.context = context; |         this.context = context; | ||||||
|  |         this.configurationIntent = configurationIntent; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static native String wgGetConfig(int handle); |     private static native String wgGetConfig(int handle); | ||||||
| @ -63,23 +73,7 @@ public final class GoBackend implements Backend { | |||||||
|     private static native String wgVersion(); |     private static native String wgVersion(); | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception { |     public Set<String> getRunningTunnelNames() { | ||||||
|         if (tunnel.getState() == State.UP) { |  | ||||||
|             // Restart the tunnel to apply the new config. |  | ||||||
|             setStateInternal(tunnel, tunnel.getConfig(), State.DOWN); |  | ||||||
|             try { |  | ||||||
|                 setStateInternal(tunnel, config, State.UP); |  | ||||||
|             } catch (final Exception e) { |  | ||||||
|                 // The new configuration didn't work, so try to go back to the old one. |  | ||||||
|                 setStateInternal(tunnel, tunnel.getConfig(), State.UP); |  | ||||||
|                 throw e; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return config; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public Set<String> enumerate() { |  | ||||||
|         if (currentTunnel != null) { |         if (currentTunnel != null) { | ||||||
|             final Set<String> runningTunnels = new ArraySet<>(); |             final Set<String> runningTunnels = new ArraySet<>(); | ||||||
|             runningTunnels.add(currentTunnel.getName()); |             runningTunnels.add(currentTunnel.getName()); | ||||||
| @ -147,25 +141,36 @@ public final class GoBackend implements Backend { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public State setState(final Tunnel tunnel, State state) throws Exception { |     public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception { | ||||||
|         final State originalState = getState(tunnel); |         final State originalState = getState(tunnel); | ||||||
|  | 
 | ||||||
|         if (state == State.TOGGLE) |         if (state == State.TOGGLE) | ||||||
|             state = originalState == State.UP ? State.DOWN : State.UP; |             state = originalState == State.UP ? State.DOWN : State.UP; | ||||||
|         if (state == originalState) |         if (state == originalState && tunnel == currentTunnel && config == currentConfig) | ||||||
|             return originalState; |             return originalState; | ||||||
|         if (state == State.UP && currentTunnel != null) |         if (state == State.UP) { | ||||||
|             throw new IllegalStateException(context.getString(R.string.multiple_tunnels_error)); |             final Config originalConfig = currentConfig; | ||||||
|         Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state); |             final Tunnel originalTunnel = currentTunnel; | ||||||
|         setStateInternal(tunnel, tunnel.getConfig(), state); |             if (currentTunnel != null) | ||||||
|  |                 setStateInternal(currentTunnel, null, State.DOWN); | ||||||
|  |             try { | ||||||
|  |                 setStateInternal(tunnel, config, state); | ||||||
|  |             } catch(final Exception e) { | ||||||
|  |                 if (originalTunnel != null) | ||||||
|  |                     setStateInternal(originalTunnel, originalConfig, State.UP); | ||||||
|  |                 throw e; | ||||||
|  |             } | ||||||
|  |         } else if (state == State.DOWN && tunnel == currentTunnel) { | ||||||
|  |             setStateInternal(tunnel, null, State.DOWN); | ||||||
|  |         } | ||||||
|         return getState(tunnel); |         return getState(tunnel); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) |     private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) | ||||||
|             throws Exception { |             throws Exception { | ||||||
|  |         Log.i(TAG, "Bringing tunnel " + tunnel.getName() + " " + state); | ||||||
| 
 | 
 | ||||||
|         if (state == State.UP) { |         if (state == State.UP) { | ||||||
|             Log.i(TAG, "Bringing tunnel up"); |  | ||||||
| 
 |  | ||||||
|             Objects.requireNonNull(config, context.getString(R.string.no_config_error)); |             Objects.requireNonNull(config, context.getString(R.string.no_config_error)); | ||||||
| 
 | 
 | ||||||
|             if (VpnService.prepare(context) != null) |             if (VpnService.prepare(context) != null) | ||||||
| @ -180,6 +185,7 @@ public final class GoBackend implements Backend { | |||||||
|             } catch (final TimeoutException e) { |             } catch (final TimeoutException e) { | ||||||
|                 throw new Exception(context.getString(R.string.vpn_start_error), e); |                 throw new Exception(context.getString(R.string.vpn_start_error), e); | ||||||
|             } |             } | ||||||
|  |             service.setOwner(this); | ||||||
| 
 | 
 | ||||||
|             if (currentTunnelHandle != -1) { |             if (currentTunnelHandle != -1) { | ||||||
|                 Log.w(TAG, "Tunnel already up"); |                 Log.w(TAG, "Tunnel already up"); | ||||||
| @ -193,9 +199,7 @@ public final class GoBackend implements Backend { | |||||||
|             final VpnService.Builder builder = service.getBuilder(); |             final VpnService.Builder builder = service.getBuilder(); | ||||||
|             builder.setSession(tunnel.getName()); |             builder.setSession(tunnel.getName()); | ||||||
| 
 | 
 | ||||||
|             final Intent configureIntent = new Intent(context, MainActivity.class); |             builder.setConfigureIntent(configurationIntent); | ||||||
|             configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |  | ||||||
|             builder.setConfigureIntent(PendingIntent.getActivity(context, 0, configureIntent, 0)); |  | ||||||
| 
 | 
 | ||||||
|             for (final String excludedApplication : config.getInterface().getExcludedApplications()) |             for (final String excludedApplication : config.getInterface().getExcludedApplications()) | ||||||
|                 builder.addDisallowedApplication(excludedApplication); |                 builder.addDisallowedApplication(excludedApplication); | ||||||
| @ -229,12 +233,11 @@ public final class GoBackend implements Backend { | |||||||
|                 throw new Exception(context.getString(R.string.tunnel_on_error, currentTunnelHandle)); |                 throw new Exception(context.getString(R.string.tunnel_on_error, currentTunnelHandle)); | ||||||
| 
 | 
 | ||||||
|             currentTunnel = tunnel; |             currentTunnel = tunnel; | ||||||
|  |             currentConfig = config; | ||||||
| 
 | 
 | ||||||
|             service.protect(wgGetSocketV4(currentTunnelHandle)); |             service.protect(wgGetSocketV4(currentTunnelHandle)); | ||||||
|             service.protect(wgGetSocketV6(currentTunnelHandle)); |             service.protect(wgGetSocketV6(currentTunnelHandle)); | ||||||
|         } else { |         } else { | ||||||
|             Log.i(TAG, "Bringing tunnel down"); |  | ||||||
| 
 |  | ||||||
|             if (currentTunnelHandle == -1) { |             if (currentTunnelHandle == -1) { | ||||||
|                 Log.w(TAG, "Tunnel already down"); |                 Log.w(TAG, "Tunnel already down"); | ||||||
|                 return; |                 return; | ||||||
| @ -243,7 +246,11 @@ public final class GoBackend implements Backend { | |||||||
|             wgTurnOff(currentTunnelHandle); |             wgTurnOff(currentTunnelHandle); | ||||||
|             currentTunnel = null; |             currentTunnel = null; | ||||||
|             currentTunnelHandle = -1; |             currentTunnelHandle = -1; | ||||||
|  |             currentConfig = null; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         for (final TunnelStateChangeNotificationReceiver notifier : notifiers) | ||||||
|  |             notifier.tunnelStateChange(tunnel, state); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void startVpnService() { |     private void startVpnService() { | ||||||
| @ -251,7 +258,23 @@ public final class GoBackend implements Backend { | |||||||
|         context.startService(new Intent(context, VpnService.class)); |         context.startService(new Intent(context, VpnService.class)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public void registerStateChangeNotification(final TunnelStateChangeNotificationReceiver receiver) { | ||||||
|  |         notifiers.add(receiver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void unregisterStateChangeNotification(final TunnelStateChangeNotificationReceiver receiver) { | ||||||
|  |         notifiers.remove(receiver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static class VpnService extends android.net.VpnService { |     public static class VpnService extends android.net.VpnService { | ||||||
|  |         @Nullable private GoBackend owner; | ||||||
|  | 
 | ||||||
|  |         public void setOwner(final GoBackend owner) { | ||||||
|  |             this.owner = owner; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         public Builder getBuilder() { |         public Builder getBuilder() { | ||||||
|             return new Builder(); |             return new Builder(); | ||||||
|         } |         } | ||||||
| @ -264,13 +287,18 @@ public final class GoBackend implements Backend { | |||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void onDestroy() { |         public void onDestroy() { | ||||||
|             Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { |             if (owner != null) { | ||||||
|                 for (final Tunnel tunnel : tunnels) { |                 final Tunnel tunnel = owner.currentTunnel; | ||||||
|                     if (tunnel != null && tunnel.getState() != State.DOWN) |                 if (tunnel != null) { | ||||||
|                         tunnel.setState(State.DOWN); |                     if (owner.currentTunnelHandle != -1) | ||||||
|  |                         wgTurnOff(owner.currentTunnelHandle); | ||||||
|  |                     owner.currentTunnel = null; | ||||||
|  |                     owner.currentTunnelHandle = -1; | ||||||
|  |                     owner.currentConfig = null; | ||||||
|  |                     for (final TunnelStateChangeNotificationReceiver notifier : owner.notifiers) | ||||||
|  |                         notifier.tunnelStateChange(tunnel, State.DOWN); | ||||||
|                 } |                 } | ||||||
|             }); |             } | ||||||
| 
 |  | ||||||
|             vpnService = vpnService.newIncompleteFuture(); |             vpnService = vpnService.newIncompleteFuture(); | ||||||
|             super.onDestroy(); |             super.onDestroy(); | ||||||
|         } |         } | ||||||
| @ -280,10 +308,10 @@ public final class GoBackend implements Backend { | |||||||
|             vpnService.complete(this); |             vpnService.complete(this); | ||||||
|             if (intent == null || intent.getComponent() == null || !intent.getComponent().getPackageName().equals(getPackageName())) { |             if (intent == null || intent.getComponent() == null || !intent.getComponent().getPackageName().equals(getPackageName())) { | ||||||
|                 Log.d(TAG, "Service started by Always-on VPN feature"); |                 Log.d(TAG, "Service started by Always-on VPN feature"); | ||||||
|                 Application.getTunnelManager().restoreState(true).whenComplete(ExceptionLoggers.D); |                 if (alwaysOnCallback != null) | ||||||
|  |                     alwaysOnCallback.alwaysOnTriggered(); | ||||||
|             } |             } | ||||||
|             return super.onStartCommand(intent, flags, startId); |             return super.onStartCommand(intent, flags, startId); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,62 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright © 2020 WireGuard LLC. All Rights Reserved. | ||||||
|  |  * SPDX-License-Identifier: Apache-2.0 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package com.wireguard.android.backend; | ||||||
|  | 
 | ||||||
|  | import android.os.SystemClock; | ||||||
|  | import android.util.Pair; | ||||||
|  | 
 | ||||||
|  | import com.wireguard.crypto.Key; | ||||||
|  | 
 | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | public class Statistics { | ||||||
|  |     private long lastTouched = SystemClock.elapsedRealtime(); | ||||||
|  |     private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>(); | ||||||
|  | 
 | ||||||
|  |     Statistics() { } | ||||||
|  | 
 | ||||||
|  |     void add(final Key key, final long rx, final long tx) { | ||||||
|  |         peerBytes.put(key, Pair.create(rx, tx)); | ||||||
|  |         lastTouched = SystemClock.elapsedRealtime(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isStale() { | ||||||
|  |         return SystemClock.elapsedRealtime() - lastTouched > 900; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Key[] peers() { | ||||||
|  |         return peerBytes.keySet().toArray(new Key[0]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long peerRx(final Key peer) { | ||||||
|  |         if (!peerBytes.containsKey(peer)) | ||||||
|  |             return 0; | ||||||
|  |         return peerBytes.get(peer).first; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long peerTx(final Key peer) { | ||||||
|  |         if (!peerBytes.containsKey(peer)) | ||||||
|  |             return 0; | ||||||
|  |         return peerBytes.get(peer).second; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long totalRx() { | ||||||
|  |         long rx = 0; | ||||||
|  |         for (final Pair<Long, Long> val : peerBytes.values()) { | ||||||
|  |             rx += val.first; | ||||||
|  |         } | ||||||
|  |         return rx; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long totalTx() { | ||||||
|  |         long tx = 0; | ||||||
|  |         for (final Pair<Long, Long> val : peerBytes.values()) { | ||||||
|  |             tx += val.second; | ||||||
|  |         } | ||||||
|  |         return tx; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								app/src/main/java/com/wireguard/android/backend/Tunnel.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/src/main/java/com/wireguard/android/backend/Tunnel.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright © 2020 WireGuard LLC. All Rights Reserved. | ||||||
|  |  * SPDX-License-Identifier: Apache-2.0 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package com.wireguard.android.backend; | ||||||
|  | 
 | ||||||
|  | import java.util.regex.Pattern; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Represents a WireGuard tunnel. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | public interface Tunnel { | ||||||
|  |     enum State { | ||||||
|  |         DOWN, | ||||||
|  |         TOGGLE, | ||||||
|  |         UP; | ||||||
|  | 
 | ||||||
|  |         public static State of(final boolean running) { | ||||||
|  |             return running ? UP : DOWN; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     int NAME_MAX_LENGTH = 15; | ||||||
|  |     Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}"); | ||||||
|  | 
 | ||||||
|  |     static boolean isNameInvalid(final CharSequence name) { | ||||||
|  |         return !NAME_PATTERN.matcher(name).matches(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     String getName(); | ||||||
|  | } | ||||||
| @ -11,9 +11,7 @@ import android.util.Log; | |||||||
| 
 | 
 | ||||||
| import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.backend.Tunnel.State; | ||||||
| import com.wireguard.android.model.Tunnel.State; |  | ||||||
| import com.wireguard.android.model.Tunnel.Statistics; |  | ||||||
| import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||||
| import com.wireguard.crypto.Key; | import com.wireguard.crypto.Key; | ||||||
| 
 | 
 | ||||||
| @ -23,10 +21,13 @@ import java.nio.charset.StandardCharsets; | |||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.HashSet; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Locale; | import java.util.Locale; | ||||||
|  | import java.util.Map; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  | import java.util.HashMap; | ||||||
| 
 | 
 | ||||||
| import java9.util.stream.Collectors; | import java9.util.stream.Collectors; | ||||||
| import java9.util.stream.Stream; | import java9.util.stream.Stream; | ||||||
| @ -40,6 +41,8 @@ public final class WgQuickBackend implements Backend { | |||||||
| 
 | 
 | ||||||
|     private final File localTemporaryDir; |     private final File localTemporaryDir; | ||||||
|     private final Context context; |     private final Context context; | ||||||
|  |     private final Map<Tunnel, Config> runningConfigs = new HashMap<>(); | ||||||
|  |     private final Set<TunnelStateChangeNotificationReceiver> notifiers = new HashSet<>(); | ||||||
| 
 | 
 | ||||||
|     public WgQuickBackend(final Context context) { |     public WgQuickBackend(final Context context) { | ||||||
|         localTemporaryDir = new File(context.getCacheDir(), "tmp"); |         localTemporaryDir = new File(context.getCacheDir(), "tmp"); | ||||||
| @ -47,23 +50,7 @@ public final class WgQuickBackend implements Backend { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception { |     public Set<String> getRunningTunnelNames() { | ||||||
|         if (tunnel.getState() == State.UP) { |  | ||||||
|             // Restart the tunnel to apply the new config. |  | ||||||
|             setStateInternal(tunnel, tunnel.getConfig(), State.DOWN); |  | ||||||
|             try { |  | ||||||
|                 setStateInternal(tunnel, config, State.UP); |  | ||||||
|             } catch (final Exception e) { |  | ||||||
|                 // The new configuration didn't work, so try to go back to the old one. |  | ||||||
|                 setStateInternal(tunnel, tunnel.getConfig(), State.UP); |  | ||||||
|                 throw e; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return config; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public Set<String> enumerate() { |  | ||||||
|         final List<String> output = new ArrayList<>(); |         final List<String> output = new ArrayList<>(); | ||||||
|         // Don't throw an exception here or nothing will show up in the UI. |         // Don't throw an exception here or nothing will show up in the UI. | ||||||
|         try { |         try { | ||||||
| @ -80,7 +67,7 @@ public final class WgQuickBackend implements Backend { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public State getState(final Tunnel tunnel) { |     public State getState(final Tunnel tunnel) { | ||||||
|         return enumerate().contains(tunnel.getName()) ? State.UP : State.DOWN; |         return getRunningTunnelNames().contains(tunnel.getName()) ? State.UP : State.DOWN; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @ -120,20 +107,36 @@ public final class WgQuickBackend implements Backend { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public State setState(final Tunnel tunnel, State state) throws Exception { |     public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception { | ||||||
|         final State originalState = getState(tunnel); |         final State originalState = getState(tunnel); | ||||||
|  |         final Config originalConfig = runningConfigs.get(tunnel); | ||||||
|  | 
 | ||||||
|         if (state == State.TOGGLE) |         if (state == State.TOGGLE) | ||||||
|             state = originalState == State.UP ? State.DOWN : State.UP; |             state = originalState == State.UP ? State.DOWN : State.UP; | ||||||
|         if (state == originalState) |         if ((state == State.UP && originalState == State.UP && originalConfig != null && originalConfig == config) || | ||||||
|  |                 (state == State.DOWN && originalState == State.DOWN)) | ||||||
|             return originalState; |             return originalState; | ||||||
|         Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state); |         if (state == State.UP) { | ||||||
|         Application.getToolsInstaller().ensureToolsAvailable(); |             Application.getToolsInstaller().ensureToolsAvailable(); | ||||||
|         setStateInternal(tunnel, tunnel.getConfig(), state); |             if (originalState == State.UP) | ||||||
|         return getState(tunnel); |                 setStateInternal(tunnel, originalConfig == null ? config : originalConfig, State.DOWN); | ||||||
|  |             try { | ||||||
|  |                 setStateInternal(tunnel, config, State.UP); | ||||||
|  |             } catch(final Exception e) { | ||||||
|  |                 if (originalState == State.UP && originalConfig != null) | ||||||
|  |                     setStateInternal(tunnel, originalConfig, State.UP); | ||||||
|  |                 throw e; | ||||||
|  |             } | ||||||
|  |         } else if (state == State.DOWN) { | ||||||
|  |             setStateInternal(tunnel, originalConfig == null ? config : originalConfig, State.DOWN); | ||||||
|  |         } | ||||||
|  |         return state; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception { |     private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception { | ||||||
|         Objects.requireNonNull(config, "Trying to set state with a null config"); |         Log.i(TAG, "Bringing tunnel " + tunnel.getName() + " " + state); | ||||||
|  | 
 | ||||||
|  |         Objects.requireNonNull(config, "Trying to set state up with a null config"); | ||||||
| 
 | 
 | ||||||
|         final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf"); |         final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf"); | ||||||
|         try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) { |         try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) { | ||||||
| @ -148,5 +151,23 @@ public final class WgQuickBackend implements Backend { | |||||||
|         tempFile.delete(); |         tempFile.delete(); | ||||||
|         if (result != 0) |         if (result != 0) | ||||||
|             throw new Exception(context.getString(R.string.tunnel_config_error, result)); |             throw new Exception(context.getString(R.string.tunnel_config_error, result)); | ||||||
|  | 
 | ||||||
|  |         if (state == State.UP) | ||||||
|  |             runningConfigs.put(tunnel, config); | ||||||
|  |         else | ||||||
|  |             runningConfigs.remove(tunnel); | ||||||
|  | 
 | ||||||
|  |         for (final TunnelStateChangeNotificationReceiver notifier : notifiers) | ||||||
|  |             notifier.tunnelStateChange(tunnel, state); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void registerStateChangeNotification(final TunnelStateChangeNotificationReceiver receiver) { | ||||||
|  |         notifiers.add(receiver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void unregisterStateChangeNotification(final TunnelStateChangeNotificationReceiver receiver) { | ||||||
|  |         notifiers.remove(receiver); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -23,8 +23,8 @@ import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListen | |||||||
| import com.wireguard.android.backend.GoBackend; | import com.wireguard.android.backend.GoBackend; | ||||||
| import com.wireguard.android.databinding.TunnelDetailFragmentBinding; | import com.wireguard.android.databinding.TunnelDetailFragmentBinding; | ||||||
| import com.wireguard.android.databinding.TunnelListItemBinding; | import com.wireguard.android.databinding.TunnelListItemBinding; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| import com.wireguard.android.model.Tunnel.State; | import com.wireguard.android.backend.Tunnel.State; | ||||||
| import com.wireguard.android.util.ErrorMessages; | import com.wireguard.android.util.ErrorMessages; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -36,11 +36,11 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC | |||||||
|     private static final int REQUEST_CODE_VPN_PERMISSION = 23491; |     private static final int REQUEST_CODE_VPN_PERMISSION = 23491; | ||||||
|     private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName(); |     private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName(); | ||||||
|     @Nullable private BaseActivity activity; |     @Nullable private BaseActivity activity; | ||||||
|     @Nullable private Tunnel pendingTunnel; |     @Nullable private ObservableTunnel pendingTunnel; | ||||||
|     @Nullable private Boolean pendingTunnelUp; |     @Nullable private Boolean pendingTunnelUp; | ||||||
| 
 | 
 | ||||||
|     @Nullable |     @Nullable | ||||||
|     protected Tunnel getSelectedTunnel() { |     protected ObservableTunnel getSelectedTunnel() { | ||||||
|         return activity != null ? activity.getSelectedTunnel() : null; |         return activity != null ? activity.getSelectedTunnel() : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -75,14 +75,14 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC | |||||||
|         super.onDetach(); |         super.onDetach(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected void setSelectedTunnel(@Nullable final Tunnel tunnel) { |     protected void setSelectedTunnel(@Nullable final ObservableTunnel tunnel) { | ||||||
|         if (activity != null) |         if (activity != null) | ||||||
|             activity.setSelectedTunnel(tunnel); |             activity.setSelectedTunnel(tunnel); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setTunnelState(final View view, final boolean checked) { |     public void setTunnelState(final View view, final boolean checked) { | ||||||
|         final ViewDataBinding binding = DataBindingUtil.findBinding(view); |         final ViewDataBinding binding = DataBindingUtil.findBinding(view); | ||||||
|         final Tunnel tunnel; |         final ObservableTunnel tunnel; | ||||||
|         if (binding instanceof TunnelDetailFragmentBinding) |         if (binding instanceof TunnelDetailFragmentBinding) | ||||||
|             tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel(); |             tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel(); | ||||||
|         else if (binding instanceof TunnelListItemBinding) |         else if (binding instanceof TunnelListItemBinding) | ||||||
| @ -107,7 +107,7 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setTunnelStateWithPermissionsResult(final Tunnel tunnel, final boolean checked) { |     private void setTunnelStateWithPermissionsResult(final ObservableTunnel tunnel, final boolean checked) { | ||||||
|         tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> { |         tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> { | ||||||
|             if (throwable == null) |             if (throwable == null) | ||||||
|                 return; |                 return; | ||||||
|  | |||||||
| @ -18,8 +18,8 @@ import android.view.ViewGroup; | |||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
| import com.wireguard.android.databinding.TunnelDetailFragmentBinding; | import com.wireguard.android.databinding.TunnelDetailFragmentBinding; | ||||||
| import com.wireguard.android.databinding.TunnelDetailPeerBinding; | import com.wireguard.android.databinding.TunnelDetailPeerBinding; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| import com.wireguard.android.model.Tunnel.State; | import com.wireguard.android.backend.Tunnel.State; | ||||||
| import com.wireguard.android.ui.EdgeToEdge; | import com.wireguard.android.ui.EdgeToEdge; | ||||||
| import com.wireguard.crypto.Key; | import com.wireguard.crypto.Key; | ||||||
| 
 | 
 | ||||||
| @ -85,7 +85,7 @@ public class TunnelDetailFragment extends BaseFragment { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) { |     public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) { | ||||||
|         if (binding == null) |         if (binding == null) | ||||||
|             return; |             return; | ||||||
|         binding.setTunnel(newTunnel); |         binding.setTunnel(newTunnel); | ||||||
| @ -123,7 +123,7 @@ public class TunnelDetailFragment extends BaseFragment { | |||||||
|     private void updateStats() { |     private void updateStats() { | ||||||
|         if (binding == null || !isResumed()) |         if (binding == null || !isResumed()) | ||||||
|             return; |             return; | ||||||
|         final Tunnel tunnel = binding.getTunnel(); |         final ObservableTunnel tunnel = binding.getTunnel(); | ||||||
|         if (tunnel == null) |         if (tunnel == null) | ||||||
|             return; |             return; | ||||||
|         final State state = tunnel.getState(); |         final State state = tunnel.getState(); | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ import androidx.databinding.ObservableList; | |||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import com.google.android.material.snackbar.Snackbar; | import com.google.android.material.snackbar.Snackbar; | ||||||
| import androidx.fragment.app.FragmentManager; | 
 | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| @ -26,7 +26,7 @@ import com.wireguard.android.Application; | |||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
| import com.wireguard.android.databinding.TunnelEditorFragmentBinding; | import com.wireguard.android.databinding.TunnelEditorFragmentBinding; | ||||||
| import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener; | import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| import com.wireguard.android.model.TunnelManager; | import com.wireguard.android.model.TunnelManager; | ||||||
| import com.wireguard.android.ui.EdgeToEdge; | import com.wireguard.android.ui.EdgeToEdge; | ||||||
| import com.wireguard.android.util.ErrorMessages; | import com.wireguard.android.util.ErrorMessages; | ||||||
| @ -47,7 +47,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi | |||||||
|     private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName(); |     private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName(); | ||||||
| 
 | 
 | ||||||
|     @Nullable private TunnelEditorFragmentBinding binding; |     @Nullable private TunnelEditorFragmentBinding binding; | ||||||
|     @Nullable private Tunnel tunnel; |     @Nullable private ObservableTunnel tunnel; | ||||||
| 
 | 
 | ||||||
|     private void onConfigLoaded(final Config config) { |     private void onConfigLoaded(final Config config) { | ||||||
|         if (binding != null) { |         if (binding != null) { | ||||||
| @ -55,7 +55,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void onConfigSaved(final Tunnel savedTunnel, |     private void onConfigSaved(final ObservableTunnel savedTunnel, | ||||||
|                                @Nullable final Throwable throwable) { |                                @Nullable final Throwable throwable) { | ||||||
|         final String message; |         final String message; | ||||||
|         if (throwable == null) { |         if (throwable == null) { | ||||||
| @ -126,7 +126,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi | |||||||
|         getActivity().runOnUiThread(() -> { |         getActivity().runOnUiThread(() -> { | ||||||
|             // TODO(smaeul): Remove this hack when fixing the Config ViewModel |             // TODO(smaeul): Remove this hack when fixing the Config ViewModel | ||||||
|             // The selected tunnel has to actually change, but we have to remember this one. |             // The selected tunnel has to actually change, but we have to remember this one. | ||||||
|             final Tunnel savedTunnel = tunnel; |             final ObservableTunnel savedTunnel = tunnel; | ||||||
|             if (savedTunnel == getSelectedTunnel()) |             if (savedTunnel == getSelectedTunnel()) | ||||||
|                 setSelectedTunnel(null); |                 setSelectedTunnel(null); | ||||||
|             setSelectedTunnel(savedTunnel); |             setSelectedTunnel(savedTunnel); | ||||||
| @ -187,8 +187,8 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, |     public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, | ||||||
|                                         @Nullable final Tunnel newTunnel) { |                                         @Nullable final ObservableTunnel newTunnel) { | ||||||
|         tunnel = newTunnel; |         tunnel = newTunnel; | ||||||
|         if (binding == null) |         if (binding == null) | ||||||
|             return; |             return; | ||||||
| @ -201,7 +201,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void onTunnelCreated(final Tunnel newTunnel, @Nullable final Throwable throwable) { |     private void onTunnelCreated(final ObservableTunnel newTunnel, @Nullable final Throwable throwable) { | ||||||
|         final String message; |         final String message; | ||||||
|         if (throwable == null) { |         if (throwable == null) { | ||||||
|             tunnel = newTunnel; |             tunnel = newTunnel; | ||||||
| @ -219,7 +219,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void onTunnelRenamed(final Tunnel renamedTunnel, final Config newConfig, |     private void onTunnelRenamed(final ObservableTunnel renamedTunnel, final Config newConfig, | ||||||
|                                  @Nullable final Throwable throwable) { |                                  @Nullable final Throwable throwable) { | ||||||
|         final String message; |         final String message; | ||||||
|         if (throwable == null) { |         if (throwable == null) { | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ import android.provider.OpenableColumns; | |||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import com.google.android.material.snackbar.Snackbar; | import com.google.android.material.snackbar.Snackbar; | ||||||
| import androidx.fragment.app.FragmentManager; | 
 | ||||||
| import androidx.appcompat.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import androidx.appcompat.view.ActionMode; | import androidx.appcompat.view.ActionMode; | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| @ -36,7 +36,7 @@ import com.wireguard.android.activity.TunnelCreatorActivity; | |||||||
| import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter; | import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter; | ||||||
| import com.wireguard.android.databinding.TunnelListFragmentBinding; | import com.wireguard.android.databinding.TunnelListFragmentBinding; | ||||||
| import com.wireguard.android.databinding.TunnelListItemBinding; | import com.wireguard.android.databinding.TunnelListItemBinding; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| import com.wireguard.android.ui.EdgeToEdge; | import com.wireguard.android.ui.EdgeToEdge; | ||||||
| import com.wireguard.android.util.ErrorMessages; | import com.wireguard.android.util.ErrorMessages; | ||||||
| import com.wireguard.android.widget.MultiselectableRelativeLayout; | import com.wireguard.android.widget.MultiselectableRelativeLayout; | ||||||
| @ -91,7 +91,7 @@ public class TunnelListFragment extends BaseFragment { | |||||||
|             return; |             return; | ||||||
|         final ContentResolver contentResolver = activity.getContentResolver(); |         final ContentResolver contentResolver = activity.getContentResolver(); | ||||||
| 
 | 
 | ||||||
|         final Collection<CompletableFuture<Tunnel>> futureTunnels = new ArrayList<>(); |         final Collection<CompletableFuture<ObservableTunnel>> futureTunnels = new ArrayList<>(); | ||||||
|         final List<Throwable> throwables = new ArrayList<>(); |         final List<Throwable> throwables = new ArrayList<>(); | ||||||
|         Application.getAsyncWorker().supplyAsync(() -> { |         Application.getAsyncWorker().supplyAsync(() -> { | ||||||
|             final String[] columns = {OpenableColumns.DISPLAY_NAME}; |             final String[] columns = {OpenableColumns.DISPLAY_NAME}; | ||||||
| @ -161,9 +161,9 @@ public class TunnelListFragment extends BaseFragment { | |||||||
|                 onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception)); |                 onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception)); | ||||||
|             } else { |             } else { | ||||||
|                 future.whenComplete((ignored1, ignored2) -> { |                 future.whenComplete((ignored1, ignored2) -> { | ||||||
|                     final List<Tunnel> tunnels = new ArrayList<>(futureTunnels.size()); |                     final List<ObservableTunnel> tunnels = new ArrayList<>(futureTunnels.size()); | ||||||
|                     for (final CompletableFuture<Tunnel> futureTunnel : futureTunnels) { |                     for (final CompletableFuture<ObservableTunnel> futureTunnel : futureTunnels) { | ||||||
|                         Tunnel tunnel = null; |                         ObservableTunnel tunnel = null; | ||||||
|                         try { |                         try { | ||||||
|                             tunnel = futureTunnel.getNow(null); |                             tunnel = futureTunnel.getNow(null); | ||||||
|                         } catch (final Exception e) { |                         } catch (final Exception e) { | ||||||
| @ -250,7 +250,7 @@ public class TunnelListFragment extends BaseFragment { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) { |     public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) { | ||||||
|         if (binding == null) |         if (binding == null) | ||||||
|             return; |             return; | ||||||
|         Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { |         Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { | ||||||
| @ -281,7 +281,7 @@ public class TunnelListFragment extends BaseFragment { | |||||||
|         showSnackbar(message); |         showSnackbar(message); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void onTunnelImportFinished(final List<Tunnel> tunnels, final Collection<Throwable> throwables) { |     private void onTunnelImportFinished(final List<ObservableTunnel> tunnels, final Collection<Throwable> throwables) { | ||||||
|         String message = null; |         String message = null; | ||||||
| 
 | 
 | ||||||
|         for (final Throwable throwable : throwables) { |         for (final Throwable throwable : throwables) { | ||||||
| @ -315,7 +315,7 @@ public class TunnelListFragment extends BaseFragment { | |||||||
| 
 | 
 | ||||||
|         binding.setFragment(this); |         binding.setFragment(this); | ||||||
|         Application.getTunnelManager().getTunnels().thenAccept(binding::setTunnels); |         Application.getTunnelManager().getTunnels().thenAccept(binding::setTunnels); | ||||||
|         binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> { |         binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, ObservableTunnel>) (binding, tunnel, position) -> { | ||||||
|             binding.setFragment(this); |             binding.setFragment(this); | ||||||
|             binding.getRoot().setOnClickListener(clicked -> { |             binding.getRoot().setOnClickListener(clicked -> { | ||||||
|                 if (actionMode == null) { |                 if (actionMode == null) { | ||||||
| @ -336,7 +336,7 @@ public class TunnelListFragment extends BaseFragment { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) { |     private MultiselectableRelativeLayout viewForTunnel(final ObservableTunnel tunnel, final List tunnels) { | ||||||
|         return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView; |         return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -355,12 +355,12 @@ public class TunnelListFragment extends BaseFragment { | |||||||
|                 case R.id.menu_action_delete: |                 case R.id.menu_action_delete: | ||||||
|                     final Iterable<Integer> copyCheckedItems = new HashSet<>(checkedItems); |                     final Iterable<Integer> copyCheckedItems = new HashSet<>(checkedItems); | ||||||
|                     Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { |                     Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { | ||||||
|                         final Collection<Tunnel> tunnelsToDelete = new ArrayList<>(); |                         final Collection<ObservableTunnel> tunnelsToDelete = new ArrayList<>(); | ||||||
|                         for (final Integer position : copyCheckedItems) |                         for (final Integer position : copyCheckedItems) | ||||||
|                             tunnelsToDelete.add(tunnels.get(position)); |                             tunnelsToDelete.add(tunnels.get(position)); | ||||||
| 
 | 
 | ||||||
|                         final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete) |                         final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete) | ||||||
|                                 .map(Tunnel::delete) |                                 .map(ObservableTunnel::delete) | ||||||
|                                 .toArray(CompletableFuture[]::new); |                                 .toArray(CompletableFuture[]::new); | ||||||
|                         CompletableFuture.allOf(futures) |                         CompletableFuture.allOf(futures) | ||||||
|                                 .thenApply(x -> futures.length) |                                 .thenApply(x -> futures.length) | ||||||
|  | |||||||
| @ -5,23 +5,17 @@ | |||||||
| 
 | 
 | ||||||
| package com.wireguard.android.model; | package com.wireguard.android.model; | ||||||
| 
 | 
 | ||||||
| import android.os.SystemClock; |  | ||||||
| import android.util.Pair; |  | ||||||
| 
 |  | ||||||
| import androidx.databinding.BaseObservable; | import androidx.databinding.BaseObservable; | ||||||
| import androidx.databinding.Bindable; | import androidx.databinding.Bindable; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| import com.wireguard.android.BR; | import com.wireguard.android.BR; | ||||||
|  | import com.wireguard.android.backend.Statistics; | ||||||
|  | import com.wireguard.android.backend.Tunnel; | ||||||
| import com.wireguard.android.util.ExceptionLoggers; | import com.wireguard.android.util.ExceptionLoggers; | ||||||
| import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||||
| import com.wireguard.crypto.Key; |  | ||||||
| import com.wireguard.util.Keyed; | import com.wireguard.util.Keyed; | ||||||
| 
 | 
 | ||||||
| import java.util.HashMap; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.regex.Pattern; |  | ||||||
| 
 |  | ||||||
| import java9.util.concurrent.CompletableFuture; | import java9.util.concurrent.CompletableFuture; | ||||||
| import java9.util.concurrent.CompletionStage; | import java9.util.concurrent.CompletionStage; | ||||||
| 
 | 
 | ||||||
| @ -29,28 +23,21 @@ import java9.util.concurrent.CompletionStage; | |||||||
|  * Encapsulates the volatile and nonvolatile state of a WireGuard tunnel. |  * Encapsulates the volatile and nonvolatile state of a WireGuard tunnel. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| public class Tunnel extends BaseObservable implements Keyed<String> { | public class ObservableTunnel extends BaseObservable implements Keyed<String>, Tunnel { | ||||||
|     public static final int NAME_MAX_LENGTH = 15; |  | ||||||
|     private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}"); |  | ||||||
| 
 |  | ||||||
|     private final TunnelManager manager; |     private final TunnelManager manager; | ||||||
|     @Nullable private Config config; |     @Nullable private Config config; | ||||||
|     private String name; |  | ||||||
|     private State state; |     private State state; | ||||||
|  |     private String name; | ||||||
|     @Nullable private Statistics statistics; |     @Nullable private Statistics statistics; | ||||||
| 
 | 
 | ||||||
|     Tunnel(final TunnelManager manager, final String name, |     ObservableTunnel(final TunnelManager manager, final String name, | ||||||
|            @Nullable final Config config, final State state) { |            @Nullable final Config config, final State state) { | ||||||
|         this.manager = manager; |  | ||||||
|         this.name = name; |         this.name = name; | ||||||
|  |         this.manager = manager; | ||||||
|         this.config = config; |         this.config = config; | ||||||
|         this.state = state; |         this.state = state; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static boolean isNameInvalid(final CharSequence name) { |  | ||||||
|         return !NAME_PATTERN.matcher(name).matches(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public CompletionStage<Void> delete() { |     public CompletionStage<Void> delete() { | ||||||
|         return manager.delete(this); |         return manager.delete(this); | ||||||
|     } |     } | ||||||
| @ -74,6 +61,7 @@ public class Tunnel extends BaseObservable implements Keyed<String> { | |||||||
|         return name; |         return name; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|     @Bindable |     @Bindable | ||||||
|     public String getName() { |     public String getName() { | ||||||
|         return name; |         return name; | ||||||
| @ -146,60 +134,4 @@ public class Tunnel extends BaseObservable implements Keyed<String> { | |||||||
|             return manager.setTunnelState(this, state); |             return manager.setTunnelState(this, state); | ||||||
|         return CompletableFuture.completedFuture(this.state); |         return CompletableFuture.completedFuture(this.state); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public enum State { |  | ||||||
|         DOWN, |  | ||||||
|         TOGGLE, |  | ||||||
|         UP; |  | ||||||
| 
 |  | ||||||
|         public static State of(final boolean running) { |  | ||||||
|             return running ? UP : DOWN; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static class Statistics extends BaseObservable { |  | ||||||
|         private long lastTouched = SystemClock.elapsedRealtime(); |  | ||||||
|         private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>(); |  | ||||||
| 
 |  | ||||||
|         public void add(final Key key, final long rx, final long tx) { |  | ||||||
|             peerBytes.put(key, Pair.create(rx, tx)); |  | ||||||
|             lastTouched = SystemClock.elapsedRealtime(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private boolean isStale() { |  | ||||||
|             return SystemClock.elapsedRealtime() - lastTouched > 900; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public Key[] peers() { |  | ||||||
|             return peerBytes.keySet().toArray(new Key[0]); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public long peerRx(final Key peer) { |  | ||||||
|             if (!peerBytes.containsKey(peer)) |  | ||||||
|                 return 0; |  | ||||||
|             return peerBytes.get(peer).first; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public long peerTx(final Key peer) { |  | ||||||
|             if (!peerBytes.containsKey(peer)) |  | ||||||
|                 return 0; |  | ||||||
|             return peerBytes.get(peer).second; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public long totalRx() { |  | ||||||
|             long rx = 0; |  | ||||||
|             for (final Pair<Long, Long> val : peerBytes.values()) { |  | ||||||
|                 rx += val.first; |  | ||||||
|             } |  | ||||||
|             return rx; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public long totalTx() { |  | ||||||
|             long tx = 0; |  | ||||||
|             for (final Pair<Long, Long> val : peerBytes.values()) { |  | ||||||
|                 tx += val.second; |  | ||||||
|             } |  | ||||||
|             return tx; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @ -15,9 +15,11 @@ import androidx.annotation.Nullable; | |||||||
| import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||||
| import com.wireguard.android.BR; | import com.wireguard.android.BR; | ||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
|  | import com.wireguard.android.backend.Backend.TunnelStateChangeNotificationReceiver; | ||||||
| import com.wireguard.android.configStore.ConfigStore; | import com.wireguard.android.configStore.ConfigStore; | ||||||
| import com.wireguard.android.model.Tunnel.State; | import com.wireguard.android.backend.Tunnel; | ||||||
| import com.wireguard.android.model.Tunnel.Statistics; | import com.wireguard.android.backend.Tunnel.State; | ||||||
|  | import com.wireguard.android.backend.Statistics; | ||||||
| import com.wireguard.android.util.ExceptionLoggers; | import com.wireguard.android.util.ExceptionLoggers; | ||||||
| import com.wireguard.android.util.ObservableSortedKeyedArrayList; | import com.wireguard.android.util.ObservableSortedKeyedArrayList; | ||||||
| import com.wireguard.android.util.ObservableSortedKeyedList; | import com.wireguard.android.util.ObservableSortedKeyedList; | ||||||
| @ -38,42 +40,49 @@ import java9.util.stream.StreamSupport; | |||||||
|  * Maintains and mediates changes to the set of available WireGuard tunnels, |  * Maintains and mediates changes to the set of available WireGuard tunnels, | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| public final class TunnelManager extends BaseObservable { | public final class TunnelManager extends BaseObservable implements TunnelStateChangeNotificationReceiver { | ||||||
|     private static final Comparator<String> COMPARATOR = Comparators.<String>thenComparing( |     private static final Comparator<String> COMPARATOR = Comparators.<String>thenComparing( | ||||||
|             String.CASE_INSENSITIVE_ORDER, Comparators.naturalOrder()); |             String.CASE_INSENSITIVE_ORDER, Comparators.naturalOrder()); | ||||||
|     private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel"; |     private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel"; | ||||||
|     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 CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>(); |     private final CompletableFuture<ObservableSortedKeyedList<String, ObservableTunnel>> completableTunnels = new CompletableFuture<>(); | ||||||
|     private final ConfigStore configStore; |     private final ConfigStore configStore; | ||||||
|     private final Context context = Application.get(); |     private final Context context = Application.get(); | ||||||
|     private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>(); |     private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>(); | ||||||
|     private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR); |     private final ObservableSortedKeyedList<String, ObservableTunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR); | ||||||
|     private boolean haveLoaded; |     private boolean haveLoaded; | ||||||
|     @Nullable private Tunnel lastUsedTunnel; |     @Nullable private ObservableTunnel lastUsedTunnel; | ||||||
| 
 | 
 | ||||||
|     public TunnelManager(final ConfigStore configStore) { |     public TunnelManager(final ConfigStore configStore) { | ||||||
|         this.configStore = configStore; |         this.configStore = configStore; | ||||||
|  |         Application.getBackendAsync().thenAccept(backend -> backend.registerStateChangeNotification(this)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static CompletionStage<State> getTunnelState(final Tunnel tunnel) { |     @Override | ||||||
|  |     protected void finalize() throws Throwable { | ||||||
|  |         Application.getBackendAsync().thenAccept(backend -> backend.unregisterStateChangeNotification(this)); | ||||||
|  |         super.finalize(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static CompletionStage<State> getTunnelState(final ObservableTunnel tunnel) { | ||||||
|         return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel)) |         return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel)) | ||||||
|                 .thenApply(tunnel::onStateChanged); |                 .thenApply(tunnel::onStateChanged); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) { |     static CompletionStage<Statistics> getTunnelStatistics(final ObservableTunnel tunnel) { | ||||||
|         return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel)) |         return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel)) | ||||||
|                 .thenApply(tunnel::onStatisticsChanged); |                 .thenApply(tunnel::onStatisticsChanged); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Tunnel addToList(final String name, @Nullable final Config config, final State state) { |     private ObservableTunnel addToList(final String name, @Nullable final Config config, final State state) { | ||||||
|         final Tunnel tunnel = new Tunnel(this, name, config, state); |         final ObservableTunnel tunnel = new ObservableTunnel(this, name, config, state); | ||||||
|         tunnels.add(tunnel); |         tunnels.add(tunnel); | ||||||
|         return tunnel; |         return tunnel; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public CompletionStage<Tunnel> create(final String name, @Nullable final Config config) { |     public CompletionStage<ObservableTunnel> create(final String name, @Nullable final Config config) { | ||||||
|         if (Tunnel.isNameInvalid(name)) |         if (Tunnel.isNameInvalid(name)) | ||||||
|             return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name))); |             return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name))); | ||||||
|         if (tunnels.containsKey(name)) { |         if (tunnels.containsKey(name)) { | ||||||
| @ -84,7 +93,7 @@ public final class TunnelManager extends BaseObservable { | |||||||
|                 .thenApply(savedConfig -> addToList(name, savedConfig, State.DOWN)); |                 .thenApply(savedConfig -> addToList(name, savedConfig, State.DOWN)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CompletionStage<Void> delete(final Tunnel tunnel) { |     CompletionStage<Void> delete(final ObservableTunnel tunnel) { | ||||||
|         final State originalState = tunnel.getState(); |         final State originalState = tunnel.getState(); | ||||||
|         final boolean wasLastUsed = tunnel == lastUsedTunnel; |         final boolean wasLastUsed = tunnel == lastUsedTunnel; | ||||||
|         // Make sure nothing touches the tunnel. |         // Make sure nothing touches the tunnel. | ||||||
| @ -93,12 +102,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) | ||||||
|                 Application.getBackend().setState(tunnel, State.DOWN); |                 Application.getBackend().setState(tunnel, State.DOWN, null); | ||||||
|             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) | ||||||
|                     Application.getBackend().setState(tunnel, State.UP); |                     Application.getBackend().setState(tunnel, State.UP, tunnel.getConfig()); | ||||||
|                 // Re-throw the exception to fail the completion. |                 // Re-throw the exception to fail the completion. | ||||||
|                 throw e; |                 throw e; | ||||||
|             } |             } | ||||||
| @ -114,22 +123,22 @@ public final class TunnelManager extends BaseObservable { | |||||||
| 
 | 
 | ||||||
|     @Bindable |     @Bindable | ||||||
|     @Nullable |     @Nullable | ||||||
|     public Tunnel getLastUsedTunnel() { |     public ObservableTunnel getLastUsedTunnel() { | ||||||
|         return lastUsedTunnel; |         return lastUsedTunnel; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CompletionStage<Config> getTunnelConfig(final Tunnel tunnel) { |     CompletionStage<Config> getTunnelConfig(final ObservableTunnel tunnel) { | ||||||
|         return Application.getAsyncWorker().supplyAsync(() -> configStore.load(tunnel.getName())) |         return Application.getAsyncWorker().supplyAsync(() -> configStore.load(tunnel.getName())) | ||||||
|                 .thenApply(tunnel::onConfigChanged); |                 .thenApply(tunnel::onConfigChanged); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> getTunnels() { |     public CompletableFuture<ObservableSortedKeyedList<String, ObservableTunnel>> getTunnels() { | ||||||
|         return completableTunnels; |         return completableTunnels; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void onCreate() { |     public void onCreate() { | ||||||
|         Application.getAsyncWorker().supplyAsync(configStore::enumerate) |         Application.getAsyncWorker().supplyAsync(configStore::enumerate) | ||||||
|                 .thenAcceptBoth(Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().enumerate()), this::onTunnelsLoaded) |                 .thenAcceptBoth(Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getRunningTunnelNames()), this::onTunnelsLoaded) | ||||||
|                 .whenComplete(ExceptionLoggers.E); |                 .whenComplete(ExceptionLoggers.E); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -159,9 +168,9 @@ public final class TunnelManager extends BaseObservable { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void refreshTunnelStates() { |     public void refreshTunnelStates() { | ||||||
|         Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().enumerate()) |         Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getRunningTunnelNames()) | ||||||
|                 .thenAccept(running -> { |                 .thenAccept(running -> { | ||||||
|                     for (final Tunnel tunnel : tunnels) |                     for (final ObservableTunnel tunnel : tunnels) | ||||||
|                         tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN); |                         tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN); | ||||||
|                 }) |                 }) | ||||||
|                 .whenComplete(ExceptionLoggers.E); |                 .whenComplete(ExceptionLoggers.E); | ||||||
| @ -189,12 +198,12 @@ public final class TunnelManager extends BaseObservable { | |||||||
|     public void saveState() { |     public void saveState() { | ||||||
|         final Set<String> runningTunnels = StreamSupport.stream(tunnels) |         final Set<String> runningTunnels = StreamSupport.stream(tunnels) | ||||||
|                 .filter(tunnel -> tunnel.getState() == State.UP) |                 .filter(tunnel -> tunnel.getState() == State.UP) | ||||||
|                 .map(Tunnel::getName) |                 .map(ObservableTunnel::getName) | ||||||
|                 .collect(Collectors.toUnmodifiableSet()); |                 .collect(Collectors.toUnmodifiableSet()); | ||||||
|         Application.getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply(); |         Application.getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setLastUsedTunnel(@Nullable final Tunnel tunnel) { |     private void setLastUsedTunnel(@Nullable final ObservableTunnel tunnel) { | ||||||
|         if (tunnel == lastUsedTunnel) |         if (tunnel == lastUsedTunnel) | ||||||
|             return; |             return; | ||||||
|         lastUsedTunnel = tunnel; |         lastUsedTunnel = tunnel; | ||||||
| @ -205,14 +214,14 @@ public final class TunnelManager extends BaseObservable { | |||||||
|             Application.getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).apply(); |             Application.getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).apply(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) { |     CompletionStage<Config> setTunnelConfig(final ObservableTunnel tunnel, final Config config) { | ||||||
|         return Application.getAsyncWorker().supplyAsync(() -> { |         return Application.getAsyncWorker().supplyAsync(() -> { | ||||||
|             final Config appliedConfig = Application.getBackend().applyConfig(tunnel, config); |             Application.getBackend().setState(tunnel, tunnel.getState(), config); | ||||||
|             return configStore.save(tunnel.getName(), appliedConfig); |             return configStore.save(tunnel.getName(), config); | ||||||
|         }).thenApply(tunnel::onConfigChanged); |         }).thenApply(tunnel::onConfigChanged); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CompletionStage<String> setTunnelName(final Tunnel tunnel, final String name) { |     CompletionStage<String> setTunnelName(final ObservableTunnel tunnel, final String name) { | ||||||
|         if (Tunnel.isNameInvalid(name)) |         if (Tunnel.isNameInvalid(name)) | ||||||
|             return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name))); |             return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name))); | ||||||
|         if (tunnels.containsKey(name)) { |         if (tunnels.containsKey(name)) { | ||||||
| @ -227,11 +236,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) | ||||||
|                 Application.getBackend().setState(tunnel, State.DOWN); |                 Application.getBackend().setState(tunnel, State.DOWN, null); | ||||||
|             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) | ||||||
|                 Application.getBackend().setState(tunnel, State.UP); |                 Application.getBackend().setState(tunnel, State.UP, tunnel.getConfig()); | ||||||
|             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. | ||||||
| @ -244,10 +253,10 @@ public final class TunnelManager extends BaseObservable { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CompletionStage<State> setTunnelState(final Tunnel tunnel, final State state) { |     CompletionStage<State> setTunnelState(final ObservableTunnel 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(config -> | ||||||
|                 Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().setState(tunnel, state)) |                 Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().setState(tunnel, state, config)) | ||||||
|         ).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()); | ||||||
| @ -257,6 +266,11 @@ public final class TunnelManager extends BaseObservable { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public void tunnelStateChange(final Tunnel tunnel, final State state) { | ||||||
|  |         ((ObservableTunnel)tunnel).onStateChanged(state); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static final class IntentReceiver extends BroadcastReceiver { |     public static final class IntentReceiver extends BroadcastReceiver { | ||||||
|         @Override |         @Override | ||||||
|         public void onReceive(final Context context, @Nullable final Intent intent) { |         public void onReceive(final Context context, @Nullable final Intent intent) { | ||||||
| @ -290,7 +304,7 @@ public final class TunnelManager extends BaseObservable { | |||||||
|             if (tunnelName == null) |             if (tunnelName == null) | ||||||
|                 return; |                 return; | ||||||
|             manager.getTunnels().thenAccept(tunnels -> { |             manager.getTunnels().thenAccept(tunnels -> { | ||||||
|                 final Tunnel tunnel = tunnels.get(tunnelName); |                 final ObservableTunnel tunnel = tunnels.get(tunnelName); | ||||||
|                 if (tunnel == null) |                 if (tunnel == null) | ||||||
|                     return; |                     return; | ||||||
|                 manager.setTunnelState(tunnel, state); |                 manager.setTunnelState(tunnel, state); | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import android.util.Log; | |||||||
| 
 | 
 | ||||||
| import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||||
| import com.wireguard.android.R; | import com.wireguard.android.R; | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.ObservableTunnel; | ||||||
| import com.wireguard.android.util.DownloadsFileSaver; | import com.wireguard.android.util.DownloadsFileSaver; | ||||||
| import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile; | import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile; | ||||||
| import com.wireguard.android.util.ErrorMessages; | import com.wireguard.android.util.ErrorMessages; | ||||||
| @ -48,9 +48,9 @@ public class ZipExporterPreference extends Preference { | |||||||
|         Application.getTunnelManager().getTunnels().thenAccept(this::exportZip); |         Application.getTunnelManager().getTunnels().thenAccept(this::exportZip); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void exportZip(final List<Tunnel> tunnels) { |     private void exportZip(final List<ObservableTunnel> tunnels) { | ||||||
|         final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size()); |         final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size()); | ||||||
|         for (final Tunnel tunnel : tunnels) |         for (final ObservableTunnel tunnel : tunnels) | ||||||
|             futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture()); |             futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture()); | ||||||
|         if (futureConfigs.isEmpty()) { |         if (futureConfigs.isEmpty()) { | ||||||
|             exportZipComplete(null, new IllegalArgumentException( |             exportZipComplete(null, new IllegalArgumentException( | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ import android.text.InputFilter; | |||||||
| import android.text.SpannableStringBuilder; | import android.text.SpannableStringBuilder; | ||||||
| import android.text.Spanned; | import android.text.Spanned; | ||||||
| 
 | 
 | ||||||
| import com.wireguard.android.model.Tunnel; | import com.wireguard.android.backend.Tunnel; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * InputFilter for entering WireGuard configuration names (Linux interface names). |  * InputFilter for entering WireGuard configuration names (Linux interface names). | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| 
 | 
 | ||||||
|     <data> |     <data> | ||||||
| 
 | 
 | ||||||
|         <import type="com.wireguard.android.model.Tunnel.State" /> |         <import type="com.wireguard.android.backend.Tunnel.State" /> | ||||||
| 
 | 
 | ||||||
|         <import type="com.wireguard.android.util.ClipboardUtils" /> |         <import type="com.wireguard.android.util.ClipboardUtils" /> | ||||||
| 
 | 
 | ||||||
| @ -15,7 +15,7 @@ | |||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="tunnel" |             name="tunnel" | ||||||
|             type="com.wireguard.android.model.Tunnel" /> |             type="com.wireguard.android.model.ObservableTunnel" /> | ||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="config" |             name="config" | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| 
 | 
 | ||||||
|     <data> |     <data> | ||||||
| 
 | 
 | ||||||
|         <import type="com.wireguard.android.model.Tunnel" /> |         <import type="com.wireguard.android.model.ObservableTunnel" /> | ||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="fragment" |             name="fragment" | ||||||
| @ -17,7 +17,7 @@ | |||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="tunnels" |             name="tunnels" | ||||||
|             type="com.wireguard.android.util.ObservableKeyedList<String, Tunnel>" /> |             type="com.wireguard.android.util.ObservableKeyedList<String, ObservableTunnel>" /> | ||||||
|     </data> |     </data> | ||||||
| 
 | 
 | ||||||
|     <androidx.coordinatorlayout.widget.CoordinatorLayout |     <androidx.coordinatorlayout.widget.CoordinatorLayout | ||||||
|  | |||||||
| @ -5,13 +5,13 @@ | |||||||
| 
 | 
 | ||||||
|     <data> |     <data> | ||||||
| 
 | 
 | ||||||
|         <import type="com.wireguard.android.model.Tunnel" /> |         <import type="com.wireguard.android.model.ObservableTunnel" /> | ||||||
| 
 | 
 | ||||||
|         <import type="com.wireguard.android.model.Tunnel.State" /> |         <import type="com.wireguard.android.backend.Tunnel.State" /> | ||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="collection" |             name="collection" | ||||||
|             type="com.wireguard.android.util.ObservableKeyedList<String, Tunnel>" /> |             type="com.wireguard.android.util.ObservableKeyedList<String, ObservableTunnel>" /> | ||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="key" |             name="key" | ||||||
| @ -19,7 +19,7 @@ | |||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="item" |             name="item" | ||||||
|             type="com.wireguard.android.model.Tunnel" /> |             type="com.wireguard.android.model.ObservableTunnel" /> | ||||||
| 
 | 
 | ||||||
|         <variable |         <variable | ||||||
|             name="fragment" |             name="fragment" | ||||||
|  | |||||||
| @ -104,7 +104,6 @@ | |||||||
|     <string name="module_installer_working">डाउनलोड कर रहा है और स्थापित कर रहा है…</string> |     <string name="module_installer_working">डाउनलोड कर रहा है और स्थापित कर रहा है…</string> | ||||||
|     <string name="module_installer_error">कुछ गलत हो गया। कृपया पुन: प्रयास करें</string> |     <string name="module_installer_error">कुछ गलत हो गया। कृपया पुन: प्रयास करें</string> | ||||||
|     <string name="mtu">MTU</string> |     <string name="mtu">MTU</string> | ||||||
|     <string name="multiple_tunnels_error">एक समय में केवल एक यूजरस्पेस टनल ही चल सकता है</string> |  | ||||||
|     <string name="name">नाम</string> |     <string name="name">नाम</string> | ||||||
|     <string name="no_config_error">बिना किसी कॉन्फ़िगरेशन के एक टनल को लाने की कोशिश करना</string> |     <string name="no_config_error">बिना किसी कॉन्फ़िगरेशन के एक टनल को लाने की कोशिश करना</string> | ||||||
|     <string name="no_configs_error">कोई कॉन्फ़िगरेशन नहीं मिला</string> |     <string name="no_configs_error">कोई कॉन्फ़िगरेशन नहीं मिला</string> | ||||||
|  | |||||||
| @ -104,7 +104,6 @@ | |||||||
|     <string name="module_installer_working">Scaricamento e installazione…</string> |     <string name="module_installer_working">Scaricamento e installazione…</string> | ||||||
|     <string name="module_installer_error">Qualcosa è andato storto. Riprova</string> |     <string name="module_installer_error">Qualcosa è andato storto. Riprova</string> | ||||||
|     <string name="mtu">MTU</string> |     <string name="mtu">MTU</string> | ||||||
|     <string name="multiple_tunnels_error">Può essere attivo solo un tunnel su spazio utente alla volta</string> |  | ||||||
|     <string name="name">Nome</string> |     <string name="name">Nome</string> | ||||||
|     <string name="no_config_error">Tentativo di attivare un tunnel senza configurazione</string> |     <string name="no_config_error">Tentativo di attivare un tunnel senza configurazione</string> | ||||||
|     <string name="no_configs_error">Nessuna configurazione trovata</string> |     <string name="no_configs_error">Nessuna configurazione trovata</string> | ||||||
|  | |||||||
| @ -98,7 +98,6 @@ | |||||||
|     <string name="module_installer_working">ダウンロードしてインストールしています…</string> |     <string name="module_installer_working">ダウンロードしてインストールしています…</string> | ||||||
|     <string name="module_installer_error">失敗しました. 再度実行してみてください</string> |     <string name="module_installer_error">失敗しました. 再度実行してみてください</string> | ||||||
|     <string name="mtu">MTU</string> |     <string name="mtu">MTU</string> | ||||||
|     <string name="multiple_tunnels_error">同時に実行できるユーザースペーストンネルは1つだけです</string> |  | ||||||
|     <string name="name">名前</string> |     <string name="name">名前</string> | ||||||
|     <string name="no_config_error">未設定のままトンネルを起動しようとしています</string> |     <string name="no_config_error">未設定のままトンネルを起動しようとしています</string> | ||||||
|     <string name="no_configs_error">設定が見つかりません</string> |     <string name="no_configs_error">設定が見つかりません</string> | ||||||
|  | |||||||
| @ -104,7 +104,6 @@ | |||||||
|     <string name="module_installer_working">Скачивание и установка…</string> |     <string name="module_installer_working">Скачивание и установка…</string> | ||||||
|     <string name="module_installer_error">Что-то пошло не так. Пожалуйста, попробуйте еще раз</string> |     <string name="module_installer_error">Что-то пошло не так. Пожалуйста, попробуйте еще раз</string> | ||||||
|     <string name="mtu">MTU</string> |     <string name="mtu">MTU</string> | ||||||
|     <string name="multiple_tunnels_error">Только один пользовательский туннель может работать одновременно</string> |  | ||||||
|     <string name="name">Имя</string> |     <string name="name">Имя</string> | ||||||
|     <string name="no_config_error">Попытка поднять туннель без конфигурации</string> |     <string name="no_config_error">Попытка поднять туннель без конфигурации</string> | ||||||
|     <string name="no_configs_error">Конфигурации не найдены</string> |     <string name="no_configs_error">Конфигурации не найдены</string> | ||||||
|  | |||||||
| @ -98,7 +98,6 @@ | |||||||
|     <string name="module_installer_working">正在下载安装...</string> |     <string name="module_installer_working">正在下载安装...</string> | ||||||
|     <string name="module_installer_error">发生错误,请重试</string> |     <string name="module_installer_error">发生错误,请重试</string> | ||||||
|     <string name="mtu">MTU</string> |     <string name="mtu">MTU</string> | ||||||
|     <string name="multiple_tunnels_error">用户空间内一次只能建立一个连接</string> |  | ||||||
|     <string name="name">名称</string> |     <string name="name">名称</string> | ||||||
|     <string name="no_config_error">尝试在无配置情况下建立连接</string> |     <string name="no_config_error">尝试在无配置情况下建立连接</string> | ||||||
|     <string name="no_configs_error">未找到配置</string> |     <string name="no_configs_error">未找到配置</string> | ||||||
|  | |||||||
| @ -104,7 +104,6 @@ | |||||||
|     <string name="module_installer_working">Downloading and installing…</string> |     <string name="module_installer_working">Downloading and installing…</string> | ||||||
|     <string name="module_installer_error">Something went wrong. Please try again</string> |     <string name="module_installer_error">Something went wrong. Please try again</string> | ||||||
|     <string name="mtu">MTU</string> |     <string name="mtu">MTU</string> | ||||||
|     <string name="multiple_tunnels_error">Only one userspace tunnel can run at a time</string> |  | ||||||
|     <string name="name">Name</string> |     <string name="name">Name</string> | ||||||
|     <string name="no_config_error">Trying to bring up a tunnel with no config</string> |     <string name="no_config_error">Trying to bring up a tunnel with no config</string> | ||||||
|     <string name="no_configs_error">No configurations found</string> |     <string name="no_configs_error">No configurations found</string> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user