Serviceless rewrite, part 1

Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
Samuel Holland
2018-01-06 04:09:29 -06:00
parent 4c0869393e
commit 609194fae2
68 changed files with 2564 additions and 2296 deletions
@@ -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;
}
}