I need to fire an event when a user holds down a button, and fire another event, when the user releases it in my app. The events are short Asyc Tasks, which run in background.
The problem is, I know how to make this happen with the OnTouchListener(), but it seems like when you hold down the button, it runs an infinite loop on the onTouch() method, stopping when the user releases it. Although since my Tasks are launched only once, It runs over and over empty, doing nothing.
I'm not sure if there is any other way possible to achieve this, which fires my back events and stops with the loop. It would be beneficial, as a lot of resources in the form of side threads are already being consumed, and I would like to save some useless running of a method. Here is my implementation, if something is wrong with it:
private OnTouchListener down = new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
//Async Task
} else if (action == MotionEvent.ACTION_UP) {
//Async Task
}
return false;
}
};
Related
I'm new to android and I'm making an app that will listen for a consecutive volume up and volume down keypress and vibrate a pattern.
I've tried using dispatchKeyEvent() and it detects volume up and down fine but it blocks the user from changing the volume.
public boolean dispatchKeyEvent(KeyEvent event) {
int action = event.getAction();
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
if (action == KeyEvent.ACTION_DOWN) {
System.out.println("UP"); // I know i can use Log but this is quicker to type
}
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
System.out.println("DOWN");
}
return true;
default:
return super.dispatchKeyEvent(event);
}
}
Additionally I'd like to be able to do this when the screen is off (and app in background) but aparently services can't interact with key events.
I've found very conflicting answers all over the place; an app I used before played a sound when it was in the background, screen turned off and the power button was pressed 3 times in short succession, however many questions here have answers akin to "not possible".
I wouldn't mind using power button instead of volume keys (infact both would be nice) but that seems harder to implement.
TLDR:
How to stop listeners blocking the keys they're listening.
2. How to run this in the background.
Any help would be greatly appreciated!
Edit: regarding no2 I found a way to do this with power button by registering a broadcast receiver in a service and listening to screen off and screen on intents.
overriding the onKeyDown() of Activity is easier; because it's always KeyEvent.ACTION_DOWN:
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP: {
System.out.println("UP");
}
case KeyEvent.KEYCODE_VOLUME_DOWN: {
System.out.println("DOWN");
}
}
return super.onKeyDown(keyCode, event);
}
How to stop listeners blocking the keys they're listening.
As you could see from android docs, if you don't need to block your keys, just call super.dispatchKeyEvent(event); otherwise if you return true; it means that you consumed event, nobody will know about event.
How to run this in the background.
I think, there is impossible. Android has service, which could do background job, but this not way to handle system button clicks. Maybe this topic will help you.
I'm making an app where some sort of widgets are created on the fly and can be re-arranged with drag and drop. I'm trying to implement 2 things:
Long press to initiate drag and drop
short press(or click) to open a menu to change some settings.
But i'm having a problem where I can't cancel the code in handler's postdelayed() function. I'm using the following code to make it happen.
_sliders_item[_sliders_counter].setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(final View v, MotionEvent event) {
Handler hndlr = new Handler();
Runnable _run = new Runnable() {
#Override
public void run() {
ClipData data = ClipData.newPlainText("", "");
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v);
v.startDrag(data, shadowBuilder, v, 0);
v.setVisibility(View.INVISIBLE);
_t1.setText("executed");
_t1.show();
}
};
if (_enable_editor) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
hndlr.postDelayed(_run,1000);
//return true;
}
if (event.getAction() == MotionEvent.ACTION_UP){
_t1.setText("menu");
_t1.show();
hndlr.removeCallbacks(_run);
//return true;
}
}
return _enable_editor;
}
});
But the problem is, When I long press the object, it works fine. I can initiate the drag and drop. But when I short press it, The code for ACTION_UP event executes but the code inside the runnable still executes. How can I cancel the runnable code when I short press the object?
You have to keep references to your Runnables and Handler and use Handler#removeCallbacks().
So basically:
Handler h = new Handler();
Runnable r = new Runnable() { /* does something */
h.postDelayed(r, 1000);
// When you want to cancel.
h.removeCallbacks(r);
Declare your Runnable outside of onTouch event, like seperate method in your Class. Then when you want to actually execute runnable, call your method.
Quick'n'Dirty Example:
onTouch(..) {
if (eventDown) {
// start Runnable
}
if (eventUp) {
// Do not call runnable
}
}
private void executeMyRunnable( /* Any arguments you need in Runnable */) {
// Your runnable code here
}
You can check for long press by taking the time difference between system time at action.down and system time at action.up. If the difference is greater than say 5000 milliseconds, then do the runnable code else the dialog code.
Simply run handler.removeCallbacksAndMessages(null);
I have an app uploaded on google play. This is the link.
It hasn't been tested on many devices yet so errors are pretty common. A user messaged me today saying that the app crashes if the togglebutton is turned ON and the button is only pressed, not held.
This is the logcat file that he sent me:
E/MessageQueue-JNI(31135): java.lang.RuntimeException: stop failed.
E/MessageQueue-JNI(31135): at android.media.MediaRecorder.stop(Native Method)
E/MessageQueue-JNI(31135): at com.whizzappseasyvoicenotepad.MainActivity.stopRecording(MainActivity.java:183)
Quote:
App doesn't always crash. Sometimes it does, sometimes it doesn't. It only happens when the togglebutton is ON. If I touch and hold the button it works fine but if I only touch it for a moment it crashes. I'm using Xperia S 4.1.2
I tried this on my phone, I only touched the button instead of holding it and it worked perfectly fine, I don't know why this is happening on his phone.
This is the code for onTouchListener:
recBtn.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
startRecording();
}
else if (event.getAction() == MotionEvent.ACTION_UP)
{
stopRecording();
nameAlert();
}
return true;
}
});
And the logcat says the problem occurs when stopRecording is called, so here is the stopRecording method:
public void stopRecording() {
final ImageButton recBtn = (ImageButton) findViewById(com.whizzappseasyvoicenotepad.R.id.recButton);
final ToggleButton tBtn = (ToggleButton) findViewById(R.id.tBtn1);
if (null != recorder) {
recorder.stop();
recorder.reset();
recorder.release();
recorder = null;
recBtn.setImageResource(com.whizzappseasyvoicenotepad.R.drawable.record_btn);
stopTimer();
tBtn.setEnabled(true);
}
}
I'm guessing that the problem is that he only touches the button for a moment, so before startRecording is completely called, the stopRecoring is already called so it crashes because startRecording wasn't even completely intiated yet. If that's the case, how can I fix it? If it isn't the case, what's wrong then? And why would an error like this appear on another phone but not mine?
According to documentation, this is the normal behavior:
public void stop ()
Added in API level 1 Stops recording. Call this after start(). Once
recording is stopped, you will have to configure it again as if it has
just been constructed. Note that a RuntimeException is intentionally
thrown to the application, if no valid audio/video data has been
received when stop() is called. This happens if stop() is called
immediately after start(). The failure lets the application take
action accordingly to clean up the output file (delete the output
file, for instance), since the output file is not properly constructed
when this happens.
So you can just add a try catch to your stop call
if (null != recorder) {
try{
recorder.stop();
}catch(RuntimeException ex){
//Ignore
}
...
}
i managed to do that by adding "actionDownChecker" on every RadioButton.
private View.OnTouchListener actionDownChecker = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){
mTabbar.check(view.getId());
}
return true;
}
};
public void add(int radioButtonResourceId, Class<? extends Fragment> contentClass) {
mContent.put(radioButtonResourceId, new TabInfo(radioButtonResourceId, contentClass));
//hack to check radio button on ACTION_DOWN, not UP!
mTabbar.findViewById(radioButtonResourceId).setOnTouchListener(actionDownChecker);
}
Is there another more elegant way to do that?
The only other way I would know how to do it, is to physically change the code in the Listener. I'm not the smartest guy when it comes to developing for android, but the default constructor sets the boxes attached to RadioButtons to check on release.
Unless someone else knows something I don't, you either have to modify the listener class, or modify the entry after the listener has been evoked (by adding your actionDownCheker). Personally if it was me, i would just copy and paste your actionDownChecker, that way your listener class isn't messed up for your next project.
I'm sure there are some applications for it, but I'm not sure why you would want to do that. If someone presses down on the wrong button, they can just move off the button before it is released to cancel the press.
I wrote a working code using isEnabled().
if(btn.isEnabled()){
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// say send a udp packet
}
}
);
}
Now instead of the packet to be sent when the button is clicked, I want to send it when it stays pressed. How do I handle this?
when i tried isPressed instead of isEnabled, there was a blank screen and the activity was not even displayed...
EDIT : also tried btn.isPressed() - it doesn't work ... the udp packet gets sent immediately after I click on the button... I want it to send ONLY when I am pressing it ...
Any help would be appreciated.
Thanks
your condition is vague. "ONLY when I am pressing it" would mean you'll start sending when the button starts being pressed, which would mean on MotionEvent.ACTION_DOWN. if you want some delay before the action gets executed, create a timer thread that would start when MotionEvent.ACTION_DOWN is detected, and will execute your action after a few seconds. the timer should also reset when MotionEvent.ACTION_UP is detected, or if the action is already in progress, interrupt the action.
but honestly, you may want to rephrase your condition.
Not sure if this will work, but worth a try
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(v.isPressed()){
//do sth.
}
else{
//do sth. else
}
}
Your going to want to use the onTouchListener instead of onClick and stuff. Small change you also want to track when the user lets go.
EDIT adding timer stuff
Timer timer;
UDP request;
btn.setOnTouchListener(new OnTouchListener(){
public boolean onTouch(View v, MotionEvent event){
if(event.getAction() == MotionEvent.ACTION_DOWN){
//TODO start sending udp in background
timer = new Timer();
timer.schedule(new TimerTask(){
public void run(){
request.start();
}
},DELAY_MS);
}
if(event.getAction() == MotionEvent.ACTION_UP){
//TODO stop sending udp
timer.cancel()
if(request.isTransmitting()){
request.stop();
}
}
//needed to get both calls
return true;
}
});