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