How to Detect Shake Event with Android

How to detect shake event with android?

From the code point of view, you need to implement the SensorListener:

public class ShakeActivity extends Activity implements SensorListener

You will need to acquire a SensorManager:

sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);

And register this sensor with desired flags:

sensorMgr.registerListener(this,
SensorManager.SENSOR_ACCELEROMETER,
SensorManager.SENSOR_DELAY_GAME);

In your onSensorChange() method, you determine whether it’s a shake or not:

public void onSensorChanged(int sensor, float[] values) {
if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
long curTime = System.currentTimeMillis();
// only allow one update every 100ms.
if ((curTime - lastUpdate) > 100) {
long diffTime = (curTime - lastUpdate);
lastUpdate = curTime;

x = values[SensorManager.DATA_X];
y = values[SensorManager.DATA_Y];
z = values[SensorManager.DATA_Z];

float speed = Math.abs(x+y+z - last_x - last_y - last_z) / diffTime * 10000;

if (speed > SHAKE_THRESHOLD) {
Log.d("sensor", "shake detected w/ speed: " + speed);
Toast.makeText(this, "shake detected w/ speed: " + speed, Toast.LENGTH_SHORT).show();
}
last_x = x;
last_y = y;
last_z = z;
}
}
}

The shake threshold is defined as:

private static final int SHAKE_THRESHOLD = 800;

There are some other methods too, to detect shake motion. look at this link.(If that link does not work or link is dead, look at this web archive.).

Have a look at this example for android shake detect listener.

Note: SensorListener is deprecated. we can use SensorEventListener instead. Here is a quick example using SensorEventListener.

Thanks.

Detect shake event with Javascript, with all major browsers/devices (iOS, Android)

There is no shake event: the closest event that exists is devicemotion.

Based on the content of your question, I infer that you just want to subscribe to events which are fired when device acceleration exceeds a certain threshold, with a debounce delay between possible triggers (timeout).

Using the "shake.js" library you linked to as a reference, I wrote a TypeScript module which you can use to accomplish essentially the same thing. It includes getting user permission approval on start, but keep in mind that you'll have to call the ShakeInstance.start() method in response to a user-initiated event (e.g. a button click).

Note: The methods used in the module are supported by the environments you listed according to the compatibility data on their related documentation pages at MDN. (Remarkably, desktop Safari simply does not support the DeviceMotionEvent whatsoever.) However, I don't have access to all of those combinations of environments you listed in order to perform the testing myself, so I'll leave that to you.

TS Playground

function createEvent <Type extends string, Detail>(
type: Type,
detail: Detail,
): CustomEvent<Detail> & {type: Type} {
return new CustomEvent(type, {detail}) as CustomEvent<Detail> & {type: Type};
}

function getMaxAcceleration (event: DeviceMotionEvent): number {
let max = 0;
if (event.acceleration) {
for (const key of ['x', 'y', 'z'] as const) {
const value = Math.abs(event.acceleration[key] ?? 0);
if (value > max) max = value;
}
}
return max;
}

export type ShakeEventData = DeviceMotionEvent;
export type ShakeEvent = CustomEvent<ShakeEventData> & {type: 'shake'};
export type ShakeEventListener = (event: ShakeEvent) => void;

export type ShakeOptions = {
/**
* Minimum acceleration needed to dispatch an event:
* meters per second squared (m/s²).
*
* https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/acceleration
*/
threshold: number;
/**
* After a shake event is dispatched, subsequent events will not be dispatched
* until after a duration greater than or equal to this value (milliseconds).
*/
timeout: number;
};

