SuperSu related changes.
This commit is contained in:
parent
a0ff8c80f0
commit
1560fd3343
@ -510,8 +510,9 @@ public class ActivityPermissions extends Activity
|
||||
case setAirplaneMode:
|
||||
addToArrayListUnique(Manifest.permission.WRITE_SETTINGS, requiredPermissions);
|
||||
addToArrayListUnique(Manifest.permission.ACCESS_NETWORK_STATE, requiredPermissions);
|
||||
addToArrayListUnique(permissionNameSuperuser, requiredPermissions);
|
||||
addToArrayListUnique(Manifest.permission.CHANGE_NETWORK_STATE, requiredPermissions);
|
||||
/* Permission was not required anymore, even before Android 6: https://su.chainfire.eu/#updates-permission
|
||||
addToArrayListUnique(permissionNameSuperuser, requiredPermissions);*/
|
||||
break;
|
||||
case setBluetooth:
|
||||
addToArrayListUnique(Manifest.permission.BLUETOOTH_ADMIN, requiredPermissions);
|
||||
@ -522,8 +523,9 @@ public class ActivityPermissions extends Activity
|
||||
case setDataConnection:
|
||||
addToArrayListUnique(Manifest.permission.WRITE_SETTINGS, requiredPermissions);
|
||||
addToArrayListUnique(Manifest.permission.ACCESS_NETWORK_STATE, requiredPermissions);
|
||||
addToArrayListUnique(permissionNameSuperuser, requiredPermissions);
|
||||
addToArrayListUnique(Manifest.permission.CHANGE_NETWORK_STATE, requiredPermissions);
|
||||
/* Permission was not required anymore, even before Android 6: https://su.chainfire.eu/#updates-permission
|
||||
addToArrayListUnique(permissionNameSuperuser, requiredPermissions);*/
|
||||
break;
|
||||
case setDisplayRotation:
|
||||
addToArrayListUnique(Manifest.permission.WRITE_SETTINGS, requiredPermissions);
|
||||
@ -1273,7 +1275,8 @@ public class ActivityPermissions extends Activity
|
||||
mapActionPermissions.put("sendTextMessage", Manifest.permission.SEND_SMS);
|
||||
mapActionPermissions.put("setAirplaneMode", Manifest.permission.WRITE_SETTINGS);
|
||||
mapActionPermissions.put("setAirplaneMode", Manifest.permission.ACCESS_NETWORK_STATE);
|
||||
mapActionPermissions.put("setAirplaneMode", permissionNameSuperuser);
|
||||
/* Permission was not required anymore, even before Android 6: https://su.chainfire.eu/#updates-permission
|
||||
mapActionPermissions.put("setAirplaneMode", permissionNameSuperuser);*/
|
||||
mapActionPermissions.put("setAirplaneMode", Manifest.permission.CHANGE_NETWORK_STATE);
|
||||
mapActionPermissions.put("setBluetooth", Manifest.permission.BLUETOOTH_ADMIN);
|
||||
mapActionPermissions.put("setBluetooth", Manifest.permission.BLUETOOTH);
|
||||
@ -1281,7 +1284,8 @@ public class ActivityPermissions extends Activity
|
||||
mapActionPermissions.put("setBluetooth", Manifest.permission.WRITE_SETTINGS);
|
||||
mapActionPermissions.put("setDataConnection", Manifest.permission.WRITE_SETTINGS);
|
||||
mapActionPermissions.put("setDataConnection", Manifest.permission.ACCESS_NETWORK_STATE);
|
||||
mapActionPermissions.put("setDataConnection", permissionNameSuperuser);
|
||||
/* Permission was not required anymore, even before Android 6: https://su.chainfire.eu/#updates-permission
|
||||
mapActionPermissions.put("setDataConnection", permissionNameSuperuser);*/
|
||||
mapActionPermissions.put("setDataConnection", Manifest.permission.CHANGE_NETWORK_STATE);
|
||||
mapActionPermissions.put("setDisplayRotation", Manifest.permission.WRITE_SETTINGS);
|
||||
mapActionPermissions.put("setUsbTethering", Manifest.permission.WRITE_SETTINGS);
|
||||
|
@ -10,6 +10,7 @@ import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.jens.automation2.AutomationService;
|
||||
import com.jens.automation2.Miscellaneous;
|
||||
import com.jens.automation2.PointOfInterest;
|
||||
import com.jens.automation2.R;
|
||||
@ -101,7 +102,7 @@ public class WifiBroadcastReceiver extends BroadcastReceiver
|
||||
Miscellaneous.logEvent("i", "WifiReceiver", context.getResources().getString(R.string.poiHasNoWifiNotStoppingCellLocationListener), 2);
|
||||
}
|
||||
|
||||
findRules(parentLocationProvider);
|
||||
findRules(AutomationService.getInstance());
|
||||
}
|
||||
else if(myWifi.isConnectedOrConnecting()) // first time connect from wifi-listener-perspective
|
||||
{
|
||||
@ -113,7 +114,7 @@ public class WifiBroadcastReceiver extends BroadcastReceiver
|
||||
String ssid = myWifiManager.getConnectionInfo().getSSID();
|
||||
setLastWifiSsid(ssid);
|
||||
lastConnectedState = true;
|
||||
findRules(parentLocationProvider);
|
||||
findRules(AutomationService.getInstance());
|
||||
}
|
||||
else if(!myWifi.isConnectedOrConnecting()) // really disconnected? because sometimes also fires on connect
|
||||
{
|
||||
@ -126,7 +127,7 @@ public class WifiBroadcastReceiver extends BroadcastReceiver
|
||||
mayCellLocationChangedReceiverBeActivatedFromWifiPointOfWifi = true;
|
||||
CellLocationChangedReceiver.startCellLocationChangedReceiver();
|
||||
lastConnectedState = false;
|
||||
findRules(parentLocationProvider);
|
||||
findRules(AutomationService.getInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -141,13 +142,13 @@ public class WifiBroadcastReceiver extends BroadcastReceiver
|
||||
}
|
||||
}
|
||||
|
||||
public static void findRules(LocationProvider parentLocationProvider)
|
||||
public static void findRules(AutomationService automationServiceInstance)
|
||||
{
|
||||
ArrayList<Rule> ruleCandidates = Rule.findRuleCandidatesByWifiConnection();
|
||||
for(Rule oneRule : ruleCandidates)
|
||||
{
|
||||
if(oneRule.applies(parentLocationProvider.parentService))
|
||||
oneRule.activate(parentLocationProvider.parentService, false);
|
||||
if(oneRule.applies(automationServiceInstance))
|
||||
oneRule.activate(automationServiceInstance, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ public class ConnectivityReceiver extends BroadcastReceiver implements Automatio
|
||||
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
WifiBroadcastReceiver.setLastWifiSsid(wifiInfo.getSSID());
|
||||
WifiBroadcastReceiver.findRules(automationServiceRef.getLocationProvider());
|
||||
WifiBroadcastReceiver.findRules(automationServiceRef);
|
||||
break;
|
||||
case ConnectivityManager.TYPE_MOBILE:
|
||||
boolean isRoaming = isRoaming(context);
|
||||
@ -219,7 +219,7 @@ public class ConnectivityReceiver extends BroadcastReceiver implements Automatio
|
||||
// This will serve as a disconnected event. Happens if wifi is connected, then module deactivated.
|
||||
Miscellaneous.logEvent("i", "Connectivity", "Wifi deactivated while having been connected before.", 4);
|
||||
WifiBroadcastReceiver.lastConnectedState = false;
|
||||
WifiBroadcastReceiver.findRules(automationServiceRef.getLocationProvider());
|
||||
WifiBroadcastReceiver.findRules(automationServiceRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,18 +20,24 @@ import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Base application class to extend from, solving some issues with
|
||||
* toasts and AsyncTasks you are likely to run into
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class Application extends android.app.Application {
|
||||
/**
|
||||
* Shows a toast message
|
||||
*
|
||||
*
|
||||
* @param context Any context belonging to this application
|
||||
* @param message The message to show
|
||||
*/
|
||||
public static void toast(Context context, String message) {
|
||||
@AnyThread
|
||||
public static void toast(@Nullable Context context, @NonNull String message) {
|
||||
// this is a static method so it is easier to call,
|
||||
// as the context checking and casting is done for you
|
||||
|
||||
@ -45,7 +51,7 @@ public class Application extends android.app.Application {
|
||||
final Context c = context;
|
||||
final String m = message;
|
||||
|
||||
((Application)context).runInApplicationThread(new Runnable() {
|
||||
((Application) context).runInApplicationThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(c, m, Toast.LENGTH_LONG).show();
|
||||
@ -54,14 +60,15 @@ public class Application extends android.app.Application {
|
||||
}
|
||||
}
|
||||
|
||||
private static Handler mApplicationHandler = new Handler();
|
||||
private static final Handler mApplicationHandler = new Handler();
|
||||
|
||||
/**
|
||||
* Run a runnable in the main application thread
|
||||
*
|
||||
*
|
||||
* @param r Runnable to run
|
||||
*/
|
||||
public void runInApplicationThread(Runnable r) {
|
||||
@AnyThread
|
||||
public void runInApplicationThread(@NonNull Runnable r) {
|
||||
mApplicationHandler.post(r);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,12 +18,19 @@ package eu.chainfire.libsuperuser;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.jens.automation2.BuildConfig;
|
||||
|
||||
/**
|
||||
* Utility class for logging and debug features that (by default) does nothing when not in debug mode
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
|
||||
@AnyThread
|
||||
public class Debug {
|
||||
|
||||
// ----- DEBUGGING -----
|
||||
@ -32,23 +39,23 @@ public class Debug {
|
||||
|
||||
/**
|
||||
* <p>Enable or disable debug mode</p>
|
||||
*
|
||||
*
|
||||
* <p>By default, debug mode is enabled for development
|
||||
* builds and disabled for exported APKs - see
|
||||
* BuildConfig.DEBUG</p>
|
||||
*
|
||||
*
|
||||
* @param enable Enable debug mode ?
|
||||
*/
|
||||
public static void setDebug(boolean enable) {
|
||||
debug = enable;
|
||||
*/
|
||||
public static void setDebug(boolean enable) {
|
||||
debug = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Is debug mode enabled ?</p>
|
||||
*
|
||||
*
|
||||
* @return Debug mode enabled
|
||||
*/
|
||||
public static boolean getDebug() {
|
||||
public static boolean getDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
@ -63,25 +70,27 @@ public class Debug {
|
||||
public static final int LOG_GENERAL = 0x0001;
|
||||
public static final int LOG_COMMAND = 0x0002;
|
||||
public static final int LOG_OUTPUT = 0x0004;
|
||||
public static final int LOG_POOL = 0x0008;
|
||||
|
||||
public static final int LOG_NONE = 0x0000;
|
||||
public static final int LOG_ALL = 0xFFFF;
|
||||
|
||||
private static int logTypes = LOG_ALL;
|
||||
|
||||
@Nullable
|
||||
private static OnLogListener logListener = null;
|
||||
|
||||
/**
|
||||
* <p>Log a message (internal)</p>
|
||||
*
|
||||
* <p>Current debug and enabled logtypes decide what gets logged -
|
||||
* even if a custom callback is registered</p>
|
||||
*
|
||||
* @param type Type of message to log
|
||||
*
|
||||
* <p>Current debug and enabled logtypes decide what gets logged -
|
||||
* even if a custom callback is registered</p>
|
||||
*
|
||||
* @param type Type of message to log
|
||||
* @param typeIndicator String indicator for message type
|
||||
* @param message The message to log
|
||||
* @param message The message to log
|
||||
*/
|
||||
private static void logCommon(int type, String typeIndicator, String message) {
|
||||
private static void logCommon(int type, @NonNull String typeIndicator, @NonNull String message) {
|
||||
if (debug && ((logTypes & type) == type)) {
|
||||
if (logListener != null) {
|
||||
logListener.onLog(type, typeIndicator, message);
|
||||
@ -89,52 +98,61 @@ public class Debug {
|
||||
Log.d(TAG, "[" + TAG + "][" + typeIndicator + "]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log a "general" message</p>
|
||||
*
|
||||
*
|
||||
* <p>These messages are infrequent and mostly occur at startup/shutdown or on error</p>
|
||||
*
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void log(String message) {
|
||||
public static void log(@NonNull String message) {
|
||||
logCommon(LOG_GENERAL, "G", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log a "per-command" message</p>
|
||||
*
|
||||
*
|
||||
* <p>This could produce a lot of output if the client runs many commands in the session</p>
|
||||
*
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void logCommand(String message) {
|
||||
public static void logCommand(@NonNull String message) {
|
||||
logCommon(LOG_COMMAND, "C", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log a line of stdout/stderr output</p>
|
||||
*
|
||||
*
|
||||
* <p>This could produce a lot of output if the shell commands are noisy</p>
|
||||
*
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void logOutput(String message) {
|
||||
public static void logOutput(@NonNull String message) {
|
||||
logCommon(LOG_OUTPUT, "O", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log pool event</p>
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void logPool(@NonNull String message) {
|
||||
logCommon(LOG_POOL, "P", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Enable or disable logging specific types of message</p>
|
||||
*
|
||||
*
|
||||
* <p>You may | (or) LOG_* constants together. Note that
|
||||
* debug mode must also be enabled for actual logging to
|
||||
* occur.</p>
|
||||
*
|
||||
* @param type LOG_* constants
|
||||
*
|
||||
* @param type LOG_* constants
|
||||
* @param enable Enable or disable
|
||||
*/
|
||||
public static void setLogTypeEnabled(int type, boolean enable) {
|
||||
public static void setLogTypeEnabled(int type, boolean enable) {
|
||||
if (enable) {
|
||||
logTypes |= type;
|
||||
} else {
|
||||
@ -144,26 +162,28 @@ public class Debug {
|
||||
|
||||
/**
|
||||
* <p>Is logging for specific types of messages enabled ?</p>
|
||||
*
|
||||
*
|
||||
* <p>You may | (or) LOG_* constants together, to learn if
|
||||
* <b>all</b> passed message types are enabled for logging. Note
|
||||
* that debug mode must also be enabled for actual logging
|
||||
* to occur.</p>
|
||||
*
|
||||
*
|
||||
* @param type LOG_* constants
|
||||
* @return enabled?
|
||||
*/
|
||||
public static boolean getLogTypeEnabled(int type) {
|
||||
return ((logTypes & type) == type);
|
||||
public static boolean getLogTypeEnabled(int type) {
|
||||
return ((logTypes & type) == type);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Is logging for specific types of messages enabled ?</p>
|
||||
*
|
||||
*
|
||||
* <p>You may | (or) LOG_* constants together, to learn if
|
||||
* <b>all</b> message types are enabled for logging. Takes
|
||||
* debug mode into account for the result.</p>
|
||||
*
|
||||
*
|
||||
* @param type LOG_* constants
|
||||
* @return enabled and in debug mode?
|
||||
*/
|
||||
public static boolean getLogTypeEnabledEffective(int type) {
|
||||
return getDebug() && getLogTypeEnabled(type);
|
||||
@ -171,22 +191,23 @@ public class Debug {
|
||||
|
||||
/**
|
||||
* <p>Register a custom log handler</p>
|
||||
*
|
||||
*
|
||||
* <p>Replaces the log method (write to logcat) with your own
|
||||
* handler. Whether your handler gets called is still dependent
|
||||
* on debug mode and message types being enabled for logging.</p>
|
||||
*
|
||||
*
|
||||
* @param onLogListener Custom log listener or NULL to revert to default
|
||||
*/
|
||||
public static void setOnLogListener(OnLogListener onLogListener) {
|
||||
public static void setOnLogListener(@Nullable OnLogListener onLogListener) {
|
||||
logListener = onLogListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get the currently registered custom log handler</p>
|
||||
*
|
||||
* @return Current custom log handler or NULL if none is present
|
||||
*
|
||||
* @return Current custom log handler or NULL if none is present
|
||||
*/
|
||||
@Nullable
|
||||
public static OnLogListener getOnLogListener() {
|
||||
return logListener;
|
||||
}
|
||||
@ -197,10 +218,10 @@ public class Debug {
|
||||
|
||||
/**
|
||||
* <p>Enable or disable sanity checks</p>
|
||||
*
|
||||
* <p>Enables or disables the library crashing when su is called
|
||||
*
|
||||
* <p>Enables or disables the library crashing when su is called
|
||||
* from the main thread.</p>
|
||||
*
|
||||
*
|
||||
* @param enable Enable or disable
|
||||
*/
|
||||
public static void setSanityChecksEnabled(boolean enable) {
|
||||
@ -209,10 +230,10 @@ public class Debug {
|
||||
|
||||
/**
|
||||
* <p>Are sanity checks enabled ?</p>
|
||||
*
|
||||
*
|
||||
* <p>Note that debug mode must also be enabled for actual
|
||||
* sanity checks to occur.</p>
|
||||
*
|
||||
* sanity checks to occur.</p>
|
||||
*
|
||||
* @return True if enabled
|
||||
*/
|
||||
public static boolean getSanityChecksEnabled() {
|
||||
@ -221,9 +242,9 @@ public class Debug {
|
||||
|
||||
/**
|
||||
* <p>Are sanity checks enabled ?</p>
|
||||
*
|
||||
* <p>Takes debug mode into account for the result.</p>
|
||||
*
|
||||
*
|
||||
* <p>Takes debug mode into account for the result.</p>
|
||||
*
|
||||
* @return True if enabled
|
||||
*/
|
||||
public static boolean getSanityChecksEnabledEffective() {
|
||||
@ -232,11 +253,11 @@ public class Debug {
|
||||
|
||||
/**
|
||||
* <p>Are we running on the main thread ?</p>
|
||||
*
|
||||
*
|
||||
* @return Running on main thread ?
|
||||
*/
|
||||
*/
|
||||
public static boolean onMainThread() {
|
||||
return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper()));
|
||||
return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper()) && (Process.myUid() != 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -37,6 +37,7 @@ import android.content.Intent;
|
||||
* window possibly obscuring SuperSU dialogs".
|
||||
* </p>
|
||||
*/
|
||||
@SuppressWarnings({"unused"})
|
||||
public abstract class HideOverlaysReceiver extends BroadcastReceiver {
|
||||
public static final String ACTION_HIDE_OVERLAYS = "eu.chainfire.supersu.action.HIDE_OVERLAYS";
|
||||
public static final String CATEGORY_HIDE_OVERLAYS = Intent.CATEGORY_INFO;
|
||||
@ -45,15 +46,17 @@ public abstract class HideOverlaysReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public final void onReceive(Context context, Intent intent) {
|
||||
if (intent.hasExtra(EXTRA_HIDE_OVERLAYS)) {
|
||||
onHideOverlays(intent.getBooleanExtra(EXTRA_HIDE_OVERLAYS, false));
|
||||
onHideOverlays(context, intent, intent.getBooleanExtra(EXTRA_HIDE_OVERLAYS, false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when overlays <em>should</em> be hidden or <em>may</em> be shown
|
||||
* again.
|
||||
*
|
||||
*
|
||||
* @param context App context
|
||||
* @param intent Received intent
|
||||
* @param hide Should overlays be hidden?
|
||||
*/
|
||||
public abstract void onHideOverlays(boolean hide);
|
||||
public abstract void onHideOverlays(Context context, Intent intent, boolean hide);
|
||||
}
|
||||
|
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@AnyThread
|
||||
public class MarkerInputStream extends InputStream {
|
||||
private static final String EXCEPTION_EOF = "EOF encountered, shell probably died";
|
||||
|
||||
@NonNull
|
||||
private final StreamGobbler gobbler;
|
||||
private final InputStream inputStream;
|
||||
private final byte[] marker;
|
||||
private final int markerLength;
|
||||
private final int markerMaxLength;
|
||||
private final byte[] read1 = new byte[1];
|
||||
private final byte[] buffer = new byte[65536];
|
||||
private int bufferUsed = 0;
|
||||
private volatile boolean eof = false;
|
||||
private volatile boolean done = false;
|
||||
|
||||
public MarkerInputStream(@NonNull StreamGobbler gobbler, @NonNull String marker) throws UnsupportedEncodingException {
|
||||
this.gobbler = gobbler;
|
||||
this.gobbler.suspendGobbling();
|
||||
this.inputStream = gobbler.getInputStream();
|
||||
this.marker = marker.getBytes("UTF-8");
|
||||
this.markerLength = marker.length();
|
||||
this.markerMaxLength = marker.length() + 5; // marker + space + exitCode(max(3)) + \n
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
while (true) {
|
||||
int r = read(read1, 0, 1);
|
||||
if (r < 0) return -1;
|
||||
if (r == 0) {
|
||||
// wait for data to become available
|
||||
try {
|
||||
Thread.sleep(16);
|
||||
} catch (InterruptedException e) {
|
||||
// no action
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return (int)read1[0] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
private void fill(int safeSizeToWaitFor) {
|
||||
// fill up our own buffer
|
||||
if (isEOF()) return;
|
||||
try {
|
||||
int a;
|
||||
while (((a = inputStream.available()) > 0) || (safeSizeToWaitFor > 0)) {
|
||||
int left = buffer.length - bufferUsed;
|
||||
if (left == 0) return;
|
||||
int r = inputStream.read(buffer, bufferUsed, Math.max(safeSizeToWaitFor, Math.min(a, left)));
|
||||
if (r >= 0) {
|
||||
bufferUsed += r;
|
||||
safeSizeToWaitFor -= r;
|
||||
} else {
|
||||
// This shouldn't happen *unless* we have both the full content and the end
|
||||
// marker, otherwise the shell was interrupted/died. An IOException is raised
|
||||
// in read() below if that is the case.
|
||||
setEOF();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
setEOF();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
if (done) return -1;
|
||||
|
||||
fill(markerLength - bufferUsed);
|
||||
|
||||
// we need our buffer to be big enough to detect the marker
|
||||
if (bufferUsed < markerLength) return 0;
|
||||
|
||||
// see if we have our marker
|
||||
int match = -1;
|
||||
for (int i = Math.max(0, bufferUsed - markerMaxLength); i < bufferUsed - markerLength; i++) {
|
||||
boolean found = true;
|
||||
for (int j = 0; j < markerLength; j++) {
|
||||
if (buffer[i + j] != marker[j]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
match = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match == 0) {
|
||||
// marker is at the front of the buffer
|
||||
while (buffer[bufferUsed -1] != (byte)'\n') {
|
||||
if (isEOF()) throw new IOException(EXCEPTION_EOF);
|
||||
fill(1);
|
||||
}
|
||||
if (gobbler.getOnLineListener() != null) gobbler.getOnLineListener().onLine(new String(buffer, 0, bufferUsed - 1, "UTF-8"));
|
||||
done = true;
|
||||
return -1;
|
||||
} else {
|
||||
int ret;
|
||||
if (match == -1) {
|
||||
if (isEOF()) throw new IOException(EXCEPTION_EOF);
|
||||
|
||||
// marker isn't in the buffer, drain as far as possible while keeping some space
|
||||
// leftover so we can still find the marker if its read is split between two fill()
|
||||
// calls
|
||||
ret = Math.min(len, bufferUsed - markerMaxLength);
|
||||
} else {
|
||||
// even if eof, it is possibly we have both the content and the end marker, which
|
||||
// counts as a completed command, so we don't throw IOException here
|
||||
|
||||
// marker found, max drain up to marker, this will eventually cause the marker to be
|
||||
// at the front of the buffer
|
||||
ret = Math.min(len, match);
|
||||
}
|
||||
if (ret > 0) {
|
||||
System.arraycopy(buffer, 0, b, off, ret);
|
||||
bufferUsed -= ret;
|
||||
System.arraycopy(buffer, ret, buffer, 0, bufferUsed);
|
||||
} else {
|
||||
try {
|
||||
// prevent 100% CPU on reading from for example /dev/random
|
||||
Thread.sleep(4);
|
||||
} catch (Exception e) {
|
||||
// no action
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
if (!isEOF() && !done) {
|
||||
// drain
|
||||
byte[] buffer = new byte[1024];
|
||||
while (read(buffer) >= 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isEOF() {
|
||||
return eof;
|
||||
}
|
||||
|
||||
public synchronized void setEOF() {
|
||||
eof = true;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,78 +21,139 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
/**
|
||||
* Thread utility class continuously reading from an InputStream
|
||||
*/
|
||||
public class StreamGobbler extends Thread {
|
||||
@SuppressWarnings({"WeakerAccess"})
|
||||
public class StreamGobbler extends Thread {
|
||||
private static int threadCounter = 0;
|
||||
private static int incThreadCounter() {
|
||||
synchronized (StreamGobbler.class) {
|
||||
int ret = threadCounter;
|
||||
threadCounter++;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Line callback interface
|
||||
*/
|
||||
public interface OnLineListener {
|
||||
public interface OnLineListener {
|
||||
/**
|
||||
* <p>Line callback</p>
|
||||
*
|
||||
*
|
||||
* <p>This callback should process the line as quickly as possible.
|
||||
* Delays in this callback may pause the native process or even
|
||||
* result in a deadlock</p>
|
||||
*
|
||||
*
|
||||
* @param line String that was gobbled
|
||||
*/
|
||||
void onLine(String line);
|
||||
}
|
||||
|
||||
private String shell = null;
|
||||
private BufferedReader reader = null;
|
||||
private List<String> writer = null;
|
||||
private OnLineListener listener = null;
|
||||
/**
|
||||
* Stream closed callback interface
|
||||
*/
|
||||
public interface OnStreamClosedListener {
|
||||
/**
|
||||
* <p>Stream closed callback</p>
|
||||
*/
|
||||
void onStreamClosed();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final String shell;
|
||||
@NonNull
|
||||
private final InputStream inputStream;
|
||||
@NonNull
|
||||
private final BufferedReader reader;
|
||||
@Nullable
|
||||
private final List<String> writer;
|
||||
@Nullable
|
||||
private final OnLineListener lineListener;
|
||||
@Nullable
|
||||
private final OnStreamClosedListener streamClosedListener;
|
||||
private volatile boolean active = true;
|
||||
private volatile boolean calledOnClose = false;
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
||||
*
|
||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||
* returning (as the buffer is full, pausing the native process)</p>
|
||||
*
|
||||
*
|
||||
* @param shell Name of the shell
|
||||
* @param inputStream InputStream to read from
|
||||
* @param outputList List<String> to write to, or null
|
||||
* @param outputList {@literal List<String>} to write to, or null
|
||||
*/
|
||||
public StreamGobbler(String shell, InputStream inputStream, List<String> outputList) {
|
||||
@AnyThread
|
||||
public StreamGobbler(@NonNull String shell, @NonNull InputStream inputStream, @Nullable List<String> outputList) {
|
||||
super("Gobbler#" + incThreadCounter());
|
||||
this.shell = shell;
|
||||
this.inputStream = inputStream;
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
writer = outputList;
|
||||
lineListener = null;
|
||||
streamClosedListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
||||
*
|
||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||
* returning (as the buffer is full, pausing the native process)</p>
|
||||
*
|
||||
*
|
||||
* @param shell Name of the shell
|
||||
* @param inputStream InputStream to read from
|
||||
* @param onLineListener OnLineListener callback
|
||||
* @param onStreamClosedListener OnStreamClosedListener callback
|
||||
*/
|
||||
public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) {
|
||||
@AnyThread
|
||||
public StreamGobbler(@NonNull String shell, @NonNull InputStream inputStream, @Nullable OnLineListener onLineListener, @Nullable OnStreamClosedListener onStreamClosedListener) {
|
||||
super("Gobbler#" + incThreadCounter());
|
||||
this.shell = shell;
|
||||
this.inputStream = inputStream;
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
listener = onLineListener;
|
||||
lineListener = onLineListener;
|
||||
streamClosedListener = onStreamClosedListener;
|
||||
writer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// keep reading the InputStream until it ends (or an error occurs)
|
||||
// optionally pausing when a command is executed that consumes the InputStream itself
|
||||
try {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Debug.logOutput(String.format("[%s] %s", shell, line));
|
||||
Debug.logOutput(String.format(Locale.ENGLISH, "[%s] %s", shell, line));
|
||||
if (writer != null) writer.add(line);
|
||||
if (listener != null) listener.onLine(line);
|
||||
if (lineListener != null) lineListener.onLine(line);
|
||||
while (!active) {
|
||||
synchronized (this) {
|
||||
try {
|
||||
this.wait(128);
|
||||
} catch (InterruptedException e) {
|
||||
// no action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// reader probably closed, expected exit condition
|
||||
if (streamClosedListener != null) {
|
||||
calledOnClose = true;
|
||||
streamClosedListener.onStreamClosed();
|
||||
}
|
||||
}
|
||||
|
||||
// make sure our stream is closed and resources will be freed
|
||||
@ -101,5 +162,96 @@ public class StreamGobbler extends Thread {
|
||||
} catch (IOException e) {
|
||||
// read already closed
|
||||
}
|
||||
|
||||
if (!calledOnClose) {
|
||||
if (streamClosedListener != null) {
|
||||
calledOnClose = true;
|
||||
streamClosedListener.onStreamClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Resume consuming the input from the stream</p>
|
||||
*/
|
||||
@AnyThread
|
||||
public void resumeGobbling() {
|
||||
if (!active) {
|
||||
synchronized (this) {
|
||||
active = true;
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Suspend gobbling, so other code may read from the InputStream instead</p>
|
||||
*
|
||||
* <p>This should <i>only</i> be called from the OnLineListener callback!</p>
|
||||
*/
|
||||
@AnyThread
|
||||
public void suspendGobbling() {
|
||||
synchronized (this) {
|
||||
active = false;
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Wait for gobbling to be suspended</p>
|
||||
*
|
||||
* <p>Obviously this cannot be called from the same thread as {@link #suspendGobbling()}</p>
|
||||
*/
|
||||
@WorkerThread
|
||||
public void waitForSuspend() {
|
||||
synchronized (this) {
|
||||
while (active) {
|
||||
try {
|
||||
this.wait(32);
|
||||
} catch (InterruptedException e) {
|
||||
// no action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Is gobbling suspended ?</p>
|
||||
*
|
||||
* @return is gobbling suspended?
|
||||
*/
|
||||
@AnyThread
|
||||
public boolean isSuspended() {
|
||||
synchronized (this) {
|
||||
return !active;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get current source InputStream</p>
|
||||
*
|
||||
* @return source InputStream
|
||||
*/
|
||||
@NonNull
|
||||
@AnyThread
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get current OnLineListener</p>
|
||||
*
|
||||
* @return OnLineListener
|
||||
*/
|
||||
@Nullable
|
||||
@AnyThread
|
||||
public OnLineListener getOnLineListener() {
|
||||
return lineListener;
|
||||
}
|
||||
|
||||
void conditionalJoin() throws InterruptedException {
|
||||
if (calledOnClose) return; // deadlock from callback, we're inside exit procedure
|
||||
if (Thread.currentThread() == this) return; // can't join self
|
||||
join();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user