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:
parent
4e134772d7
commit
a264f7ab36
@ -11,10 +11,10 @@
|
|||||||
|
|
||||||
<permission
|
<permission
|
||||||
android:name="${applicationId}.permission.CONTROL_TUNNELS"
|
android:name="${applicationId}.permission.CONTROL_TUNNELS"
|
||||||
android:protectionLevel="dangerous"
|
android:description="@string/permission_description"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/permission_label"
|
android:label="@string/permission_label"
|
||||||
android:description="@string/permission_description" />
|
android:protectionLevel="dangerous" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Application"
|
android:name=".Application"
|
||||||
|
@ -53,70 +53,18 @@ import java9.util.concurrent.CompletableFuture;
|
|||||||
compress = true)
|
compress = true)
|
||||||
public class Application extends android.app.Application {
|
public class Application extends android.app.Application {
|
||||||
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
|
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
|
||||||
|
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
|
||||||
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
|
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
|
||||||
|
@Nullable private Backend backend;
|
||||||
@SuppressWarnings("NullableProblems") private RootShell rootShell;
|
@SuppressWarnings("NullableProblems") private RootShell rootShell;
|
||||||
@SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
|
@SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
|
||||||
@SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
|
@SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
|
||||||
@SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
|
@SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
|
||||||
@Nullable private Backend backend;
|
|
||||||
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
|
|
||||||
|
|
||||||
public Application() {
|
public Application() {
|
||||||
weakSelf = new WeakReference<>(this);
|
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() {
|
public static Application get() {
|
||||||
return weakSelf.get();
|
return weakSelf.get();
|
||||||
}
|
}
|
||||||
@ -149,6 +97,40 @@ public class Application extends android.app.Application {
|
|||||||
return get().futureBackend;
|
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() {
|
public static RootShell getRootShell() {
|
||||||
return get().rootShell;
|
return get().rootShell;
|
||||||
}
|
}
|
||||||
@ -165,6 +147,26 @@ public class Application extends android.app.Application {
|
|||||||
return get().tunnelManager;
|
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
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
@ -40,9 +40,9 @@ public class QuickTileService extends TileService {
|
|||||||
|
|
||||||
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
|
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
|
||||||
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
|
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
|
||||||
@Nullable private Tunnel tunnel;
|
|
||||||
@Nullable private Icon iconOn;
|
|
||||||
@Nullable private Icon iconOff;
|
@Nullable private Icon iconOff;
|
||||||
|
@Nullable private Icon iconOn;
|
||||||
|
@Nullable private Tunnel tunnel;
|
||||||
|
|
||||||
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
||||||
@Override
|
@Override
|
||||||
@ -57,6 +57,22 @@ public class QuickTileService extends TileService {
|
|||||||
return ret;
|
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
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
@ -79,22 +95,6 @@ public class QuickTileService extends TileService {
|
|||||||
iconOff = Icon.createWithBitmap(b);
|
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
|
@Override
|
||||||
public void onStartListening() {
|
public void onStartListening() {
|
||||||
Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback);
|
Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback);
|
||||||
|
@ -19,9 +19,9 @@ import java.lang.reflect.Field;
|
|||||||
|
|
||||||
public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName();
|
||||||
|
|
||||||
@Nullable private static Resources lastResources;
|
|
||||||
private static boolean lastDarkMode;
|
private static boolean lastDarkMode;
|
||||||
|
@Nullable private static Resources lastResources;
|
||||||
|
|
||||||
private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) {
|
private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) {
|
||||||
if (resources == lastResources && darkMode == lastDarkMode)
|
if (resources == lastResources && darkMode == lastDarkMode)
|
||||||
return;
|
return;
|
||||||
@ -33,7 +33,8 @@ public abstract class ThemeChangeAwareActivity extends AppCompatActivity impleme
|
|||||||
f = o.getClass().getDeclaredField("mResourcesImpl");
|
f = o.getClass().getDeclaredField("mResourcesImpl");
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
o = f.get(o);
|
o = f.get(o);
|
||||||
} catch (final Exception ignored) { }
|
} catch (final Exception ignored) {
|
||||||
|
}
|
||||||
f = o.getClass().getDeclaredField("mDrawableCache");
|
f = o.getClass().getDeclaredField("mDrawableCache");
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
o = f.get(o);
|
o = f.get(o);
|
||||||
|
@ -53,14 +53,11 @@ public interface Backend {
|
|||||||
Statistics getStatistics(Tunnel tunnel) throws Exception;
|
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.
|
* @return Type name
|
||||||
* @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
|
|
||||||
* {@code TOGGLE}.
|
|
||||||
* @return The updated state of the tunnel.
|
|
||||||
*/
|
*/
|
||||||
State setState(Tunnel tunnel, State state) throws Exception;
|
String getTypeName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine version of underlying backend.
|
* Determine version of underlying backend.
|
||||||
@ -71,9 +68,12 @@ public interface Backend {
|
|||||||
String getVersion() throws Exception;
|
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;
|
||||||
}
|
}
|
||||||
|
@ -59,12 +59,6 @@ public final class GoBackend implements Backend {
|
|||||||
|
|
||||||
private static native String wgVersion();
|
private static native String wgVersion();
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getVersion() { return wgVersion(); }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTypeName() { return "Go userspace"; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
|
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
|
||||||
if (tunnel.getState() == State.UP) {
|
if (tunnel.getState() == State.UP) {
|
||||||
@ -101,6 +95,16 @@ public final class GoBackend implements Backend {
|
|||||||
return new Statistics();
|
return new Statistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeName() {
|
||||||
|
return "Go userspace";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVersion() {
|
||||||
|
return wgVersion();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public State setState(final Tunnel tunnel, State state) throws Exception {
|
public State setState(final Tunnel tunnel, State state) throws Exception {
|
||||||
final State originalState = getState(tunnel);
|
final State originalState = getState(tunnel);
|
||||||
|
@ -40,18 +40,6 @@ public final class WgQuickBackend implements Backend {
|
|||||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
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
|
@Override
|
||||||
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
|
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
|
||||||
if (tunnel.getState() == State.UP) {
|
if (tunnel.getState() == State.UP) {
|
||||||
@ -94,6 +82,20 @@ public final class WgQuickBackend implements Backend {
|
|||||||
return new Statistics();
|
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
|
@Override
|
||||||
public State setState(final Tunnel tunnel, State state) throws Exception {
|
public State setState(final Tunnel tunnel, State state) throws Exception {
|
||||||
final State originalState = getState(tunnel);
|
final State originalState = getState(tunnel);
|
||||||
|
@ -40,11 +40,6 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
|
|||||||
setList(list);
|
setList(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return list != null ? list.size() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private E getItem(final int position) {
|
private E getItem(final int position) {
|
||||||
if (list == null || position < 0 || position >= list.size())
|
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);
|
return list.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return list != null ? list.size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(final int position) {
|
public long getItemId(final int position) {
|
||||||
final K key = getKey(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;
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(final ViewHolder holder, final int position) {
|
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) {
|
void setList(@Nullable final ObservableKeyedList<K, E> newList) {
|
||||||
if (list != null)
|
if (list != null)
|
||||||
list.removeOnListChangedCallback(callback);
|
list.removeOnListChangedCallback(callback);
|
||||||
@ -99,6 +99,10 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
|
|||||||
this.rowConfigurationHandler = rowConfigurationHandler;
|
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<?>>
|
private static final class OnListChangedCallback<E extends Keyed<?>>
|
||||||
extends ObservableList.OnListChangedCallback<ObservableList<E>> {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,8 @@ import java.util.List;
|
|||||||
public class AppListDialogFragment extends DialogFragment {
|
public class AppListDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
private static final String KEY_EXCLUDED_APPS = "excludedApps";
|
private static final String KEY_EXCLUDED_APPS = "excludedApps";
|
||||||
|
|
||||||
private List<String> currentlyExcludedApps;
|
|
||||||
private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
|
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) {
|
public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(final String[] excludedApps, final T target) {
|
||||||
final Bundle extras = new Bundle();
|
final Bundle extras = new Bundle();
|
||||||
@ -47,39 +46,6 @@ public class AppListDialogFragment extends DialogFragment {
|
|||||||
return fragment;
|
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() {
|
private void loadData() {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (activity == null) {
|
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() {
|
void setExclusionsAndDismiss() {
|
||||||
final List<String> excludedApps = new ArrayList<>();
|
final List<String> excludedApps = new ArrayList<>();
|
||||||
for (final ApplicationData data : appData) {
|
for (final ApplicationData data : appData) {
|
||||||
|
@ -33,9 +33,8 @@ import com.wireguard.android.util.ExceptionLoggers;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener {
|
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 int REQUEST_CODE_VPN_PERMISSION = 23491;
|
||||||
|
private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName();
|
||||||
@Nullable private BaseActivity activity;
|
@Nullable private BaseActivity activity;
|
||||||
@Nullable private Tunnel pendingTunnel;
|
@Nullable private Tunnel pendingTunnel;
|
||||||
@Nullable private Boolean pendingTunnelUp;
|
@Nullable private Boolean pendingTunnelUp;
|
||||||
@ -45,6 +44,18 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
|
|||||||
return activity != null ? activity.getSelectedTunnel() : null;
|
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
|
@Override
|
||||||
public void onAttach(final Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
@ -64,18 +75,6 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
|
|||||||
super.onDetach();
|
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) {
|
protected void setSelectedTunnel(@Nullable final Tunnel tunnel) {
|
||||||
if (activity != null)
|
if (activity != null)
|
||||||
activity.setSelectedTunnel(tunnel);
|
activity.setSelectedTunnel(tunnel);
|
||||||
|
@ -13,7 +13,6 @@ import android.os.Bundle;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
import com.wireguard.android.Application;
|
import com.wireguard.android.Application;
|
||||||
@ -27,9 +26,8 @@ import java.util.Objects;
|
|||||||
public class ConfigNamingDialogFragment extends DialogFragment {
|
public class ConfigNamingDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
private static final String KEY_CONFIG_TEXT = "config_text";
|
private static final String KEY_CONFIG_TEXT = "config_text";
|
||||||
|
|
||||||
@Nullable private Config config;
|
|
||||||
@Nullable private ConfigNamingDialogFragmentBinding binding;
|
@Nullable private ConfigNamingDialogFragmentBinding binding;
|
||||||
|
@Nullable private Config config;
|
||||||
@Nullable private InputMethodManager imm;
|
@Nullable private InputMethodManager imm;
|
||||||
|
|
||||||
public static ConfigNamingDialogFragment newInstance(final String configText) {
|
public static ConfigNamingDialogFragment newInstance(final String configText) {
|
||||||
@ -40,6 +38,26 @@ public class ConfigNamingDialogFragment extends DialogFragment {
|
|||||||
return fragment;
|
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
|
@Override
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(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
|
@Override
|
||||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
@ -81,23 +88,14 @@ public class ConfigNamingDialogFragment extends DialogFragment {
|
|||||||
return alertDialogBuilder.create();
|
return alertDialogBuilder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void onResume() {
|
||||||
public void dismiss() {
|
super.onResume();
|
||||||
setKeyboardVisible(false);
|
|
||||||
super.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createTunnelAndDismiss() {
|
final AlertDialog dialog = (AlertDialog) getDialog();
|
||||||
if (binding != null) {
|
if (dialog != null) {
|
||||||
final String name = binding.tunnelNameText.getText().toString();
|
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss());
|
||||||
|
|
||||||
Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> {
|
setKeyboardVisible(true);
|
||||||
if (tunnel != null) {
|
|
||||||
dismiss();
|
|
||||||
} else {
|
|
||||||
binding.tunnelNameTextLayout.setError(throwable.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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_LOCAL_CONFIG = "local_config";
|
||||||
private static final String KEY_ORIGINAL_NAME = "original_name";
|
private static final String KEY_ORIGINAL_NAME = "original_name";
|
||||||
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
|
||||||
|
private final Collection<Object> breakObjectOrientedLayeringHandlerReceivers = new ArrayList<>();
|
||||||
@Nullable private TunnelEditorFragmentBinding binding;
|
@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;
|
@Nullable private Tunnel tunnel;
|
||||||
|
|
||||||
private void onConfigLoaded(final String name, final Config config) {
|
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);
|
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
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
@Nullable final Bundle savedInstanceState) {
|
@Nullable final Bundle savedInstanceState) {
|
||||||
@ -159,6 +162,12 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
|
|||||||
super.onDestroyView();
|
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() {
|
private void onFinished() {
|
||||||
// Hide the keyboard; it rarely goes away on its own.
|
// Hide the keyboard; it rarely goes away on its own.
|
||||||
final Activity activity = getActivity();
|
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
|
@Override
|
||||||
public void onSaveInstanceState(final Bundle outState) {
|
public void onSaveInstanceState(final Bundle outState) {
|
||||||
outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
|
outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
|
||||||
@ -294,19 +312,4 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
|
|||||||
super.onViewStateRestored(savedInstanceState);
|
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,11 @@ import com.wireguard.android.databinding.TunnelListFragmentBinding;
|
|||||||
import com.wireguard.android.databinding.TunnelListItemBinding;
|
import com.wireguard.android.databinding.TunnelListItemBinding;
|
||||||
import com.wireguard.android.model.Tunnel;
|
import com.wireguard.android.model.Tunnel;
|
||||||
import com.wireguard.android.util.ExceptionLoggers;
|
import com.wireguard.android.util.ExceptionLoggers;
|
||||||
import com.wireguard.android.util.ObservableSortedKeyedList;
|
|
||||||
import com.wireguard.android.widget.MultiselectableRelativeLayout;
|
import com.wireguard.android.widget.MultiselectableRelativeLayout;
|
||||||
import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
|
import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
|
||||||
import com.wireguard.config.Config;
|
import com.wireguard.config.Config;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
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
|
@Override
|
||||||
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
|
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
@ -228,6 +239,14 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
if (binding != null) {
|
||||||
|
binding.createMenu.collapse();
|
||||||
|
}
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
|
public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
|
||||||
startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
|
startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
|
||||||
if (binding != null)
|
if (binding != null)
|
||||||
@ -255,15 +274,10 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onSaveInstanceState(final Bundle outState) {
|
||||||
if (binding != null) {
|
super.onSaveInstanceState(outState);
|
||||||
binding.createMenu.collapse();
|
|
||||||
}
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) {
|
outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems());
|
||||||
return (MultiselectableRelativeLayout)binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -317,26 +331,6 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
|
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
|
@Override
|
||||||
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
|
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
@ -368,11 +362,19 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 class ActionModeListener implements ActionMode.Callback {
|
||||||
private final Collection<Integer> checkedItems = new HashSet<>();
|
private final Collection<Integer> checkedItems = new HashSet<>();
|
||||||
|
|
||||||
@Nullable private Resources resources;
|
@Nullable private Resources resources;
|
||||||
|
|
||||||
|
public ArrayList<Integer> getCheckedItems() {
|
||||||
|
return new ArrayList<>(checkedItems);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
@ -425,12 +427,10 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
binding.tunnelList.getAdapter().notifyDataSetChanged();
|
binding.tunnelList.getAdapter().notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleItemChecked(final int position) {
|
@Override
|
||||||
setItemChecked(position, !checkedItems.contains(position));
|
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
|
||||||
}
|
updateTitle(mode);
|
||||||
|
return false;
|
||||||
public ArrayList<Integer> getCheckedItems() {
|
|
||||||
return new ArrayList<>(checkedItems);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setItemChecked(final int position, final boolean checked) {
|
void setItemChecked(final int position, final boolean checked) {
|
||||||
@ -454,10 +454,8 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
updateTitle(actionMode);
|
updateTitle(actionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void toggleItemChecked(final int position) {
|
||||||
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
|
setItemChecked(position, !checkedItems.contains(position));
|
||||||
updateTitle(mode);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTitle(@Nullable final ActionMode mode) {
|
private void updateTitle(@Nullable final ActionMode mode) {
|
||||||
|
@ -30,6 +30,11 @@ public class ApplicationData extends BaseObservable implements Keyed<String> {
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@ -47,9 +52,4 @@ public class ApplicationData extends BaseObservable implements Keyed<String> {
|
|||||||
this.excludedFromTunnel = excludedFromTunnel;
|
this.excludedFromTunnel = excludedFromTunnel;
|
||||||
notifyPropertyChanged(BR.excludedFromTunnel);
|
notifyPropertyChanged(BR.excludedFromTunnel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getKey() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -151,5 +151,6 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Statistics extends BaseObservable { }
|
public static class Statistics extends BaseObservable {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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_LAST_USED_TUNNEL = "last_used_tunnel";
|
||||||
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
|
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
|
||||||
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
||||||
|
private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>();
|
||||||
private final ConfigStore configStore;
|
private final ConfigStore configStore;
|
||||||
private final Context context = Application.get();
|
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 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) {
|
public TunnelManager(final ConfigStore configStore) {
|
||||||
this.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) {
|
private Tunnel addToList(final String name, @Nullable final Config config, final State state) {
|
||||||
final Tunnel tunnel = new Tunnel(this, name, config, state);
|
final Tunnel tunnel = new Tunnel(this, name, config, state);
|
||||||
tunnels.add(tunnel);
|
tunnels.add(tunnel);
|
||||||
@ -112,16 +121,6 @@ public final class TunnelManager extends BaseObservable {
|
|||||||
.thenApply(tunnel::onConfigChanged);
|
.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() {
|
public CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> getTunnels() {
|
||||||
return completableTunnels;
|
return completableTunnels;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,8 @@ public class VersionPreference extends Preference {
|
|||||||
intent.setData(Uri.parse("https://www.wireguard.com/"));
|
intent.setData(Uri.parse("https://www.wireguard.com/"));
|
||||||
try {
|
try {
|
||||||
getContext().startActivity(intent);
|
getContext().startActivity(intent);
|
||||||
} catch (final ActivityNotFoundException ignored) { }
|
} catch (final ActivityNotFoundException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@ import java.util.zip.ZipFile;
|
|||||||
public final class SharedLibraryLoader {
|
public final class SharedLibraryLoader {
|
||||||
private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName();
|
||||||
|
|
||||||
private SharedLibraryLoader() { }
|
private SharedLibraryLoader() {
|
||||||
|
}
|
||||||
|
|
||||||
public static void loadSharedLibrary(final Context context, final String libName) {
|
public static void loadSharedLibrary(final Context context, final String libName) {
|
||||||
Throwable noAbiException;
|
Throwable noAbiException;
|
||||||
|
@ -26,11 +26,10 @@ import java.util.List;
|
|||||||
|
|
||||||
public final class ToolsInstaller {
|
public final class ToolsInstaller {
|
||||||
public static final int ERROR = 0x0;
|
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 MAGISK = 0x4;
|
||||||
|
public static final int NO = 0x2;
|
||||||
public static final int SYSTEM = 0x8;
|
public static final int SYSTEM = 0x8;
|
||||||
|
public static final int YES = 0x1;
|
||||||
private static final String[][] EXECUTABLES = {
|
private static final String[][] EXECUTABLES = {
|
||||||
{"libwg.so", "wg"},
|
{"libwg.so", "wg"},
|
||||||
{"libwg-quick.so", "wg-quick"},
|
{"libwg-quick.so", "wg-quick"},
|
||||||
@ -107,34 +106,8 @@ public final class ToolsInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean willInstallAsMagiskModule() {
|
public int install() throws NoRootException {
|
||||||
synchronized (lock) {
|
return willInstallAsMagiskModule() ? installMagisk() : installSystem();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int installMagisk() throws NoRootException {
|
private int installMagisk() throws NoRootException {
|
||||||
@ -158,8 +131,21 @@ public final class ToolsInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int install() throws NoRootException {
|
private int installSystem() throws NoRootException {
|
||||||
return willInstallAsMagiskModule() ? installMagisk() : installSystem();
|
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 {
|
public int symlink() throws NoRootException {
|
||||||
@ -184,4 +170,17 @@ public final class ToolsInstaller {
|
|||||||
return OsConstants.EXIT_FAILURE;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,23 +12,25 @@ import android.widget.RelativeLayout;
|
|||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
|
|
||||||
public class MultiselectableRelativeLayout extends RelativeLayout {
|
public class MultiselectableRelativeLayout extends RelativeLayout {
|
||||||
|
private static final int[] STATE_MULTISELECTED = {R.attr.state_multiselected};
|
||||||
|
private boolean multiselected;
|
||||||
|
|
||||||
public MultiselectableRelativeLayout(final Context context) {
|
public MultiselectableRelativeLayout(final Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) {
|
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
|
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int[] STATE_MULTISELECTED = { R.attr.state_multiselected };
|
|
||||||
|
|
||||||
private boolean multiselected;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int[] onCreateDrawableState(final int extraSpace) {
|
protected int[] onCreateDrawableState(final int extraSpace) {
|
||||||
if (multiselected) {
|
if (multiselected) {
|
||||||
|
@ -31,95 +31,43 @@ import android.util.FloatProperty;
|
|||||||
@TargetApi(Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
public class SlashDrawable extends Drawable {
|
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_X = 10.65f;
|
||||||
private static final float CENTER_Y = 11.869239f;
|
private static final float CENTER_Y = 11.869239f;
|
||||||
private static final float SCALE = 24f;
|
private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f;
|
||||||
|
|
||||||
// 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
|
// Draw the slash washington-monument style; rotate to no-u-turn style
|
||||||
private static final float DEFAULT_ROTATION = -45f;
|
private static final float DEFAULT_ROTATION = -45f;
|
||||||
|
private static final long QS_ANIM_LENGTH = 350;
|
||||||
private final Drawable mDrawable;
|
private static final float SCALE = 24f;
|
||||||
private final RectF mSlashRect = new RectF(0, 0, 0, 0);
|
private static final float SLASH_HEIGHT = 28f;
|
||||||
private float mRotation;
|
// These values are derived in un-rotated (vertical) orientation
|
||||||
private boolean mSlashed;
|
private static final float SLASH_WIDTH = 1.8384776f;
|
||||||
private boolean mAnimationEnabled = true;
|
// Bottom is derived during animation
|
||||||
|
private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
|
||||||
public SlashDrawable(final Drawable d) {
|
private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
|
||||||
mDrawable = d;
|
private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
|
||||||
}
|
|
||||||
|
|
||||||
@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 FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
|
private static final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
|
||||||
@Override
|
|
||||||
public void setValue(final SlashDrawable object, final float value) {
|
|
||||||
object.mCurrentSlashLength = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Float get(final SlashDrawable object) {
|
public Float get(final SlashDrawable object) {
|
||||||
return object.mCurrentSlashLength;
|
return object.mCurrentSlashLength;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@Override
|
||||||
public void setSlashed(final boolean slashed) {
|
public void setValue(final SlashDrawable object, final float value) {
|
||||||
if (mSlashed == slashed) return;
|
object.mCurrentSlashLength = value;
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
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")
|
@SuppressWarnings("deprecation")
|
||||||
@ -166,15 +114,76 @@ public class SlashDrawable extends Drawable {
|
|||||||
canvas.restore();
|
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) {
|
private float scale(final float frac, final int width) {
|
||||||
return frac * width;
|
return frac * width;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRect(final float left, final float top, final float right, final float bottom) {
|
@Override
|
||||||
mSlashRect.left = left;
|
public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) {
|
||||||
mSlashRect.top = top;
|
mDrawable.setAlpha(alpha);
|
||||||
mSlashRect.right = right;
|
mPaint.setAlpha(alpha);
|
||||||
mSlashRect.bottom = bottom;
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
@ -192,30 +201,16 @@ public class SlashDrawable extends Drawable {
|
|||||||
invalidateSelf();
|
invalidateSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDrawableTintList(@Nullable final ColorStateList tint) {
|
|
||||||
mDrawable.setTintList(tint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTintMode(final Mode tintMode) {
|
public void setTintMode(final Mode tintMode) {
|
||||||
super.setTintMode(tintMode);
|
super.setTintMode(tintMode);
|
||||||
mDrawable.setTintMode(tintMode);
|
mDrawable.setTintMode(tintMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void updateRect(final float left, final float top, final float right, final float bottom) {
|
||||||
public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) {
|
mSlashRect.left = left;
|
||||||
mDrawable.setAlpha(alpha);
|
mSlashRect.top = top;
|
||||||
mPaint.setAlpha(alpha);
|
mSlashRect.right = right;
|
||||||
}
|
mSlashRect.bottom = bottom;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setColorFilter(@Nullable final ColorFilter colorFilter) {
|
|
||||||
mDrawable.setColorFilter(colorFilter);
|
|
||||||
mPaint.setColorFilter(colorFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOpacity() {
|
|
||||||
return PixelFormat.OPAQUE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,19 +18,13 @@ import android.view.View;
|
|||||||
|
|
||||||
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
|
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) {
|
public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) {
|
||||||
super(context, 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) {
|
private static void animateChange(final FloatingActionsMenu child, final float destination, final float fullSpan) {
|
||||||
final float origin = child.getBehaviorYTranslation();
|
final float origin = child.getBehaviorYTranslation();
|
||||||
if (Math.abs(destination - origin) < fullSpan / 2) {
|
if (Math.abs(destination - origin) < fullSpan / 2) {
|
||||||
@ -51,6 +45,12 @@ public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<Flo
|
|||||||
animator.start();
|
animator.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child,
|
||||||
|
final View dependency) {
|
||||||
|
return dependency instanceof Snackbar.SnackbarLayout;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child,
|
public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child,
|
||||||
final View dependency) {
|
final View dependency) {
|
||||||
|
@ -37,39 +37,38 @@ import android.widget.TextView;
|
|||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
|
|
||||||
public class FloatingActionsMenu extends ViewGroup {
|
public class FloatingActionsMenu extends ViewGroup {
|
||||||
public static final int EXPAND_UP = 0;
|
|
||||||
public static final int EXPAND_DOWN = 1;
|
public static final int EXPAND_DOWN = 1;
|
||||||
public static final int EXPAND_LEFT = 2;
|
public static final int EXPAND_LEFT = 2;
|
||||||
public static final int EXPAND_RIGHT = 3;
|
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_LEFT_SIDE = 0;
|
||||||
public static final int LABELS_ON_RIGHT_SIDE = 1;
|
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 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 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 float EXPANDED_PLUS_ROTATION = 90f + 45f;
|
||||||
private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator();
|
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);
|
private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
|
||||||
@Nullable private FloatingActionButton mAddButton;
|
private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
|
||||||
@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 Rect touchArea = new Rect(0, 0, 0, 0);
|
private final Rect touchArea = new Rect(0, 0, 0, 0);
|
||||||
private float scrollYTranslation;
|
|
||||||
private float behaviorYTranslation;
|
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) {
|
public FloatingActionsMenu(final Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@ -85,51 +84,39 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
init(context, attrs);
|
init(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init(final Context context, @Nullable final AttributeSet attributeSet) {
|
private static int adjustForOvershoot(final int dimension) {
|
||||||
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing));
|
return dimension * 12 / 10;
|
||||||
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 void addButton(final LabeledFloatingActionButton button) {
|
||||||
|
addView(button, mButtonsCount - 1);
|
||||||
|
mButtonsCount++;
|
||||||
|
|
||||||
|
if (mLabelsStyle != 0) {
|
||||||
|
createLabels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getScrollYTranslation() {
|
public void collapse() {
|
||||||
return scrollYTranslation;
|
collapse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setScrollYTranslation(final float scrollYTranslation) {
|
private void collapse(final boolean immediately) {
|
||||||
this.scrollYTranslation = scrollYTranslation;
|
if (mExpanded) {
|
||||||
setTranslationY(behaviorYTranslation + scrollYTranslation);
|
mExpanded = false;
|
||||||
|
mTouchDelegateGroup.setEnabled(false);
|
||||||
|
mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
|
||||||
|
mCollapseAnimation.start();
|
||||||
|
mExpandAnimation.cancel();
|
||||||
|
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onMenuCollapsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getBehaviorYTranslation() {
|
public void collapseImmediately() {
|
||||||
return behaviorYTranslation;
|
collapse(true);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAddButton(final Context context) {
|
private void createAddButton(final Context context) {
|
||||||
@ -156,87 +143,103 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
mButtonsCount++;
|
mButtonsCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addButton(final LabeledFloatingActionButton button) {
|
private void createLabels() {
|
||||||
addView(button, mButtonsCount - 1);
|
final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle);
|
||||||
mButtonsCount++;
|
|
||||||
|
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) {
|
if (mLabelsStyle != 0) {
|
||||||
createLabels();
|
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
|
@Override
|
||||||
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
|
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
|
||||||
switch (mExpandDirection) {
|
switch (mExpandDirection) {
|
||||||
@ -367,115 +370,64 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||||
return new LayoutParams(super.generateDefaultLayoutParams());
|
measureChildren(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
int width = 0;
|
||||||
public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) {
|
int height = 0;
|
||||||
return new LayoutParams(super.generateLayoutParams(attrs));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
mMaxButtonWidth = 0;
|
||||||
protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) {
|
mMaxButtonHeight = 0;
|
||||||
return new LayoutParams(super.generateLayoutParams(p));
|
int maxLabelWidth = 0;
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
|
|
||||||
for (int i = 0; i < mButtonsCount; i++) {
|
for (int i = 0; i < mButtonsCount; i++) {
|
||||||
final FloatingActionButton button = (FloatingActionButton) getChildAt(i);
|
final View child = getChildAt(i);
|
||||||
|
|
||||||
if (button instanceof LabeledFloatingActionButton) {
|
if (child.getVisibility() == GONE) {
|
||||||
final String title = ((LabeledFloatingActionButton) button).getTitle();
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final AppCompatTextView label = new AppCompatTextView(context);
|
switch (mExpandDirection) {
|
||||||
if (!BROKEN_LABEL_STYLE)
|
case EXPAND_UP:
|
||||||
label.setTextAppearance(context, mLabelsStyle);
|
case EXPAND_DOWN:
|
||||||
label.setText(title);
|
mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
|
||||||
addView(label);
|
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() {
|
if (expandsHorizontally()) {
|
||||||
collapse(false);
|
height = mMaxButtonHeight;
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
} else {
|
} else {
|
||||||
expand();
|
width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void expand() {
|
switch (mExpandDirection) {
|
||||||
if (!mExpanded) {
|
case EXPAND_UP:
|
||||||
mExpanded = true;
|
case EXPAND_DOWN:
|
||||||
mTouchDelegateGroup.setEnabled(true);
|
height += mButtonSpacing * (mButtonsCount - 1);
|
||||||
mCollapseAnimation.cancel();
|
height = adjustForOvershoot(height);
|
||||||
mExpandAnimation.start();
|
break;
|
||||||
|
case EXPAND_LEFT:
|
||||||
if (mListener != null) {
|
case EXPAND_RIGHT:
|
||||||
mListener.onMenuExpanded();
|
width += mButtonSpacing * (mButtonsCount - 1);
|
||||||
}
|
width = adjustForOvershoot(width);
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExpanded() {
|
setMeasuredDimension(width, height);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -495,10 +447,55 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnFloatingActionsMenuUpdateListener {
|
@Override
|
||||||
void onMenuExpanded();
|
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 onMenuCollapsed();
|
||||||
|
|
||||||
|
void onMenuExpanded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RotatingDrawable extends LayerDrawable {
|
private static class RotatingDrawable extends LayerDrawable {
|
||||||
@ -508,6 +505,14 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
super(new Drawable[]{drawable});
|
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")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
public float getRotation() {
|
public float getRotation() {
|
||||||
return mRotation;
|
return mRotation;
|
||||||
@ -519,14 +524,6 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
mRotation = rotation;
|
mRotation = rotation;
|
||||||
invalidateSelf();
|
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 {
|
public static class SavedState extends BaseSavedState {
|
||||||
@ -562,10 +559,10 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
|
|
||||||
private class LayoutParams extends ViewGroup.LayoutParams {
|
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 mCollapseAlpha = new ObjectAnimator();
|
||||||
|
private final ObjectAnimator mCollapseDir = new ObjectAnimator();
|
||||||
|
private final ObjectAnimator mExpandAlpha = new ObjectAnimator();
|
||||||
|
private final ObjectAnimator mExpandDir = new ObjectAnimator();
|
||||||
private boolean animationsSetToPlay;
|
private boolean animationsSetToPlay;
|
||||||
|
|
||||||
LayoutParams(final ViewGroup.LayoutParams source) {
|
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) {
|
public void setAnimationsTarget(final View view) {
|
||||||
mCollapseAlpha.setTarget(view);
|
mCollapseAlpha.setTarget(view);
|
||||||
mCollapseDir.setTarget(view);
|
mCollapseDir.setTarget(view);
|
||||||
@ -614,19 +625,5 @@ public class FloatingActionsMenu extends ViewGroup {
|
|||||||
animationsSetToPlay = true;
|
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,6 @@ public class TouchDelegateGroup extends TouchDelegate {
|
|||||||
mTouchDelegates.add(touchDelegate);
|
mTouchDelegates.add(touchDelegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeTouchDelegate(final TouchDelegate touchDelegate) {
|
|
||||||
mTouchDelegates.remove(touchDelegate);
|
|
||||||
if (mCurrentTouchDelegate == touchDelegate) {
|
|
||||||
mCurrentTouchDelegate = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearTouchDelegates() {
|
public void clearTouchDelegates() {
|
||||||
mTouchDelegates.clear();
|
mTouchDelegates.clear();
|
||||||
mCurrentTouchDelegate = null;
|
mCurrentTouchDelegate = null;
|
||||||
@ -72,6 +65,13 @@ public class TouchDelegateGroup extends TouchDelegate {
|
|||||||
return delegate != null && delegate.onTouchEvent(event);
|
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) {
|
public void setEnabled(final boolean enabled) {
|
||||||
mEnabled = enabled;
|
mEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
@ -105,9 +105,9 @@ public class Config {
|
|||||||
return new Observable[size];
|
return new Observable[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@Nullable private String name;
|
|
||||||
private final Interface.Observable observableInterface;
|
private final Interface.Observable observableInterface;
|
||||||
private final ObservableList<Peer.Observable> observablePeers;
|
private final ObservableList<Peer.Observable> observablePeers;
|
||||||
|
@Nullable private String name;
|
||||||
|
|
||||||
public Observable(@Nullable final Config parent, @Nullable final String name) {
|
public Observable(@Nullable final Config parent, @Nullable final String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -28,12 +28,12 @@ import java.util.List;
|
|||||||
|
|
||||||
public class Interface {
|
public class Interface {
|
||||||
private final List<InetNetwork> addressList;
|
private final List<InetNetwork> addressList;
|
||||||
|
private final Context context = Application.get();
|
||||||
private final List<InetAddress> dnsList;
|
private final List<InetAddress> dnsList;
|
||||||
private final List<String> excludedApplications;
|
private final List<String> excludedApplications;
|
||||||
@Nullable private Keypair keypair;
|
@Nullable private Keypair keypair;
|
||||||
private int listenPort;
|
private int listenPort;
|
||||||
private int mtu;
|
private int mtu;
|
||||||
private final Context context = Application.get();
|
|
||||||
|
|
||||||
public Interface() {
|
public Interface() {
|
||||||
addressList = new ArrayList<>();
|
addressList = new ArrayList<>();
|
||||||
@ -94,6 +94,10 @@ public class Interface {
|
|||||||
return dnsList.toArray(new InetAddress[dnsList.size()]);
|
return dnsList.toArray(new InetAddress[dnsList.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String[] getExcludedApplications() {
|
||||||
|
return excludedApplications.toArray(new String[excludedApplications.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getExcludedApplicationsString() {
|
private String getExcludedApplicationsString() {
|
||||||
if (excludedApplications.isEmpty())
|
if (excludedApplications.isEmpty())
|
||||||
@ -101,10 +105,6 @@ public class Interface {
|
|||||||
return Attribute.iterableToString(excludedApplications);
|
return Attribute.iterableToString(excludedApplications);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getExcludedApplications() {
|
|
||||||
return excludedApplications.toArray(new String[excludedApplications.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getListenPort() {
|
public int getListenPort() {
|
||||||
return listenPort;
|
return listenPort;
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,11 @@ import java9.lang.Iterables;
|
|||||||
|
|
||||||
public class Peer {
|
public class Peer {
|
||||||
private final List<InetNetwork> allowedIPsList;
|
private final List<InetNetwork> allowedIPsList;
|
||||||
|
private final Context context = Application.get();
|
||||||
@Nullable private InetEndpoint endpoint;
|
@Nullable private InetEndpoint endpoint;
|
||||||
private int persistentKeepalive;
|
private int persistentKeepalive;
|
||||||
@Nullable private String preSharedKey;
|
@Nullable private String preSharedKey;
|
||||||
@Nullable private String publicKey;
|
@Nullable private String publicKey;
|
||||||
private final Context context = Application.get();
|
|
||||||
|
|
||||||
public Peer() {
|
public Peer() {
|
||||||
allowedIPsList = new ArrayList<>();
|
allowedIPsList = new ArrayList<>();
|
||||||
@ -201,13 +201,15 @@ public class Peer {
|
|||||||
return new Observable[size];
|
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 allowedIPs;
|
||||||
@Nullable private String endpoint;
|
@Nullable private String endpoint;
|
||||||
|
private int numSiblings;
|
||||||
@Nullable private String persistentKeepalive;
|
@Nullable private String persistentKeepalive;
|
||||||
@Nullable private String preSharedKey;
|
@Nullable private String preSharedKey;
|
||||||
@Nullable private String publicKey;
|
@Nullable private String publicKey;
|
||||||
private final List<String> interfaceDNSRoutes = new ArrayList<>();
|
|
||||||
private int numSiblings;
|
|
||||||
|
|
||||||
public Observable(final Peer parent) {
|
public Observable(final Peer parent) {
|
||||||
loadData(parent);
|
loadData(parent);
|
||||||
@ -244,22 +246,9 @@ public class Peer {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String DEFAULT_ROUTE_V4 = "0.0.0.0/0";
|
@Bindable @Nullable
|
||||||
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 String getAllowedIPs() {
|
||||||
|
return allowedIPs;
|
||||||
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
|
@Bindable
|
||||||
@ -268,21 +257,16 @@ public class Peer {
|
|||||||
return numSiblings == 0 && (ips.contains(DEFAULT_ROUTE_V4) || ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4));
|
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
|
@Bindable @Nullable
|
||||||
public String getEndpoint() {
|
public String getEndpoint() {
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bindable
|
||||||
|
public boolean getIsExcludePrivateIPsOn() {
|
||||||
|
return numSiblings == 0 && Arrays.asList(Attribute.stringToList(allowedIPs)).containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
|
||||||
|
}
|
||||||
|
|
||||||
@Bindable @Nullable
|
@Bindable @Nullable
|
||||||
public String getPersistentKeepalive() {
|
public String getPersistentKeepalive() {
|
||||||
return persistentKeepalive;
|
return persistentKeepalive;
|
||||||
@ -318,21 +302,6 @@ public class Peer {
|
|||||||
notifyPropertyChanged(BR.endpoint);
|
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) {
|
public void setInterfaceDNSRoutes(@Nullable final String dnsServers) {
|
||||||
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
|
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
|
||||||
final boolean modifyAllowedIPs = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
|
final boolean modifyAllowedIPs = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
|
||||||
@ -354,6 +323,36 @@ public class Peer {
|
|||||||
notifyPropertyChanged(BR.isExcludePrivateIPsOn);
|
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
|
@Override
|
||||||
public void writeToParcel(final Parcel dest, final int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
dest.writeString(allowedIPs);
|
dest.writeString(allowedIPs);
|
||||||
|
@ -22,4 +22,5 @@ import javax.annotation.meta.TypeQualifierDefault;
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
@TypeQualifierDefault({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
|
@TypeQualifierDefault({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface NonNullForAll { }
|
public @interface NonNullForAll {
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp">
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="?android:attr/colorForeground"
|
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" />
|
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" />
|
||||||
|
@ -3,10 +3,14 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item>
|
<item>
|
||||||
<selector>
|
<selector>
|
||||||
<item app:state_multiselected="true" android:state_activated="true">
|
<item
|
||||||
|
android:state_activated="true"
|
||||||
|
app:state_multiselected="true">
|
||||||
<color android:color="?attr/colorControlActivated" />
|
<color android:color="?attr/colorControlActivated" />
|
||||||
</item>
|
</item>
|
||||||
<item app:state_multiselected="false" android:state_activated="true">
|
<item
|
||||||
|
android:state_activated="true"
|
||||||
|
app:state_multiselected="false">
|
||||||
<color android:color="?attr/colorControlHighlight" />
|
<color android:color="?attr/colorControlHighlight" />
|
||||||
</item>
|
</item>
|
||||||
</selector>
|
</selector>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
@ -23,10 +22,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/list_item_background"
|
android:background="@drawable/list_item_background"
|
||||||
android:padding="16dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
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
|
<ImageView
|
||||||
android:id="@+id/app_icon"
|
android:id="@+id/app_icon"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
<import type="com.wireguard.android.widget.NameInputFilter" />
|
<import type="com.wireguard.android.widget.NameInputFilter" />
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
|
@ -100,8 +100,8 @@
|
|||||||
app:filter="@{KeyInputFilter.newInstance()}" />
|
app:filter="@{KeyInputFilter.newInstance()}" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
|
||||||
android:id="@+id/generate_private_key_button"
|
android:id="@+id/generate_private_key_button"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||||
android:layout_width="96dp"
|
android:layout_width="96dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignBottom="@id/private_key_text"
|
android:layout_alignBottom="@id/private_key_text"
|
||||||
@ -217,12 +217,12 @@
|
|||||||
android:textAlignment="center" />
|
android:textAlignment="center" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
|
||||||
android:id="@+id/set_excluded_applications"
|
android:id="@+id/set_excluded_applications"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="-8dp"
|
|
||||||
android:layout_below="@+id/dns_servers_text"
|
android:layout_below="@+id/dns_servers_text"
|
||||||
|
android:layout_marginLeft="-8dp"
|
||||||
android:onClick="@{fragment::onRequestSetExcludedApplications}"
|
android:onClick="@{fragment::onRequestSetExcludedApplications}"
|
||||||
android:text="@{@plurals/set_excluded_applications(config.interfaceSection.excludedApplicationsCount, config.interfaceSection.excludedApplicationsCount)}" />
|
android:text="@{@plurals/set_excluded_applications(config.interfaceSection.excludedApplicationsCount, config.interfaceSection.excludedApplicationsCount)}" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<data>
|
<data>
|
||||||
|
|
||||||
<import type="android.view.View" />
|
<import type="android.view.View" />
|
||||||
|
|
||||||
<import type="com.wireguard.android.widget.KeyInputFilter" />
|
<import type="com.wireguard.android.widget.KeyInputFilter" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
|
@ -30,44 +30,46 @@
|
|||||||
android:id="@+id/tunnel_list"
|
android:id="@+id/tunnel_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="@{@dimen/design_fab_size_normal * 1.1f}"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:choiceMode="multipleChoiceModal"
|
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}"
|
android:visibility="@{tunnels.size() > 0 ? android.view.View.VISIBLE : android.view.View.GONE}"
|
||||||
|
app:configurationHandler="@{rowConfigurationHandler}"
|
||||||
app:items="@{tunnels}"
|
app:items="@{tunnels}"
|
||||||
app:layout="@{@layout/tunnel_list_item}"
|
app:layout="@{@layout/tunnel_list_item}" />
|
||||||
app:configurationHandler="@{rowConfigurationHandler}" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}"
|
android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}">
|
||||||
android:layout_gravity="center">
|
|
||||||
<android.support.v7.widget.AppCompatImageView
|
<android.support.v7.widget.AppCompatImageView
|
||||||
android:id="@+id/logo_placeholder"
|
android:id="@+id/logo_placeholder"
|
||||||
android:layout_width="140dp"
|
android:layout_width="140dp"
|
||||||
android:layout_height="140dp"
|
android:layout_height="140dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:alpha="0.3333333"
|
|
||||||
android:layout_marginTop="-70dp"
|
|
||||||
android:layout_marginBottom="20dp"
|
android:layout_marginBottom="20dp"
|
||||||
|
android:layout_marginTop="-70dp"
|
||||||
|
android:alpha="0.3333333"
|
||||||
android:src="@mipmap/ic_launcher" />
|
android:src="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:textSize="20sp"
|
android:text="@string/tunnel_list_placeholder"
|
||||||
android:text="@string/tunnel_list_placeholder" />
|
android:textSize="20sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.wireguard.android.widget.fab.FloatingActionsMenu
|
<com.wireguard.android.widget.fab.FloatingActionsMenu
|
||||||
android:id="@+id/create_menu"
|
android:id="@+id/create_menu"
|
||||||
android:clipChildren="false"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_margin="@dimen/fab_margin"
|
android:layout_margin="@dimen/fab_margin"
|
||||||
|
android:clipChildren="false"
|
||||||
app:fab_labelStyle="@style/fab_label"
|
app:fab_labelStyle="@style/fab_label"
|
||||||
app:fab_labelsPosition="@integer/label_position"
|
app:fab_labelsPosition="@integer/label_position"
|
||||||
app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior">
|
app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior">
|
||||||
@ -77,18 +79,18 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="@{fragment::onRequestImportConfig}"
|
android:onClick="@{fragment::onRequestImportConfig}"
|
||||||
app:srcCompat="@drawable/ic_action_open_white"
|
|
||||||
app:fabSize="mini"
|
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
|
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
|
||||||
android:id="@+id/scan_qr_code"
|
android:id="@+id/scan_qr_code"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="@{fragment::onRequestScanQRCode}"
|
android:onClick="@{fragment::onRequestScanQRCode}"
|
||||||
app:srcCompat="@drawable/ic_action_scan_qr_code_white"
|
|
||||||
app:fabSize="mini"
|
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
|
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
|
||||||
android:id="@+id/create_empty"
|
android:id="@+id/create_empty"
|
||||||
@ -96,8 +98,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="@{fragment::onRequestCreateConfig}"
|
android:onClick="@{fragment::onRequestCreateConfig}"
|
||||||
app:fabSize="mini"
|
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>
|
</com.wireguard.android.widget.fab.FloatingActionsMenu>
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</android.support.design.widget.CoordinatorLayout>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
android:id="@+id/menu_settings"
|
android:id="@+id/menu_settings"
|
||||||
android:alphabeticShortcut="s"
|
android:alphabeticShortcut="s"
|
||||||
android:icon="@drawable/ic_settings"
|
android:icon="@drawable/ic_settings"
|
||||||
|
android:orderInCategory="1000"
|
||||||
android:title="@string/settings"
|
android:title="@string/settings"
|
||||||
app:showAsAction="always"
|
app:showAsAction="always" />
|
||||||
android:orderInCategory="1000" />
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="dark_theme"
|
android:key="dark_theme"
|
||||||
android:summaryOn="@string/dark_theme_summary_on"
|
|
||||||
android:summaryOff="@string/dark_theme_summary_off"
|
android:summaryOff="@string/dark_theme_summary_off"
|
||||||
|
android:summaryOn="@string/dark_theme_summary_on"
|
||||||
android:title="@string/dark_theme_title" />
|
android:title="@string/dark_theme_title" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
Loading…
Reference in New Issue
Block a user