Skip to content

Commit

Permalink
Merge pull request AdAway#861 from PerfectSlayer/master
Browse files Browse the repository at this point in the history
Add generic systemless support
  • Loading branch information
0-kaladin authored Feb 8, 2017
2 parents 80d5c70 + 6a30845 commit 5426442
Show file tree
Hide file tree
Showing 7 changed files with 615 additions and 215 deletions.
72 changes: 36 additions & 36 deletions AdAway/src/main/java/org/adaway/ui/PrefsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import org.adaway.service.DailyListener;
import org.adaway.util.Constants;
import org.adaway.util.Log;
import org.adaway.util.systemless.AbstractSystemlessMode;
import org.adaway.util.systemless.NotSupportedSystemlessMode;
import org.adaway.util.SystemlessUtils;
import org.adaway.util.Utils;
import org.adaway.util.WebserverUtils;
Expand Down Expand Up @@ -88,30 +90,40 @@ public void onCreate(Bundle savedInstanceState) {
addPreferencesFromResource(R.xml.preferences);

/*
* Install systemless script if pref is enabled.
* Enable systemless mode systemless mode if supported.
*/
Preference SystemlessPref = findPreference(getString(R.string.pref_enable_systemless_key));
SystemlessPref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
try {
Shell rootShell = Shell.startRootShell();
boolean successful;
if (newValue.equals(true)) {
successful = SystemlessUtils.enableSystemlessMode(PrefsActivity.this, rootShell);
} else {
successful = SystemlessUtils.disableSystemlessMode(rootShell);
if (successful) {
Utils.rebootQuestion(PrefsActivity.this, R.string.disable_systemless_successful_title,
R.string.disable_systemless_successful);
}
}
rootShell.close();
return successful;
} catch (Exception exception) {
Log.e(Constants.TAG, "Problem while installing/removing systemless script.", exception);
// Get device systemless mode
AbstractSystemlessMode systemlessMode = SystemlessUtils.getSystemlessMode();
// Check if systemless is supported
if (!systemlessMode.isSupported()) {
return false;
}
// Declare successful action status
boolean successful;
// Check action to apply
if (newValue.equals(true)) {
// Enable systemless mode
successful = systemlessMode.enable(PrefsActivity.this);
// Check if reboot is needed
if (successful && systemlessMode.isRebootNeededAfterActivation()) {
Utils.rebootQuestion(PrefsActivity.this, R.string.enable_systemless_successful_title,
R.string.enable_systemless_successful);
}
} else {
// Disable systemless mode
successful = systemlessMode.disable(PrefsActivity.this);
// Check if reboot is needed
if (successful && systemlessMode.isRebootNeededAfterDeactivation()) {
Utils.rebootQuestion(PrefsActivity.this, R.string.disable_systemless_successful_title,
R.string.disable_systemless_successful);
}
}
// Return successful action status
return successful;
}
});

