Home | History | Annotate | Download | only in window_orientation
      1 /*
      2  * Copyright (C) 2016 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 #include <stdlib.h>
     18 #include <string.h>
     19 #include <timer.h>
     20 #include <heap.h>
     21 #include <plat/rtc.h>
     22 #include <plat/syscfg.h>
     23 #include <hostIntf.h>
     24 #include <nanohubPacket.h>
     25 #include <floatRt.h>
     26 
     27 #include <seos.h>
     28 
     29 #include <nanohub_math.h>
     30 #include <sensors.h>
     31 #include <limits.h>
     32 
     33 #define WINDOW_ORIENTATION_APP_VERSION  2
     34 
     35 #define LOG_TAG "[WO]"
     36 
     37 #define LOGW(fmt, ...) do { \
     38         osLog(LOG_WARN, LOG_TAG " " fmt,  ##__VA_ARGS__);  \
     39     } while (0);
     40 
     41 #define LOGI(fmt, ...) do { \
     42         osLog(LOG_INFO, LOG_TAG " " fmt,  ##__VA_ARGS__);  \
     43     } while (0);
     44 
     45 #define LOGD(fmt, ...) do { \
     46         if (DBG_ENABLE) {  \
     47             osLog(LOG_DEBUG, LOG_TAG " " fmt,  ##__VA_ARGS__);  \
     48         } \
     49     } while (0);
     50 
     51 #define DBG_ENABLE  0
     52 
     53 #define ACCEL_MIN_RATE_HZ                  SENSOR_HZ(15) // 15 HZ
     54 #define ACCEL_MAX_LATENCY_NS               40000000ull   // 40 ms in nsec
     55 
     56 // all time units in usec, angles in degrees
     57 #define RADIANS_TO_DEGREES                              (180.0f / M_PI)
     58 
     59 #define NS2US(x) (x >> 10)   // convert nsec to approx usec
     60 
     61 #define PROPOSAL_MIN_SETTLE_TIME                        NS2US(40000000ull)       // 40 ms
     62 #define PROPOSAL_MAX_SETTLE_TIME                        NS2US(400000000ull)      // 400 ms
     63 #define PROPOSAL_TILT_ANGLE_KNEE                        20                       // 20 deg
     64 #define PROPOSAL_SETTLE_TIME_SLOPE                      NS2US(12000000ull)       // 12 ms/deg
     65 
     66 #define PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED              NS2US(500000000ull)      // 500 ms
     67 #define PROPOSAL_MIN_TIME_SINCE_SWING_ENDED             NS2US(300000000ull)      // 300 ms
     68 #define PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED      NS2US(500000000ull)      // 500 ms
     69 
     70 #define FLAT_ANGLE                      80
     71 #define FLAT_TIME                       NS2US(1000000000ull)     // 1 sec
     72 
     73 #define SWING_AWAY_ANGLE_DELTA          20
     74 #define SWING_TIME                      NS2US(300000000ull)      // 300 ms
     75 
     76 #define MAX_FILTER_DELTA_TIME           NS2US(1000000000ull)     // 1 sec
     77 #define FILTER_TIME_CONSTANT            NS2US(200000000ull)      // 200 ms
     78 
     79 #define NEAR_ZERO_MAGNITUDE             1.0f        // m/s^2
     80 #define ACCELERATION_TOLERANCE          4.0f
     81 #define STANDARD_GRAVITY                9.8f
     82 #define MIN_ACCELERATION_MAGNITUDE  (STANDARD_GRAVITY - ACCELERATION_TOLERANCE)
     83 #define MAX_ACCELERATION_MAGNITUDE  (STANDARD_GRAVITY + ACCELERATION_TOLERANCE)
     84 
     85 #define MAX_TILT                        80
     86 #define TILT_OVERHEAD_ENTER             -40
     87 #define TILT_OVERHEAD_EXIT              -15
     88 
     89 #define ADJACENT_ORIENTATION_ANGLE_GAP  45
     90 
     91 // TILT_HISTORY_SIZE has to be greater than the time constant
     92 // max(FLAT_TIME, SWING_TIME) multiplied by the highest accel sample rate after
     93 // interpolation (1.0 / MIN_ACCEL_INTERVAL).
     94 #define TILT_HISTORY_SIZE               64
     95 #define TILT_REFERENCE_PERIOD           NS2US(1800000000000ull)  // 30 min
     96 #define TILT_REFERENCE_BACKOFF          NS2US(300000000000ull)   // 5 min
     97 
     98 // Allow up to 2.5x of the desired rate (ACCEL_MIN_RATE_HZ)
     99 // The concerns are complexity and (not so much) the size of tilt_history.
    100 #define MIN_ACCEL_INTERVAL              NS2US(26666667ull)       // 26.7 ms for 37.5 Hz
    101 
    102 #define EVT_SENSOR_ACC_DATA_RDY sensorGetMyEventType(SENS_TYPE_ACCEL)
    103 #define EVT_SENSOR_WIN_ORIENTATION_DATA_RDY sensorGetMyEventType(SENS_TYPE_WIN_ORIENTATION)
    104 
    105 static int8_t Tilt_Tolerance[4][2] = {
    106     /* ROTATION_0   */ { -25, 70 },
    107     /* ROTATION_90  */ { -25, 65 },
    108     /* ROTATION_180 */ { -25, 60 },
    109     /* ROTATION_270 */ { -25, 65 }
    110 };
    111 
    112 struct WindowOrientationTask {
    113     uint32_t tid;
    114     uint32_t handle;
    115     uint32_t accelHandle;
    116 
    117     uint64_t last_filtered_time;
    118     struct TripleAxisDataPoint last_filtered_sample;
    119 
    120     uint64_t tilt_reference_time;
    121     uint64_t accelerating_time;
    122     uint64_t predicted_rotation_time;
    123     uint64_t flat_time;
    124     uint64_t swinging_time;
    125 
    126     uint32_t tilt_history_time[TILT_HISTORY_SIZE];
    127     int tilt_history_index;
    128     int8_t tilt_history[TILT_HISTORY_SIZE];
    129 
    130     int8_t current_rotation;
    131     int8_t prev_valid_rotation;
    132     int8_t proposed_rotation;
    133     int8_t predicted_rotation;
    134 
    135     bool flat;
    136     bool swinging;
    137     bool accelerating;
    138     bool overhead;
    139 };
    140 
    141 static struct WindowOrientationTask mTask;
    142 
    143 static const struct SensorInfo mSi =
    144 {
    145     .sensorName = "Window Orientation",
    146     .sensorType = SENS_TYPE_WIN_ORIENTATION,
    147     .numAxis = NUM_AXIS_EMBEDDED,
    148     .interrupt = NANOHUB_INT_NONWAKEUP,
    149     .minSamples = 20
    150 };
    151 
    152 static bool isTiltAngleAcceptable(int rotation, int8_t tilt_angle)
    153 {
    154     return ((tilt_angle >= Tilt_Tolerance[rotation][0])
    155                 && (tilt_angle <= Tilt_Tolerance[rotation][1]));
    156 }
    157 
    158 static bool isOrientationAngleAcceptable(int current_rotation, int rotation,
    159                                             int orientation_angle)
    160 {
    161     // If there is no current rotation, then there is no gap.
    162     // The gap is used only to introduce hysteresis among advertised orientation
    163     // changes to avoid flapping.
    164     int lower_bound, upper_bound;
    165 
    166     LOGD("current %d, new %d, orientation %d",
    167          (int)current_rotation, (int)rotation, (int)orientation_angle);
    168 
    169     if (current_rotation >= 0) {
    170         // If the specified rotation is the same or is counter-clockwise
    171         // adjacent to the current rotation, then we set a lower bound on the
    172         // orientation angle.
    173         // For example, if currentRotation is ROTATION_0 and proposed is
    174         // ROTATION_90, then we want to check orientationAngle > 45 + GAP / 2.
    175         if ((rotation == current_rotation)
    176                 || (rotation == (current_rotation + 1) % 4)) {
    177             lower_bound = rotation * 90 - 45
    178                     + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
    179             if (rotation == 0) {
    180                 if ((orientation_angle >= 315)
    181                         && (orientation_angle < lower_bound + 360)) {
    182                     return false;
    183                 }
    184             } else {
    185                 if (orientation_angle < lower_bound) {
    186                     return false;
    187                 }
    188             }
    189         }
    190 
    191         // If the specified rotation is the same or is clockwise adjacent,
    192         // then we set an upper bound on the orientation angle.
    193         // For example, if currentRotation is ROTATION_0 and rotation is
    194         // ROTATION_270, then we want to check orientationAngle < 315 - GAP / 2.
    195         if ((rotation == current_rotation)
    196                 || (rotation == (current_rotation + 3) % 4)) {
    197             upper_bound = rotation * 90 + 45
    198                     - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
    199             if (rotation == 0) {
    200                 if ((orientation_angle <= 45)
    201                         && (orientation_angle > upper_bound)) {
    202                     return false;
    203                 }
    204             } else {
    205                 if (orientation_angle > upper_bound) {
    206                     return false;
    207                 }
    208             }
    209         }
    210     }
    211     return true;
    212 }
    213 
    214 static bool isPredictedRotationAcceptable(uint64_t now, int8_t tilt_angle)
    215 {
    216     // piecewise linear settle_time qualification:
    217     // settle_time_needed =
    218     // 1) PROPOSAL_MIN_SETTLE_TIME, for |tilt_angle| < PROPOSAL_TILT_ANGLE_KNEE.
    219     // 2) linearly increasing with |tilt_angle| at slope PROPOSAL_SETTLE_TIME_SLOPE
    220     // until it reaches PROPOSAL_MAX_SETTLE_TIME.
    221     int abs_tilt = (tilt_angle >= 0) ? tilt_angle : -tilt_angle;
    222     uint64_t settle_time_needed = PROPOSAL_MIN_SETTLE_TIME;
    223     if (abs_tilt > PROPOSAL_TILT_ANGLE_KNEE) {
    224         settle_time_needed += PROPOSAL_SETTLE_TIME_SLOPE
    225             * (abs_tilt - PROPOSAL_TILT_ANGLE_KNEE);
    226     }
    227     if (settle_time_needed > PROPOSAL_MAX_SETTLE_TIME) {
    228         settle_time_needed = PROPOSAL_MAX_SETTLE_TIME;
    229     }
    230     LOGD("settle_time_needed ~%llu (msec), settle_time ~%llu (msec)",
    231          settle_time_needed >> 10, (now - mTask.predicted_rotation_time) >> 10);
    232 
    233     // The predicted rotation must have settled long enough.
    234     if (now < mTask.predicted_rotation_time + settle_time_needed) {
    235         LOGD("...rejected by settle_time");
    236         return false;
    237     }
    238 
    239     // The last flat state (time since picked up) must have been sufficiently
    240     // long ago.
    241     if (now < mTask.flat_time + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED) {
    242         LOGD("...rejected by flat_time");
    243         return false;
    244     }
    245 
    246     // The last swing state (time since last movement to put down) must have
    247     // been sufficiently long ago.
    248     if (now < mTask.swinging_time + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED) {
    249         LOGD("...rejected by swing_time");
    250         return false;
    251     }
    252 
    253     // The last acceleration state must have been sufficiently long ago.
    254     if (now < mTask.accelerating_time
    255             + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED) {
    256         LOGD("...rejected by acceleration_time");
    257         return false;
    258     }
    259 
    260     // Looks good!
    261     return true;
    262 }
    263 
    264 static void clearPredictedRotation()
    265 {
    266     mTask.predicted_rotation = -1;
    267     mTask.predicted_rotation_time = 0;
    268 }
    269 
    270 static void clearTiltHistory()
    271 {
    272     mTask.tilt_history_time[0] = 0;
    273     mTask.tilt_history_index = 1;
    274     mTask.tilt_reference_time = 0;
    275 }
    276 
    277 static void reset()
    278 {
    279     mTask.last_filtered_time = 0;
    280     mTask.proposed_rotation = -1;
    281 
    282     mTask.flat_time = 0;
    283     mTask.flat = false;
    284 
    285     mTask.swinging_time = 0;
    286     mTask.swinging = false;
    287 
    288     mTask.accelerating_time = 0;
    289     mTask.accelerating = false;
    290 
    291     mTask.overhead = false;
    292 
    293     clearPredictedRotation();
    294     clearTiltHistory();
    295 }
    296 
    297 static void updatePredictedRotation(uint64_t now, int rotation)
    298 {
    299     if (mTask.predicted_rotation != rotation) {
    300         mTask.predicted_rotation = rotation;
    301         mTask.predicted_rotation_time = now;
    302     }
    303 }
    304 
    305 static bool isAccelerating(float magnitude)
    306 {
    307     return ((magnitude < MIN_ACCELERATION_MAGNITUDE)
    308                 || (magnitude > MAX_ACCELERATION_MAGNITUDE));
    309 }
    310 
    311 static void addTiltHistoryEntry(uint64_t now, int8_t tilt)
    312 {
    313     uint64_t old_reference_time, delta;
    314     size_t i;
    315     int index;
    316 
    317     if (mTask.tilt_reference_time == 0) {
    318         // set reference_time after reset()
    319 
    320         mTask.tilt_reference_time = now - 1;
    321     } else if (mTask.tilt_reference_time + TILT_REFERENCE_PERIOD < now) {
    322         // uint32_t tilt_history_time[] is good up to 71 min (2^32 * 1e-6 sec).
    323         // proactively shift reference_time every 30 min,
    324         // all history entries are within 4.3sec interval (15Hz x 64 samples)
    325 
    326         old_reference_time = mTask.tilt_reference_time;
    327         mTask.tilt_reference_time = now - TILT_REFERENCE_BACKOFF;
    328 
    329         delta = mTask.tilt_reference_time - old_reference_time;
    330         for (i = 0; i < TILT_HISTORY_SIZE; ++i) {
    331             mTask.tilt_history_time[i] = (mTask.tilt_history_time[i] > delta)
    332                 ? (mTask.tilt_history_time[i] - delta) : 0;
    333         }
    334     }
    335 
    336     index = mTask.tilt_history_index;
    337     mTask.tilt_history[index] = tilt;
    338     mTask.tilt_history_time[index] = now - mTask.tilt_reference_time;
    339 
    340     index = ((index + 1) == TILT_HISTORY_SIZE) ? 0 : (index + 1);
    341     mTask.tilt_history_index = index;
    342     mTask.tilt_history_time[index] = 0;
    343 }
    344 
    345 static int nextTiltHistoryIndex(int index)
    346 {
    347     int next = (index == 0) ? (TILT_HISTORY_SIZE - 1): (index - 1);
    348     return ((mTask.tilt_history_time[next] != 0) ? next : -1);
    349 }
    350 
    351 static bool isFlat(uint64_t now)
    352 {
    353     int i = mTask.tilt_history_index;
    354     for (; (i = nextTiltHistoryIndex(i)) >= 0;) {
    355         if (mTask.tilt_history[i] < FLAT_ANGLE) {
    356             break;
    357         }
    358         if (mTask.tilt_reference_time + mTask.tilt_history_time[i] + FLAT_TIME <= now) {
    359             // Tilt has remained greater than FLAT_ANGLE for FLAT_TIME.
    360             return true;
    361         }
    362     }
    363     return false;
    364 }
    365 
    366 static bool isSwinging(uint64_t now, int8_t tilt)
    367 {
    368     int i = mTask.tilt_history_index;
    369     for (; (i = nextTiltHistoryIndex(i)) >= 0;) {
    370         if (mTask.tilt_reference_time + mTask.tilt_history_time[i] + SWING_TIME
    371                 < now) {
    372             break;
    373         }
    374         if (mTask.tilt_history[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
    375             // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME.
    376             // This is one-sided protection. No latency will be added when
    377             // picking up the device and rotating.
    378             return true;
    379         }
    380     }
    381     return false;
    382 }
    383 
    384 static bool add_samples(struct TripleAxisDataEvent *ev)
    385 {
    386     int i, tilt_tmp;
    387     int orientation_angle, nearest_rotation;
    388     float x, y, z, alpha, magnitude;
    389     uint64_t now_nsec = ev->referenceTime, now;
    390     uint64_t then, time_delta;
    391     struct TripleAxisDataPoint *last_sample;
    392     size_t sampleCnt = ev->samples[0].firstSample.numSamples;
    393     bool skip_sample;
    394     bool accelerating, flat, swinging;
    395     bool change_detected;
    396     int8_t old_proposed_rotation, proposed_rotation;
    397     int8_t tilt_angle;
    398 
    399     for (i = 0; i < sampleCnt; i++) {
    400 
    401         x = ev->samples[i].x;
    402         y = ev->samples[i].y;
    403         z = ev->samples[i].z;
    404 
    405         // Apply a low-pass filter to the acceleration up vector in cartesian space.
    406         // Reset the orientation listener state if the samples are too far apart in time.
    407 
    408         now_nsec += i > 0 ? ev->samples[i].deltaTime : 0;
    409         now = NS2US(now_nsec); // convert to ~usec
    410 
    411         last_sample = &mTask.last_filtered_sample;
    412         then = mTask.last_filtered_time;
    413         time_delta = now - then;
    414 
    415         if ((now < then) || (now > then + MAX_FILTER_DELTA_TIME)) {
    416             reset();
    417             skip_sample = true;
    418         } else {
    419             // alpha is the weight on the new sample
    420             alpha = floatFromUint64(time_delta) / floatFromUint64(FILTER_TIME_CONSTANT + time_delta);
    421             x = alpha * (x - last_sample->x) + last_sample->x;
    422             y = alpha * (y - last_sample->y) + last_sample->y;
    423             z = alpha * (z - last_sample->z) + last_sample->z;
    424 
    425             skip_sample = false;
    426         }
    427 
    428         // poor man's interpolator for reduced complexity:
    429         // drop samples when input sampling rate is 2.5x higher than requested
    430         if (!skip_sample && (time_delta < MIN_ACCEL_INTERVAL)) {
    431             skip_sample = true;
    432         } else {
    433             mTask.last_filtered_time = now;
    434             mTask.last_filtered_sample.x = x;
    435             mTask.last_filtered_sample.y = y;
    436             mTask.last_filtered_sample.z = z;
    437         }
    438 
    439         accelerating = false;
    440         flat = false;
    441         swinging = false;
    442 
    443         if (!skip_sample) {
    444             // Calculate the magnitude of the acceleration vector.
    445             magnitude = sqrtf(x * x + y * y + z * z);
    446 
    447             if (magnitude < NEAR_ZERO_MAGNITUDE) {
    448                 LOGD("Ignoring sensor data, magnitude too close to zero.");
    449                 clearPredictedRotation();
    450             } else {
    451                 // Determine whether the device appears to be undergoing
    452                 // external acceleration.
    453                 if (isAccelerating(magnitude)) {
    454                     accelerating = true;
    455                     mTask.accelerating_time = now;
    456                 }
    457 
    458                 // Calculate the tilt angle.
    459                 // This is the angle between the up vector and the x-y plane
    460                 // (the plane of the screen) in a range of [-90, 90] degrees.
    461                 //  -90 degrees: screen horizontal and facing the ground (overhead)
    462                 //    0 degrees: screen vertical
    463                 //   90 degrees: screen horizontal and facing the sky (on table)
    464                 tilt_tmp = (int)(asinf(z / magnitude) * RADIANS_TO_DEGREES);
    465                 tilt_tmp = (tilt_tmp > 127) ? 127 : tilt_tmp;
    466                 tilt_tmp = (tilt_tmp < -128) ? -128 : tilt_tmp;
    467                 tilt_angle = tilt_tmp;
    468                 addTiltHistoryEntry(now, tilt_angle);
    469 
    470                 // Determine whether the device appears to be flat or swinging.
    471                 if (isFlat(now)) {
    472                     flat = true;
    473                     mTask.flat_time = now;
    474                 }
    475                 if (isSwinging(now, tilt_angle)) {
    476                     swinging = true;
    477                     mTask.swinging_time = now;
    478                 }
    479 
    480                 // If the tilt angle is too close to horizontal then we cannot
    481                 // determine the orientation angle of the screen.
    482                 if (tilt_angle <= TILT_OVERHEAD_ENTER) {
    483                     mTask.overhead = true;
    484                 } else if (tilt_angle >= TILT_OVERHEAD_EXIT) {
    485                     mTask.overhead = false;
    486                 }
    487 
    488                 if (mTask.overhead) {
    489                     LOGD("Ignoring sensor data, device is overhead: %d", (int)tilt_angle);
    490                     clearPredictedRotation();
    491                 } else if (fabsf(tilt_angle) > MAX_TILT) {
    492                     LOGD("Ignoring sensor data, tilt angle too high: %d", (int)tilt_angle);
    493                     clearPredictedRotation();
    494                 } else {
    495                     // Calculate the orientation angle.
    496                     // This is the angle between the x-y projection of the up
    497                     // vector onto the +y-axis, increasing clockwise in a range
    498                     // of [0, 360] degrees.
    499                     orientation_angle = (int)(-atan2f(-x, y) * RADIANS_TO_DEGREES);
    500                     if (orientation_angle < 0) {
    501                         // atan2 returns [-180, 180]; normalize to [0, 360]
    502                         orientation_angle += 360;
    503                     }
    504 
    505                     // Find the nearest rotation.
    506                     nearest_rotation = (orientation_angle + 45) / 90;
    507                     if (nearest_rotation == 4) {
    508                         nearest_rotation = 0;
    509                     }
    510                     // Determine the predicted orientation.
    511                     if (isTiltAngleAcceptable(nearest_rotation, tilt_angle)
    512                         && isOrientationAngleAcceptable(mTask.current_rotation,
    513                                                            nearest_rotation,
    514                                                            orientation_angle)) {
    515                         LOGD("Predicted: tilt %d, orientation %d, predicted %d",
    516                              (int)tilt_angle, (int)orientation_angle, (int)mTask.predicted_rotation);
    517                         updatePredictedRotation(now, nearest_rotation);
    518                     } else {
    519                         LOGD("Ignoring sensor data, no predicted rotation: "
    520                              "tilt %d, orientation %d",
    521                              (int)tilt_angle, (int)orientation_angle);
    522                         clearPredictedRotation();
    523                     }
    524                 }
    525             }
    526 
    527             mTask.flat = flat;
    528             mTask.swinging = swinging;
    529             mTask.accelerating = accelerating;
    530 
    531             // Determine new proposed rotation.
    532             old_proposed_rotation = mTask.proposed_rotation;
    533             if ((mTask.predicted_rotation < 0)
    534                     || isPredictedRotationAcceptable(now, tilt_angle)) {
    535 
    536                 mTask.proposed_rotation = mTask.predicted_rotation;
    537             }
    538             proposed_rotation = mTask.proposed_rotation;
    539 
    540             if ((proposed_rotation != old_proposed_rotation)
    541                     && (proposed_rotation >= 0)) {
    542                 mTask.current_rotation = proposed_rotation;
    543 
    544                 change_detected = (proposed_rotation != mTask.prev_valid_rotation);
    545                 mTask.prev_valid_rotation = proposed_rotation;
    546 
    547                 if (change_detected) {
    548                     return true;
    549                 }
    550             }
    551         }
    552     }
    553 
    554     return false;
    555 }
    556 
    557 
    558 static bool windowOrientationPower(bool on, void *cookie)
    559 {
    560     if (on == false && mTask.accelHandle != 0) {
    561         sensorRelease(mTask.tid, mTask.accelHandle);
    562         mTask.accelHandle = 0;
    563         osEventUnsubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY);
    564     }
    565 
    566     sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0);
    567 
    568     return true;
    569 }
    570 
    571 static bool windowOrientationSetRate(uint32_t rate, uint64_t latency, void *cookie)
    572 {
    573     int i;
    574 
    575     if (mTask.accelHandle == 0) {
    576         for (i = 0; sensorFind(SENS_TYPE_ACCEL, i, &mTask.accelHandle) != NULL; i++) {
    577             if (sensorRequest(mTask.tid, mTask.accelHandle, ACCEL_MIN_RATE_HZ, ACCEL_MAX_LATENCY_NS)) {
    578                 // clear hysteresis
    579                 mTask.current_rotation = -1;
    580                 mTask.prev_valid_rotation = -1;
    581                 reset();
    582                 osEventSubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY);
    583                 break;
    584             }
    585         }
    586     }
    587 
    588     if (mTask.accelHandle != 0)
    589         sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
    590 
    591     return true;
    592 }
    593 
    594 static bool windowOrientationFirmwareUpload(void *cookie)
    595 {
    596     sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG,
    597             1, 0);
    598     return true;
    599 }
    600 
    601 static bool windowOrientationFlush(void *cookie)
    602 {
    603     return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_WIN_ORIENTATION), SENSOR_DATA_EVENT_FLUSH, NULL);
    604 }
    605 
    606 static void windowOrientationHandleEvent(uint32_t evtType, const void* evtData)
    607 {
    608     struct TripleAxisDataEvent *ev;
    609     union EmbeddedDataPoint sample;
    610     bool rotation_changed;
    611 
    612     if (evtData == SENSOR_DATA_EVENT_FLUSH)
    613         return;
    614 
    615     switch (evtType) {
    616     case EVT_SENSOR_ACC_DATA_RDY:
    617         ev = (struct TripleAxisDataEvent *)evtData;
    618         rotation_changed = add_samples(ev);
    619 
    620         if (rotation_changed) {
    621             LOGI("rotation changed to: ******* %d *******\n",
    622                  (int)mTask.proposed_rotation);
    623 
    624             // send a single int32 here so no memory alloc/free needed.
    625             sample.idata = mTask.proposed_rotation;
    626             if (!osEnqueueEvt(EVT_SENSOR_WIN_ORIENTATION_DATA_RDY, sample.vptr, NULL)) {
    627                 LOGW("osEnqueueEvt failure");
    628             }
    629         }
    630         break;
    631     }
    632 }
    633 
    634 static const struct SensorOps mSops =
    635 {
    636     .sensorPower = windowOrientationPower,
    637     .sensorFirmwareUpload = windowOrientationFirmwareUpload,
    638     .sensorSetRate = windowOrientationSetRate,
    639     .sensorFlush = windowOrientationFlush,
    640 };
    641 
    642 static bool window_orientation_start(uint32_t tid)
    643 {
    644     mTask.tid = tid;
    645 
    646     mTask.current_rotation = -1;
    647     mTask.prev_valid_rotation = -1;
    648     reset();
    649 
    650     mTask.handle = sensorRegister(&mSi, &mSops, NULL, true);
    651 
    652     return true;
    653 }
    654 
    655 static void windowOrientationEnd()
    656 {
    657 }
    658 
    659 INTERNAL_APP_INIT(
    660         APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 3),
    661         WINDOW_ORIENTATION_APP_VERSION,
    662         window_orientation_start,
    663         windowOrientationEnd,
    664         windowOrientationHandleEvent);
    665