export class Shake extends EventTarget {
#approved?: boolean;
#threshold: ShakeOptions['threshold'];
#timeout: ShakeOptions['timeout'];
#timeStamp: number;

constructor (options?: Partial<ShakeOptions>) {
super();
const {
threshold = 15,
timeout = 1000,
} = options ?? {};
this.#threshold = threshold;
this.#timeout = timeout;
this.#timeStamp = timeout * -1;
}

// @ts-ignore
addEventListener (
type: 'shake',
listener: ShakeEventListener | null,
options?: boolean | AddEventListenerOptions
): void {
type Arg1 = Parameters<EventTarget['addEventListener']>[1];
super.addEventListener(type, listener as Arg1, options);
}

dispatchEvent (event: ShakeEvent): boolean {
return super.dispatchEvent(event);
}

// @ts-ignore
removeEventListener (
type: 'shake',
callback: ShakeEventListener | null,
options?: EventListenerOptions | boolean
): void {
type Arg1 = Parameters<EventTarget['removeEventListener']>[1];
super.removeEventListener(type, callback as Arg1, options);
}

async approve (): Promise<boolean> {
if (typeof this.#approved === 'undefined') {
if (!('DeviceMotionEvent' in window)) return this.#approved = false;
try {
type PermissionRequestFn = () => Promise<PermissionState>;
type DME = typeof DeviceMotionEvent & { requestPermission: PermissionRequestFn };
if (typeof (DeviceMotionEvent as DME).requestPermission === 'function') {
const permissionState = await (DeviceMotionEvent as DME).requestPermission();
this.#approved = permissionState === 'granted';
}
else this.#approved = true;
}
catch {
this.#approved = false;
}
}
return this.#approved;
}

#handleDeviceMotion = (event: DeviceMotionEvent): void => {
const diff = event.timeStamp - this.#timeStamp;
if (diff < this.#timeout) return;
const accel = getMaxAcceleration(event);
if (accel < this.#threshold) return;
this.#timeStamp = event.timeStamp;
this.dispatchEvent(createEvent('shake', event));
};

async start (): Promise<boolean> {
const approved = await this.approve();
if (!approved) return false;
window.addEventListener('devicemotion', this.#handleDeviceMotion);
return true;
}

stop (): void {
window.removeEventListener('devicemotion', this.#handleDeviceMotion);
}
}

Use like this:

const shake = new Shake({threshold: 15, timeout: 1000});

shake.addEventListener('shake', ev => {
console.log('Shake!', ev.detail.timeStamp, ev.detail.acceleration);
});

// Then, in response to a user-initiated event:
const approved = await shake.start();

I'm not sure whether the SO snippet environment will cause a problem for demoing this or not, but I've included the compiled JS from the TS Playground link just in case:





"use strict";
function createEvent(type, detail) {
return new CustomEvent(type, { detail });
}
function getMaxAcceleration(event) {
let max = 0;
if (event.acceleration) {
for (const key of ['x', 'y', 'z']) {
const value = Math.abs(event.acceleration[key] ?? 0);
if (value > max)
max = value;
}
}
return max;
}
class Shake extends EventTarget {
constructor(options) {
super();
this.#handleDeviceMotion = (event) => {
const diff = event.timeStamp - this.#timeStamp;
if (diff < this.#timeout)
return;
const accel = getMaxAcceleration(event);
if (accel < this.#threshold)
return;
this.#timeStamp = event.timeStamp;
this.dispatchEvent(createEvent('shake', event));
};
const { threshold = 15, timeout = 1000, } = options ?? {};
this.#threshold = threshold;
this.#timeout = timeout;
this.#timeStamp = timeout * -1;
}
#approved;
#threshold;
#timeout;
#timeStamp;
// @ts-ignore
addEventListener(type, listener, options) {
super.addEventListener(type, listener, options);
}
dispatchEvent(event) {
return super.dispatchEvent(event);
}
// @ts-ignore
removeEventListener(type, callback, options) {
super.removeEventListener(type, callback, options);
}
async approve() {
if (typeof this.#approved === 'undefined') {
if (!('DeviceMotionEvent' in window))
return this.#approved = false;
try {
if (typeof DeviceMotionEvent.requestPermission === 'function') {
const permissionState = await DeviceMotionEvent.requestPermission();
this.#approved = permissionState === 'granted';
}
else
this.#approved = true;
}
catch {
this.#approved = false;
}
}
return this.#approved;
}
#handleDeviceMotion;
async start() {
const approved = await this.approve();
if (!approved)
return false;
window.addEventListener('devicemotion', this.#handleDeviceMotion);
return true;
}
stop() {
window.removeEventListener('devicemotion', this.#handleDeviceMotion);
}
}
////////////////////////////////////////////////////////////////////////////////
// Use:
const shake = new Shake({ threshold: 15, timeout: 1000 });
shake.addEventListener('shake', ev => {
console.log('Shake!', ev.detail.timeStamp, ev.detail.acceleration);
});
const button = document.getElementById('start');
if (button) {
button.addEventListener('click', async () => {
const approved = await shake.start();
const div = document.body.appendChild(document.createElement('div'));
div.textContent = `Approved: ${String(approved)}`;
button.remove();
}, { once: true });
}
<button id="start">Approve</button>

How to detect shake and toast a message after shaking the phone 3 times in android

Add this as Global Var

private static int counter = 0;

now add these lines in your onSensorChanged

if (shake > 12) {
counter++;
}

if (counter >= 3) {
counter = 0;
Toast t = Toast.makeText(getApplicationContext(), "Don't Shake phone", Toast.LENGTH_LONG);
t.show();
}

This will show toast every 3rd time user shakes the phone, I also suggest to change Toast length to LENGTH_SHORT

Hope this will help!

Detect Shake for 8 Sec

Try below code for detecting continues shake for 8 second.

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;


/**
* Listener that detects shake gesture.
*/
public class ShakeEventListener implements SensorEventListener {


/** Minimum movement force to consider. */
private static final int MIN_FORCE = 10;

/**
* Minimum times in a shake gesture that the direction of movement needs to
* change.
*/
private static final int MIN_DIRECTION_CHANGE = 3;

/** Maximum pause between movements. */
private static final int MAX_PAUSE_BETHWEEN_DIRECTION_CHANGE = 1000;

/** Minimum allowed time for shake gesture. */
private static final int MIN_TOTAL_DURATION_OF_SHAKE = 8000; // 8 seconds

/** Time when the gesture started. */
private long mFirstDirectionChangeTime = 0;

/** Time when the last movement started. */
private long mLastDirectionChangeTime;

/** How many movements are considered so far. */
private int mDirectionChangeCount = 0;

/** The last x position. */
private float lastX = 0;

/** The last y position. */
private float lastY = 0;

/** The last z position. */
private float lastZ = 0;

/** OnShakeListener that is called when shake is detected. */
private OnShakeListener mShakeListener;

/**
* Interface for shake gesture.
*/
public interface OnShakeListener {

/**
* Called when shake gesture is detected.
*/
void onShake();
}

public void setOnShakeListener(OnShakeListener listener) {
mShakeListener = listener;
}

@Override
public void onSensorChanged(SensorEvent se) {
// get sensor data
float x = se.values[SensorManager.DATA_X];
float y = se.values[SensorManager.DATA_Y];
float z = se.values[SensorManager.DATA_Z];

// calculate movement
float totalMovement = Math.abs(x + y + z - lastX - lastY - lastZ);

if (totalMovement > MIN_FORCE) {

// get time
long now = System.currentTimeMillis();

// store first movement time
if (mFirstDirectionChangeTime == 0) {
mFirstDirectionChangeTime = now;
mLastDirectionChangeTime = now;
}

// check if the last movement was not long ago
long lastChangeWasAgo = now - mLastDirectionChangeTime;
if (lastChangeWasAgo < MAX_PAUSE_BETHWEEN_DIRECTION_CHANGE) {

// store movement data
mLastDirectionChangeTime = now;
mDirectionChangeCount++;

// store last sensor data
lastX = x;
lastY = y;
lastZ = z;

// check how many movements are so far
if (mDirectionChangeCount >= MIN_DIRECTION_CHANGE) {

// check total duration
long totalDuration = now - mFirstDirectionChangeTime;
if (totalDuration >= MIN_TOTAL_DURATION_OF_SHAKE) {
mShakeListener.onShake();
resetShakeParameters();
}
}

} else {
resetShakeParameters();
}
}
}

/**
* Resets the shake parameters to their default values.
*/
private void resetShakeParameters() {
mFirstDirectionChangeTime = 0;
mDirectionChangeCount = 0;
mLastDirectionChangeTime = 0;
lastX = 0;
lastY = 0;
lastZ = 0;
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

}

Add below code in your activity:

  private SensorManager mSensorManager;

private ShakeEventListener mSensorListener;


mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensorListener = new ShakeEventListener();

mSensorListener.setOnShakeListener(new ShakeEventListener.OnShakeListener() {

public void onShake() {
Toast.makeText(this, "Shake detected!", Toast.LENGTH_SHORT).show();
}
});

Register for shake listener in onResume method and unregister it in onPause method as below.

  @Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorListener,
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_UI);
}

@Override
protected void onPause() {
mSensorManager.unregisterListener(mSensorListener);
super.onPause();
}

How to refresh app upon shaking the device?

Here is an example code.
Put this into your activity class:

  /* put this into your activity class */
private SensorManager mSensorManager;
private float mAccel; // acceleration apart from gravity
private float mAccelCurrent; // current acceleration including gravity
private float mAccelLast; // last acceleration including gravity

private final SensorEventListener mSensorListener = new SensorEventListener() {

public void onSensorChanged(SensorEvent se) {
float x = se.values[0];
float y = se.values[1];
float z = se.values[2];
mAccelLast = mAccelCurrent;
mAccelCurrent = (float) Math.sqrt((double) (x*x + y*y + z*z));
float delta = mAccelCurrent - mAccelLast;
mAccel = mAccel * 0.9f + delta; // perform low-cut filter
}

public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};

@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
}

@Override
protected void onPause() {
mSensorManager.unregisterListener(mSensorListener);
super.onPause();
}

And add this to your onCreate method:

    /* do this in onCreate */
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
mAccel = 0.00f;
mAccelCurrent = SensorManager.GRAVITY_EARTH;
mAccelLast = SensorManager.GRAVITY_EARTH;

You can then ask "mAccel" wherever you want in your application for the current acceleration, independent from the axis and cleaned from static acceleration such as gravity.
It will be approx. 0 if there is no movement, and, lets say >2 if the device is shaked.

Based on the comments - to test this:

if (mAccel > 12) {
Toast toast = Toast.makeText(getApplicationContext(), "Device has shaken.", Toast.LENGTH_LONG);
toast.show();
}

Notes:

The accelometer should be deactivated onPause and activated onResume to save resources (CPU, Battery).
The code assumes we are on planet Earth ;-) and initializes the acceleration to earth gravity. Otherwise you would get a strong "shake" when the application starts and "hits" the ground from free-fall. However, the code gets used to the gravitation due to the low-cut filter and would work also on other planets or in free space, once it is initialized.
(you never know how long your application will be in use...;-)

Android Shake(Sensor) Service For shake Detection in Application Background

I have created this type of functionality in my application https://play.google.com/store/apps/details?id=com.deep.profilemaper .

To achieve this functionality you just have to create background service with returning flag START_STICKY in onStartCommand() method of service. This service restart if someone kill your service.

    public int onStartCommand (Intent intent, int flags, int startId)
{
super.onStartCommand(intent, flags, startId);

//your code here

return START_STICKY;

}

It is just demo.



Related Topics



Leave a reply



Submit