Auto-format the source directories

Blame Jason for writing Java in vim.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Samuel Holland 2018-09-05 18:33:42 -05:00
parent 4e134772d7
commit a264f7ab36
42 changed files with 850 additions and 841 deletions

View File

@ -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"

View File

@ -53,70 +53,18 @@ 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);
}
/* 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
* check the DN of the certs that signed the apk, without even bothering to try
* validating that they're authentic. It's a good enough heuristic.
*/
@SuppressWarnings("deprecation")
@Nullable
private static String getInstallSource(final Context context) {
if (BuildConfig.DEBUG)
return null;
try {
final CertificateFactory cf = CertificateFactory.getInstance("X509");
for (final Signature sig : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures) {
try {
for (final String category : ((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(sig.toByteArray()))).getSubjectDN().getName().split(", *")) {
final String[] parts = category.split("=", 2);
if (!"O".equals(parts[0]))
continue;
switch (parts[1]) {
case "Google Inc.":
return "Play Store";
case "fdroid.org":
return "F-Droid";
}
}
} catch (final Exception ignored) { }
}
} catch (final Exception ignored) { }
return null;
}
@Override
protected void attachBaseContext(final Context context) {
super.attachBaseContext(context);
if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
System.exit(0);
}
final String installSource = getInstallSource(context);
if (installSource != null) {
ACRA.init(this);
ACRA.getErrorReporter().putCustomData("installSource", installSource);
}
}
public static Application get() {
return weakSelf.get();
}
@ -149,6 +97,40 @@ public class Application extends android.app.Application {
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
* check the DN of the certs that signed the apk, without even bothering to try
* validating that they're authentic. It's a good enough heuristic.
*/
@SuppressWarnings("deprecation")
@Nullable
private static String getInstallSource(final Context context) {
if (BuildConfig.DEBUG)
return null;
try {
final CertificateFactory cf = CertificateFactory.getInstance("X509");
for (final Signature sig : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures) {
try {
for (final String category : ((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(sig.toByteArray()))).getSubjectDN().getName().split(", *")) {
final String[] parts = category.split("=", 2);
if (!"O".equals(parts[0]))
continue;
switch (parts[1]) {
case "Google Inc.":
return "Play Store";
case "fdroid.org":
return "F-Droid";
}
}
} catch (final Exception ignored) {
}
}
} catch (final Exception ignored) {
}
return null;
}
public static RootShell getRootShell() {
return get().rootShell;
}
@ -165,6 +147,26 @@ public class Application extends android.app.Application {
return get().tunnelManager;
}
@Override
protected void attachBaseContext(final Context context) {
super.attachBaseContext(context);
if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
System.exit(0);
}
final String installSource = getInstallSource(context);
if (installSource != null) {
ACRA.init(this);
ACRA.getErrorReporter().putCustomData("installSource", installSource);
}
}
@Override
public void onCreate() {
super.onCreate();

View File

@ -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
@ -57,6 +57,22 @@ public class QuickTileService extends TileService {
return ret;
}
@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) {
@ -79,22 +95,6 @@ public class QuickTileService extends TileService {
iconOff = Icon.createWithBitmap(b);
}
@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);

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -59,12 +59,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) {
@ -101,6 +95,16 @@ public final class GoBackend implements Backend {
return new Statistics();
}
@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);

View File

@ -40,18 +40,6 @@ public final class WgQuickBackend implements Backend {
localTemporaryDir = new File(context.getCacheDir(), "tmp");
}
@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) {
@ -94,6 +82,20 @@ public final class WgQuickBackend implements Backend {
return new Statistics();
}
@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);

View File

@ -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())
@ -52,6 +47,11 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
return list.get(position);
}
@Override
public int getItemCount() {
return list != null ? list.size() : 0;
}
@Override
public long getItemId(final int position) {
final K key = getKey(position);
@ -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);
}
}

View File

@ -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) {

View File

@ -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;
@ -45,6 +44,18 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
return activity != null ? activity.getSelectedTunnel() : null;
}
@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);
@ -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);

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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;
@ -185,6 +183,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) {
@ -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
@ -317,26 +331,6 @@ public class TunnelListFragment extends BaseFragment {
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
}
@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) {

View File

@ -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;
}
}

View File

@ -151,5 +151,6 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
}
}
public static class Statistics extends BaseObservable { }
public static class Statistics extends BaseObservable {
}
}

View File

@ -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 ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR);
@Nullable private Tunnel lastUsedTunnel;
private boolean haveLoaded;
private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>();
private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR);
private boolean haveLoaded;
@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;
}

View File

@ -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) {
}
}
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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) {

View File

@ -31,95 +31,43 @@ import android.util.FloatProperty;
@TargetApi(Build.VERSION_CODES.N)
public class SlashDrawable extends Drawable {
private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f;
private static final long QS_ANIM_LENGTH = 350;
private final Path mPath = new Path();
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 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;
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 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 long QS_ANIM_LENGTH = 350;
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;
// Bottom is derived during animation
private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
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;
}
@Override
public Float get(final SlashDrawable object) {
return object.mCurrentSlashLength;
}
};
@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
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;
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;
}
}

View File

