blob: 0fa0f1229ce827a872e0b5777ac411980b6a06f9 [file] [log] [blame]
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -08001/*
2 * Copyright (C) 2022 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#define LOG_TAG "MotionPredictor"
18
19#include <input/MotionPredictor.h>
20
21/**
22 * Log debug messages about predictions.
23 * Enable this via "adb shell setprop log.tag.MotionPredictor DEBUG"
24 */
25static bool isDebug() {
26 return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
27}
28
29namespace android {
30
31// --- MotionPredictor ---
32
33MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
34 std::function<bool()> checkMotionPredictionEnabled)
35 : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
36 mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
37
38void MotionPredictor::record(const MotionEvent& event) {
39 mEvents.push_back({});
40 mEvents.back().copyFrom(&event, /*keepHistory=*/true);
41 if (mEvents.size() > 2) {
42 // Just need 2 samples in order to extrapolate
43 mEvents.erase(mEvents.begin());
44 }
45}
46
47/**
48 * This is an example implementation that should be replaced with the actual prediction.
49 * The returned MotionEvent should be similar to the incoming MotionEvent, except for the
50 * fields that are predicted:
51 *
52 * 1) event.getEventTime
53 * 2) event.getPointerCoords
54 *
55 * The returned event should not contain any of the real, existing data. It should only
56 * contain the predicted samples.
57 */
Siarhei Vishniakou0839bd62023-01-05 17:20:00 -080058std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) {
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -080059 if (mEvents.size() < 2) {
60 return {};
61 }
62
63 const MotionEvent& event = mEvents.back();
64 if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
65 return {};
66 }
67
68 std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
69 std::vector<PointerCoords> futureCoords;
Siarhei Vishniakou0839bd62023-01-05 17:20:00 -080070 const nsecs_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -080071 const nsecs_t currentTime = event.getEventTime();
72 const MotionEvent& previous = mEvents.rbegin()[1];
73 const nsecs_t oldTime = previous.getEventTime();
74 if (currentTime == oldTime) {
75 // This can happen if it's an ACTION_POINTER_DOWN event, for example.
76 return {}; // prevent division by zero.
77 }
78
79 for (size_t i = 0; i < event.getPointerCount(); i++) {
80 const int32_t pointerId = event.getPointerId(i);
Siarhei Vishniakou7ac691e2023-01-11 15:37:47 -080081 const PointerCoords* currentPointerCoords = event.getRawPointerCoords(i);
82 const float currentX = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
83 const float currentY = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
84
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -080085 PointerCoords coords;
86 coords.clear();
87
88 ssize_t index = previous.findPointerIndex(pointerId);
89 if (index >= 0) {
90 // We have old data for this pointer. Compute the prediction.
Siarhei Vishniakou7ac691e2023-01-11 15:37:47 -080091 const PointerCoords* oldPointerCoords = previous.getRawPointerCoords(index);
92 const float oldX = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
93 const float oldY = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -080094
95 // Let's do a linear interpolation while waiting for a real model
96 const float scale =
97 static_cast<float>(futureTime - currentTime) / (currentTime - oldTime);
98 const float futureX = currentX + (currentX - oldX) * scale;
99 const float futureY = currentY + (currentY - oldY) * scale;
100
101 coords.setAxisValue(AMOTION_EVENT_AXIS_X, futureX);
102 coords.setAxisValue(AMOTION_EVENT_AXIS_Y, futureY);
Siarhei Vishniakou7ac691e2023-01-11 15:37:47 -0800103 ALOGD_IF(isDebug(),
104 "Prediction by %.1f ms, (%.1f, %.1f), (%.1f, %.1f) --> (%.1f, %.1f)",
105 (futureTime - event.getEventTime()) * 1E-6, oldX, oldY, currentX, currentY,
106 futureX, futureY);
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -0800107 }
108
109 futureCoords.push_back(coords);
110 }
111
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -0800112 /**
113 * The process of adding samples is different for the first and subsequent samples:
114 * 1. Add the first sample via 'initialize' as below
115 * 2. Add subsequent samples via 'addSample'
116 */
117 prediction->initialize(event.getId(), event.getDeviceId(), event.getSource(),
118 event.getDisplayId(), event.getHmac(), event.getAction(),
119 event.getActionButton(), event.getFlags(), event.getEdgeFlags(),
120 event.getMetaState(), event.getButtonState(), event.getClassification(),
121 event.getTransform(), event.getXPrecision(), event.getYPrecision(),
122 event.getRawXCursorPosition(), event.getRawYCursorPosition(),
123 event.getRawTransform(), event.getDownTime(), futureTime,
124 event.getPointerCount(), event.getPointerProperties(),
125 futureCoords.data());
126
127 // To add more predicted samples, use 'addSample':
128 prediction->addSample(futureTime + 1, futureCoords.data());
129
130 std::vector<std::unique_ptr<MotionEvent>> out;
131 out.push_back(std::move(prediction));
132 return out;
133}
134
135bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) {
136 // Global flag override
137 if (!mCheckMotionPredictionEnabled()) {
138 ALOGD_IF(isDebug(), "Prediction not available due to flag override");
139 return false;
140 }
141
142 // Prediction is only supported for stylus sources.
143 if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
144 ALOGD_IF(isDebug(), "Prediction not available for non-stylus source: %s",
145 inputEventSourceToString(source).c_str());
146 return false;
147 }
148 return true;
149}
150
Siarhei Vishniakou39147ce2022-11-15 12:13:04 -0800151} // namespace android