diff options
Diffstat (limited to 'app/src/main')
42 files changed, 819 insertions, 810 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b97dbc7d..02930b27 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,10 +11,10 @@ <permission android:name="${applicationId}.permission.CONTROL_TUNNELS" - android:protectionLevel="dangerous" + android:description="@string/permission_description" android:icon="@mipmap/ic_launcher" android:label="@string/permission_label" - android:description="@string/permission_description" /> + android:protectionLevel="dangerous" /> <application android:name=".Application" diff --git a/app/src/main/java/com/wireguard/android/Application.java b/app/src/main/java/com/wireguard/android/Application.java index d528629a..9921f615 100644 --- a/app/src/main/java/com/wireguard/android/Application.java +++ b/app/src/main/java/com/wireguard/android/Application.java @@ -53,18 +53,50 @@ import java9.util.concurrent.CompletableFuture; compress = true) public class Application extends android.app.Application { @SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf; + private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>(); @SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker; + @Nullable private Backend backend; @SuppressWarnings("NullableProblems") private RootShell rootShell; @SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences; @SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller; @SuppressWarnings("NullableProblems") private TunnelManager tunnelManager; - @Nullable private Backend backend; - private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>(); public Application() { weakSelf = new WeakReference<>(this); } + public static Application get() { + return weakSelf.get(); + } + + public static AsyncWorker getAsyncWorker() { + return get().asyncWorker; + } + + public static Backend getBackend() { + final Application app = get(); + synchronized (app.futureBackend) { + if (app.backend == null) { + Backend backend = null; + if (new File("/sys/module/wireguard").exists()) { + try { + app.rootShell.start(); + backend = new WgQuickBackend(app.getApplicationContext()); + } catch (final Exception ignored) { + } + } + if (backend == null) + backend = new GoBackend(app.getApplicationContext()); + app.backend = backend; + } + return app.backend; + } + } + + public static CompletableFuture<Backend> getBackendAsync() { + return get().futureBackend; + } + /* The ACRA password can be trivially reverse engineered and is open source anyway, * so there's no point in trying to protect it. However, we do want to at least * prevent innocent self-builders from uploading stuff to our crash reporter. So, we @@ -91,12 +123,30 @@ public class Application extends android.app.Application { return "F-Droid"; } } - } catch (final Exception ignored) { } + } catch (final Exception ignored) { + } } - } catch (final Exception ignored) { } + } catch (final Exception ignored) { + } return null; } + public static RootShell getRootShell() { + return get().rootShell; + } + + public static SharedPreferences getSharedPreferences() { + return get().sharedPreferences; + } + + public static ToolsInstaller getToolsInstaller() { + return get().toolsInstaller; + } + + public static TunnelManager getTunnelManager() { + return get().tunnelManager; + } + @Override protected void attachBaseContext(final Context context) { super.attachBaseContext(context); @@ -117,54 +167,6 @@ public class Application extends android.app.Application { } } - public static Application get() { - return weakSelf.get(); - } - - public static AsyncWorker getAsyncWorker() { - return get().asyncWorker; - } - - public static Backend getBackend() { - final Application app = get(); - synchronized (app.futureBackend) { - if (app.backend == null) { - Backend backend = null; - if (new File("/sys/module/wireguard").exists()) { - try { - app.rootShell.start(); - backend = new WgQuickBackend(app.getApplicationContext()); - } catch (final Exception ignored) { - } - } - if (backend == null) - backend = new GoBackend(app.getApplicationContext()); - app.backend = backend; - } - return app.backend; - } - } - - public static CompletableFuture<Backend> getBackendAsync() { - return get().futureBackend; - } - - public static RootShell getRootShell() { - return get().rootShell; - } - - public static SharedPreferences getSharedPreferences() { - return get().sharedPreferences; - } - - public static ToolsInstaller getToolsInstaller() { - return get().toolsInstaller; - } - - public static TunnelManager getTunnelManager() { - return get().tunnelManager; - } - @Override public void onCreate() { super.onCreate(); diff --git a/app/src/main/java/com/wireguard/android/QuickTileService.java b/app/src/main/java/com/wireguard/android/QuickTileService.java index 28e2b2ee..2463896e 100644 --- a/app/src/main/java/com/wireguard/android/QuickTileService.java +++ b/app/src/main/java/com/wireguard/android/QuickTileService.java @@ -40,9 +40,9 @@ public class QuickTileService extends TileService { private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback(); private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback(); - @Nullable private Tunnel tunnel; - @Nullable private Icon iconOn; @Nullable private Icon iconOff; + @Nullable private Icon iconOn; + @Nullable private Tunnel tunnel; /* This works around an annoying unsolved frameworks bug some people are hitting. */ @Override @@ -58,6 +58,22 @@ public class QuickTileService extends TileService { } @Override + public void onClick() { + if (tunnel != null) { + final Tile tile = getQsTile(); + if (tile != null) { + tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn); + tile.updateTile(); + } + tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished); + } else { + final Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityAndCollapse(intent); + } + } + + @Override public void onCreate() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { iconOff = iconOn = Icon.createWithResource(this, R.drawable.ic_tile); @@ -80,22 +96,6 @@ public class QuickTileService extends TileService { } @Override - public void onClick() { - if (tunnel != null) { - final Tile tile = getQsTile(); - if (tile != null) { - tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn); - tile.updateTile(); - } - tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished); - } else { - final Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityAndCollapse(intent); - } - } - - @Override public void onStartListening() { Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback); if (tunnel != null) diff --git a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java index f8fea403..b827ca1b 100644 --- a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java @@ -92,8 +92,8 @@ public class SettingsActivity extends ThemeChangeAwareActivity { public void onCreatePreferences(final Bundle savedInstanceState, final String key) { addPreferencesFromResource(R.xml.preferences); final Preference wgQuickOnlyPrefs[] = { - getPreferenceManager().findPreference("tools_installer"), - getPreferenceManager().findPreference("restore_on_boot") + getPreferenceManager().findPreference("tools_installer"), + getPreferenceManager().findPreference("restore_on_boot") }; for (final Preference pref : wgQuickOnlyPrefs) pref.setVisible(false); diff --git a/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java b/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java index 9a6e2edc..fbae2165 100644 --- a/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java @@ -19,9 +19,9 @@ import java.lang.reflect.Field; public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName(); - - @Nullable private static Resources lastResources; private static boolean lastDarkMode; + @Nullable private static Resources lastResources; + private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) { if (resources == lastResources && darkMode == lastDarkMode) return; @@ -33,7 +33,8 @@ public abstract class ThemeChangeAwareActivity extends AppCompatActivity impleme f = o.getClass().getDeclaredField("mResourcesImpl"); f.setAccessible(true); o = f.get(o); - } catch (final Exception ignored) { } + } catch (final Exception ignored) { + } f = o.getClass().getDeclaredField("mDrawableCache"); f.setAccessible(true); o = f.get(o); diff --git a/app/src/main/java/com/wireguard/android/backend/Backend.java b/app/src/main/java/com/wireguard/android/backend/Backend.java index 000a572a..64bb3d02 100644 --- a/app/src/main/java/com/wireguard/android/backend/Backend.java +++ b/app/src/main/java/com/wireguard/android/backend/Backend.java @@ -53,14 +53,11 @@ public interface Backend { Statistics getStatistics(Tunnel tunnel) throws Exception; /** - * Set the state of a tunnel. + * Determine type name of underlying backend. * - * @param tunnel The tunnel to control the state of. - * @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or - * {@code TOGGLE}. - * @return The updated state of the tunnel. + * @return Type name */ - State setState(Tunnel tunnel, State state) throws Exception; + String getTypeName(); /** * Determine version of underlying backend. @@ -71,9 +68,12 @@ public interface Backend { String getVersion() throws Exception; /** - * Determine type name of underlying backend. + * Set the state of a tunnel. * - * @return Type name + * @param tunnel The tunnel to control the state of. + * @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or + * {@code TOGGLE}. + * @return The updated state of the tunnel. */ - String getTypeName(); + State setState(Tunnel tunnel, State state) throws Exception; } diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java index 0d21c599..295df9d0 100644 --- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -60,12 +60,6 @@ public final class GoBackend implements Backend { private static native String wgVersion(); @Override - public String getVersion() { return wgVersion(); } - - @Override - public String getTypeName() { return "Go userspace"; } - - @Override public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception { if (tunnel.getState() == State.UP) { // Restart the tunnel to apply the new config. @@ -102,6 +96,16 @@ public final class GoBackend implements Backend { } @Override + public String getTypeName() { + return "Go userspace"; + } + + @Override + public String getVersion() { + return wgVersion(); + } + + @Override public State setState(final Tunnel tunnel, State state) throws Exception { final State originalState = getState(tunnel); if (state == State.TOGGLE) diff --git a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java index a4edfc27..bfc363a4 100644 --- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java @@ -41,18 +41,6 @@ public final class WgQuickBackend implements Backend { } @Override - public String getVersion() throws Exception { - final List<String> output = new ArrayList<>(); - if (Application.getRootShell() - .run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty()) - throw new Exception("Unable to determine kernel module version"); - return output.get(0); - } - - @Override - public String getTypeName() { return "Kernel module"; } - - @Override public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception { if (tunnel.getState() == State.UP) { // Restart the tunnel to apply the new config. @@ -95,6 +83,20 @@ public final class WgQuickBackend implements Backend { } @Override + public String getTypeName() { + return "Kernel module"; + } + + @Override + public String getVersion() throws Exception { + final List<String> output = new ArrayList<>(); + if (Application.getRootShell() + .run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty()) + throw new Exception("Unable to determine kernel module version"); + return output.get(0); + } + + @Override public State setState(final Tunnel tunnel, State state) throws Exception { final State originalState = getState(tunnel); if (state == State.TOGGLE) diff --git a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java b/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java index 0420dee0..26e7687f 100644 --- a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java +++ b/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java @@ -40,11 +40,6 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> setList(list); } - @Override - public int getItemCount() { - return list != null ? list.size() : 0; - } - @Nullable private E getItem(final int position) { if (list == null || position < 0 || position >= list.size()) @@ -53,6 +48,11 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> } @Override + public int getItemCount() { + return list != null ? list.size() : 0; + } + + @Override public long getItemId(final int position) { final K key = getKey(position); return key != null ? key.hashCode() : -1; @@ -64,11 +64,6 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> return item != null ? item.getKey() : null; } - @Override - public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { - return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false)); - } - @SuppressWarnings("unchecked") @Override public void onBindViewHolder(final ViewHolder holder, final int position) { @@ -85,6 +80,11 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> } } + @Override + public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { + return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false)); + } + void setList(@Nullable final ObservableKeyedList<K, E> newList) { if (list != null) list.removeOnListChangedCallback(callback); @@ -99,6 +99,10 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> this.rowConfigurationHandler = rowConfigurationHandler; } + public interface RowConfigurationHandler<B extends ViewDataBinding, T> { + void onConfigureRow(B binding, T item, int position); + } + private static final class OnListChangedCallback<E extends Keyed<?>> extends ObservableList.OnListChangedCallback<ObservableList<E>> { @@ -152,8 +156,4 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> } } - public interface RowConfigurationHandler<B extends ViewDataBinding, T> { - void onConfigureRow(B binding, T item, int position); - } - } diff --git a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java index 8bc44ddf..8bf5a22d 100644 --- a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java @@ -34,9 +34,8 @@ import java.util.List; public class AppListDialogFragment extends DialogFragment { private static final String KEY_EXCLUDED_APPS = "excludedApps"; - - private List<String> currentlyExcludedApps; private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>(); + private List<String> currentlyExcludedApps; public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(final String[] excludedApps, final T target) { final Bundle extras = new Bundle(); @@ -47,39 +46,6 @@ public class AppListDialogFragment extends DialogFragment { return fragment; } - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS)); - } - - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()); - alertDialogBuilder.setTitle(R.string.excluded_applications); - - final AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false); - binding.executePendingBindings(); - alertDialogBuilder.setView(binding.getRoot()); - - alertDialogBuilder.setPositiveButton(R.string.set_exclusions, (dialog, which) -> setExclusionsAndDismiss()); - alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); - alertDialogBuilder.setNeutralButton(R.string.deselect_all, (dialog, which) -> { }); - - binding.setFragment(this); - binding.setAppData(appData); - - loadData(); - - final AlertDialog dialog = alertDialogBuilder.create(); - dialog.setOnShowListener(d -> dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(view -> { - for (final ApplicationData app : appData) - app.setExcludedFromTunnel(false); - })); - return dialog; - } - private void loadData() { final Activity activity = getActivity(); if (activity == null) { @@ -113,6 +79,40 @@ public class AppListDialogFragment extends DialogFragment { })); } + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS)); + } + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()); + alertDialogBuilder.setTitle(R.string.excluded_applications); + + final AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false); + binding.executePendingBindings(); + alertDialogBuilder.setView(binding.getRoot()); + + alertDialogBuilder.setPositiveButton(R.string.set_exclusions, (dialog, which) -> setExclusionsAndDismiss()); + alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + alertDialogBuilder.setNeutralButton(R.string.deselect_all, (dialog, which) -> { + }); + + binding.setFragment(this); + binding.setAppData(appData); + + loadData(); + + final AlertDialog dialog = alertDialogBuilder.create(); + dialog.setOnShowListener(d -> dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(view -> { + for (final ApplicationData app : appData) + app.setExcludedFromTunnel(false); + })); + return dialog; + } + void setExclusionsAndDismiss() { final List<String> excludedApps = new ArrayList<>(); for (final ApplicationData data : appData) { diff --git a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java index ca1b9922..e3003f2e 100644 --- a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java @@ -33,9 +33,8 @@ import com.wireguard.android.util.ExceptionLoggers; */ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener { - private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName(); private static final int REQUEST_CODE_VPN_PERMISSION = 23491; - + private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName(); @Nullable private BaseActivity activity; @Nullable private Tunnel pendingTunnel; @Nullable private Boolean pendingTunnelUp; @@ -46,6 +45,18 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC } @Override + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_CODE_VPN_PERMISSION) { + if (pendingTunnel != null && pendingTunnelUp != null) + setTunnelStateWithPermissionsResult(pendingTunnel, pendingTunnelUp); + pendingTunnel = null; + pendingTunnelUp = null; + } + } + + @Override public void onAttach(final Context context) { super.onAttach(context); if (context instanceof BaseActivity) { @@ -64,18 +75,6 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC super.onDetach(); } - @Override - public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == REQUEST_CODE_VPN_PERMISSION) { - if (pendingTunnel != null && pendingTunnelUp != null) - setTunnelStateWithPermissionsResult(pendingTunnel, pendingTunnelUp); - pendingTunnel = null; - pendingTunnelUp = null; - } - } - protected void setSelectedTunnel(@Nullable final Tunnel tunnel) { if (activity != null) activity.setSelectedTunnel(tunnel); diff --git a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java index 3d2c6294..83799818 100644 --- a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java @@ -13,7 +13,6 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; -import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import com.wireguard.android.Application; @@ -27,9 +26,8 @@ import java.util.Objects; public class ConfigNamingDialogFragment extends DialogFragment { private static final String KEY_CONFIG_TEXT = "config_text"; - - @Nullable private Config config; @Nullable private ConfigNamingDialogFragmentBinding binding; + @Nullable private Config config; @Nullable private InputMethodManager imm; public static ConfigNamingDialogFragment newInstance(final String configText) { @@ -40,6 +38,26 @@ public class ConfigNamingDialogFragment extends DialogFragment { return fragment; } + private void createTunnelAndDismiss() { + if (binding != null) { + final String name = binding.tunnelNameText.getText().toString(); + + Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> { + if (tunnel != null) { + dismiss(); + } else { + binding.tunnelNameTextLayout.setError(throwable.getMessage()); + } + }); + } + } + + @Override + public void dismiss() { + setKeyboardVisible(false); + super.dismiss(); + } + @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -51,17 +69,6 @@ public class ConfigNamingDialogFragment extends DialogFragment { } } - @Override public void onResume() { - super.onResume(); - - final AlertDialog dialog = (AlertDialog) getDialog(); - if (dialog != null) { - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss()); - - setKeyboardVisible(true); - } - } - @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { final Activity activity = getActivity(); @@ -81,23 +88,14 @@ public class ConfigNamingDialogFragment extends DialogFragment { return alertDialogBuilder.create(); } - @Override - public void dismiss() { - setKeyboardVisible(false); - super.dismiss(); - } + @Override public void onResume() { + super.onResume(); - private void createTunnelAndDismiss() { - if (binding != null) { - final String name = binding.tunnelNameText.getText().toString(); + final AlertDialog dialog = (AlertDialog) getDialog(); + if (dialog != null) { + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss()); - Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> { - if (tunnel != null) { - dismiss(); - } else { - binding.tunnelNameTextLayout.setError(throwable.getMessage()); - } - }); + setKeyboardVisible(true); } } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java index b51feb37..8f319e1e 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java @@ -48,8 +48,59 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi private static final String KEY_LOCAL_CONFIG = "local_config"; private static final String KEY_ORIGINAL_NAME = "original_name"; private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName(); - + private final Collection<Object> breakObjectOrientedLayeringHandlerReceivers = new ArrayList<>(); @Nullable private TunnelEditorFragmentBinding binding; + private final Observable.OnPropertyChangedCallback breakObjectOrientedLayeringHandler = new Observable.OnPropertyChangedCallback() { + @Override + public void onPropertyChanged(final Observable sender, final int propertyId) { + if (binding == null) + return; + final Config.Observable config = binding.getConfig(); + if (config == null) + return; + if (propertyId == BR.config) { + config.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler); + breakObjectOrientedLayeringHandlerReceivers.add(config); + config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler); + breakObjectOrientedLayeringHandlerReceivers.add(config.getInterfaceSection()); + config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler); + breakObjectOrientedLayeringHandlerReceivers.add(config.getPeers()); + } else if (propertyId == BR.dnses || propertyId == BR.peers) + ; + else + return; + final int numSiblings = config.getPeers().size() - 1; + for (final Peer.Observable peer : config.getPeers()) { + peer.setInterfaceDNSRoutes(config.getInterfaceSection().getDnses()); + peer.setNumSiblings(numSiblings); + } + } + }; + private final ObservableList.OnListChangedCallback<? extends ObservableList<Peer.Observable>> breakObjectListOrientedLayeringHandler = new ObservableList.OnListChangedCallback<ObservableList<Peer.Observable>>() { + @Override + public void onChanged(final ObservableList<Peer.Observable> sender) { + } + + @Override + public void onItemRangeChanged(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { + } + + @Override + public void onItemRangeInserted(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { + if (binding != null) + breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers); + } + + @Override + public void onItemRangeMoved(final ObservableList<Peer.Observable> sender, final int fromPosition, final int toPosition, final int itemCount) { + } + + @Override + public void onItemRangeRemoved(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { + if (binding != null) + breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers); + } + }; @Nullable private Tunnel tunnel; private void onConfigLoaded(final String name, final Config config) { @@ -87,54 +138,6 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi inflater.inflate(R.menu.config_editor, menu); } - private final ObservableList.OnListChangedCallback<? extends ObservableList<Peer.Observable>> breakObjectListOrientedLayeringHandler = new ObservableList.OnListChangedCallback<ObservableList<Peer.Observable>>() { - @Override - public void onChanged(final ObservableList<Peer.Observable> sender) { } - @Override - public void onItemRangeChanged(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { } - @Override - public void onItemRangeMoved(final ObservableList<Peer.Observable> sender, final int fromPosition, final int toPosition, final int itemCount) { } - - @Override - public void onItemRangeInserted(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { - if (binding != null) - breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers); - } - @Override - public void onItemRangeRemoved(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { - if (binding != null) - breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers); - } - }; - - private final Collection<Object> breakObjectOrientedLayeringHandlerReceivers = new ArrayList<>(); - private final Observable.OnPropertyChangedCallback breakObjectOrientedLayeringHandler = new Observable.OnPropertyChangedCallback() { - @Override - public void onPropertyChanged(final Observable sender, final int propertyId) { - if (binding == null) - return; - final Config.Observable config = binding.getConfig(); - if (config == null) - return; - if (propertyId == BR.config) { - config.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler); - breakObjectOrientedLayeringHandlerReceivers.add(config); - config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler); - breakObjectOrientedLayeringHandlerReceivers.add(config.getInterfaceSection()); - config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler); - breakObjectOrientedLayeringHandlerReceivers.add(config.getPeers()); - } else if (propertyId == BR.dnses || propertyId == BR.peers) - ; - else - return; - final int numSiblings = config.getPeers().size() - 1; - for (final Peer.Observable peer : config.getPeers()) { - peer.setInterfaceDNSRoutes(config.getInterfaceSection().getDnses()); - peer.setNumSiblings(numSiblings); - } - } - }; - @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { @@ -152,13 +155,19 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi binding = null; for (final Object o : breakObjectOrientedLayeringHandlerReceivers) { if (o instanceof Observable) - ((Observable)o).removeOnPropertyChangedCallback(breakObjectOrientedLayeringHandler); + ((Observable) o).removeOnPropertyChangedCallback(breakObjectOrientedLayeringHandler); else if (o instanceof ObservableList) - ((ObservableList)o).removeOnListChangedCallback(breakObjectListOrientedLayeringHandler); + ((ObservableList) o).removeOnListChangedCallback(breakObjectListOrientedLayeringHandler); } super.onDestroyView(); } + @Override + public void onExcludedAppsSelected(final List<String> excludedApps) { + Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded"); + binding.getConfig().getInterfaceSection().setExcludedApplications(Attribute.iterableToString(excludedApps)); + } + private void onFinished() { // Hide the keyboard; it rarely goes away on its own. final Activity activity = getActivity(); @@ -217,6 +226,15 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi } } + public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) { + final FragmentManager fragmentManager = getFragmentManager(); + if (fragmentManager != null && binding != null) { + final String[] excludedApps = Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications()); + final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this); + fragment.show(fragmentManager, null); + } + } + @Override public void onSaveInstanceState(final Bundle outState) { outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig()); @@ -294,19 +312,4 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi super.onViewStateRestored(savedInstanceState); } - public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) { - final FragmentManager fragmentManager = getFragmentManager(); - if (fragmentManager != null && binding != null) { - final String[] excludedApps = Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications()); - final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this); - fragment.show(fragmentManager, null); - } - } - - @Override - public void onExcludedAppsSelected(final List<String> excludedApps) { - Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded"); - binding.getConfig().getInterfaceSection().setExcludedApplications(Attribute.iterableToString(excludedApps)); - } - } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java index 80895041..7509e40c 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java @@ -38,13 +38,11 @@ import com.wireguard.android.databinding.TunnelListFragmentBinding; import com.wireguard.android.databinding.TunnelListItemBinding; import com.wireguard.android.model.Tunnel; import com.wireguard.android.util.ExceptionLoggers; -import com.wireguard.android.util.ObservableSortedKeyedList; import com.wireguard.android.widget.MultiselectableRelativeLayout; import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener; import com.wireguard.config.Config; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -186,6 +184,19 @@ public class TunnelListFragment extends BaseFragment { } @Override + public void onActivityCreated(@Nullable final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (savedInstanceState != null) { + final Collection<Integer> checkedItems = savedInstanceState.getIntegerArrayList("CHECKED_ITEMS"); + if (checkedItems != null) { + for (final Integer i : checkedItems) + actionModeListener.setItemChecked(i, true); + } + } + } + + @Override public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { switch (requestCode) { case REQUEST_IMPORT: @@ -228,6 +239,14 @@ public class TunnelListFragment extends BaseFragment { super.onDestroyView(); } + @Override + public void onPause() { + if (binding != null) { + binding.createMenu.collapse(); + } + super.onPause(); + } + public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) { startActivity(new Intent(getActivity(), TunnelCreatorActivity.class)); if (binding != null) @@ -255,15 +274,10 @@ public class TunnelListFragment extends BaseFragment { } @Override - public void onPause() { - if (binding != null) { - binding.createMenu.collapse(); - } - super.onPause(); - } + public void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); - private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) { - return (MultiselectableRelativeLayout)binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView; + outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems()); } @Override @@ -318,26 +332,6 @@ public class TunnelListFragment extends BaseFragment { } @Override - public void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems()); - } - - @Override - public void onActivityCreated(@Nullable final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - if (savedInstanceState != null) { - final Collection<Integer> checkedItems = savedInstanceState.getIntegerArrayList("CHECKED_ITEMS"); - if (checkedItems != null) { - for (final Integer i : checkedItems) - actionModeListener.setItemChecked(i, true); - } - } - } - - @Override public void onViewStateRestored(@Nullable final Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); @@ -362,17 +356,25 @@ public class TunnelListFragment extends BaseFragment { }); if (actionMode != null) - ((MultiselectableRelativeLayout)binding.getRoot()).setMultiSelected(actionModeListener.checkedItems.contains(position)); + ((MultiselectableRelativeLayout) binding.getRoot()).setMultiSelected(actionModeListener.checkedItems.contains(position)); else - ((MultiselectableRelativeLayout)binding.getRoot()).setSingleSelected(getSelectedTunnel() == tunnel); + ((MultiselectableRelativeLayout) binding.getRoot()).setSingleSelected(getSelectedTunnel() == tunnel); }); } + private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) { + return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView; + } + private final class ActionModeListener implements ActionMode.Callback { private final Collection<Integer> checkedItems = new HashSet<>(); @Nullable private Resources resources; + public ArrayList<Integer> getCheckedItems() { + return new ArrayList<>(checkedItems); + } + @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { switch (item.getItemId()) { @@ -425,12 +427,10 @@ public class TunnelListFragment extends BaseFragment { binding.tunnelList.getAdapter().notifyDataSetChanged(); } - void toggleItemChecked(final int position) { - setItemChecked(position, !checkedItems.contains(position)); - } - - public ArrayList<Integer> getCheckedItems() { - return new ArrayList<>(checkedItems); + @Override + public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) { + updateTitle(mode); + return false; } void setItemChecked(final int position, final boolean checked) { @@ -454,10 +454,8 @@ public class TunnelListFragment extends BaseFragment { updateTitle(actionMode); } - @Override - public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) { - updateTitle(mode); - return false; + void toggleItemChecked(final int position) { + setItemChecked(position, !checkedItems.contains(position)); } private void updateTitle(@Nullable final ActionMode mode) { diff --git a/app/src/main/java/com/wireguard/android/model/ApplicationData.java b/app/src/main/java/com/wireguard/android/model/ApplicationData.java index 15e2d5c3..efe1ef87 100644 --- a/app/src/main/java/com/wireguard/android/model/ApplicationData.java +++ b/app/src/main/java/com/wireguard/android/model/ApplicationData.java @@ -30,6 +30,11 @@ public class ApplicationData extends BaseObservable implements Keyed<String> { return icon; } + @Override + public String getKey() { + return name; + } + public String getName() { return name; } @@ -47,9 +52,4 @@ public class ApplicationData extends BaseObservable implements Keyed<String> { this.excludedFromTunnel = excludedFromTunnel; notifyPropertyChanged(BR.excludedFromTunnel); } - - @Override - public String getKey() { - return name; - } } diff --git a/app/src/main/java/com/wireguard/android/model/Tunnel.java b/app/src/main/java/com/wireguard/android/model/Tunnel.java index 7eeb2d9e..6d37e009 100644 --- a/app/src/main/java/com/wireguard/android/model/Tunnel.java +++ b/app/src/main/java/com/wireguard/android/model/Tunnel.java @@ -151,5 +151,6 @@ public class Tunnel extends BaseObservable implements Keyed<String> { } } - public static class Statistics extends BaseObservable { } + public static class Statistics extends BaseObservable { + } } diff --git a/app/src/main/java/com/wireguard/android/model/TunnelManager.java b/app/src/main/java/com/wireguard/android/model/TunnelManager.java index c6bc5e75..3fd7bfc0 100644 --- a/app/src/main/java/com/wireguard/android/model/TunnelManager.java +++ b/app/src/main/java/com/wireguard/android/model/TunnelManager.java @@ -44,19 +44,28 @@ public final class TunnelManager extends BaseObservable { 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_RUNNING_TUNNELS = "enabled_configs"; - + private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>(); private final ConfigStore configStore; private final Context context = Application.get(); - private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>(); + private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>(); private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR); - @Nullable private Tunnel lastUsedTunnel; private boolean haveLoaded; - private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>(); + @Nullable private Tunnel lastUsedTunnel; public TunnelManager(final ConfigStore configStore) { this.configStore = configStore; } + static CompletionStage<State> getTunnelState(final Tunnel tunnel) { + return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel)) + .thenApply(tunnel::onStateChanged); + } + + static CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) { + return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel)) + .thenApply(tunnel::onStatisticsChanged); + } + private Tunnel addToList(final String name, @Nullable final Config config, final State state) { final Tunnel tunnel = new Tunnel(this, name, config, state); tunnels.add(tunnel); @@ -112,16 +121,6 @@ public final class TunnelManager extends BaseObservable { .thenApply(tunnel::onConfigChanged); } - static CompletionStage<State> getTunnelState(final Tunnel tunnel) { - return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel)) - .thenApply(tunnel::onStateChanged); - } - - static CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) { - return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel)) - .thenApply(tunnel::onStatisticsChanged); - } - public CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> getTunnels() { return completableTunnels; } diff --git a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java index 7f39b1eb..228facc7 100644 --- a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java @@ -50,7 +50,8 @@ public class VersionPreference extends Preference { intent.setData(Uri.parse("https://www.wireguard.com/")); try { getContext().startActivity(intent); - } catch (final ActivityNotFoundException ignored) { } + } catch (final ActivityNotFoundException ignored) { + } } } diff --git a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java b/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java index 6270d0fe..d6032a18 100644 --- a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java +++ b/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java @@ -19,7 +19,8 @@ import java.util.zip.ZipFile; public final class SharedLibraryLoader { private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName(); - private SharedLibraryLoader() { } + private SharedLibraryLoader() { + } public static void loadSharedLibrary(final Context context, final String libName) { Throwable noAbiException; diff --git a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java index 5a667baa..143a8724 100644 --- a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java +++ b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java @@ -26,11 +26,10 @@ import java.util.List; public final class ToolsInstaller { public static final int ERROR = 0x0; - public static final int YES = 0x1; - public static final int NO = 0x2; public static final int MAGISK = 0x4; + public static final int NO = 0x2; public static final int SYSTEM = 0x8; - + public static final int YES = 0x1; private static final String[][] EXECUTABLES = { {"libwg.so", "wg"}, {"libwg-quick.so", "wg-quick"}, @@ -107,34 +106,8 @@ public final class ToolsInstaller { } } - private boolean willInstallAsMagiskModule() { - synchronized (lock) { - if (installAsMagiskModule == null) { - try { - installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.core/mirror -a -d /sbin/.core/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS; - } catch (final Exception ignored) { - installAsMagiskModule = false; - } - } - return installAsMagiskModule; - } - } - - private int installSystem() throws NoRootException { - if (INSTALL_DIR == null) - return OsConstants.ENOENT; - final StringBuilder script = new StringBuilder("set -ex; "); - script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; "); - for (final String[] names : EXECUTABLES) { - final File destination = new File(INSTALL_DIR, names[1]); - script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ", - new File(nativeLibraryDir, names[0]), destination, destination, destination)); - } - try { - return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR; - } catch (final IOException ignored) { - return ERROR; - } + public int install() throws NoRootException { + return willInstallAsMagiskModule() ? installMagisk() : installSystem(); } private int installMagisk() throws NoRootException { @@ -158,8 +131,21 @@ public final class ToolsInstaller { } } - public int install() throws NoRootException { - return willInstallAsMagiskModule() ? installMagisk() : installSystem(); + private int installSystem() throws NoRootException { + if (INSTALL_DIR == null) + return OsConstants.ENOENT; + final StringBuilder script = new StringBuilder("set -ex; "); + script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; "); + for (final String[] names : EXECUTABLES) { + final File destination = new File(INSTALL_DIR, names[1]); + script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ", + new File(nativeLibraryDir, names[0]), destination, destination, destination)); + } + try { + return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR; + } catch (final IOException ignored) { + return ERROR; + } } public int symlink() throws NoRootException { @@ -184,4 +170,17 @@ public final class ToolsInstaller { return OsConstants.EXIT_FAILURE; } } + + private boolean willInstallAsMagiskModule() { + synchronized (lock) { + if (installAsMagiskModule == null) { + try { + installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.core/mirror -a -d /sbin/.core/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS; + } catch (final Exception ignored) { + installAsMagiskModule = false; + } + } + return installAsMagiskModule; + } + } } diff --git a/app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java b/app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java index 8fa4f9ae..0759903b 100644 --- a/app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java +++ b/app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java @@ -12,23 +12,25 @@ import android.widget.RelativeLayout; import com.wireguard.android.R; public class MultiselectableRelativeLayout extends RelativeLayout { + private static final int[] STATE_MULTISELECTED = {R.attr.state_multiselected}; + private boolean multiselected; + public MultiselectableRelativeLayout(final Context context) { super(context); } + public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) { super(context, attrs); } + public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) { super(context, attrs, defStyleAttr); } + public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } - private static final int[] STATE_MULTISELECTED = { R.attr.state_multiselected }; - - private boolean multiselected; - @Override protected int[] onCreateDrawableState(final int extraSpace) { if (multiselected) { diff --git a/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java b/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java index d4d3d8cc..408d7888 100644 --- a/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java +++ b/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java @@ -31,95 +31,43 @@ import android.util.FloatProperty; @TargetApi(Build.VERSION_CODES.N) public class SlashDrawable extends Drawable { + private static final float CENTER_X = 10.65f; + private static final float CENTER_Y = 11.869239f; private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f; + // Draw the slash washington-monument style; rotate to no-u-turn style + private static final float DEFAULT_ROTATION = -45f; private static final long QS_ANIM_LENGTH = 350; - - private final Path mPath = new Path(); - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - + private static final float SCALE = 24f; + private static final float SLASH_HEIGHT = 28f; // These values are derived in un-rotated (vertical) orientation private static final float SLASH_WIDTH = 1.8384776f; - private static final float SLASH_HEIGHT = 28f; - private static final float CENTER_X = 10.65f; - private static final float CENTER_Y = 11.869239f; - private static final float SCALE = 24f; - // Bottom is derived during animation private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE; - private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE; private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE; - // Draw the slash washington-monument style; rotate to no-u-turn style - private static final float DEFAULT_ROTATION = -45f; - - private final Drawable mDrawable; - private final RectF mSlashRect = new RectF(0, 0, 0, 0); - private float mRotation; - private boolean mSlashed; - private boolean mAnimationEnabled = true; - - public SlashDrawable(final Drawable d) { - mDrawable = d; - } - - @Override - public int getIntrinsicHeight() { - return mDrawable.getIntrinsicHeight(); - } - - @Override - public int getIntrinsicWidth() { - return mDrawable.getIntrinsicWidth(); - } - - @Override - protected void onBoundsChange(final Rect bounds) { - super.onBoundsChange(bounds); - mDrawable.setBounds(bounds); - } - - public void setRotation(final float rotation) { - if (mRotation == rotation) - return; - mRotation = rotation; - invalidateSelf(); - } - - public void setAnimationEnabled(final boolean enabled) { - mAnimationEnabled = enabled; - } - - // Animate this value on change - private float mCurrentSlashLength; + private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE; private static final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") { @Override - public void setValue(final SlashDrawable object, final float value) { - object.mCurrentSlashLength = value; + public Float get(final SlashDrawable object) { + return object.mCurrentSlashLength; } @Override - public Float get(final SlashDrawable object) { - return object.mCurrentSlashLength; + public void setValue(final SlashDrawable object, final float value) { + object.mCurrentSlashLength = value; } }; + private final Drawable mDrawable; + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Path mPath = new Path(); + private final RectF mSlashRect = new RectF(0, 0, 0, 0); + private boolean mAnimationEnabled = true; + // Animate this value on change + private float mCurrentSlashLength; + private float mRotation; + private boolean mSlashed; - @SuppressWarnings("unchecked") - public void setSlashed(final boolean slashed) { - if (mSlashed == slashed) return; - - mSlashed = slashed; - - final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f; - final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE; - - if (mAnimationEnabled) { - final ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end); - anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf()); - anim.setDuration(QS_ANIM_LENGTH); - anim.start(); - } else { - mCurrentSlashLength = end; - invalidateSelf(); - } + public SlashDrawable(final Drawable d) { + mDrawable = d; } @SuppressWarnings("deprecation") @@ -166,15 +114,76 @@ public class SlashDrawable extends Drawable { canvas.restore(); } + @Override + public int getIntrinsicHeight() { + return mDrawable.getIntrinsicHeight(); + } + + @Override + public int getIntrinsicWidth() { + return mDrawable.getIntrinsicWidth(); + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + @Override + protected void onBoundsChange(final Rect bounds) { + super.onBoundsChange(bounds); + mDrawable.setBounds(bounds); + } + private float scale(final float frac, final int width) { return frac * width; } - private void updateRect(final float left, final float top, final float right, final float bottom) { - mSlashRect.left = left; - mSlashRect.top = top; - mSlashRect.right = right; - mSlashRect.bottom = bottom; + @Override + public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) { + mDrawable.setAlpha(alpha); + mPaint.setAlpha(alpha); + } + + public void setAnimationEnabled(final boolean enabled) { + mAnimationEnabled = enabled; + } + + @Override + public void setColorFilter(@Nullable final ColorFilter colorFilter) { + mDrawable.setColorFilter(colorFilter); + mPaint.setColorFilter(colorFilter); + } + + private void setDrawableTintList(@Nullable final ColorStateList tint) { + mDrawable.setTintList(tint); + } + + public void setRotation(final float rotation) { + if (mRotation == rotation) + return; + mRotation = rotation; + invalidateSelf(); + } + + @SuppressWarnings("unchecked") + public void setSlashed(final boolean slashed) { + if (mSlashed == slashed) return; + + mSlashed = slashed; + + final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f; + final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE; + + if (mAnimationEnabled) { + final ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end); + anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf()); + anim.setDuration(QS_ANIM_LENGTH); + anim.start(); + } else { + mCurrentSlashLength = end; + invalidateSelf(); + } } @Override @@ -192,30 +201,16 @@ public class SlashDrawable extends Drawable { invalidateSelf(); } - private void setDrawableTintList(@Nullable final ColorStateList tint) { - mDrawable.setTintList(tint); - } - @Override public void setTintMode(final Mode tintMode) { super.setTintMode(tintMode); mDrawable.setTintMode(tintMode); } - @Override - public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) { - mDrawable.setAlpha(alpha); - mPaint.setAlpha(alpha); - } - - @Override - public void setColorFilter(@Nullable final ColorFilter colorFilter) { - mDrawable.setColorFilter(colorFilter); - mPaint.setColorFilter(colorFilter); - } - - @Override - public int getOpacity() { - return PixelFormat.OPAQUE; + private void updateRect(final float left, final float top, final float right, final float bottom) { + mSlashRect.left = left; + mSlashRect.top = top; + mSlashRect.right = right; + mSlashRect.bottom = bottom; } } diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java index 54679046..3741c29e 100644 --- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java +++ b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java @@ -18,19 +18,13 @@ import android.view.View; public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> { + private static final long ANIMATION_DURATION = 250; + private static final TimeInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator(); + public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) { super(context, attrs); } - @Override - public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child, - final View dependency) { - return dependency instanceof Snackbar.SnackbarLayout; - } - - private static final long ANIMATION_DURATION = 250; - private static final TimeInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator(); - private static void animateChange(final FloatingActionsMenu child, final float destination, final float fullSpan) { final float origin = child.getBehaviorYTranslation(); if (Math.abs(destination - origin) < fullSpan / 2) { @@ -40,18 +34,24 @@ public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<Flo final ValueAnimator animator = new ValueAnimator(); animator.setFloatValues(origin, destination); animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); - animator.setDuration((long)(ANIMATION_DURATION * (Math.abs(destination - origin) / fullSpan))); + animator.setDuration((long) (ANIMATION_DURATION * (Math.abs(destination - origin) / fullSpan))); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(final Animator a) { child.setBehaviorYTranslation(destination); } }); - animator.addUpdateListener(a -> child.setBehaviorYTranslation((float)a.getAnimatedValue())); + animator.addUpdateListener(a -> child.setBehaviorYTranslation((float) a.getAnimatedValue())); animator.start(); } @Override + public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child, + final View dependency) { + return dependency instanceof Snackbar.SnackbarLayout; + } + + @Override public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child, final View dependency) { animateChange(child, Math.min(0, dependency.getTranslationY() - dependency.getMeasuredHeight()), dependency.getMeasuredHeight()); diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java index 8a5a193b..ed838914 100644 --- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java +++ b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java @@ -37,39 +37,38 @@ import android.widget.TextView; import com.wireguard.android.R; public class FloatingActionsMenu extends ViewGroup { - public static final int EXPAND_UP = 0; public static final int EXPAND_DOWN = 1; public static final int EXPAND_LEFT = 2; public static final int EXPAND_RIGHT = 3; - + public static final int EXPAND_UP = 0; public static final int LABELS_ON_LEFT_SIDE = 0; public static final int LABELS_ON_RIGHT_SIDE = 1; - + private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator(); private static final int ANIMATION_DURATION = 300; + private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS"); private static final float COLLAPSED_PLUS_ROTATION = 0f; + private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f); private static final float EXPANDED_PLUS_ROTATION = 90f + 45f; private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator(); - private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f); - private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator(); - private int mExpandDirection; + private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); + private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); + private final Rect touchArea = new Rect(0, 0, 0, 0); + private float behaviorYTranslation; + @Nullable private FloatingActionButton mAddButton; private int mButtonSpacing; + private int mButtonsCount; + private int mExpandDirection; + private boolean mExpanded; private int mLabelsMargin; + private int mLabelsPosition; + private int mLabelsStyle; private int mLabelsVerticalOffset; - private boolean mExpanded; - private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); - private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); - @Nullable private FloatingActionButton mAddButton; - @Nullable private RotatingDrawable mRotatingDrawable; - private int mMaxButtonWidth; + @Nullable private OnFloatingActionsMenuUpdateListener mListener; private int mMaxButtonHeight; - private int mLabelsStyle; - private int mLabelsPosition; - private int mButtonsCount; + private int mMaxButtonWidth; + @Nullable private RotatingDrawable mRotatingDrawable; @Nullable private TouchDelegateGroup mTouchDelegateGroup; - @Nullable private OnFloatingActionsMenuUpdateListener mListener; - private final Rect touchArea = new Rect(0, 0, 0, 0); private float scrollYTranslation; - private float behaviorYTranslation; public FloatingActionsMenu(final Context context) { this(context, null); @@ -85,51 +84,39 @@ public class FloatingActionsMenu extends ViewGroup { init(context, attrs); } - private void init(final Context context, @Nullable final AttributeSet attributeSet) { - mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing)); - mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin); - mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset); - - mTouchDelegateGroup = new TouchDelegateGroup(this); - setTouchDelegate(mTouchDelegateGroup); - - final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0); - mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP); - mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0); - mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE); - attr.recycle(); - - if (mLabelsStyle != 0 && expandsHorizontally()) { - throw new IllegalStateException("Action labels in horizontal expand orientation is not supported."); - } - - createAddButton(context); + private static int adjustForOvershoot(final int dimension) { + return dimension * 12 / 10; } - public float getScrollYTranslation() { - return scrollYTranslation; - } + public void addButton(final LabeledFloatingActionButton button) { + addView(button, mButtonsCount - 1); + mButtonsCount++; - public void setScrollYTranslation(final float scrollYTranslation) { - this.scrollYTranslation = scrollYTranslation; - setTranslationY(behaviorYTranslation + scrollYTranslation); + if (mLabelsStyle != 0) { + createLabels(); + } } - public float getBehaviorYTranslation() { - return behaviorYTranslation; + public void collapse() { + collapse(false); } - public void setBehaviorYTranslation(final float behaviorYTranslation) { - this.behaviorYTranslation = behaviorYTranslation; - setTranslationY(behaviorYTranslation + scrollYTranslation); - } + private void collapse(final boolean immediately) { + if (mExpanded) { + mExpanded = false; + mTouchDelegateGroup.setEnabled(false); + mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION); + mCollapseAnimation.start(); + mExpandAnimation.cancel(); - public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) { - mListener = listener; + if (mListener != null) { + mListener.onMenuCollapsed(); + } + } } - private boolean expandsHorizontally() { - return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT; + public void collapseImmediately() { + collapse(true); } private void createAddButton(final Context context) { @@ -156,85 +143,101 @@ public class FloatingActionsMenu extends ViewGroup { mButtonsCount++; } - public void addButton(final LabeledFloatingActionButton button) { - addView(button, mButtonsCount - 1); - mButtonsCount++; + private void createLabels() { + final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle); - if (mLabelsStyle != 0) { - createLabels(); + for (int i = 0; i < mButtonsCount; i++) { + final FloatingActionButton button = (FloatingActionButton) getChildAt(i); + + if (button instanceof LabeledFloatingActionButton) { + final String title = ((LabeledFloatingActionButton) button).getTitle(); + + final AppCompatTextView label = new AppCompatTextView(context); + if (!BROKEN_LABEL_STYLE) + label.setTextAppearance(context, mLabelsStyle); + label.setText(title); + addView(label); + + button.setTag(R.id.fab_label, label); + } } } - public void removeButton(final LabeledFloatingActionButton button) { - removeView(button.getLabelView()); - removeView(button); - button.setTag(R.id.fab_label, null); - mButtonsCount--; + public void expand() { + if (!mExpanded) { + mExpanded = true; + mTouchDelegateGroup.setEnabled(true); + mCollapseAnimation.cancel(); + mExpandAnimation.start(); + + if (mListener != null) { + mListener.onMenuExpanded(); + } + } + } + + private boolean expandsHorizontally() { + return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT; } @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - measureChildren(widthMeasureSpec, heightMeasureSpec); + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(super.generateDefaultLayoutParams()); + } - int width = 0; - int height = 0; + @Override + public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) { + return new LayoutParams(super.generateLayoutParams(attrs)); + } - mMaxButtonWidth = 0; - mMaxButtonHeight = 0; - int maxLabelWidth = 0; + @Override + protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) { + return new LayoutParams(super.generateLayoutParams(p)); + } - for (int i = 0; i < mButtonsCount; i++) { - final View child = getChildAt(i); + public float getBehaviorYTranslation() { + return behaviorYTranslation; + } - if (child.getVisibility() == GONE) { - continue; - } + public float getScrollYTranslation() { + return scrollYTranslation; + } - switch (mExpandDirection) { - case EXPAND_UP: - case EXPAND_DOWN: - mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth()); - height += child.getMeasuredHeight(); - break; - case EXPAND_LEFT: - case EXPAND_RIGHT: - width += child.getMeasuredWidth(); - mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight()); - break; - } + private void init(final Context context, @Nullable final AttributeSet attributeSet) { + mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing)); + mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin); + mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset); - if (!expandsHorizontally()) { - final TextView label = (TextView) child.getTag(R.id.fab_label); - if (label != null) { - maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth()); - } - } - } + mTouchDelegateGroup = new TouchDelegateGroup(this); + setTouchDelegate(mTouchDelegateGroup); - if (expandsHorizontally()) { - height = mMaxButtonHeight; - } else { - width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0); - } + final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0); + mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP); + mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0); + mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE); + attr.recycle(); - switch (mExpandDirection) { - case EXPAND_UP: - case EXPAND_DOWN: - height += mButtonSpacing * (mButtonsCount - 1); - height = adjustForOvershoot(height); - break; - case EXPAND_LEFT: - case EXPAND_RIGHT: - width += mButtonSpacing * (mButtonsCount - 1); - width = adjustForOvershoot(width); - break; + if (mLabelsStyle != 0 && expandsHorizontally()) { + throw new IllegalStateException("Action labels in horizontal expand orientation is not supported."); } - setMeasuredDimension(width, height); + createAddButton(context); } - private static int adjustForOvershoot(final int dimension) { - return dimension * 12 / 10; + public boolean isExpanded() { + return mExpanded; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + bringChildToFront(mAddButton); + mButtonsCount = getChildCount(); + + if (mLabelsStyle != 0) { + createLabels(); + } } @Override @@ -367,99 +370,102 @@ public class FloatingActionsMenu extends ViewGroup { } @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(super.generateDefaultLayoutParams()); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) { - return new LayoutParams(super.generateLayoutParams(attrs)); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) { - return new LayoutParams(super.generateLayoutParams(p)); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - bringChildToFront(mAddButton); - mButtonsCount = getChildCount(); - - if (mLabelsStyle != 0) { - createLabels(); - } - } + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + measureChildren(widthMeasureSpec, heightMeasureSpec); - private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS"); + int width = 0; + int height = 0; - private void createLabels() { - final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle); + mMaxButtonWidth = 0; + mMaxButtonHeight = 0; + int maxLabelWidth = 0; for (int i = 0; i < mButtonsCount; i++) { - final FloatingActionButton button = (FloatingActionButton) getChildAt(i); + final View child = getChildAt(i); - if (button instanceof LabeledFloatingActionButton) { - final String title = ((LabeledFloatingActionButton) button).getTitle(); + if (child.getVisibility() == GONE) { + continue; + } - final AppCompatTextView label = new AppCompatTextView(context); - if (!BROKEN_LABEL_STYLE) - label.setTextAppearance(context, mLabelsStyle); - label.setText(title); - addView(label); + switch (mExpandDirection) { + case EXPAND_UP: + case EXPAND_DOWN: + mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth()); + height += child.getMeasuredHeight(); + break; + case EXPAND_LEFT: + case EXPAND_RIGHT: + width += child.getMeasuredWidth(); + mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight()); + break; + } - button.setTag(R.id.fab_label, label); + if (!expandsHorizontally()) { + final TextView label = (TextView) child.getTag(R.id.fab_label); + if (label != null) { + maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth()); + } } } - } - public void collapse() { - collapse(false); - } + if (expandsHorizontally()) { + height = mMaxButtonHeight; + } else { + width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0); + } - public void collapseImmediately() { - collapse(true); + switch (mExpandDirection) { + case EXPAND_UP: + case EXPAND_DOWN: + height += mButtonSpacing * (mButtonsCount - 1); + height = adjustForOvershoot(height); + break; + case EXPAND_LEFT: + case EXPAND_RIGHT: + width += mButtonSpacing * (mButtonsCount - 1); + width = adjustForOvershoot(width); + break; + } + + setMeasuredDimension(width, height); } - private void collapse(final boolean immediately) { - if (mExpanded) { - mExpanded = false; - mTouchDelegateGroup.setEnabled(false); - mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION); - mCollapseAnimation.start(); - mExpandAnimation.cancel(); + @Override + public void onRestoreInstanceState(final Parcelable state) { + if (state instanceof SavedState) { + final SavedState savedState = (SavedState) state; + mExpanded = savedState.mExpanded; + mTouchDelegateGroup.setEnabled(mExpanded); - if (mListener != null) { - mListener.onMenuCollapsed(); + if (mRotatingDrawable != null) { + mRotatingDrawable.setRotation(mExpanded ? EXPANDED_PLUS_ROTATION : COLLAPSED_PLUS_ROTATION); } - } - } - public void toggle() { - if (mExpanded) { - collapse(); + super.onRestoreInstanceState(savedState.getSuperState()); } else { - expand(); + super.onRestoreInstanceState(state); } } - public void expand() { - if (!mExpanded) { - mExpanded = true; - mTouchDelegateGroup.setEnabled(true); - mCollapseAnimation.cancel(); - mExpandAnimation.start(); + @Override + public Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + final SavedState savedState = new SavedState(superState); + savedState.mExpanded = mExpanded; - if (mListener != null) { - mListener.onMenuExpanded(); - } - } + return savedState; } - public boolean isExpanded() { - return mExpanded; + public void removeButton(final LabeledFloatingActionButton button) { + removeView(button.getLabelView()); + removeView(button); + button.setTag(R.id.fab_label, null); + mButtonsCount--; + } + + public void setBehaviorYTranslation(final float behaviorYTranslation) { + this.behaviorYTranslation = behaviorYTranslation; + setTranslationY(behaviorYTranslation + scrollYTranslation); } @Override @@ -469,36 +475,27 @@ public class FloatingActionsMenu extends ViewGroup { mAddButton.setEnabled(enabled); } - @Override - public Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - final SavedState savedState = new SavedState(superState); - savedState.mExpanded = mExpanded; - - return savedState; + public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) { + mListener = listener; } - @Override - public void onRestoreInstanceState(final Parcelable state) { - if (state instanceof SavedState) { - final SavedState savedState = (SavedState) state; - mExpanded = savedState.mExpanded; - mTouchDelegateGroup.setEnabled(mExpanded); - - if (mRotatingDrawable != null) { - mRotatingDrawable.setRotation(mExpanded ? EXPANDED_PLUS_ROTATION : COLLAPSED_PLUS_ROTATION); - } + public void setScrollYTranslation(final float scrollYTranslation) { + this.scrollYTranslation = scrollYTranslation; + setTranslationY(behaviorYTranslation + scrollYTranslation); + } - super.onRestoreInstanceState(savedState.getSuperState()); + public void toggle() { + if (mExpanded) { + collapse(); } else { - super.onRestoreInstanceState(state); + expand(); } } public interface OnFloatingActionsMenuUpdateListener { - void onMenuExpanded(); - void onMenuCollapsed(); + + void onMenuExpanded(); } private static class RotatingDrawable extends LayerDrawable { @@ -508,6 +505,14 @@ public class FloatingActionsMenu extends ViewGroup { super(new Drawable[]{drawable}); } + @Override + public void draw(final Canvas canvas) { + canvas.save(); + canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY()); + super.draw(canvas); + canvas.restore(); + } + @SuppressWarnings("UnusedDeclaration") public float getRotation() { return mRotation; @@ -519,14 +524,6 @@ public class FloatingActionsMenu extends ViewGroup { mRotation = rotation; invalidateSelf(); } - - @Override - public void draw(final Canvas canvas) { - canvas.save(); - canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY()); - super.draw(canvas); - canvas.restore(); - } } public static class SavedState extends BaseSavedState { @@ -562,10 +559,10 @@ public class FloatingActionsMenu extends ViewGroup { private class LayoutParams extends ViewGroup.LayoutParams { - private final ObjectAnimator mExpandDir = new ObjectAnimator(); - private final ObjectAnimator mExpandAlpha = new ObjectAnimator(); - private final ObjectAnimator mCollapseDir = new ObjectAnimator(); private final ObjectAnimator mCollapseAlpha = new ObjectAnimator(); + private final ObjectAnimator mCollapseDir = new ObjectAnimator(); + private final ObjectAnimator mExpandAlpha = new ObjectAnimator(); + private final ObjectAnimator mExpandDir = new ObjectAnimator(); private boolean animationsSetToPlay; LayoutParams(final ViewGroup.LayoutParams source) { @@ -596,6 +593,20 @@ public class FloatingActionsMenu extends ViewGroup { } } + private void addLayerTypeListener(final Animator animator, final View view) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(final Animator animation) { + view.setLayerType(LAYER_TYPE_NONE, null); + } + + @Override + public void onAnimationStart(final Animator animation) { + view.setLayerType(LAYER_TYPE_HARDWARE, null); + } + }); + } + public void setAnimationsTarget(final View view) { mCollapseAlpha.setTarget(view); mCollapseDir.setTarget(view); @@ -614,19 +625,5 @@ public class FloatingActionsMenu extends ViewGroup { animationsSetToPlay = true; } } - - private void addLayerTypeListener(final Animator animator, final View view) { - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final Animator animation) { - view.setLayerType(LAYER_TYPE_NONE, null); - } - - @Override - public void onAnimationStart(final Animator animation) { - view.setLayerType(LAYER_TYPE_HARDWARE, null); - } - }); - } } } diff --git a/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java b/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java index 4bb82f4c..ddb2899b 100644 --- a/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java +++ b/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java @@ -29,13 +29,6 @@ public class TouchDelegateGroup extends TouchDelegate { mTouchDelegates.add(touchDelegate); } - public void removeTouchDelegate(final TouchDelegate touchDelegate) { - mTouchDelegates.remove(touchDelegate); - if (mCurrentTouchDelegate == touchDelegate) { - mCurrentTouchDelegate = null; - } - } - public void clearTouchDelegates() { mTouchDelegates.clear(); mCurrentTouchDelegate = null; @@ -72,6 +65,13 @@ public class TouchDelegateGroup extends TouchDelegate { return delegate != null && delegate.onTouchEvent(event); } + public void removeTouchDelegate(final TouchDelegate touchDelegate) { + mTouchDelegates.remove(touchDelegate); + if (mCurrentTouchDelegate == touchDelegate) { + mCurrentTouchDelegate = null; + } + } + public void setEnabled(final boolean enabled) { mEnabled = enabled; } diff --git a/app/src/main/java/com/wireguard/config/Config.java b/app/src/main/java/com/wireguard/config/Config.java index 31fe13cf..61e31838 100644 --- a/app/src/main/java/com/wireguard/config/Config.java +++ b/app/src/main/java/com/wireguard/config/Config.java @@ -105,9 +105,9 @@ public class Config { return new Observable[size]; } }; - @Nullable private String name; private final Interface.Observable observableInterface; private final ObservableList<Peer.Observable> observablePeers; + @Nullable private String name; public Observable(@Nullable final Config parent, @Nullable final String name) { this.name = name; diff --git a/app/src/main/java/com/wireguard/config/Interface.java b/app/src/main/java/com/wireguard/config/Interface.java index a1b9125a..aa1d986b 100644 --- a/app/src/main/java/com/wireguard/config/Interface.java +++ b/app/src/main/java/com/wireguard/config/Interface.java @@ -28,12 +28,12 @@ import java.util.List; public class Interface { private final List<InetNetwork> addressList; + private final Context context = Application.get(); private final List<InetAddress> dnsList; private final List<String> excludedApplications; @Nullable private Keypair keypair; private int listenPort; private int mtu; - private final Context context = Application.get(); public Interface() { addressList = new ArrayList<>(); @@ -94,6 +94,10 @@ public class Interface { return dnsList.toArray(new InetAddress[dnsList.size()]); } + public String[] getExcludedApplications() { + return excludedApplications.toArray(new String[excludedApplications.size()]); + } + @Nullable private String getExcludedApplicationsString() { if (excludedApplications.isEmpty()) @@ -101,10 +105,6 @@ public class Interface { return Attribute.iterableToString(excludedApplications); } - public String[] getExcludedApplications() { - return excludedApplications.toArray(new String[excludedApplications.size()]); - } - public int getListenPort() { return listenPort; } diff --git a/app/src/main/java/com/wireguard/config/Peer.java b/app/src/main/java/com/wireguard/config/Peer.java index 914516ba..5cf0283c 100644 --- a/app/src/main/java/com/wireguard/config/Peer.java +++ b/app/src/main/java/com/wireguard/config/Peer.java @@ -37,11 +37,11 @@ import java9.lang.Iterables; public class Peer { private final List<InetNetwork> allowedIPsList; + private final Context context = Application.get(); @Nullable private InetEndpoint endpoint; private int persistentKeepalive; @Nullable private String preSharedKey; @Nullable private String publicKey; - private final Context context = Application.get(); public Peer() { allowedIPsList = new ArrayList<>(); @@ -201,13 +201,15 @@ public class Peer { return new Observable[size]; } }; + private static final List<String> DEFAULT_ROUTE_MOD_RFC1918_V4 = Arrays.asList("0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"); + private static final String DEFAULT_ROUTE_V4 = "0.0.0.0/0"; + private final List<String> interfaceDNSRoutes = new ArrayList<>(); @Nullable private String allowedIPs; @Nullable private String endpoint; + private int numSiblings; @Nullable private String persistentKeepalive; @Nullable private String preSharedKey; @Nullable private String publicKey; - private final List<String> interfaceDNSRoutes = new ArrayList<>(); - private int numSiblings; public Observable(final Peer parent) { loadData(parent); @@ -244,22 +246,9 @@ public class Peer { return 0; } - private static final String DEFAULT_ROUTE_V4 = "0.0.0.0/0"; - private static final List<String> DEFAULT_ROUTE_MOD_RFC1918_V4 = Arrays.asList("0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"); - - public void toggleExcludePrivateIPs() { - final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs))); - final boolean hasDefaultRoute = ips.contains(DEFAULT_ROUTE_V4); - final boolean hasDefaultRouteModRFC1918 = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4); - if ((!hasDefaultRoute && !hasDefaultRouteModRFC1918) || numSiblings > 0) - return; - Iterables.removeIf(ips, ip -> !ip.contains(":")); - if (hasDefaultRoute) { - ips.addAll(DEFAULT_ROUTE_MOD_RFC1918_V4); - ips.addAll(interfaceDNSRoutes); - } else if (hasDefaultRouteModRFC1918) - ips.add(DEFAULT_ROUTE_V4); - setAllowedIPs(Attribute.iterableToString(ips)); + @Bindable @Nullable + public String getAllowedIPs() { + return allowedIPs; } @Bindable @@ -268,21 +257,16 @@ public class Peer { return numSiblings == 0 && (ips.contains(DEFAULT_ROUTE_V4) || ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4)); } - @Bindable - public boolean getIsExcludePrivateIPsOn() { - return numSiblings == 0 && Arrays.asList(Attribute.stringToList(allowedIPs)).containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4); - } - - @Bindable @Nullable - public String getAllowedIPs() { - return allowedIPs; - } - @Bindable @Nullable public String getEndpoint() { return endpoint; } + @Bindable + public boolean getIsExcludePrivateIPsOn() { + return numSiblings == 0 && Arrays.asList(Attribute.stringToList(allowedIPs)).containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4); + } + @Bindable @Nullable public String getPersistentKeepalive() { return persistentKeepalive; @@ -318,21 +302,6 @@ public class Peer { notifyPropertyChanged(BR.endpoint); } - public void setPersistentKeepalive(final String persistentKeepalive) { - this.persistentKeepalive = persistentKeepalive; - notifyPropertyChanged(BR.persistentKeepalive); - } - - public void setPreSharedKey(final String preSharedKey) { - this.preSharedKey = preSharedKey; - notifyPropertyChanged(BR.preSharedKey); - } - - public void setPublicKey(final String publicKey) { - this.publicKey = publicKey; - notifyPropertyChanged(BR.publicKey); - } - public void setInterfaceDNSRoutes(@Nullable final String dnsServers) { final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs))); final boolean modifyAllowedIPs = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4); @@ -354,6 +323,36 @@ public class Peer { notifyPropertyChanged(BR.isExcludePrivateIPsOn); } + public void setPersistentKeepalive(final String persistentKeepalive) { + this.persistentKeepalive = persistentKeepalive; + notifyPropertyChanged(BR.persistentKeepalive); + } + + public void setPreSharedKey(final String preSharedKey) { + this.preSharedKey = preSharedKey; + notifyPropertyChanged(BR.preSharedKey); + } + + public void setPublicKey(final String publicKey) { + this.publicKey = publicKey; + notifyPropertyChanged(BR.publicKey); + } + + public void toggleExcludePrivateIPs() { + final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs))); + final boolean hasDefaultRoute = ips.contains(DEFAULT_ROUTE_V4); + final boolean hasDefaultRouteModRFC1918 = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4); + if ((!hasDefaultRoute && !hasDefaultRouteModRFC1918) || numSiblings > 0) + return; + Iterables.removeIf(ips, ip -> !ip.contains(":")); + if (hasDefaultRoute) { + ips.addAll(DEFAULT_ROUTE_MOD_RFC1918_V4); + ips.addAll(interfaceDNSRoutes); + } else if (hasDefaultRouteModRFC1918) + ips.add(DEFAULT_ROUTE_V4); + setAllowedIPs(Attribute.iterableToString(ips)); + } + @Override public void writeToParcel(final Parcel dest, final int flags) { dest.writeString(allowedIPs); diff --git a/app/src/main/java/com/wireguard/util/NonNullForAll.java b/app/src/main/java/com/wireguard/util/NonNullForAll.java index f2bd8255..4207cf85 100644 --- a/app/src/main/java/com/wireguard/util/NonNullForAll.java +++ b/app/src/main/java/com/wireguard/util/NonNullForAll.java @@ -22,4 +22,5 @@ import javax.annotation.meta.TypeQualifierDefault; @Nonnull @TypeQualifierDefault({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) -public @interface NonNullForAll { } +public @interface NonNullForAll { +} diff --git a/app/src/main/res/drawable/ic_action_select_all.xml b/app/src/main/res/drawable/ic_action_select_all.xml index 2ddfb85a..9c560297 100644 --- a/app/src/main/res/drawable/ic_action_select_all.xml +++ b/app/src/main/res/drawable/ic_action_select_all.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportWidth="24" - android:viewportHeight="24" android:width="24dp" - android:height="24dp"> + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> <path android:fillColor="?android:attr/colorForeground" android:pathData="M3 5L5 5 5 3C3.9 3 3 3.9 3 5Zm0 8l2 0 0 -2 -2 0 0 2zm4 8l2 0 0 -2 -2 0 0 2zM3 9L5 9 5 7 3 7 3 9Zm10 -6l-2 0 0 2 2 0 0 -2zm6 0l0 2 2 0C21 3.9 20.1 3 19 3ZM5 21L5 19 3 19c0 1.1 0.9 2 2 2zm-2 -4l2 0 0 -2 -2 0 0 2zM9 3L7 3 7 5 9 5 9 3Zm2 18l2 0 0 -2 -2 0 0 2zm8 -8l2 0 0 -2 -2 0 0 2zm0 8c1.1 0 2 -0.9 2 -2l-2 0 0 2zm0 -12l2 0 0 -2 -2 0 0 2zm0 8l2 0 0 -2 -2 0 0 2zm-4 4l2 0 0 -2 -2 0 0 2zm0 -16l2 0 0 -2 -2 0 0 2zM7 17L17 17 17 7 7 7 7 17Zm2 -8l6 0 0 6 -6 0 0 -6z" /> -</vector>
\ No newline at end of file +</vector> diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml index e8571eb3..aabfce2a 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -5,5 +5,5 @@ android:viewportWidth="24"> <path android:fillColor="?android:attr/colorForeground" - android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/> + android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" /> </vector> diff --git a/app/src/main/res/drawable/list_item_background.xml b/app/src/main/res/drawable/list_item_background.xml index 6a69bcda..f86d4df1 100644 --- a/app/src/main/res/drawable/list_item_background.xml +++ b/app/src/main/res/drawable/list_item_background.xml @@ -3,10 +3,14 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> <item> <selector> - <item app:state_multiselected="true" android:state_activated="true"> + <item + android:state_activated="true" + app:state_multiselected="true"> <color android:color="?attr/colorControlActivated" /> </item> - <item app:state_multiselected="false" android:state_activated="true"> + <item + android:state_activated="true" + app:state_multiselected="false"> <color android:color="?attr/colorControlHighlight" /> </item> </selector> diff --git a/app/src/main/res/layout/app_list_dialog_fragment.xml b/app/src/main/res/layout/app_list_dialog_fragment.xml index 25879b6b..c975778a 100644 --- a/app/src/main/res/layout/app_list_dialog_fragment.xml +++ b/app/src/main/res/layout/app_list_dialog_fragment.xml @@ -20,7 +20,7 @@ <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:minHeight="200dp" > + android:minHeight="200dp"> <ProgressBar android:id="@+id/progress_bar" @@ -28,7 +28,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminate="true" - android:visibility="@{appData.isEmpty() ? View.VISIBLE : View.GONE}"/> + android:visibility="@{appData.isEmpty() ? View.VISIBLE : View.GONE}" /> <android.support.v7.widget.RecyclerView android:id="@+id/app_list" diff --git a/app/src/main/res/layout/app_list_item.xml b/app/src/main/res/layout/app_list_item.xml index 4519a3a4..825b828b 100644 --- a/app/src/main/res/layout/app_list_item.xml +++ b/app/src/main/res/layout/app_list_item.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<layout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> +<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> @@ -23,10 +22,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_item_background" - android:padding="16dp" - android:orientation="horizontal" android:gravity="center_vertical" - android:onClick="@{(view) -> item.setExcludedFromTunnel(!item.excludedFromTunnel)}"> + android:onClick="@{(view) -> item.setExcludedFromTunnel(!item.excludedFromTunnel)}" + android:orientation="horizontal" + android:padding="16dp"> <ImageView android:id="@+id/app_icon" diff --git a/app/src/main/res/layout/config_naming_dialog_fragment.xml b/app/src/main/res/layout/config_naming_dialog_fragment.xml index 12871a85..bf8b4267 100644 --- a/app/src/main/res/layout/config_naming_dialog_fragment.xml +++ b/app/src/main/res/layout/config_naming_dialog_fragment.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> <data> + <import type="com.wireguard.android.widget.NameInputFilter" /> </data> diff --git a/app/src/main/res/layout/tunnel_editor_fragment.xml b/app/src/main/res/layout/tunnel_editor_fragment.xml index 1020e34c..f7976459 100644 --- a/app/src/main/res/layout/tunnel_editor_fragment.xml +++ b/app/src/main/res/layout/tunnel_editor_fragment.xml @@ -100,8 +100,8 @@ app:filter="@{KeyInputFilter.newInstance()}" /> <Button - style="@style/Widget.AppCompat.Button.Borderless.Colored" android:id="@+id/generate_private_key_button" + style="@style/Widget.AppCompat.Button.Borderless.Colored" android:layout_width="96dp" android:layout_height="wrap_content" android:layout_alignBottom="@id/private_key_text" @@ -217,12 +217,12 @@ android:textAlignment="center" /> <Button - style="@style/Widget.AppCompat.Button.Borderless.Colored" android:id="@+id/set_excluded_applications" + style="@style/Widget.AppCompat.Button.Borderless.Colored" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="-8dp" android:layout_below="@+id/dns_servers_text" + android:layout_marginLeft="-8dp" android:onClick="@{fragment::onRequestSetExcludedApplications}" android:text="@{@plurals/set_excluded_applications(config.interfaceSection.excludedApplicationsCount, config.interfaceSection.excludedApplicationsCount)}" /> </RelativeLayout> diff --git a/app/src/main/res/layout/tunnel_editor_peer.xml b/app/src/main/res/layout/tunnel_editor_peer.xml index 472b4cac..e270b71a 100644 --- a/app/src/main/res/layout/tunnel_editor_peer.xml +++ b/app/src/main/res/layout/tunnel_editor_peer.xml @@ -4,7 +4,8 @@ <data> - <import type="android.view.View"/> + <import type="android.view.View" /> + <import type="com.wireguard.android.widget.KeyInputFilter" /> <variable diff --git a/app/src/main/res/layout/tunnel_list_fragment.xml b/app/src/main/res/layout/tunnel_list_fragment.xml index a442c3be..ccc1c5ae 100644 --- a/app/src/main/res/layout/tunnel_list_fragment.xml +++ b/app/src/main/res/layout/tunnel_list_fragment.xml @@ -30,65 +30,67 @@ android:id="@+id/tunnel_list" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingBottom="@{@dimen/design_fab_size_normal * 1.1f}" - android:clipToPadding="false" android:choiceMode="multipleChoiceModal" + android:clipToPadding="false" + android:paddingBottom="@{@dimen/design_fab_size_normal * 1.1f}" android:visibility="@{tunnels.size() > 0 ? android.view.View.VISIBLE : android.view.View.GONE}" + app:configurationHandler="@{rowConfigurationHandler}" app:items="@{tunnels}" - app:layout="@{@layout/tunnel_list_item}" - app:configurationHandler="@{rowConfigurationHandler}" /> + app:layout="@{@layout/tunnel_list_item}" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_gravity="center" android:orientation="vertical" - android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}" - android:layout_gravity="center"> + android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}"> + <android.support.v7.widget.AppCompatImageView android:id="@+id/logo_placeholder" android:layout_width="140dp" android:layout_height="140dp" android:layout_gravity="center" - android:alpha="0.3333333" - android:layout_marginTop="-70dp" android:layout_marginBottom="20dp" + android:layout_marginTop="-70dp" + android:alpha="0.3333333" android:src="@mipmap/ic_launcher" /> + <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:textSize="20sp" - android:text="@string/tunnel_list_placeholder" /> + android:text="@string/tunnel_list_placeholder" + android:textSize="20sp" /> </LinearLayout> <com.wireguard.android.widget.fab.FloatingActionsMenu android:id="@+id/create_menu" - android:clipChildren="false" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" + android:clipChildren="false" app:fab_labelStyle="@style/fab_label" app:fab_labelsPosition="@integer/label_position" - app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior" > + app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior"> <com.wireguard.android.widget.fab.LabeledFloatingActionButton android:id="@+id/create_from_file" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{fragment::onRequestImportConfig}" - app:srcCompat="@drawable/ic_action_open_white" app:fabSize="mini" - app:fab_title="@string/create_from_file" /> + app:fab_title="@string/create_from_file" + app:srcCompat="@drawable/ic_action_open_white" /> <com.wireguard.android.widget.fab.LabeledFloatingActionButton android:id="@+id/scan_qr_code" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{fragment::onRequestScanQRCode}" - app:srcCompat="@drawable/ic_action_scan_qr_code_white" app:fabSize="mini" - app:fab_title="@string/create_from_qrcode" /> + app:fab_title="@string/create_from_qrcode" + app:srcCompat="@drawable/ic_action_scan_qr_code_white" /> <com.wireguard.android.widget.fab.LabeledFloatingActionButton android:id="@+id/create_empty" @@ -96,8 +98,8 @@ android:layout_height="wrap_content" android:onClick="@{fragment::onRequestCreateConfig}" app:fabSize="mini" - app:srcCompat="@drawable/ic_action_edit_white" - app:fab_title="@string/create_empty" /> + app:fab_title="@string/create_empty" + app:srcCompat="@drawable/ic_action_edit_white" /> </com.wireguard.android.widget.fab.FloatingActionsMenu> </android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/menu/main_activity.xml b/app/src/main/res/menu/main_activity.xml index 5fd11821..68bce52e 100644 --- a/app/src/main/res/menu/main_activity.xml +++ b/app/src/main/res/menu/main_activity.xml @@ -5,7 +5,7 @@ android:id="@+id/menu_settings" android:alphabeticShortcut="s" android:icon="@drawable/ic_settings" + android:orderInCategory="1000" android:title="@string/settings" - app:showAsAction="always" - android:orderInCategory="1000" /> + app:showAsAction="always" /> </menu> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 85a987f9..86a86e63 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Multiselected"> - <attr name="state_multiselected" format="boolean"/> + <attr name="state_multiselected" format="boolean" /> </declare-styleable> -</resources>
\ No newline at end of file +</resources> diff --git a/app/src/main/res/values/fab.xml b/app/src/main/res/values/fab.xml index e1d10df1..8fbdc724 100644 --- a/app/src/main/res/values/fab.xml +++ b/app/src/main/res/values/fab.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <item name="fab_expand_menu_button" type="id"/> - <item name="fab_label" type="id"/> + <item name="fab_expand_menu_button" type="id" /> + <item name="fab_label" type="id" /> <dimen name="fab_shadow_offset">3dp</dimen> <dimen name="fab_shadow_radius">9dp</dimen> @@ -12,19 +12,19 @@ <dimen name="fab_labels_margin">8dp</dimen> <declare-styleable name="LabeledFloatingActionButton"> - <attr name="fab_title" format="string"/> + <attr name="fab_title" format="string" /> </declare-styleable> <declare-styleable name="FloatingActionsMenu"> - <attr name="fab_labelStyle" format="reference"/> + <attr name="fab_labelStyle" format="reference" /> <attr name="fab_labelsPosition" format="enum"> - <enum name="left" value="0"/> - <enum name="right" value="1"/> + <enum name="left" value="0" /> + <enum name="right" value="1" /> </attr> <attr name="fab_expandDirection" format="enum"> - <enum name="up" value="0"/> - <enum name="down" value="1"/> - <enum name="left" value="2"/> - <enum name="right" value="3"/> + <enum name="up" value="0" /> + <enum name="down" value="1" /> + <enum name="left" value="2" /> + <enum name="right" value="3" /> </attr> </declare-styleable> <integer name="label_position">0</integer> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c864ecfd..fce295ea 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -12,7 +12,7 @@ <CheckBoxPreference android:defaultValue="false" android:key="dark_theme" - android:summaryOn="@string/dark_theme_summary_on" android:summaryOff="@string/dark_theme_summary_off" + android:summaryOn="@string/dark_theme_summary_on" android:title="@string/dark_theme_title" /> </PreferenceScreen> |