@ -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,17 +34,23 @@ 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) {

View File

@ -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 int mButtonSpacing;
private int mLabelsMargin;
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;
private int mMaxButtonHeight;
private int mLabelsStyle;
private int mLabelsPosition;
private int mButtonsCount;
@Nullable private TouchDelegateGroup mTouchDelegateGroup;
@Nullable private OnFloatingActionsMenuUpdateListener mListener;
private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private final Rect touchArea = new Rect(0, 0, 0, 0);
private float scrollYTranslation;
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;
@Nullable private OnFloatingActionsMenuUpdateListener mListener;
private int mMaxButtonHeight;
private int mMaxButtonWidth;
@Nullable private RotatingDrawable mRotatingDrawable;
@Nullable private TouchDelegateGroup mTouchDelegateGroup;
private float scrollYTranslation;
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);
private static int adjustForOvershoot(final int dimension) {
return dimension * 12 / 10;
}
mTouchDelegateGroup = new TouchDelegateGroup(this);
setTouchDelegate(mTouchDelegateGroup);
public void addButton(final LabeledFloatingActionButton button) {
addView(button, mButtonsCount - 1);
mButtonsCount++;
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.");
if (mLabelsStyle != 0) {
createLabels();
}
createAddButton(context);
}
public float getScrollYTranslation() {
return scrollYTranslation;
public void collapse() {
collapse(false);
}
public void setScrollYTranslation(final float scrollYTranslation) {
this.scrollYTranslation = scrollYTranslation;
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();
if (mListener != null) {
mListener.onMenuCollapsed();
}
}
}
public float getBehaviorYTranslation() {
return behaviorYTranslation;
}
public void setBehaviorYTranslation(final float behaviorYTranslation) {
this.behaviorYTranslation = behaviorYTranslation;
setTranslationY(behaviorYTranslation + scrollYTranslation);
}
public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) {
mListener = listener;
}
private boolean expandsHorizontally() {
return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT;
public void collapseImmediately() {
collapse(true);
}
private void createAddButton(final Context context) {
@ -156,87 +143,103 @@ 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);
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 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 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));
}
public float getBehaviorYTranslation() {
return behaviorYTranslation;
}
public float getScrollYTranslation() {
return scrollYTranslation;
}
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);
}
public boolean isExpanded() {
return mExpanded;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
bringChildToFront(mAddButton);
mButtonsCount = getChildCount();
if (mLabelsStyle != 0) {
createLabels();
}
}
public void removeButton(final LabeledFloatingActionButton button) {
removeView(button.getLabelView());
removeView(button);
button.setTag(R.id.fab_label, null);
mButtonsCount--;
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
mMaxButtonWidth = 0;
mMaxButtonHeight = 0;
int maxLabelWidth = 0;
for (int i = 0; i < mButtonsCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
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;
}
if (!expandsHorizontally()) {
final TextView label = (TextView) child.getTag(R.id.fab_label);
if (label != null) {
maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
}
}
}
if (expandsHorizontally()) {
height = mMaxButtonHeight;
} else {
width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
}
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 static int adjustForOvershoot(final int dimension) {
return dimension * 12 / 10;
}
@Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
switch (mExpandDirection) {
@ -367,115 +370,64 @@ public class FloatingActionsMenu extends ViewGroup {
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(super.generateDefaultLayoutParams());
}
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
@Override
public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) {
return new LayoutParams(super.generateLayoutParams(attrs));
}
int width = 0;
int height = 0;
@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();
}
}
private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS");
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);
}
public void collapseImmediately() {
collapse(true);
}
private void collapse(final boolean immediately) {
if (mExpanded) {
mExpanded = false;
mTouchDelegateGroup.setEnabled(false);
mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
mCollapseAnimation.start();
mExpandAnimation.cancel();
if (mListener != null) {
mListener.onMenuCollapsed();
}
}
}
public void toggle() {
if (mExpanded) {
collapse();
if (expandsHorizontally()) {
height = mMaxButtonHeight;
} else {
expand();
width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
}
}
public void expand() {
if (!mExpanded) {
mExpanded = true;
mTouchDelegateGroup.setEnabled(true);
mCollapseAnimation.cancel();
mExpandAnimation.start();
if (mListener != null) {
mListener.onMenuExpanded();
}
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;
}
}
public boolean isExpanded() {
return mExpanded;
}
@Override
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
mAddButton.setEnabled(enabled);
}
@Override
public Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState savedState = new SavedState(superState);
savedState.mExpanded = mExpanded;
return savedState;
setMeasuredDimension(width, height);
}
@Override
@ -495,10 +447,55 @@ public class FloatingActionsMenu extends ViewGroup {
}
}
public interface OnFloatingActionsMenuUpdateListener {
void onMenuExpanded();
@Override
public Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState savedState = new SavedState(superState);
savedState.mExpanded = mExpanded;
return savedState;
}
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
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
mAddButton.setEnabled(enabled);
}
public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) {
mListener = listener;
}
public void setScrollYTranslation(final float scrollYTranslation) {
this.scrollYTranslation = scrollYTranslation;
setTranslationY(behaviorYTranslation + scrollYTranslation);
}
public void toggle() {
if (mExpanded) {
collapse();
} else {
expand();
}
}
public interface OnFloatingActionsMenuUpdateListener {
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);
}
});
}
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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 {
}

View File

@ -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>
</vector>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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"

View File

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.wireguard.android.widget.NameInputFilter" />
</data>

View File

@ -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>

View File

@ -4,7 +4,8 @@
<data>
<import type="android.view.View"/>
<import type="android.view.View" />
<import type="com.wireguard.android.widget.KeyInputFilter" />
<variable

View File

@ -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>

View File

@ -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>

View File

@ -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>
</resources>

View File

@ -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>

View File

@ -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>