ToolsInstaller: Extract to its own classes

Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
Samuel Holland 2018-01-08 00:34:55 -06:00
parent 1f30e133d6
commit 08cca56388
6 changed files with 171 additions and 82 deletions

View File

@ -15,6 +15,7 @@ import com.wireguard.android.configStore.FileConfigStore;
import com.wireguard.android.model.TunnelManager; import com.wireguard.android.model.TunnelManager;
import com.wireguard.android.util.AsyncWorker; import com.wireguard.android.util.AsyncWorker;
import com.wireguard.android.util.RootShell; import com.wireguard.android.util.RootShell;
import com.wireguard.android.util.ToolsInstaller;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -56,6 +57,8 @@ public class Application extends android.app.Application {
SharedPreferences getPreferences(); SharedPreferences getPreferences();
ToolsInstaller getToolsInstaller();
TunnelManager getTunnelManager(); TunnelManager getTunnelManager();
} }

View File

@ -1,14 +1,10 @@
package com.wireguard.android.activity; package com.wireguard.android.activity;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import com.wireguard.android.R; import com.wireguard.android.R;
import com.wireguard.android.util.RootShell;
/** /**
* Interface for changing application-global persistent settings. * Interface for changing application-global persistent settings.
@ -30,74 +26,6 @@ public class SettingsActivity extends Activity {
public void onCreate(final Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
final Preference installTools = findPreference("install_cmd_line_tools");
installTools.setOnPreferenceClickListener(preference -> {
new ToolsInstaller(preference).execute();
return true;
});
}
}
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);
} }
} }
} }

View File

@ -0,0 +1,91 @@
package com.wireguard.android.preference;
import android.content.Context;
import android.preference.Preference;
import android.system.OsConstants;
import android.util.AttributeSet;
import com.wireguard.android.Application;
import com.wireguard.android.Application.ApplicationComponent;
import com.wireguard.android.R;
import com.wireguard.android.util.AsyncWorker;
import com.wireguard.android.util.ToolsInstaller;
/**
* Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the
* result as the preference summary.
*/
public class ToolsInstallerPreference extends Preference {
private final AsyncWorker asyncWorker;
private final ToolsInstaller toolsInstaller;
private State state = State.INITIAL;
public ToolsInstallerPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
final ApplicationComponent applicationComponent = Application.getComponent();
asyncWorker = applicationComponent.getAsyncWorker();
toolsInstaller = applicationComponent.getToolsInstaller();
}
public ToolsInstallerPreference(final Context context) {
this(context, null);
}
private static State mapResultToState(final int scriptResult) {
if (scriptResult == OsConstants.EXIT_SUCCESS)
return State.SUCCESS;
else if (scriptResult == OsConstants.EALREADY)
return State.ALREADY;
else
return State.FAILURE;
}
@Override
public CharSequence getSummary() {
return getContext().getString(state.messageResourceId);
}
@Override
public CharSequence getTitle() {
return getContext().getString(getTitleRes());
}
@Override
public int getTitleRes() {
return R.string.tools_installer_title;
}
@Override
protected void onClick() {
setState(State.WORKING);
asyncWorker.supplyAsync(toolsInstaller::install)
.thenApply(ToolsInstallerPreference::mapResultToState)
.thenAccept(this::setState);
}
private void setState(final State state) {
if (this.state == state)
return;
this.state = state;
if (isEnabled() != state.shouldEnableView)
setEnabled(state.shouldEnableView);
notifyChanged();
}
private enum State {
ALREADY(R.string.tools_installer_already, false),
FAILURE(R.string.tools_installer_failure, true),
INITIAL(R.string.tools_installer_initial, true),
SUCCESS(R.string.tools_installer_success, false),
WORKING(R.string.tools_installer_working, false);
private final int messageResourceId;
private final boolean shouldEnableView;
State(final int messageResourceId, final boolean shouldEnableView) {
this.messageResourceId = messageResourceId;
this.shouldEnableView = shouldEnableView;
}
}
}

View File

