Signed-off-by: Samuel Holland <samuel@sholland.org>master
@@ -15,6 +15,7 @@ import com.wireguard.android.configStore.FileConfigStore; | |||
import com.wireguard.android.model.TunnelManager; | |||
import com.wireguard.android.util.AsyncWorker; | |||
import com.wireguard.android.util.RootShell; | |||
import com.wireguard.android.util.ToolsInstaller; | |||
import java.util.concurrent.Executor; | |||
@@ -56,6 +57,8 @@ public class Application extends android.app.Application { | |||
SharedPreferences getPreferences(); | |||
ToolsInstaller getToolsInstaller(); | |||
TunnelManager getTunnelManager(); | |||
} | |||
@@ -1,14 +1,10 @@ | |||
package com.wireguard.android.activity; | |||
import android.app.Activity; | |||
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.util.RootShell; | |||
/** | |||
* Interface for changing application-global persistent settings. | |||
@@ -30,74 +26,6 @@ public class SettingsActivity extends Activity { | |||
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; | |||
}); | |||
} | |||
} | |||
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,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; | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -58,16 +58,16 @@ | |||
<string name="public_key_description">Public key</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="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="settings">Settings</string> | |||
<string name="status">Status</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="never">never</string> | |||
</resources> |
@@ -5,8 +5,5 @@ | |||
android:key="restore_on_boot" | |||
android:summary="@string/restore_on_boot_summary" | |||
android:title="@string/restore_on_boot" /> | |||
<Preference | |||
android:key="install_cmd_line_tools" | |||
android:summary="@string/install_cmd_line_tools_summary" | |||
android:title="@string/install_cmd_line_tools" /> | |||
<com.wireguard.android.preference.ToolsInstallerPreference /> | |||
</PreferenceScreen> |