blob: b80be40e5401dcd0fab0ce757b2822fca5b718ac [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.cts.verifier.sensors;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.cts.helpers.SensorNotSupportedException;
import android.hardware.cts.helpers.SuspendStateMonitor;
import android.os.PowerManager;
import android.os.SystemClock;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.Log;
import com.android.cts.verifier.R;
import com.android.cts.verifier.sensors.helpers.SensorTestScreenManipulator;
import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.Assert;
import static junit.framework.Assert.fail;
/**
* Manual test for testing the low-latency offbody detect sensor. This test consists of 3
* sub-tests designed to verify the sensor event data, verify event trigger response times
* are within spec for the sensor type, and to verify that the sensor can wake the device.
*/
public class OffBodySensorTestActivity
extends SensorCtsVerifierTestActivity {
private static final String TAG="OffbodySensorTest";
private static String ACTION_ALARM = "OffBodySensorTestActivity.ACTION_ALARM";
private static final int MAX_OFF_BODY_EVENT_LATENCY_MS = 1000;
private static final int MAX_ON_BODY_EVENT_LATENCY_MS = 5000;
private static final int COUNTDOWN_INTERVAL_MS = 1000;
private static final int LLOB_EVENT_MAX_DELAY_SEC = 20;
private static final long MAX_ALLOWED_DELAY_MS = TimeUnit.SECONDS.toMillis(1);
private static final long RESULT_REPORT_SHOW_TIME_MS = TimeUnit.SECONDS.toMillis(5);
private static final int OFFBODY_EVENT_VALUES_LENGTH = 1;
private static final int COUNTDOWN_NUM_INTERVALS = 3;
private static final float OFF_BODY_EVENT_VALUE = 0;
private static final float ON_BODY_EVENT_VALUE = 1;
private static final float BAD_VALUE_SEEN_INIT = 0;
private static float mBadValueSeen = BAD_VALUE_SEEN_INIT;
private enum State {
OFF_BODY, ON_BODY, UNKNOWN
}
// time to wait for offbody event after the device has gone into suspend. Even after
// 45 secs if LLOB sensor does not trigger, the test will fail.
private static final long ALARM_WAKE_UP_AP_DELAY_MS = TimeUnit.SECONDS.toMillis(45);
// acceptable time difference between event time and AP wake up time.
private static final long MAX_ACCEPTABLE_DELAY_EVENT_AP_WAKE_UP_NS =
TimeUnit.MILLISECONDS.toNanos(1200);
private static final int NANOSECONDS_PER_MILLISECOND = 1000000;
private AlarmManager mAlarmManager;
private SensorManager mSensorManager;
private Sensor mOffBodySensor;
private boolean mOffBodySensorRegistered;
private long mTestStartTimestampMs;
private State mPreviousSensorState;
private PendingIntent mPendingIntent;
private PowerManager.WakeLock mDeviceSuspendLock;
private SensorEventVerifier mVerifier;
private SensorTestScreenManipulator mScreenManipulator;
public class SensorEventRegistry {
public final SensorEvent event;
public final long receiveTimestampNanos;
public SensorEventRegistry(SensorEvent event, long realtimeTimestampNanos) {
this.event = event;
this.receiveTimestampNanos = realtimeTimestampNanos;
}
}
private class SensorEventVerifier implements SensorEventListener {
private volatile CountDownLatch mCountDownLatch;
private volatile SensorEventRegistry mEventRegistry;
private volatile long mTimestampForLastSensorEvent = 0;
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@Override
public void onSensorChanged(SensorEvent event) {
long elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
int type = event.sensor.getType();
if (type == Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT) {
switch((int) event.values[0]) {
case (int) OFF_BODY_EVENT_VALUE:
Log.i(TAG, "onSensorChanged(): OFF_BODY ts="+event.timestamp+
", now="+elapsedRealtimeNanos+", delta="+
(elapsedRealtimeNanos-event.timestamp)/1000000+"mS");
mPreviousSensorState = State.OFF_BODY;
break;
case (int) ON_BODY_EVENT_VALUE:
Log.i(TAG, "onSensorChanged(): ON_BODY ts = "+event.timestamp+
", now="+elapsedRealtimeNanos+", delta="+
(elapsedRealtimeNanos-event.timestamp)/1000000+"mS");
mPreviousSensorState = State.ON_BODY;
break;
default:
Log.e(TAG, "onSensorChanged(): invalid value "+event.values[0]+
" received");
mBadValueSeen = event.values[0];
break;
}
mEventRegistry = new SensorEventRegistry(event, elapsedRealtimeNanos);
getTestLogger().logMessage(
R.string.snsr_offbody_state_change,
(int) event.values[0],
elapsedRealtimeNanos);
releaseLatch();
}
}
public void releaseLatch() {
if (mCountDownLatch != null) {
mCountDownLatch.countDown();
}
}
public long getTimeStampForSensorEvent() {
return mTimestampForLastSensorEvent;
}
public String awaitAndVerifyEvent(float expectedResponseValue) throws Throwable {
return awaitAndVerifyEvent(expectedResponseValue, 0);
}
public String awaitAndVerifyEvent(float expectedResponseValue, int maxEventLatencyMs)
throws Throwable {
SensorEventRegistry registry = waitForEvent();
String eventArrivalMessage;
if ((registry == null) || (registry.event == null)) {
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival, false);
Assert.fail(eventArrivalMessage);
}
// verify an event arrived, and it is indeed a Low Latency Offbody Detect event
SensorEvent event = registry.event;
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival, event != null);
Assert.assertNotNull(eventArrivalMessage, event);
String result = verifyEvent(registry, expectedResponseValue, maxEventLatencyMs);
return result;
}
public String verifyEvent(SensorEventRegistry registry, float expectedResponseValue,
int maxEventLatencyMs) throws Throwable {
int eventType = registry.event.sensor.getType();
String eventTypeMessage = getString(
R.string.snsr_offbody_event_type,
Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT,
eventType);
Assert.assertEquals(eventTypeMessage,
Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT,
eventType);
float value = registry.event.values[0];
String sensorName = registry.event.sensor.getName();
String eventName = (value == ON_BODY_EVENT_VALUE) ? "ON-BODY" : "OFF-BODY";
long eventLatencyMs = (registry.receiveTimestampNanos/NANOSECONDS_PER_MILLISECOND)
- mTestStartTimestampMs;
int valuesLength = registry.event.values.length;
String valuesLengthMessage = getString(
R.string.snsr_event_length,
OFFBODY_EVENT_VALUES_LENGTH,
valuesLength,
sensorName);
Assert.assertEquals(valuesLengthMessage, OFFBODY_EVENT_VALUES_LENGTH, valuesLength);
String valuesMessage = getString(
R.string.snsr_event_value,
expectedResponseValue,
value,
sensorName);
Assert.assertEquals(valuesMessage, expectedResponseValue, value);
if (maxEventLatencyMs != 0) {
Log.i(TAG, "event latency was "+eventLatencyMs+" ms for "+
eventName+" event");
String responseViolationMessage = getString(
R.string.snsr_offbody_response_timing_violation,
eventName,
maxEventLatencyMs,
eventLatencyMs);
boolean violation = (eventLatencyMs > maxEventLatencyMs);
Assert.assertFalse(responseViolationMessage, violation);
}
return null;
}
private void verifyOffbodyEventNotInvalid() throws InterruptedException {
if (mBadValueSeen != BAD_VALUE_SEEN_INIT) {
Assert.fail(
String.format(getString(R.string.snsr_offbody_event_invalid_value),
OFF_BODY_EVENT_VALUE,
ON_BODY_EVENT_VALUE,
mBadValueSeen));
}
}
private SensorEventRegistry waitForEvent() throws InterruptedException {
return waitForEvent(null);
}
private SensorEventRegistry waitForEvent(PowerManager.WakeLock suspendLock)
throws InterruptedException {
mCountDownLatch = new CountDownLatch(1);
if ((suspendLock != null) && suspendLock.isHeld()) {
suspendLock.release();
}
mCountDownLatch.await(LLOB_EVENT_MAX_DELAY_SEC, TimeUnit.SECONDS);
if ((suspendLock != null) && !suspendLock.isHeld()) {
suspendLock.acquire();
}
SensorEventRegistry registry = mEventRegistry;
// Save the last timestamp when the event triggered.
if (mEventRegistry != null && mEventRegistry.event != null) {
mTimestampForLastSensorEvent = mEventRegistry.event.timestamp;
}
mEventRegistry = null;
verifyOffbodyEventNotInvalid();
return registry != null ? registry : new SensorEventRegistry(null, 0);
}
public SensorEvent waitForSensorEvent() throws InterruptedException {
SensorEvent event = null;
mCountDownLatch = new CountDownLatch(1);
mCountDownLatch.await(LLOB_EVENT_MAX_DELAY_SEC, TimeUnit.SECONDS);
if (mEventRegistry != null && mEventRegistry.event != null) {
event = mEventRegistry.event;
}
mEventRegistry = null;
verifyOffbodyEventNotInvalid();
return event;
}
}
public OffBodySensorTestActivity() {
super(OffBodySensorTestActivity.class);
}
@Override
protected void activitySetUp() throws InterruptedException {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mDeviceSuspendLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"OffBodySensorTestActivity");
mDeviceSuspendLock.acquire();
mOffBodySensorRegistered = false;
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mOffBodySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT,
true);
if (mOffBodySensor == null) {
setTestResultAndFinish(true);
return;
}
LocalBroadcastManager.getInstance(this).registerReceiver(myBroadCastReceiver,
new IntentFilter(ACTION_ALARM));
Intent intent = new Intent(this, AlarmReceiver.class);
mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
mScreenManipulator = new SensorTestScreenManipulator(this);
try {
mScreenManipulator.initialize(this);
} catch (InterruptedException e) {
}
}
private void startTimeoutTimer(long delayMs) {
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delayMs,
mPendingIntent);
}
private void stopTimeoutTimer() {
mAlarmManager.cancel(mPendingIntent);
}
private void stopOffbodySensorListener(SensorEventVerifier verifier) {
if (mOffBodySensorRegistered) {
mSensorManager.unregisterListener(verifier);
mOffBodySensorRegistered = false;
}
}
private boolean startOffbodySensorListener(SensorEventVerifier verifier) {
if (!mOffBodySensorRegistered) {
if (!mSensorManager.registerListener(verifier, mOffBodySensor,
SensorManager.SENSOR_DELAY_FASTEST)) {
Log.e(TAG, "error registering listener for LLOB");
setTestResultAndFinish(true);
return false;
}
mOffBodySensorRegistered = true;
}
return true;
}
public static class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent alarm_intent = new Intent(context, OffBodySensorTestActivity.class);
alarm_intent.setAction(OffBodySensorTestActivity.ACTION_ALARM);
LocalBroadcastManager.getInstance(context).sendBroadcastSync(alarm_intent);
}
}
public BroadcastReceiver myBroadCastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mVerifier.releaseLatch();
mScreenManipulator.turnScreenOn();
if (!mDeviceSuspendLock.isHeld()) {
mDeviceSuspendLock.acquire();
}
}
};
public String testOffbodyDetectResponseTime() throws Throwable {
Sensor wakeUpSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
if (wakeUpSensor == null) {
throw new SensorNotSupportedException(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
}
return runOffbodyDetectResponseTimeTest(wakeUpSensor);
}
public String testOnbodyDetectResponseTime() throws Throwable {
Sensor wakeUpSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
if (wakeUpSensor == null) {
throw new SensorNotSupportedException(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
}
return runOnbodyDetectResponseTimeTest(wakeUpSensor);
}
public String testWakeAPOffbodyDetect() throws Throwable {
Sensor wakeUpSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
if (wakeUpSensor == null) {
throw new SensorNotSupportedException(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
}
return runWakeAPOffbodyDetectTest(wakeUpSensor);
}
public String runOffbodyDetectResponseTimeTest(Sensor sensor) throws Throwable {
boolean success;
String eventArrivalMessage;
mOffBodySensor = sensor;
mBadValueSeen = BAD_VALUE_SEEN_INIT;
try {
// If device not currently on-body, instruct user to put it on wrist
mTestStartTimestampMs = 0;
mVerifier = new SensorEventVerifier();
success = startOffbodySensorListener(mVerifier);
Assert.assertTrue(
getString(R.string.snsr_offbody_sensor_registration, success),
success);
SensorEvent event = mVerifier.waitForSensorEvent();
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival, event != null);
Assert.assertNotNull(eventArrivalMessage, event);
SensorTestLogger logger = getTestLogger();
if (event.values[0] != ON_BODY_EVENT_VALUE) {
// Instruct user on how to perform offbody detect test
logger.logInstructions(R.string.snsr_start_offbody_sensor_test_instr);
waitForUserToBegin();
if (mPreviousSensorState != State.ON_BODY) {
event = mVerifier.waitForSensorEvent();
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival,
event != null);
Assert.assertNotNull(eventArrivalMessage, event);
if (event.values[0] != ON_BODY_EVENT_VALUE) {
Assert.fail(
String.format(getString(R.string.snsr_offbody_event_wrong_value),
ON_BODY_EVENT_VALUE,
event.values[0]));
}
}
}
// Instruct user on how to perform offbody detect test
logger.logInstructions(R.string.snsr_offbody_detect_test_instr);
waitForUserToBegin();
// Count down before actually starting, leaving time to react after pressing the Next
// button.
for (int i = 0; i < COUNTDOWN_NUM_INTERVALS; i++) {
try {
Thread.sleep(COUNTDOWN_INTERVAL_MS);
} catch (InterruptedException e) {
// Ignore the interrupt and continue counting down.
}
logger.logInstructions(R.string.snsr_offbody_detect_test_countdown,
COUNTDOWN_NUM_INTERVALS - i - 1);
}
mTestStartTimestampMs = SystemClock.elapsedRealtime();
// Verify off-body event latency is within spec
mVerifier.awaitAndVerifyEvent(OFF_BODY_EVENT_VALUE, MAX_OFF_BODY_EVENT_LATENCY_MS);
} finally {
stopOffbodySensorListener(mVerifier);
}
return null;
}
public String runOnbodyDetectResponseTimeTest(Sensor sensor) throws Throwable {
mOffBodySensor = sensor;
SensorTestLogger logger = getTestLogger();
mBadValueSeen = BAD_VALUE_SEEN_INIT;
try {
// If device not currently off-body, instruct user to remove it from wrist
mTestStartTimestampMs = 0;
mVerifier = new SensorEventVerifier();
boolean success = startOffbodySensorListener(mVerifier);
Assert.assertTrue(
getString(R.string.snsr_offbody_sensor_registration, success),
success);
SensorEvent event = mVerifier.waitForSensorEvent();
String eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival,
event != null);
Assert.assertNotNull(eventArrivalMessage, event);
if (event.values[0] != OFF_BODY_EVENT_VALUE) {
// Instruct user on how to perform offbody detect test
logger.logInstructions(R.string.snsr_start_onbody_sensor_test_instr);
waitForUserToBegin();
if (mPreviousSensorState != State.OFF_BODY) {
event = mVerifier.waitForSensorEvent();
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival,
event != null);
Assert.assertNotNull(eventArrivalMessage, event);
if (event.values[0] != OFF_BODY_EVENT_VALUE) {
Assert.fail(
String.format(getString(R.string.snsr_offbody_event_wrong_value),
OFF_BODY_EVENT_VALUE,
event.values[0]));
}
}
}
// Display on-body latency test instructions
logger.logInstructions(R.string.snsr_onbody_detect_test_instr);
waitForUserToBegin();
mTestStartTimestampMs = SystemClock.elapsedRealtime();
mVerifier.awaitAndVerifyEvent(ON_BODY_EVENT_VALUE, MAX_ON_BODY_EVENT_LATENCY_MS);
} finally {
stopOffbodySensorListener(mVerifier);
}
return null;
}
public String runWakeAPOffbodyDetectTest(Sensor sensor) throws Throwable {
final long ALARM_WAKE_UP_DELAY_MS = 40000;
String eventArrivalMessage;
SensorEventRegistry registry;
SensorTestLogger logger = getTestLogger();
mBadValueSeen = BAD_VALUE_SEEN_INIT;
mVerifier = new SensorEventVerifier();
mOffBodySensor = sensor;
mTestStartTimestampMs = 0;
mTestStartTimestampMs = SystemClock.elapsedRealtime();
SuspendStateMonitor suspendStateMonitor = new SuspendStateMonitor();
try {
boolean success = startOffbodySensorListener(mVerifier);
Assert.assertTrue(
getString(R.string.snsr_offbody_sensor_registration, success),
success);
// grab the current off-body state, which should be ON-BODY
if (mPreviousSensorState != State.ON_BODY) {
registry = mVerifier.waitForEvent();
if ((registry == null) || (registry.event == null) ||
(registry.event.values[0] != ON_BODY_EVENT_VALUE)) {
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival, false);
Assert.fail(eventArrivalMessage);
// Tell user to put watch on wrist
logger.logInstructions(R.string.snsr_start_offbody_sensor_test_instr);
waitForUserToBegin();
if (mPreviousSensorState != State.ON_BODY) {
registry = mVerifier.waitForEvent();
if ((registry == null) || (registry.event == null)) {
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival, false);
Assert.fail(eventArrivalMessage);
} else {
Assert.assertTrue(
String.format(getString(R.string.snsr_offbody_event_wrong_value),
ON_BODY_EVENT_VALUE,
registry.event.values[0]),
ON_BODY_EVENT_VALUE == registry.event.values[0]);
}
}
}
}
// Instruct user on how to perform offbody detect sleep test
logger.logInstructions(R.string.snsr_ap_wake_offbody_detect_test_instr);
waitForUserToBegin();
long testStartTimeNs = SystemClock.elapsedRealtimeNanos();
startTimeoutTimer(ALARM_WAKE_UP_AP_DELAY_MS);
// Wait for the first event to trigger. Device is expected to go into suspend here.
registry = mVerifier.waitForEvent(mDeviceSuspendLock);
if ((registry == null) || (registry.event == null)) {
eventArrivalMessage = getString(R.string.snsr_offbody_event_arrival, false);
Assert.fail(eventArrivalMessage);
}
mVerifier.verifyEvent(registry, OFF_BODY_EVENT_VALUE, 0);
long eventTimeStampNs = registry.event.timestamp;
long endTimeNs = SystemClock.elapsedRealtimeNanos();
long lastWakeupTimeNs = TimeUnit.MILLISECONDS.toNanos(
suspendStateMonitor.getLastWakeUpTime());
Assert.assertTrue(getString(R.string.snsr_device_did_not_go_into_suspend),
testStartTimeNs < lastWakeupTimeNs && lastWakeupTimeNs < endTimeNs);
long timestampDelta = Math.abs(lastWakeupTimeNs - eventTimeStampNs);
Assert.assertTrue(
String.format(getString(R.string.snsr_device_did_not_wake_up_at_trigger),
TimeUnit.NANOSECONDS.toMillis(lastWakeupTimeNs),
TimeUnit.NANOSECONDS.toMillis(eventTimeStampNs)),
timestampDelta < MAX_ACCEPTABLE_DELAY_EVENT_AP_WAKE_UP_NS);
} finally {
stopTimeoutTimer();
suspendStateMonitor.cancel();
mScreenManipulator.turnScreenOn();
playSound();
}
return null;
}
@Override
protected void activityCleanUp() {
if (mOffBodySensorRegistered) {
stopOffbodySensorListener(mVerifier);
}
stopTimeoutTimer();
LocalBroadcastManager.getInstance(this).unregisterReceiver(myBroadCastReceiver);
if (mOffBodySensor != null) {
mOffBodySensor = null;
}
if (mScreenManipulator != null){
mScreenManipulator.close();
}
if ((mDeviceSuspendLock != null) && mDeviceSuspendLock.isHeld()) {
mDeviceSuspendLock.release();
}
}
}