0

On devices running Android 2.2, I want to detect when the user has pressed the screen for a certain length of time. Imagine sending a morse code message, with short taps (dots) and longer presses (dashes). I want to react to short taps as soon as the user lifts her finger, and to longer presses after (say) 500 milliseconds, even if she continues to hold her finger down.

I've looked at both FutureTask and ScheduledExecutorService but these look like overkill for this implementation. Or perhaps I just have cold feet about dealing directly with threads, and seeing all the code that is needed to handle them.

Here's simplified pseudo-code for how I have done this sort of thing in other languages:

public boolean onTouch(MotionEvent event) {
  if (event.getAction() == MotionEvent.ACTION_DOWN) {
    timer = createObjectToCallback(callbackMethod, 500); // milliseconds

  } else if (event.getAction() == MotionEvent.ACTION_UP) {
    if (timer still exists) {
      timer.kill();
      // Do the short press thing
    } else {
      // Do nothing. It already happened when callbackMethod was triggered
    }
  }
}

public void callbackMethod() {
  // Do the long press thing. The timer has already auto-destructed.
}

What simple ways are there of doing this in Java?

== EDIT in response to the answer from @zapl ==

Writing code that works is one thing. Understanding how it works is another.

If I understand correctly, the thread that updates the UI is already running in a loop. Let's imagine a very simple case.

The Main activity creates a black canvas, and contains an onTouch method. When it starts, it calls setOnTouchListener. The Main thread now listens constantly to input from the screen. If the way the user is touching the screen has changed, it calls the onTouch method, with information about the change.

Let's say that the onTouch method draws a green circle around the touch point. This circle is drawn using cycles belonging to the Main thread. When the drawing is finished, the Main thread starts checking for new changes from the screen. If there are no changes, then onTouch is not called again, and the green dot does not move.

When the user lifts her finger, the screen provides changed information to the Main thread, and the code in the onTouch method erases the dot.

Create interface
Has the screen detected a change? No: loop
Has the screen detected a change? No: loop
...
Has the screen detected a change? Yes: draw a green dot; loop
Has the screen detected a change? No: loop.
...
Has the screen detected a change? Yes: new position => redraw the green dot; loop
...
Has the screen detected a change? Yes: not touching => remove dot; loop
Has the screen detected a change? ...

Suppose that I want the dot to turn red if the user's finger does not move for at least 500 ms. No move means no callback to onTouch. So I can set up a Handler, which adds itself to the loop in the Main thread. The Main thread now has two actions in its loop.

Create interface
Has the screen detected a change? No: loop
Has the screen detected a change? No: loop
...
Has the screen detected a change? Yes: a touch; draw a green dot; add Handler; loop
Has the screen detected a change? No;
  Is it time for Handler to trigger? No: loop.
...
Has the screen detected a change? No;
  Is it time for Handler to trigger? Yes: change dot color to red; remove Handler; loop.
Has the screen detected a change? No: loop.
...
Has the screen detected a change? Yes: not touching => remove dot; loop
Has the screen detected a change? ...

Any code executed by the Handler will block the Main thread until it has completed.
Is that an accurate description of what Handler does?

James Newton
  • 6,623
  • 8
  • 49
  • 113
  • See if any of these help: http://stackoverflow.com/questions/3553163/android-long-touch-event, http://stackoverflow.com/questions/4324362/detect-touch-press-vs-long-press-vs-movement, http://developer.android.com/training/gestures/detector.html – PM 77-1 Oct 29 '14 at 01:51
  • Thanks. I was hoping for something that is touch independent, so that I can reuse the technique in other situations. If I understand correctly, onLongPress triggers when the user removes her finger from the screen, which is not what I want. My situation is somewhat complicated in that it also needs to allow the user to move her finger, and that has a separate meaning. – James Newton Oct 29 '14 at 02:00
  • It seems that this is what I was looking for: http://stackoverflow.com/a/11679788/1927589 – James Newton Oct 29 '14 at 02:06
  • 1
    @JamesNewton I'v added a bit to the answer in response to your edit. – zapl Oct 31 '14 at 00:36

