Serviceless rewrite, part 1
Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.databinding.CallbackRegistry;
|
||||
import android.databinding.CallbackRegistry.NotifierCallback;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Base class for activities that need to remember the currently-selected tunnel.
|
||||
*/
|
||||
|
||||
public abstract class BaseActivity extends Activity {
|
||||
private static final String TAG = BaseActivity.class.getSimpleName();
|
||||
|
||||
private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry();
|
||||
private Tunnel selectedTunnel;
|
||||
|
||||
public void addOnSelectedTunnelChangedListener(
|
||||
final OnSelectedTunnelChangedListener listener) {
|
||||
selectionChangeRegistry.add(listener);
|
||||
}
|
||||
|
||||
public Tunnel getSelectedTunnel() {
|
||||
return selectedTunnel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
// Restore the saved tunnel if there is one; otherwise grab it from the arguments.
|
||||
String savedTunnelName = null;
|
||||
if (savedInstanceState != null)
|
||||
savedTunnelName = savedInstanceState.getString(TunnelManager.KEY_SELECTED_TUNNEL);
|
||||
else if (getIntent() != null)
|
||||
savedTunnelName = getIntent().getStringExtra(TunnelManager.KEY_SELECTED_TUNNEL);
|
||||
if (savedTunnelName != null) {
|
||||
final TunnelManager manager = Application.getComponent().getTunnelManager();
|
||||
selectedTunnel = manager.getTunnels().get(savedTunnelName);
|
||||
}
|
||||
// The selected tunnel must be set before the superclass method recreates fragments.
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(final Bundle outState) {
|
||||
if (selectedTunnel != null)
|
||||
outState.putString(TunnelManager.KEY_SELECTED_TUNNEL, selectedTunnel.getName());
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
protected abstract Tunnel onSelectedTunnelChanged(Tunnel oldTunnel, Tunnel newTunnel);
|
||||
|
||||
public void removeOnSelectedTunnelChangedListener(
|
||||
final OnSelectedTunnelChangedListener listener) {
|
||||
selectionChangeRegistry.remove(listener);
|
||||
}
|
||||
|
||||
public void setSelectedTunnel(final Tunnel tunnel) {
|
||||
final Tunnel oldTunnel = selectedTunnel;
|
||||
if (Objects.equals(oldTunnel, tunnel))
|
||||
return;
|
||||
// Give the activity a chance to override the tunnel change.
|
||||
selectedTunnel = onSelectedTunnelChanged(oldTunnel, tunnel);
|
||||
if (Objects.equals(oldTunnel, selectedTunnel))
|
||||
return;
|
||||
selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, selectedTunnel);
|
||||
}
|
||||
|
||||
public interface OnSelectedTunnelChangedListener {
|
||||
void onSelectedTunnelChanged(Tunnel oldTunnel, Tunnel newTunnel);
|
||||
}
|
||||
|
||||
private static final class SelectionChangeNotifier
|
||||
extends NotifierCallback<OnSelectedTunnelChangedListener, Tunnel, Tunnel> {
|
||||
@Override
|
||||
public void onNotifyCallback(final OnSelectedTunnelChangedListener listener,
|
||||
final Tunnel oldTunnel, final int ignored,
|
||||
final Tunnel newTunnel) {
|
||||
listener.onSelectedTunnelChanged(oldTunnel, newTunnel);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SelectionChangeRegistry
|
||||
extends CallbackRegistry<OnSelectedTunnelChangedListener, Tunnel, Tunnel> {
|
||||
private SelectionChangeRegistry() {
|
||||
super(new SelectionChangeNotifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.fragment.ConfigEditorFragment;
|
||||
import com.wireguard.android.fragment.TunnelDetailFragment;
|
||||
import com.wireguard.android.fragment.TunnelListFragment;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
|
||||
import java9.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* CRUD interface for WireGuard tunnels. This activity serves as the main entry point to the
|
||||
* WireGuard application, and contains several fragments for listing, viewing details of, and
|
||||
* editing the configuration and interface state of WireGuard tunnels.
|
||||
*/
|
||||
|
||||
public class MainActivity extends BaseActivity {
|
||||
private static final String KEY_STATE = "fragment_state";
|
||||
private static final String TAG = MainActivity.class.getSimpleName();
|
||||
private State state = State.EMPTY;
|
||||
|
||||
private boolean moveToState(final State nextState) {
|
||||
Log.i(TAG, "Moving from " + state.name() + " to " + nextState.name());
|
||||
if (nextState == state) {
|
||||
return false;
|
||||
} else if (nextState.layer > state.layer + 1) {
|
||||
moveToState(State.ofLayer(state.layer + 1));
|
||||
moveToState(nextState);
|
||||
return true;
|
||||
} else if (nextState.layer == state.layer + 1) {
|
||||
final Fragment fragment = Fragment.instantiate(this, nextState.fragment);
|
||||
final FragmentTransaction transaction = getFragmentManager().beginTransaction()
|
||||
.replace(R.id.master_fragment, fragment)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
||||
if (state.layer > 0)
|
||||
transaction.addToBackStack(null);
|
||||
transaction.commit();
|
||||
} else if (nextState.layer == state.layer - 1) {
|
||||
if (getFragmentManager().getBackStackEntryCount() == 0)
|
||||
return false;
|
||||
getFragmentManager().popBackStack();
|
||||
} else if (nextState.layer < state.layer - 1) {
|
||||
moveToState(State.ofLayer(state.layer - 1));
|
||||
moveToState(nextState);
|
||||
return true;
|
||||
}
|
||||
state = nextState;
|
||||
if (state.layer > 1) {
|
||||
if (getActionBar() != null)
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
} else {
|
||||
if (getActionBar() != null)
|
||||
getActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
setSelectedTunnel(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!moveToState(State.ofLayer(state.layer - 1)))
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
if (savedInstanceState != null && savedInstanceState.getString(KEY_STATE) != null)
|
||||
state = State.valueOf(savedInstanceState.getString(KEY_STATE));
|
||||
if (state == State.EMPTY) {
|
||||
State initialState = getSelectedTunnel() != null ? State.DETAIL : State.LIST;
|
||||
if (getIntent() != null && getIntent().getStringExtra(KEY_STATE) != null)
|
||||
initialState = State.valueOf(getIntent().getStringExtra(KEY_STATE));
|
||||
moveToState(initialState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.main_activity, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// The back arrow in the action bar should act the same as the back button.
|
||||
moveToState(State.ofLayer(state.layer - 1));
|
||||
return true;
|
||||
case R.id.menu_action_edit:
|
||||
if (getSelectedTunnel() != null)
|
||||
moveToState(State.EDITOR);
|
||||
return true;
|
||||
case R.id.menu_action_save:
|
||||
// This menu item is handled by the editor fragment.
|
||||
return false;
|
||||
case R.id.menu_settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(final Bundle outState) {
|
||||
outState.putString(KEY_STATE, state.name());
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Tunnel onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
moveToState(newTunnel != null ? State.DETAIL : State.LIST);
|
||||
return newTunnel;
|
||||
}
|
||||
|
||||
private enum State {
|
||||
EMPTY(null, 0),
|
||||
LIST(TunnelListFragment.class, 1),
|
||||
DETAIL(TunnelDetailFragment.class, 2),
|
||||
EDITOR(ConfigEditorFragment.class, 3);
|
||||
|
||||
private final String fragment;
|
||||
private final int layer;
|
||||
|
||||
State(final Class<? extends Fragment> fragment, final int layer) {
|
||||
this.fragment = fragment != null ? fragment.getName() : null;
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
private static State ofLayer(final int layer) {
|
||||
return Stream.of(State.values()).filter(s -> s.layer == layer).findFirst().get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.preference.TunnelListPreference;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
|
||||
/**
|
||||
* Interface for changing application-global persistent settings.
|
||||
*/
|
||||
|
||||
public class SettingsActivity extends Activity {
|
||||
public static final String KEY_SHOW_QUICK_TILE_SETTINGS = "show_quick_tile_settings";
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
|
||||
final Fragment fragment = new SettingsFragment();
|
||||
fragment.setArguments(getIntent().getExtras());
|
||||
getFragmentManager().beginTransaction()
|
||||
.add(android.R.id.content, fragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
final Preference installTools = findPreference("install_cmd_line_tools");
|
||||
installTools.setOnPreferenceClickListener(preference -> {
|
||||
new ToolsInstaller(preference).execute();
|
||||
return true;
|
||||
});
|
||||
if (getArguments() != null && getArguments().getBoolean(KEY_SHOW_QUICK_TILE_SETTINGS))
|
||||
((TunnelListPreference) findPreference("primary_config")).show();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ToolsInstaller extends AsyncTask<Void, Void, Integer> {
|
||||
private static final String[][] LIBRARY_NAMED_EXECUTABLES = {
|
||||
{"libwg.so", "wg"},
|
||||
{"libwg-quick.so", "wg-quick"}
|
||||
};
|
||||
|
||||
private final Context context;
|
||||
private final Preference preference;
|
||||
|
||||
private ToolsInstaller(final Preference preference) {
|
||||
context = preference.getContext();
|
||||
this.preference = preference;
|
||||
preference.setEnabled(false);
|
||||
preference.setSummary(context.getString(R.string.install_cmd_line_tools_progress));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(final Void... voids) {
|
||||
final String libDir = context.getApplicationInfo().nativeLibraryDir;
|
||||
final StringBuilder cmd = new StringBuilder();
|
||||
|
||||
cmd.append("set -ex;");
|
||||
|
||||
for (final String[] libraryNamedExecutable : LIBRARY_NAMED_EXECUTABLES) {
|
||||
final String arg1 = '\'' + libDir + '/' + libraryNamedExecutable[0] + '\'';
|
||||
final String arg2 = "'/system/xbin/" + libraryNamedExecutable[1] + '\'';
|
||||
|
||||
cmd.append(String.format("cmp -s %s %s && ", arg1, arg2));
|
||||
}
|
||||
cmd.append("exit 114;");
|
||||
|
||||
cmd.append("trap 'mount -o ro,remount /system' EXIT;");
|
||||
cmd.append("mount -o rw,remount /system;");
|
||||
|
||||
for (final String[] libraryNamedExecutable : LIBRARY_NAMED_EXECUTABLES) {
|
||||
final String arg1 = '\'' + libDir + '/' + libraryNamedExecutable[0] + '\'';
|
||||
final String arg2 = "'/system/xbin/" + libraryNamedExecutable[1] + '\'';
|
||||
cmd.append(String.format("cp %s %s; chmod 755 %s;", arg1, arg2, arg2));
|
||||
}
|
||||
|
||||
return new RootShell(context).run(null, cmd.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Integer ret) {
|
||||
final String status;
|
||||
|
||||
switch (ret) {
|
||||
case 0:
|
||||
status = context.getString(R.string.install_cmd_line_tools_success);
|
||||
break;
|
||||
case 114 /* OsConstants.EALREADY */:
|
||||
status = context.getString(R.string.install_cmd_line_tools_already);
|
||||
break;
|
||||
default:
|
||||
status = context.getString(R.string.install_cmd_line_tools_failure);
|
||||
break;
|
||||
}
|
||||
preference.setSummary(status);
|
||||
preference.setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.wireguard.android.fragment.ConfigEditorFragment;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
|
||||
/**
|
||||
* Created by samuel on 12/29/17.
|
||||
*/
|
||||
|
||||
public class TunnelCreatorActivity extends BaseActivity {
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
|
||||
getFragmentManager().beginTransaction()
|
||||
.add(android.R.id.content, new ConfigEditorFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Tunnel onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
finish();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user