1 /*
2  * Copyright (C) 2023 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 android.view;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import libcore.util.NativeAllocationRegistry;
26 
27 /**
28  * Calculate motion predictions.
29  *
30  * Feed motion events to this class in order to generate predicted future events. The prediction
31  * functionality may not be available on all devices: check if a specific source is supported on a
32  * given input device using {@link #isPredictionAvailable}.
33  *
34  * Send all of the events that were received from the system to {@link #record} to generate
35  * complete, accurate predictions from {@link #predict}. When processing the returned predictions,
36  * make sure to consider all of the {@link MotionEvent#getHistoricalAxisValue historical samples}.
37  */
38 public final class MotionPredictor {
39 
40     // This is a pass-through to the native MotionPredictor object (mPtr). Do not store any state or
41     // add any business logic here -- all of the implementation details should go into the native
42     // MotionPredictor (except for accessing the context/resources, which have no corresponding
43     // native API).
44 
45     private static class RegistryHolder {
46         public static final NativeAllocationRegistry REGISTRY =
47                 NativeAllocationRegistry.createMalloced(
48                         MotionPredictor.class.getClassLoader(),
49                         nativeGetNativeMotionPredictorFinalizer());
50     }
51 
52     // Pointer to the native object.
53     private final long mPtr;
54     // Device-specific override to enable/disable motion prediction.
55     private final boolean mIsPredictionEnabled;
56 
57     /**
58      * Create a new MotionPredictor for the provided {@link Context}.
59      * @param context The context for the predictions
60      */
MotionPredictor(@onNull Context context)61     public MotionPredictor(@NonNull Context context) {
62         this(
63                 context.getResources().getBoolean(
64                         com.android.internal.R.bool.config_enableMotionPrediction),
65                 context.getResources().getInteger(
66                         com.android.internal.R.integer.config_motionPredictionOffsetNanos));
67     }
68 
69     /**
70      * Internal constructor for testing.
71      * @hide
72      */
73     @VisibleForTesting
MotionPredictor(boolean isPredictionEnabled, int motionPredictionOffsetNanos)74     public MotionPredictor(boolean isPredictionEnabled, int motionPredictionOffsetNanos) {
75         mIsPredictionEnabled = isPredictionEnabled;
76         mPtr = nativeInitialize(motionPredictionOffsetNanos);
77         RegistryHolder.REGISTRY.registerNativeAllocation(this, mPtr);
78     }
79 
80     /**
81      * Record a movement so that in the future, a prediction for the current gesture can be
82      * generated. Only gestures from one input device at a time should be provided to an instance of
83      * MotionPredictor.
84      *
85      * @param event The received event
86      *
87      * @throws IllegalArgumentException if an inconsistent MotionEvent stream is sent.
88      */
record(@onNull MotionEvent event)89     public void record(@NonNull MotionEvent event) {
90         if (!mIsPredictionEnabled) {
91             return;
92         }
93         nativeRecord(mPtr, event);
94     }
95 
96     /**
97      * Get a predicted event for the gesture that has been provided to {@link #record}.
98      * Predictions may not reach the requested timestamp if the confidence in the prediction results
99      * is low.
100      *
101      * @param predictionTimeNanos The time that the prediction should target, in the
102      * {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds.
103      *
104      * @return The predicted motion event, or `null` if predictions are not supported, or not
105      * possible for the current gesture. Be sure to check the historical data in addition to the
106      * latest ({@link MotionEvent#getX getX()}, {@link MotionEvent#getY getY()}) coordinates for
107      * smooth prediction curves.
108      */
109     @Nullable
predict(long predictionTimeNanos)110     public MotionEvent predict(long predictionTimeNanos) {
111         if (!mIsPredictionEnabled) {
112             return null;
113         }
114         return nativePredict(mPtr, predictionTimeNanos);
115     }
116 
117     /**
118      * Check whether a device supports motion predictions for a given source type.
119      *
120      * @param deviceId The input device id.
121      * @param source The source of input events.
122      * @return True if the current device supports predictions, false otherwise.
123      *
124      * @see MotionEvent#getDeviceId
125      * @see MotionEvent#getSource
126      */
isPredictionAvailable(int deviceId, int source)127     public boolean isPredictionAvailable(int deviceId, int source) {
128         return mIsPredictionEnabled && nativeIsPredictionAvailable(mPtr, deviceId, source);
129     }
130 
nativeInitialize(int offsetNanos)131     private static native long nativeInitialize(int offsetNanos);
nativeRecord(long nativePtr, MotionEvent event)132     private static native void nativeRecord(long nativePtr, MotionEvent event);
nativePredict(long nativePtr, long predictionTimeNanos)133     private static native MotionEvent nativePredict(long nativePtr, long predictionTimeNanos);
nativeIsPredictionAvailable(long nativePtr, int deviceId, int source)134     private static native boolean nativeIsPredictionAvailable(long nativePtr, int deviceId,
135             int source);
nativeGetNativeMotionPredictorFinalizer()136     private static native long nativeGetNativeMotionPredictorFinalizer();
137 }
138