1 Answers1

1

Using threads when you actually don't need parallelism is indeed way overkill because it adds it's own set of problems. What you need is to schedule code that runs in the future, but on the same thread. Android's Handler can do exactly that. You can either schedule a Runnable or a Message to arrive. There is also the derived CountDownTimer for simpler scheduling of periodic events.

But it's probably not required in this case because there is the GestureDetector.

It comes with Android and can distinguish between several types of single, long and double taps. It also behaves consistent with the rest of the system. You will probably want to use that.

More about that http://developer.android.com/training/gestures/detector.html

If you really want to implement your own or just to see an example of how to use a Handler, have a look at GestureDetector's source . It's full of code like the one you've posted (mHandler.hasMessages, mHandler.removeMessages, mHandler.sendEmptyMessageDelayed).

Note: Handler is not standard Java class because scheduling events in the same thread requires a thread to be message queue based and there is no standard solution for that. It's also UI framework dependent how threadsafe the framework is. Android's tries to throw exceptions if you even try to modify the ui from some background thread. The same aproach should however work with Swing's Event Dispatch Thread (SwingUtilities.invokeLater) for example.


Edit: trying to explain Ui thread & handler:

Any code executed by the Handler will block the Main thread until it has completed.

correct. Android's main thread works very simplified like this:

public BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
public void loop() {
    while (true) {
        Runnable currentTask = queue.take(); // blocks until something happens
        currentTask.run();
        // repeat.
    }
}
public void enqueue(Runnable runnable) {
    queue.put(runnable);
}

public static void main(String[] args) {
    startThreadsThatReceiveSystemEvents();
    enqueue(new Runnable() {
        @Override
        public void run() {
            Activity activity = createStartActivity();
            activity.onCreate();
            activity.onResume();
        }
    });
    loop(); // fun fact: an android app will never return from here
            // it's process is simply killed by the system
}

The real world equivalent is often found in stacktraces pretty far down:

E/AndroidRuntime(20941): at android.os.Looper.loop(Looper.java:130)
E/AndroidRuntime(20941): at android.app.ActivityThread.main(ActivityThread.jav a:3691)

Everything that Android does in terms of starting / stopping Activites, drawing the screen, .. is the result of something getting enqueued so that the run loop evaluates it at some point. A Handler uses the very same queue. And everything you enqueue will be executed interspersed with all the other things that already happen withing that loop. One thread can't do things parallel to itself so it's all sequential. This is why Handler tasks block other tasks.

Your example with touch events is basically correct, it's just not actively looking at the touchscreen. It is getting notified via it's connection to the system. There is essentially another thread (the Binder threads in case you have ever looked at the list of threads) listening for messages from the system and once they arrive, all this thread needs to do is to enqueue them for the main loop. This can automatically wake the loop up if it was waiting.

The main thread queue is actually not a simple BlockingQueue since it needs to support scheduled events as well. It's an android specific implementation called MessageQueue and is partially implemented in native code. The loop method is in it's own class as well (Looper). And the queue isn't using Runnable directly, it's actually a queue of Messages - which can contain a Runnable (in a hidden field) and the run loop when it finds a Runnable in a Message will just execute it like in above sample code.

Each Handler is bound to one Looper/MessageQueue combination (and therefore to 1 thread). Handler.post/sendMessage does the dirty work of constructing a proper message & enqueueing it. The Message has a link back to your Handler so the loop know's which Handler's handleMessage method to call.

Besides using the already existing main thread loop/queue, you're free to create additional queue based threads and Handlers for those. https://stackoverflow.com/a/13369215/995891 contains a tiny example for that.

Community
  • 1
  • 1
zapl
  • 63,179
  • 10
  • 123
  • 154