13

I have a BroadcastReceiver that receives input from an outside source.

This receiver must then behave as a "mouse-like" program and send input events to the system. I have Root access and permissions.

My problem is that when I send a String such as "input tap 275 410", the program behaves correctly, if I even split the string such as "input" + " tap" + " 275" + " 410", it still works...

However, when I assemble the string as the desired:

"input " + CursorSystem.command + " " + coords[0] + " " + coords[1]

Then nothing happens... while debugging, all the watched Strings are exactly the same (minus the location):

value = {char[26]@830031306976}  0 = 'i' 105 1 = 'n' 110 2 = 'p' 112 3 = 'u' 117 4 = 't' 116 5 = ' ' 32 6 = 't' 116 7 = 'a' 97 8 = 'p' 112 9 = ' ' 32 10 = '2' 50 11 = '7' 55 12 = '5' 53 13 = ' ' 32 14 = '4' 52 15 = '1' 49 16 = '0' 48 17 = '\u0000' 0 18 = '\u0000' 0 19 = '\u0000' 0 20 = '\u0000' 0 21 = '\u0000' 0 22 = '\u0000' 0 23 = '\u0000' 0 24 = '\u0000' 0 25 = '\u0000' 0

I would like some guidance or a property to study, since my results are not being effective. As far as I can tell, this is not a permission issue, nor is it a thread issue, since Logs show the permission request (and the grant), an the thread lives for as long as the Receiver is being executed (but if I debug it for too long, then it gets killed after a while [30+ seconds])

The Receiver.onReceive:

public void onReceive(Context context, Intent intent) {
    String command = intent.getStringExtra("command");
    String touch = intent.getStringExtra("touch");
    if (Executor.isRootAvailable()) {
        Executor executor = new Executor();
        ArrayList<String> commands = new ArrayList<>();
        if (command != null) {
            if (command.length() > 0) {
                if (commands.add("input " + command)) {
                    executor.execute(commands);
                }
            }
        } else if (touch != null) {
            if (CursorSystem.isAlive) { // Always true for the executions
                CursorSystem.doInput();
                if (CursorSystem.state == CursorSystem.STATE) { // Always true for the executions
                    int[] coords = CursorSystem.coordinates;
                    if (coords != null) {
// This line is where (I guess) that the problem lies.
// CursorSystem.command is "tap", coords[0] and coords[1] is the result of (a view).getLocationOnScreen(coordinates)
// It results in a 2 positions int array with (x, y) coordinates, these values are always correct.
                        if (commands.add("input " + CursorSystem.command + " " + coords[0] + " " + coords[1])) {
                            executor.execute(commands);
                            CursorSystem.doInput();
                        } else {
                            // error...
                        }
                    } else {
                        // error...
                    }
                } else {
                    // error...
                }
            } else {
                error...
            }
        } else {
            error...
        }
    } else {
        error...
    }
}

The Executor.execute:

public final boolean execute(ArrayList<String> commands) {
    boolean resp = false;
    try {
        if (commands != null && commands.size() > 0) {
            Process suProcess = Runtime.getRuntime().exec("su");
            DataOutputStream dataOutputStream = new DataOutputStream(suProcess.getOutputStream());
            for (String currCommand : commands) {
                dataOutputStream.writeBytes(currCommand);
                dataOutputStream.writeBytes("\n");
                dataOutputStream.flush();
            }
            dataOutputStream.writeBytes("exit\n");
            dataOutputStream.flush();
            try {
                int suProcessRetval = suProcess.waitFor();
                return (suProcessRetval != 255); // Always yields 0
            } catch (Exception ex) {
                // errors...
            }
        } else {
            // error...
        }
    } catch (IOException ex) {
        Log.w(TAG, "IOException: ", ex);
    } catch (SecurityException ex) {
        Log.w(TAG, "SecurityException: ", ex);
    } catch (Exception ex) {
        Log.w(TAG, "Generic Exception: ", ex);
    }
    return resp;
}
Bonatti
  • 2,778
  • 5
  • 23
  • 42
  • In your `Executor.execute`, you return `resp` which will always be false since you're not changing it anywhere. Is it how it's supposed to be? – Akash Agarwal May 04 '16 at 18:52
  • Also could you please mention what exactly `CursorSystem` class is and format the `value` line if it can be? I'm guessing `CursorSystem` is your own class. It will be helpful to see its complete code. – Akash Agarwal May 04 '16 at 18:58
  • @AkashAggarwal Yes, mainly because the code now is a mess of reviews. The code will eventually reach `return (suProcessRetval != 255);` that evaluates to true... Also, `CursorSystem ` class has over two thousand lines of code. The main variables used are always set (never null at least), `CursorSystem.isAlive` is a `boolean` always debugged as `true`, most of other codes shown are actually different, and have been simplified for the question... At the same time, no LogCat errors appear, no `Exception`s are thrown, no failures.... and no response. – Bonatti May 04 '16 at 19:15
  • I think I'm getting on the same page as you are. I still don't understand the format of the `value` line. Could you please explain it more and also could you confirm that it is an output? – Akash Agarwal May 04 '16 at 19:30
  • Well. `CursorSystem` is a `Service` that keeps on running. the `isAlive` (boolean) is a flag to indicate that the Service is executing correctly. `doInput()` is a Static function that executes the currently present task in `CursorSystem`, its triggered in several different areas of the application, but does not alter anything outside of CursorSystem. `dataOutputStream` is never (during debugging sessions) null.... Finally, on the `executor.execute`, its argument is an ArrayList of Strings. The Strings inside are always equivalent (meaning String (from run 0) equals String (from run 1)).... – Bonatti May 04 '16 at 20:02
  • It is worth noticing that the code has been executed on 3 different devices (Motorolla E3, Samsung J1 and Sony Experia), as well as on the emulator (in the emulator, it does not work, since its "core" is missing features the program uses). On all devices, the binary representation of each string is identical (when saved to a `File`). Finally its also worth noticing that the issue has been bypassed. Still, I want to know what could have happened that caused such a bug/issue. I personally believe it to be some "Guard" system that blocks commands, even in rooted devices. Or indirect `windows`used – Bonatti May 04 '16 at 20:06
  • I don't understand this line I've been talking about: `value = {char[26]@830031306976} 0 = 'i' 105 1 = 'n' 110 2 = 'p' 112 3 = 'u' 117 4 = 't' 116 5 = ' ' 32 6 = 't' 116 7 = 'a' 97 8 = 'p' 112 9 = ' ' 32 10 = '2' 50 11 = '7' 55 12 = '5' 53 13 = ' ' 32 14 = '4' 52 15 = '1' 49 16 = '0' 48 17 = '\u0000' 0 18 = '\u0000' 0 19 = '\u0000' 0 20 = '\u0000' 0 21 = '\u0000' 0 22 = '\u0000' 0 23 = '\u0000' 0 24 = '\u0000' 0 25 = '\u0000' 0` – Akash Agarwal May 04 '16 at 21:58
  • `value` is the debug position of the `commands` argument in the `Executor.execute` function. An ArrayList of Strings, with size 1, a String (Array of Char), with contents, 0....25 (default String allocation in that enviroment)...... While I understand that watching the code shown is likely to show some failure, I have some knowledge on what I am doing, and can pretty much tell that the issue is not inside my code, but rather in some indirect situation, or indirect activaton of Java Objects, that is blocking the processing somehow. While I studied this, some scenarios have been dropped – Bonatti May 05 '16 at 11:43
  • You have mentioned there is no issue with the thread but I think Executor will run asynchronously, so your onReceive() function may be returning before your execute() function is finishing. So if your broadcast receiver is receiving intents when your app is not running then its possible that your application process is getting killed after returning from onReceive() function. Read about Broadcast Receiver Life Cycle [here](http://developer.android.com/reference/android/content/BroadcastReceiver.html). Its no recomended to do asynchronous stuff in onReceiver(). Use services for this purpose. – Ankit Aggarwal May 05 '16 at 18:28
  • Thanks for the info, however, I cant see "synchronicity" as an issue. Executor can both be created, or be executing, and the function will still be run (in debug, that is exactly what I am seeing). My application reaches all breakpoints and `return`s involved in the application. The onReceiver would merely "stack" more commands on the Executor. Also, the `CursorSystem` is a Service, that is running correctly. I can tell that due to this "problem" some code has been changed, and the issue is no longer reproducible. I can however state that this Receiver and the Services involved did not change. – Bonatti May 05 '16 at 19:55
  • rather than appending strings, have you tried StringBuilder and converting to string ? StringBuilder sb=new StringBuilder("input ");sb.append(CursorSystem.command);sb.append(" ");sb.append(coords[0]);sb.append(coords[1]); pass String strToPass=sb.toString(); – Sreehari May 10 '16 at 05:26
  • @Stallion Since `Executor` could send single commands, as well as batch commands, the argument is an ArrayList. I have directly sent String to that class, in order to test it. It works as expected. – Bonatti May 10 '16 at 11:04

2 Answers2

3

For anyone interested:

The problem found was that the CursorSystem used ImageViews, that aided the user to "target" what interaction was desired (and where). During execution, the command could be received, and the image be moved to where the command would happens, thus "receiving" the event.

Adding the following flags:

.LayoutParams.FLAG_NOT_FOCUSABLE
.LayoutParams.FLAG_NOT_TOUCHABLE
.LayoutParams.FLAG_LAYOUT_NO_LIMITS

Allowed the system to not consider the Views as interactable, as well as "draw" them outside of bounds, so that the user could "swipe" the device hidden menus, as well as click on a target that could potentially NOT exist. This caused some "Launcher" programs to intercept the MotionEvent, and do nothing.

The command was being executed successfully, just no result was being displayed, and caused us to interpret it as "doing nothing".

Bonatti
  • 2,778
  • 5
  • 23
  • 42
-1

replace

 if (commands.add("input " + CursorSystem.command + " " + coords[0] + " " + coords[1])) {
                            executor.execute(commands);
                            CursorSystem.doInput();
                        }

with

String newCommand = "input " + CursorSystem.command + " " + coords[0] + " " + coords[1];
newCommand = newCommand.replace("\u0000", "").replace("\\u0000", "");// removes NUL chars and backslash+u0000
if(commands.add(newCommand)){
   executor.execute(commands);
   CursorSystem.doInput();
   }
Linh Nguyen
  • 1,264
  • 1
  • 10
  • 22
  • While this may answer the OP's question, posting solid code blocks without any explanation serves no educational purpose. Please improve your answer by explaining why this code is a solution or if it is based on OP's what has changed and for what reason. – Marcin Orlowski May 09 '16 at 10:08
  • I've commented my line of code : "// removes NUL chars and backslash+u0000 " isn't it enough? – Linh Nguyen May 09 '16 at 10:11
  • @LinhNguyen As stated in the question, the String argument is equal. There are no null characters or unwanted characters (such as a backlash, new line, line feed, etc.). The issue does not appear to be within the code, since the "fix" given to the application did not alter either Service or class within it. – Bonatti May 09 '16 at 11:31
  • value = {char[26]@830031306976} 0 = 'i' 105 1 = 'n' 110 2 = 'p' 112 3 = 'u' 117 4 = 't' 116 5 = ' ' 32 6 = 't' 116 7 = 'a' 97 8 = 'p' 112 9 = ' ' 32 10 = '2' 50 11 = '7' 55 12 = '5' 53 13 = ' ' 32 14 = '4' 52 15 = '1' 49 16 = '0' 48 17 = '\u0000' 0 18 = '\u0000' 0 19 = '\u0000' 0 20 = '\u0000' 0 21 = '\u0000' 0 22 = '\u0000' 0 23 = '\u0000' 0 24 = '\u0000' 0 25 = '\u0000' 0 your log showing alot of null/blank character ? – Linh Nguyen May 09 '16 at 14:43