62

I'm wondering how apps like SwipePad and Wave Launcher are able to detect touch gestures/events simply through a service. These apps are able to detect a touch gestures even though it is not in their own Activity. I've looked all over the Internet and haven't found how they can do that.

My main question is how a service can listen in on touch guestures/events just as a regular Activity may receive MotionEvents even though it may not be in the original Activity or context. I'm essentially trying a build an app that will recongize a particular touch gesture from a user regardless which Activity is on top and do something when that gesture is recongized. The touch recongition will be a thread running in the background as a service.

Brian
  • 7,955
  • 16
  • 66
  • 107
  • 1
    SwipePad looks like it might just use transparent system alert windows for the touchable areas rather than the whole screen. – Sam Nov 21 '14 at 23:09
  • I have the same problem which I am trying to solve since three days ago. The problem is that I need to collect the touch events data of taps under the foreground service class. Can you please help me to answer my question here: https://stackoverflow.com/questions/65405516/touch-event-getaction-does-not-work-under-service-class-in-android-studio – Mohsen Ali Dec 28 '20 at 11:23
  • This question was from a long time ago. I've since learned that the premise of my own question is not very good. A service itself can't receive any touch events since it's not a UI component. There are hacky approaches that involve using system alert window flag to create a view that is launched by a service. But this approach is discouraged by Android and is effectively being discontinued with the introduction of the Bubbles API in Android 11. – Brian Jan 07 '21 at 18:41

5 Answers5

17

I had this same problem and I've finally figured it out! Thanks to this post: Creating a system overlay window (always on top). You need to use an alert window instead of an overlay (and this also means you can use it in Andoid ICS):

WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                PixelFormat.TRANSLUCENT);

Then just attach a GestureListener in this manner:

GestureDetector gestureDetector = new GestureDetector(this, new AwesomeGestureListener());
View.OnTouchListener gestureListener = new View.OnTouchListener() {
      public boolean onTouch(View v, MotionEvent event) {
            return gestureDetector.onTouchEvent(event);
      }
};

overlayView.setOnTouchListener(gestureListener);

Yay!

Community
  • 1
  • 1
pypmannetjies
  • 25,734
  • 7
  • 39
  • 49
  • will this interfere with the users ability to click buttons in the activity they think they are viewing – ChuckKelly Sep 16 '13 at 04:09
  • @ChuckKelly yes it will interfere – SIr Codealot Dec 26 '13 at 20:28
  • 3
    I am trying to listen taps from a service, service is attached with a Activity. The activity is used as usual but service keep recording taps. when using TYPE_SYSTEM_OVERLAY its works very fine on Android 2.3 but on 4.1 the onTouchEvent is not invoked. When using TYPE_SYSTEM_ALERT onTouchEvent is invoked but the the touch event is not passed to the activity, and it becomes unresponsive. Please help – Sanjay Singh Mar 02 '14 at 16:17
  • 2
    This works for me in Android 4.3, but the touch event is consumed by the service, leaving the phone mostly unusable. – Sam Nov 21 '14 at 22:40
  • 6
    Not a good answer as it blocks all other running applications. – Jono Dec 04 '14 at 15:25
  • See below, return falsely – Hack5 Oct 23 '16 at 06:07
  • does this work only for the corresponding "foreground" app or for all other apps on the phone as well? And can you get the X and Y coordinates? – keinabel Dec 13 '16 at 19:27
  • Doesn't work across all APKs / packages on system I think. – John61590 Apr 11 '18 at 23:39
  • can anyone please explain what is overlayview here – Vipul Chauhan Mar 20 '19 at 14:12
5

Interesting question. I don't know how they did that and I found google group posts which tell me that there is no global touch listener. But I have an idea anyways...

I found this post where someone succeeds to display a popupwindow from a service. If I would make that popup transparent and fullscreen, I'm sure I could capture the touches since I'm allowed to set a touch interceptor.

Edit: Please report results when you try that, would be interesting to know if this works...

Community
  • 1
  • 1
Knickedi
  • 8,742
  • 3
  • 43
  • 45
  • This seems like an interesting method. I've seen PopupWindows be used before for similar things but I will have to give it a try. At the moment, I'm frustrated that by how difficult it is to get touch events from a service. It'll be a while before I get a chance to try this out, but I'll let you know how it goes! – Brian Sep 22 '11 at 21:55
  • I just tried this, and I couldn't even get the alert dialog to show. The `show` method threw a `BadTokenException`. – Sam Nov 21 '14 at 22:48
  • The exception message is `Unable to add window -- token null is not valid; is your activity running?` – Sam Nov 21 '14 at 22:52
  • It looks like that post has access to an input view, which is probably why they got it to work. – Sam Nov 21 '14 at 22:53
3

I tested every possible solution but nothing worked some didn't fired touch event which did they frozed the screen .

So I did some reverse engineering and now posting solution which works

  WindowManager.LayoutParams params = new android.view.WindowManager.LayoutParams(0, 0, 0, 0, 2003, 0x40028, -3);
        View mView = new View(this);

        mView.setOnTouchListener(this);

        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        wm.addView(mView, params);
Mr Coder
  • 8,169
  • 5
  • 45
  • 74
  • This seem to be working only for the first view/activity. Not for subsequent activities created. I get following warning: W/ViewRootImpl﹕ Dropping event due to no window focus: MotionEvent { action=ACTION_OUTSIDE, actionButton=0, id[0]=0, x[0]=0.0, y[0]=0.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=220247332, downTime=220247332, deviceId=4, source=0x1002 } – AllThatICode Oct 16 '15 at 12:13
  • 1
    You saved my day! – Duna Nov 22 '18 at 20:57
3

I tested this on API 21 and a nexus 5 and I was able to log, from a service, when a touch event was fired by the system. However, the data inside the MotionEvent object, for example coordinates, returns 0.0 when OUTSIDE of what seems to be my app's package.

Two sources integration, permission

I'm curious as to why the event.getX(), event.getY() and event.getPressure() return 0.0 when not in an activity of the app where the service lives.

Edit

This solution does not prevent other listeners from receiving the touch event capured by the service because of

public boolean onTouch(View v, MotionEvent e){
    Log.d(LOG_TAG, "Touch event captured");
    return false;      

By returning false, it allows other listeners to receive the event.

Edit2

Apparently, the input dispatcher in the OS will set the coordinates and pressure to 0 if the current activity and the listener do not share an UID, here is source

Community
  • 1
  • 1
Clocker
  • 1,316
  • 2
  • 19
  • 29
  • do you see a way to work around this? like changing the UID dynamically? – keinabel Dec 13 '16 at 19:25
  • Unless you have your own OS, I don't think it'll be possible. Maybe if the service is registered as a system service? I mean the values are there but the OS will remove the values of interest and propagate the touch event up to any other listeners. – Clocker Dec 13 '16 at 22:12
0

I've searched through many SO threads, but for security reasons, I don't think this is possible for all packages on the system. It certainly is for your own app though.

WindowManager

 WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
            PixelFormat.TRANSLUCENT);

Floating / Overlay layout

floatyView = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate
            (R.layout.floating_view, null);

    floatyView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.d(TAG, "event.x " + event.getX());
            Log.d(TAG, "event.y " + event.getY());
            if (event.getAction() == MotionEvent.ACTION_UP) {
                v.performClick();
            }
            return false;
        }
    });

Layout

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:background="@android:color/transparent"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
John61590
  • 1,106
  • 1
  • 13
  • 29