@ -0,0 +1,70 @@
package com.wireguard.android.util;
import android.content.Context;
import android.system.OsConstants;
import com.wireguard.android.Application.ApplicationContext;
import com.wireguard.android.Application.ApplicationScope;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
/**
* Helper to install WireGuard tools to the system partition.
*/
@ApplicationScope
public final class ToolsInstaller {
private static final String[][] EXECUTABLES = {
{"libwg.so", "wg"},
{"libwg-quick.so", "wg-quick"},
};
private static final File[] INSTALL_DIRS = {
new File("/system/xbin"),
new File("/system/bin"),
};
private final String nativeLibraryDir;
private final RootShell rootShell;
@Inject
public ToolsInstaller(@ApplicationContext final Context context, final RootShell rootShell) {
nativeLibraryDir = context.getApplicationInfo().nativeLibraryDir;
this.rootShell = rootShell;
}
private static File getInstallDir() {
final String path = System.getenv("PATH");
if (path == null)
return INSTALL_DIRS[0];
final List<String> paths = Arrays.asList(path.split(":"));
for (final File dir : INSTALL_DIRS)
if (paths.contains(dir.getPath()) && dir.isDirectory())
return dir;
return null;
}
public int install() {
final File installDir = getInstallDir();
if (installDir == null)
return OsConstants.ENOENT;
final StringBuilder script = new StringBuilder("set -ex; ");
for (final String[] names : EXECUTABLES) {
script.append(String.format("cmp -s '%s' '%s' && ",
new File(nativeLibraryDir, names[0]),
new File(installDir, names[1])));
}
script.append("exit ").append(OsConstants.EALREADY).append("; ");
script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
for (final String[] names : EXECUTABLES) {
script.append(String.format("cp %s %s; chmod 755 %s; ",
new File(nativeLibraryDir, names[0]),
new File(installDir, names[1]),
new File(installDir, names[1])));
}
return rootShell.run(null, script.toString());
}
}

View File

@ -58,16 +58,16 @@
<string name="public_key_description">Public key</string> <string name="public_key_description">Public key</string>
<string name="restore_on_boot">Restore on boot</string> <string name="restore_on_boot">Restore on boot</string>
<string name="restore_on_boot_summary">Restore previously enabled configurations on boot</string> <string name="restore_on_boot_summary">Restore previously enabled configurations on boot</string>
<string name="install_cmd_line_tools">Install command line tools</string>
<string name="install_cmd_line_tools_summary">Install optional tools for scripting into /system/xbin</string>
<string name="install_cmd_line_tools_success">wg and wg-quick installed into /system/xbin</string>
<string name="install_cmd_line_tools_progress">Installing wg and wg-quick into /system/xbin</string>
<string name="install_cmd_line_tools_already">wg and wg-quick are already installed</string>
<string name="install_cmd_line_tools_failure">Command line tools could not be installed</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="status">Status</string> <string name="status">Status</string>
<string name="toggle">Toggle</string> <string name="toggle">Toggle</string>
<string name="tools_installer_already">wg and wg-quick are already installed</string>
<string name="tools_installer_failure">Command line tools could not be installed (no root?)</string>
<string name="tools_installer_initial">Install optional tools for scripting into the system partition</string>
<string name="tools_installer_success">wg and wg-quick installed into the system partition</string>
<string name="tools_installer_title">Install command line tools</string>
<string name="tools_installer_working">Installing wg and wg-quick into the system partition</string>
<string name="last_change">Last change</string> <string name="last_change">Last change</string>
<string name="never">never</string> <string name="never">never</string>
</resources> </resources>

View File

@ -5,8 +5,5 @@
android:key="restore_on_boot" android:key="restore_on_boot"
android:summary="@string/restore_on_boot_summary" android:summary="@string/restore_on_boot_summary"
android:title="@string/restore_on_boot" /> android:title="@string/restore_on_boot" />
<Preference <com.wireguard.android.preference.ToolsInstallerPreference />
android:key="install_cmd_line_tools"
android:summary="@string/install_cmd_line_tools_summary"
android:title="@string/install_cmd_line_tools" />
</PreferenceScreen> </PreferenceScreen>