Expand Down Expand Up @@ -219,34 +231,22 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
private class SystemlessCheckTask extends AsyncTask<Void, Void, SystemlessUtils.SystemlessModeStatus> {
private class SystemlessCheckTask extends AsyncTask<Void, Void, AbstractSystemlessMode> {
@Override
protected SystemlessUtils.SystemlessModeStatus doInBackground(Void... params) {
// Declare statuses
boolean supported = false;
boolean enabled = false;
// Check statuses from shell
try {
Shell rootShell = Shell.startRootShell();
supported = SystemlessUtils.isSystemlessModeSupported(rootShell);
enabled = SystemlessUtils.isSystemlessModeEnabled(rootShell);
rootShell.close();
} catch (Exception exception) {
Log.e(Constants.TAG, "Problem while checking systemless mode.", exception);
}
// Return systemless mode statuses
return new SystemlessUtils.SystemlessModeStatus(supported, enabled);
protected AbstractSystemlessMode doInBackground(Void... params) {
// Retrieve the systemless mode
return SystemlessUtils.getSystemlessMode();
}

@Override
protected void onPostExecute(SystemlessUtils.SystemlessModeStatus status) {
protected void onPostExecute(AbstractSystemlessMode systemlessMode) {
// Ensure reference exists
if (mSystemless == null) {
return;
}
// Enable setting and set initial value
mSystemless.setEnabled(status.isSupported());
mSystemless.setChecked(status.isEnabled());
mSystemless.setEnabled(systemlessMode.isSupported());
mSystemless.setChecked(systemlessMode.isEnabled(PrefsActivity.this));
}
}
}
220 changes: 42 additions & 178 deletions AdAway/src/main/java/org/adaway/util/SystemlessUtils.java
Original file line number Diff line number Diff line change
@@ -1,206 +1,70 @@
package org.adaway.util;

import android.content.Context;

import org.adaway.util.systemless.AbstractSystemlessMode;
import org.adaway.util.systemless.NotSupportedSystemlessMode;
import org.adaway.util.systemless.SuperSuSystemlessMode;
import org.adaway.util.systemless.SuperUserSystemlessMode;
import org.sufficientlysecure.rootcommands.Shell;
import org.sufficientlysecure.rootcommands.Toolbox;
import org.sufficientlysecure.rootcommands.command.SimpleCommand;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
* This class provides methods to check, install and remove systemless script.
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public class SystemlessUtils {

/**
* Check if the systemless mode is supported.
*
* @param shell The shell used to check.
* @return <code>true</code> if the systemless mode is supported, <code>false</code> otherwise.
* The cached systemless mode.
*/
public static boolean isSystemlessModeSupported(Shell shell) {
try {
// Check if SuperSU systemless root is installed
Toolbox toolbox = new Toolbox(shell);
return toolbox.fileExists("/su/bin/su");
} catch (Exception exception) {
Log.e(Constants.TAG, "Error while checking if systemless mode is supported.", exception);
return false;
}
}
private static AbstractSystemlessMode systemlessMode;

/**
* Check if systemless mode is enabled.
* Get the systemless mode.
*
* @param shell The root shell used to check.
* @return The systemless mode installation status (<code>true</code> if enabled,
* <code>false</code> otherwise).
* @return The systemless mode.
*/
public static boolean isSystemlessModeEnabled(Shell shell) {
try {
// Look for mount point of system hosts file
SimpleCommand command = new SimpleCommand("mount | grep " + Constants.ANDROID_SYSTEM_ETC_HOSTS);
shell.add(command).waitForFinish();
return command.getExitCode() == 0;
} catch (Exception exception) {
Log.e(Constants.TAG, "Error while checking if systemless mode is installed.", exception);
// Consider systemless mode is not installed if script could not be checked
return false;
public static AbstractSystemlessMode getSystemlessMode() {
// Check cached systemless mode
if (SystemlessUtils.systemlessMode != null) {
return SystemlessUtils.systemlessMode;
}
}

/**
* Install systemless script.<br>
* Create and execute<code>/su/su.d/0000adaway.script</code> file to mount hosts file to
* <code>/su/etc/hosts</code> location and ensure mounted hosts file is present.<br>
* Require SuperSU >= 2.56.
*
* @param context The application context (current activity).
* @param shell The current root shell to install script.
* @return The script installation status (<code>true</code> if the systemless script is installed,
* <code>false</code> otherwise).
*/
public static boolean enableSystemlessMode(Context context, Shell shell) {
// Declare shell
Shell shell = null;
try {
// Start shell
shell = Shell.startShell();
Toolbox toolbox = new Toolbox(shell);
// Ensure mounted hosts file exists
if (!toolbox.fileExists(Constants.ANDROID_SU_ETC_HOSTS)) {
// Copy current hosts file to mounted host file
toolbox.copyFile(Constants.ANDROID_SYSTEM_ETC_HOSTS, Constants.ANDROID_SU_ETC_HOSTS, false, true);
}
// Check if systemless mode is already enabled
if (SystemlessUtils.isSystemlessModeEnabled(shell)) {
return true;
}
/*
* Install systemless script by writing content to a temporary file, copying to su hook location then running it.
*/
// Create temp file
File cacheDir = context.getCacheDir();
File tempFile = File.createTempFile(Constants.TAG, ".script", cacheDir);
// Write script content to temp file
BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile));
writer.write("mount -o bind " + Constants.ANDROID_SU_ETC_HOSTS + " " + Constants.ANDROID_SYSTEM_ETC_HOSTS + ";");
writer.newLine();
writer.close();
// Copy temp file to /su partition
if (!toolbox.copyFile(tempFile.getAbsolutePath(), Constants.ANDROID_SYSTEMLESS_SCRIPT, false, false)) {
Log.w(Constants.TAG, "Could not copy the systemless script to " + Constants.ANDROID_SYSTEMLESS_SCRIPT + ".");
return false;
}
// Apply script permissions
if (!toolbox.setFilePermissions(Constants.ANDROID_SYSTEMLESS_SCRIPT, "755")) {
Log.w(Constants.TAG, "Could not set systemless script rights.");
return false;
}
// Remove temp file
if (!tempFile.delete()) {
Log.i(Constants.TAG, "Could not delete the temporary script file.");
}
// Execute script
SimpleCommand command = new SimpleCommand(Constants.ANDROID_SYSTEMLESS_SCRIPT);
shell.add(command).waitForFinish();
if (command.getExitCode() != 0) {
Log.w(Constants.TAG, "Could not execute the systemless script.");
return false;
}
// Check if installation is successful
if (!SystemlessUtils.isSystemlessModeEnabled(shell)) {
Log.w(Constants.TAG, "Systemless mode installation was successful but systemless is not working.");
return false;
// Check if ChainFire's SuperSU systemless root is installed
if (toolbox.fileExists("/su/bin/su")) {
SystemlessUtils.systemlessMode = new SuperSuSystemlessMode();
} else {
// Check if phh's SuperUser su bind is installed
SimpleCommand command = new SimpleCommand("su -v | grep subind");
shell.add(command).waitForFinish();
if (command.getExitCode() == 0) {
SystemlessUtils.systemlessMode = new SuperUserSystemlessMode();
} else {
// Otherwise not supported systemless mode
SystemlessUtils.systemlessMode = new NotSupportedSystemlessMode();
}
}
// Systemless mode is installed and working
return true;
} catch (Exception exception) {
Log.e(Constants.TAG, "Error while enabling systemless mode.", exception);
return false;
}
}

/**
* Remove systemless script.<br>
* Remove <code>/su/su.d/0000adaway.script</code> and mounted hosts files.
*
* @param shell The current root shell to install script.
* @return The script removal status (<code>true</code> if the systemless script is removed,
* <code>false</code> otherwise).
*/
public static boolean disableSystemlessMode(Shell shell) {
try {
// Check if systemless mode is enabled
if (!SystemlessUtils.isSystemlessModeEnabled(shell)) {
return true;
}
// Remove systemless script
SimpleCommand removeScriptCommand =
new SimpleCommand(Constants.COMMAND_RM + " " + Constants.ANDROID_SYSTEMLESS_SCRIPT);
shell.add(removeScriptCommand).waitForFinish();
if (removeScriptCommand.getExitCode() != 0) {
Log.w(Constants.TAG, "Couldn't remove systemless script.");
return false;
Log.e(Constants.TAG, "Error while getting systemless mode.", exception);
SystemlessUtils.systemlessMode = new NotSupportedSystemlessMode();
} finally {
// Close shell
if (shell != null) {
try {
shell.close();
} catch (IOException exception) {
Log.d(Constants.TAG, "Error while closing shell.", exception);
}
}
// Try to umount hosts file (resource must be used: it requires reboot)
SimpleCommand umountCommand =
new SimpleCommand("umount " + Constants.ANDROID_SYSTEM_ETC_HOSTS);
shell.add(umountCommand).waitForFinish();
// Remove mounted hosts file
SimpleCommand removeMountedHostsCommand =
new SimpleCommand(Constants.COMMAND_RM + " " + Constants.ANDROID_SU_ETC_HOSTS);
shell.add(removeMountedHostsCommand).waitForFinish();
return true;
} catch (Exception exception) {
Log.e(Constants.TAG, "Error while disabling systemless mode.", exception);
return false;
}
}

/**
* This class provides systemless mode statuses (support and activation).
*
* @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
*/
public static class SystemlessModeStatus {
/**
* The systemless mode support status (<code>true</code> if supported, <code>false</code> otherwise).
*/
private final boolean supported;
/**
* The systemless mode activation status (<code>true</code> if enabled, <code>false</code> otherwise).
*/
private final boolean enabled;

/**
* Constructor.
*
* @param supported The systemless mode support status (<code>true</code> if supported, <code>false</code> otherwise).
* @param enabled The systemless mode activation status (<code>true</code> if enabled, <code>false</code> otherwise).
*/
public SystemlessModeStatus(boolean supported, boolean enabled) {
this.supported = supported;
this.enabled = enabled;
}

/**
* Get the systemless mode support status.
*
* @return The systemless mode support status (<code>true</code> if supported, <code>false</code> otherwise).
*/
public boolean isSupported() {
return this.supported;
}

/**
* Get the systemless mode activation status.
*
* @return The systemless mode activation status (<code>true</code> if enabled, <code>false</code> otherwise).
*/
public boolean isEnabled() {
return this.enabled;
}
// Return found systemless mode
return SystemlessUtils.systemlessMode;
}
}
Loading

0 comments on commit 5426442

Please sign in to comment.