Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import android.hardware.Sensor;
     20 import android.hardware.SensorEvent;
     21 import android.hardware.SensorEventListener;
     22 import android.hardware.SensorManager;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 import android.os.PowerManager;
     26 import android.os.SystemClock;
     27 import android.util.Slog;
     28 
     29 import java.lang.Float;
     30 
     31 /**
     32  * Determines if the device has been set upon a stationary object.
     33  */
     34 public class AnyMotionDetector {
     35     interface DeviceIdleCallback {
     36         public void onAnyMotionResult(int result);
     37     }
     38 
     39     private static final String TAG = "AnyMotionDetector";
     40 
     41     private static final boolean DEBUG = false;
     42 
     43     /** Stationary status is unknown due to insufficient orientation measurements. */
     44     public static final int RESULT_UNKNOWN = -1;
     45 
     46     /** Device is stationary, e.g. still on a table. */
     47     public static final int RESULT_STATIONARY = 0;
     48 
     49     /** Device has been moved. */
     50     public static final int RESULT_MOVED = 1;
     51 
     52     /** Orientation measurements are being performed or are planned. */
     53     private static final int STATE_INACTIVE = 0;
     54 
     55     /** No orientation measurements are being performed or are planned. */
     56     private static final int STATE_ACTIVE = 1;
     57 
     58     /** Current measurement state. */
     59     private int mState;
     60 
     61     /** Threshold energy above which the device is considered moving. */
     62     private final float THRESHOLD_ENERGY = 5f;
     63 
     64     /** The duration of the accelerometer orientation measurement. */
     65     private static final long ORIENTATION_MEASUREMENT_DURATION_MILLIS = 2500;
     66 
     67     /** The maximum duration we will collect accelerometer data. */
     68     private static final long ACCELEROMETER_DATA_TIMEOUT_MILLIS = 3000;
     69 
     70     /** The interval between accelerometer orientation measurements. */
     71     private static final long ORIENTATION_MEASUREMENT_INTERVAL_MILLIS = 5000;
     72 
     73     /**
     74      * The duration in milliseconds after which an orientation measurement is considered
     75      * too stale to be used.
     76      */
     77     private static final int STALE_MEASUREMENT_TIMEOUT_MILLIS = 2 * 60 * 1000;
     78 
     79     /** The accelerometer sampling interval. */
     80     private static final int SAMPLING_INTERVAL_MILLIS = 40;
     81 
     82     private final Handler mHandler;
     83     private final Object mLock = new Object();
     84     private Sensor mAccelSensor;
     85     private SensorManager mSensorManager;
     86     private PowerManager.WakeLock mWakeLock;
     87 
     88     /** Threshold angle in degrees beyond which the device is considered moving. */
     89     private final float mThresholdAngle;
     90 
     91     /** The minimum number of samples required to detect AnyMotion. */
     92     private int mNumSufficientSamples;
     93 
     94     /** True if an orientation measurement is in progress. */
     95     private boolean mMeasurementInProgress;
     96 
     97     /** The most recent gravity vector. */
     98     private Vector3 mCurrentGravityVector = null;
     99 
    100     /** The second most recent gravity vector. */
    101     private Vector3 mPreviousGravityVector = null;
    102 
    103     /** Running sum of squared errors. */
    104     private RunningSignalStats mRunningStats;
    105 
    106     private DeviceIdleCallback mCallback = null;
    107 
    108     public AnyMotionDetector(PowerManager pm, Handler handler, SensorManager sm,
    109             DeviceIdleCallback callback, float thresholdAngle) {
    110         if (DEBUG) Slog.d(TAG, "AnyMotionDetector instantiated.");
    111         synchronized (mLock) {
    112             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    113             mWakeLock.setReferenceCounted(false);
    114             mHandler = handler;
    115             mSensorManager = sm;
    116             mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    117             mMeasurementInProgress = false;
    118             mState = STATE_INACTIVE;
    119             mCallback = callback;
    120             mThresholdAngle = thresholdAngle;
    121             mRunningStats = new RunningSignalStats();
    122             mNumSufficientSamples = (int) Math.ceil(
    123                     ((double)ORIENTATION_MEASUREMENT_DURATION_MILLIS / SAMPLING_INTERVAL_MILLIS));
    124             if (DEBUG) Slog.d(TAG, "mNumSufficientSamples = " + mNumSufficientSamples);
    125         }
    126     }
    127 
    128     /*
    129      * Acquire accel data until we determine AnyMotion status.
    130      */
    131     public void checkForAnyMotion() {
    132         if (DEBUG) {
    133             Slog.d(TAG, "checkForAnyMotion(). mState = " + mState);
    134         }
    135         if (mState != STATE_ACTIVE) {
    136             synchronized (mLock) {
    137                 mState = STATE_ACTIVE;
    138                 if (DEBUG) {
    139                     Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_ACTIVE.");
    140                 }
    141                 mCurrentGravityVector = null;
    142                 mPreviousGravityVector = null;
    143                 mWakeLock.acquire();
    144                 startOrientationMeasurementLocked();
    145             }
    146         }
    147     }
    148 
    149     public void stop() {
    150         if (mState == STATE_ACTIVE) {
    151             synchronized (mLock) {
    152                 mState = STATE_INACTIVE;
    153                 if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE.");
    154                 if (mMeasurementInProgress) {
    155                     mMeasurementInProgress = false;
    156                     mSensorManager.unregisterListener(mListener);
    157                 }
    158                 mHandler.removeCallbacks(mMeasurementTimeout);
    159                 mHandler.removeCallbacks(mSensorRestart);
    160                 mCurrentGravityVector = null;
    161                 mPreviousGravityVector = null;
    162                 mWakeLock.release();
    163             }
    164         }
    165     }
    166 
    167     private void startOrientationMeasurementLocked() {
    168         if (DEBUG) Slog.d(TAG, "startOrientationMeasurementLocked: mMeasurementInProgress=" +
    169             mMeasurementInProgress + ", (mAccelSensor != null)=" + (mAccelSensor != null));
    170         if (!mMeasurementInProgress && mAccelSensor != null) {
    171             if (mSensorManager.registerListener(mListener, mAccelSensor,
    172                     SAMPLING_INTERVAL_MILLIS * 1000)) {
    173                 mMeasurementInProgress = true;
    174                 mRunningStats.reset();
    175             }
    176             Message msg = Message.obtain(mHandler, mMeasurementTimeout);
    177             msg.setAsynchronous(true);
    178             mHandler.sendMessageDelayed(msg, ACCELEROMETER_DATA_TIMEOUT_MILLIS);
    179         }
    180     }
    181 
    182     private int stopOrientationMeasurementLocked() {
    183         if (DEBUG) Slog.d(TAG, "stopOrientationMeasurement. mMeasurementInProgress=" +
    184                 mMeasurementInProgress);
    185         int status = RESULT_UNKNOWN;
    186         if (mMeasurementInProgress) {
    187             mSensorManager.unregisterListener(mListener);
    188             mHandler.removeCallbacks(mMeasurementTimeout);
    189             long detectionEndTime = SystemClock.elapsedRealtime();
    190             mMeasurementInProgress = false;
    191             mPreviousGravityVector = mCurrentGravityVector;
    192             mCurrentGravityVector = mRunningStats.getRunningAverage();
    193             if (DEBUG) {
    194                 Slog.d(TAG, "mRunningStats = " + mRunningStats.toString());
    195                 String currentGravityVectorString = (mCurrentGravityVector == null) ?
    196                         "null" : mCurrentGravityVector.toString();
    197                 String previousGravityVectorString = (mPreviousGravityVector == null) ?
    198                         "null" : mPreviousGravityVector.toString();
    199                 Slog.d(TAG, "mCurrentGravityVector = " + currentGravityVectorString);
    200                 Slog.d(TAG, "mPreviousGravityVector = " + previousGravityVectorString);
    201             }
    202             mRunningStats.reset();
    203             status = getStationaryStatus();
    204             if (DEBUG) Slog.d(TAG, "getStationaryStatus() returned " + status);
    205             if (status != RESULT_UNKNOWN) {
    206                 mWakeLock.release();
    207                 if (DEBUG) {
    208                     Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE. status = " + status);
    209                 }
    210                 mState = STATE_INACTIVE;
    211             } else {
    212                 /*
    213                  * Unknown due to insufficient measurements. Schedule another orientation
    214                  * measurement.
    215                  */
    216                 if (DEBUG) Slog.d(TAG, "stopOrientationMeasurementLocked(): another measurement" +
    217                         " scheduled in " + ORIENTATION_MEASUREMENT_INTERVAL_MILLIS +
    218                         " milliseconds.");
    219                 Message msg = Message.obtain(mHandler, mSensorRestart);
    220                 msg.setAsynchronous(true);
    221                 mHandler.sendMessageDelayed(msg, ORIENTATION_MEASUREMENT_INTERVAL_MILLIS);
    222             }
    223         }
    224         return status;
    225     }
    226 
    227     /*
    228      * Updates mStatus to the current AnyMotion status.
    229      */
    230     public int getStationaryStatus() {
    231         if ((mPreviousGravityVector == null) || (mCurrentGravityVector == null)) {
    232             return RESULT_UNKNOWN;
    233         }
    234         Vector3 previousGravityVectorNormalized = mPreviousGravityVector.normalized();
    235         Vector3 currentGravityVectorNormalized = mCurrentGravityVector.normalized();
    236         float angle = previousGravityVectorNormalized.angleBetween(currentGravityVectorNormalized);
    237         if (DEBUG) Slog.d(TAG, "getStationaryStatus: angle = " + angle
    238                 + " energy = " + mRunningStats.getEnergy());
    239         if ((angle < mThresholdAngle) && (mRunningStats.getEnergy() < THRESHOLD_ENERGY)) {
    240             return RESULT_STATIONARY;
    241         } else if (Float.isNaN(angle)) {
    242           /**
    243            * Floating point rounding errors have caused the angle calcuation's dot product to
    244            * exceed 1.0. In such case, we report RESULT_MOVED to prevent devices from rapidly
    245            * retrying this measurement.
    246            */
    247             return RESULT_MOVED;
    248         }
    249         long diffTime = mCurrentGravityVector.timeMillisSinceBoot -
    250                 mPreviousGravityVector.timeMillisSinceBoot;
    251         if (diffTime > STALE_MEASUREMENT_TIMEOUT_MILLIS) {
    252             if (DEBUG) Slog.d(TAG, "getStationaryStatus: mPreviousGravityVector is too stale at " +
    253                     diffTime + " ms ago. Returning RESULT_UNKNOWN.");
    254             return RESULT_UNKNOWN;
    255         }
    256         return RESULT_MOVED;
    257     }
    258 
    259     private final SensorEventListener mListener = new SensorEventListener() {
    260         @Override
    261         public void onSensorChanged(SensorEvent event) {
    262             int status = RESULT_UNKNOWN;
    263             synchronized (mLock) {
    264                 Vector3 accelDatum = new Vector3(SystemClock.elapsedRealtime(), event.values[0],
    265                         event.values[1], event.values[2]);
    266                 mRunningStats.accumulate(accelDatum);
    267 
    268                 // If we have enough samples, stop accelerometer data acquisition.
    269                 if (mRunningStats.getSampleCount() >= mNumSufficientSamples) {
    270                     status = stopOrientationMeasurementLocked();
    271                 }
    272             }
    273             if (status != RESULT_UNKNOWN) {
    274                 mCallback.onAnyMotionResult(status);
    275             }
    276         }
    277 
    278         @Override
    279         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    280         }
    281     };
    282 
    283     private final Runnable mSensorRestart = new Runnable() {
    284         @Override
    285         public void run() {
    286             synchronized (mLock) {
    287                 startOrientationMeasurementLocked();
    288             }
    289         }
    290     };
    291 
    292     private final Runnable mMeasurementTimeout = new Runnable() {
    293       @Override
    294       public void run() {
    295           int status = RESULT_UNKNOWN;
    296           synchronized (mLock) {
    297               if (DEBUG) Slog.i(TAG, "mMeasurementTimeout. Failed to collect sufficient accel " +
    298                       "data within " + ACCELEROMETER_DATA_TIMEOUT_MILLIS + " ms. Stopping " +
    299                       "orientation measurement.");
    300               status = stopOrientationMeasurementLocked();
    301           }
    302           if (status != RESULT_UNKNOWN) {
    303               mCallback.onAnyMotionResult(status);
    304           }
    305       }
    306   };
    307 
    308     /**
    309      * A timestamped three dimensional vector and some vector operations.
    310      */
    311     public static final class Vector3 {
    312         public long timeMillisSinceBoot;
    313         public float x;
    314         public float y;
    315         public float z;
    316 
    317         public Vector3(long timeMillisSinceBoot, float x, float y, float z) {
    318             this.timeMillisSinceBoot = timeMillisSinceBoot;
    319             this.x = x;
    320             this.y = y;
    321             this.z = z;
    322         }
    323 
    324         public float norm() {
    325             return (float) Math.sqrt(dotProduct(this));
    326         }
    327 
    328         public Vector3 normalized() {
    329             float mag = norm();
    330             return new Vector3(timeMillisSinceBoot, x / mag, y / mag, z / mag);
    331         }
    332 
    333         /**
    334          * Returns the angle between this 3D vector and another given 3D vector.
    335          * Assumes both have already been normalized.
    336          *
    337          * @param other The other Vector3 vector.
    338          * @return angle between this vector and the other given one.
    339          */
    340         public float angleBetween(Vector3 other) {
    341             Vector3 crossVector = cross(other);
    342             float degrees = Math.abs((float)Math.toDegrees(
    343                     Math.atan2(crossVector.norm(), dotProduct(other))));
    344             Slog.d(TAG, "angleBetween: this = " + this.toString() +
    345                 ", other = " + other.toString() + ", degrees = " + degrees);
    346             return degrees;
    347         }
    348 
    349         public Vector3 cross(Vector3 v) {
    350             return new Vector3(
    351                 v.timeMillisSinceBoot,
    352                 y * v.z - z * v.y,
    353                 z * v.x - x * v.z,
    354                 x * v.y - y * v.x);
    355         }
    356 
    357         @Override
    358         public String toString() {
    359             String msg = "";
    360             msg += "timeMillisSinceBoot=" + timeMillisSinceBoot;
    361             msg += " | x=" + x;
    362             msg += ", y=" + y;
    363             msg += ", z=" + z;
    364             return msg;
    365         }
    366 
    367         public float dotProduct(Vector3 v) {
    368             return x * v.x + y * v.y + z * v.z;
    369         }
    370 
    371         public Vector3 times(float val) {
    372             return new Vector3(timeMillisSinceBoot, x * val, y * val, z * val);
    373         }
    374 
    375         public Vector3 plus(Vector3 v) {
    376             return new Vector3(v.timeMillisSinceBoot, x + v.x, y + v.y, z + v.z);
    377         }
    378 
    379         public Vector3 minus(Vector3 v) {
    380             return new Vector3(v.timeMillisSinceBoot, x - v.x, y - v.y, z - v.z);
    381         }
    382     }
    383 
    384     /**
    385      * Maintains running statistics on the signal revelant to AnyMotion detection, including:
    386      * <ul>
    387      *   <li>running average.
    388      *   <li>running sum-of-squared-errors as the energy of the signal derivative.
    389      * <ul>
    390      */
    391     private static class RunningSignalStats {
    392         Vector3 previousVector;
    393         Vector3 currentVector;
    394         Vector3 runningSum;
    395         float energy;
    396         int sampleCount;
    397 
    398         public RunningSignalStats() {
    399             reset();
    400         }
    401 
    402         public void reset() {
    403             previousVector = null;
    404             currentVector = null;
    405             runningSum = new Vector3(0, 0, 0, 0);
    406             energy = 0;
    407             sampleCount = 0;
    408         }
    409 
    410         /**
    411          * Apply a 3D vector v as the next element in the running SSE.
    412          */
    413         public void accumulate(Vector3 v) {
    414             if (v == null) {
    415                 if (DEBUG) Slog.i(TAG, "Cannot accumulate a null vector.");
    416                 return;
    417             }
    418             sampleCount++;
    419             runningSum = runningSum.plus(v);
    420             previousVector = currentVector;
    421             currentVector = v;
    422             if (previousVector != null) {
    423                 Vector3 dv = currentVector.minus(previousVector);
    424                 float incrementalEnergy = dv.x * dv.x + dv.y * dv.y + dv.z * dv.z;
    425                 energy += incrementalEnergy;
    426                 if (DEBUG) Slog.i(TAG, "Accumulated vector " + currentVector.toString() +
    427                         ", runningSum = " + runningSum.toString() +
    428                         ", incrementalEnergy = " + incrementalEnergy +
    429                         ", energy = " + energy);
    430             }
    431         }
    432 
    433         public Vector3 getRunningAverage() {
    434             if (sampleCount > 0) {
    435               return runningSum.times((float)(1.0f / sampleCount));
    436             }
    437             return null;
    438         }
    439 
    440         public float getEnergy() {
    441             return energy;
    442         }
    443 
    444         public int getSampleCount() {
    445             return sampleCount;
    446         }
    447 
    448         @Override
    449         public String toString() {
    450             String msg = "";
    451             String currentVectorString = (currentVector == null) ?
    452                 "null" : currentVector.toString();
    453             String previousVectorString = (previousVector == null) ?
    454                 "null" : previousVector.toString();
    455             msg += "previousVector = " + previousVectorString;
    456             msg += ", currentVector = " + currentVectorString;
    457             msg += ", sampleCount = " + sampleCount;
    458             msg += ", energy = " + energy;
    459             return msg;
    460         }
    461     }
    462 }
    463