문제

I'm detecting the orientation of the device to check if it's placed upside-down on a surface, then calling a method on that. The problem I ran into is the fact that a user may unintentionally turn the device's orientation to that position for a split second and return a face-up position to correct their error, however the method is still called the first time they do it.

I'm trying to add a 'double check' to the orientation on a delay of 800ms so the user has time to correct the orientation before that method is triggered. Unfortunately I cannot control how often onSensorChanged is called in order to add a thread with a 800ms delay which would double check the orientation in order to prevent the unintentional orientation change.

My code is as follows,

public SensorEventListener accelerometerListener = new SensorEventListener(){

    @Override
    public void onSensorChanged(SensorEvent event) {

            float z_value = event.values[2];

                if (z_value < 0) {
                    //the device is now upside-down

                       try {

                           Thread.sleep(800);

                           //this if statement is never called correctly because
                           // onSensorChanged is not called again.
                                if (z_value < 0) {
                                    toggleMethod();
                                }

                        } catch (InterruptedException e) {
                               e.printStackTrace();
                        }
                 }
          }

My question: Is there a way I can call onSensorChanged on a delay within the onSensorChanged method itself in order to preform a double check?

도움이 되었습니까?

해결책

A very simple approach would be to give a chance for the sensor to give a new value in the upcoming n milliseconds (just as asked) would be the following:

1- Decide on how many milliseconds you want to give the user in order to undo his action (you used 800):

private static final long TIME_MARGIN_FOR_USER_TO_UNDO = 800;

2- Create a handler and a runnable to do your action (let them be in a bigger scope than your onSensorChanged - e.g. your activity):

Handler sensorHandler = new Handler();
Runnable toggleRunnable = new Runnable() {
   public void run() {
      toggleMethod();
   }
}

3- Post this runnable whenever your if statement evaluates to true; However, post it after n milliseconds.

@Override
public void onSensorChanged(SensorEvent event) {
   float z_value = event.values[2];
   if (z_value < 0) {
      sensorHandler.postDelayed(toggleRunnable, TIME_MARGIN_FOR_USER_TO_UNDO);
   }
}

4- Since onSensorChanged will be called when the sensor values are changed, you can stop the runnable from running if the user fixed it. Therefore, you will need an else statement that will remove the runnable.

@Override
public void onSensorChanged(SensorEvent event) {
   float z_value = event.values[2];
   if (z_value < 0) {
      sensorHandler.postDelayed(toggleRunnable, TIME_MARGIN_FOR_USER_TO_UNDO);
   }
   else {
      sensorHandler.removeCallbacks(toggleRunnable);
   }
}

다른 팁

Best thing you do when you use onSensorCheck is to use a filter : you always make a decision on the average of the n last values given by the sensor, this way your app will behave smoothly and sudden sensor changes won't affect it.
Here is how you do it :

z_value = 0.2*event.values[2]+0.8*z_value; // 0.2+0.8 = 1 

this way you're taking only 20% of your new value and the rest is an average of the four last values.

try playing with the coefs until you are satisfied with result.

for me this worked quite well :

 z_value = 0.02*event.values[2]+0.98*z_value;

---------------------------------------------------------------

EDIT

Here is how to do to initialize z_value : z_value should be a field, an you have to add another boolean field used only for the initialization, ie for the first onSensorChanged call. So you have two fields :

double z_value =0;
boolean firstTime = true;

and here is the onSensorChanged method :

@Override
public void onSensorChanged(SensorEvent event) {
        if(firstTime){
            z_value = event.values[2]; // you will enter here only the first time
            firstTime = false
        }
        else{
            z_value = 0.02*event.values[2] + 0.98*z_value;
        }

        if (z_value < 0) {
                //the device is now upside-down

               // do what you gotta do
        }
 }

Another approach is to smooth the input over time, so take the average reading over 800ms of input.

(YAT's response has the right idea, but the execution doesn't actually take into account the amount of samples being delivered over time which will be unknown from one phone to the next. It also never forgets any measurement, behaviour after 10 seconds may be significantly different after 1 minute.)

//the idea here is to store all the inputs over the last 800ms and average them
LinkedList<Entry<long, double>> inputs = new LinkedList<Entry<long,double>>
double totalZ = 0.0f;   //keep a running total so we don't have to iterate through the list to calculate the average

@Override
public void onSensorChanged(SensorEvent event) {
    //clear out values older than 800ms
    while(inputs.size() > 0 && inputs.getLast().getKey() < System.currentTimeInMillis() - 800){
        Entry<long, double> deletedEntry = inputs.removeLast();
        totalZ -= deletedEntry.getValue(); //update the running total
    }

    //add the new measurement to the head
    inputs.addFirst(new Entry<long, double>(System.currentTimeInMillis(),event.values[2]));
    totalZ += event.values[2]; //update the running total with the new measurement

    //this is the Z averaged over 800ms
    float averagedZ = totalZ / inputs.size();
}

Or a simpler approach, more in line with the question being asked, upon detection of the initial change.

Save the time, save the new potential state

Then when you get another onSensorChanged()

Is that state the same as the potential state? If it is not, clear the time and clear the potentials state

If 800ms have elapsed and the potential state is still the current state, then initiate that change.

onSensorChanged is called many times a second in most accuracy modes so you should be close to your 800ms just waiting for a normal onSensorChanged call to occur.

Rough psuedo code below

long lastChange = 0;
int potentialState = 0;

@Override
public void onSensorChanged(SensorEvent event) {
    int currentState = determineCurrentState(event); //whatever your logic is and states

    if(potentialState != currentState){
         lastChange = System.currentTimeInMillis();
         potentialState = currentState;
    }
    else if(lastChange + 800 < System.currentTimeInMillis()){
         //execute state change, 800ms have elapses with no further change in state
         stateChange(currentState);
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top