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/inc/rtc.h> 22 #include <plat/inc/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(APP_ID_VENDOR_GOOGLE, 3), 661 WINDOW_ORIENTATION_APP_VERSION, 662 window_orientation_start, 663 windowOrientationEnd, 664 windowOrientationHandleEvent); 665