1 /*
2  * Copyright (C) 2013 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.compat.annotation.UnsupportedAppUsage;
20 import android.os.Build;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.MessageQueue;
24 import android.util.Log;
25 
26 import dalvik.system.CloseGuard;
27 
28 import java.lang.ref.WeakReference;
29 import java.util.concurrent.Callable;
30 import java.util.concurrent.ExecutionException;
31 import java.util.concurrent.FutureTask;
32 import java.util.concurrent.RunnableFuture;
33 
34 /**
35  * Provides a low-level mechanism for an application to send input events.
36  * @hide
37  */
38 public abstract class InputEventSender {
39     private static final String TAG = "InputEventSender";
40 
41     private final CloseGuard mCloseGuard = CloseGuard.get();
42 
43     private long mSenderPtr;
44 
45     // We keep references to the input channel and message queue objects (indirectly through
46     // Handler) here so that they are not GC'd while the native peer of the receiver is using them.
47     private InputChannel mInputChannel;
48     private Handler mHandler;
49 
nativeInit(WeakReference<InputEventSender> sender, InputChannel inputChannel, MessageQueue messageQueue)50     private static native long nativeInit(WeakReference<InputEventSender> sender,
51             InputChannel inputChannel, MessageQueue messageQueue);
nativeDispose(long senderPtr)52     private static native void nativeDispose(long senderPtr);
nativeSendKeyEvent(long senderPtr, int seq, KeyEvent event)53     private static native boolean nativeSendKeyEvent(long senderPtr, int seq, KeyEvent event);
nativeSendMotionEvent(long senderPtr, int seq, MotionEvent event)54     private static native boolean nativeSendMotionEvent(long senderPtr, int seq, MotionEvent event);
55 
56     /**
57      * Creates an input event sender bound to the specified input channel.
58      *
59      * @param inputChannel The input channel.
60      * @param looper The looper to use when invoking callbacks.
61      */
InputEventSender(InputChannel inputChannel, Looper looper)62     public InputEventSender(InputChannel inputChannel, Looper looper) {
63         if (inputChannel == null) {
64             throw new IllegalArgumentException("inputChannel must not be null");
65         }
66         if (looper == null) {
67             throw new IllegalArgumentException("looper must not be null");
68         }
69 
70         mInputChannel = inputChannel;
71         mHandler = new Handler(looper);
72         mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
73                 mInputChannel, looper.getQueue());
74 
75         mCloseGuard.open("InputEventSender.dispose");
76     }
77 
78     @Override
finalize()79     protected void finalize() throws Throwable {
80         try {
81             dispose(true);
82         } finally {
83             super.finalize();
84         }
85     }
86 
87     /**
88      * Disposes the receiver.
89      */
dispose()90     public void dispose() {
91         dispose(false);
92     }
93 
dispose(boolean finalized)94     private void dispose(boolean finalized) {
95         if (mCloseGuard != null) {
96             if (finalized) {
97                 mCloseGuard.warnIfOpen();
98             }
99             mCloseGuard.close();
100         }
101 
102         if (mSenderPtr != 0) {
103             nativeDispose(mSenderPtr);
104             mSenderPtr = 0;
105         }
106         mHandler = null;
107         mInputChannel = null;
108     }
109 
110     /**
111      * Called when an input event is finished.
112      *
113      * @param seq The input event sequence number.
114      * @param handled True if the input event was handled.
115      */
onInputEventFinished(int seq, boolean handled)116     public void onInputEventFinished(int seq, boolean handled) {
117     }
118 
119     /**
120      * Called when timeline is sent to the publisher.
121      *
122      * @param inputEventId The id of the input event that caused the frame being reported
123      * @param gpuCompletedTime The time when the frame left the app process
124      * @param presentTime The time when the frame was presented on screen
125      */
onTimelineReported(int inputEventId, long gpuCompletedTime, long presentTime)126     public void onTimelineReported(int inputEventId, long gpuCompletedTime, long presentTime) {
127     }
128 
129     /**
130      * Sends an input event. Can be called from any thread. Do not call this if the looper thread
131      * is blocked! It would cause a deadlock.
132      *
133      * @param seq The input event sequence number.
134      * @param event The input event to send.
135      * @return True if the entire event was sent successfully.  May return false
136      * if the input channel buffer filled before all samples were dispatched.
137      */
sendInputEvent(int seq, InputEvent event)138     public final boolean sendInputEvent(int seq, InputEvent event) {
139         if (event == null) {
140             throw new IllegalArgumentException("event must not be null");
141         }
142         if (mSenderPtr == 0) {
143             Log.w(TAG, "Attempted to send an input event but the input event "
144                     + "sender has already been disposed.");
145             return false;
146         }
147 
148         if (mHandler.getLooper().isCurrentThread()) {
149             return sendInputEventInternal(seq, event);
150         }
151         // This is being called on another thread. Post a runnable to the looper thread
152         // with the event injection, and wait until it's processed.
153         final RunnableFuture<Boolean> task = new FutureTask<>(new Callable<Boolean>() {
154             @Override
155             public Boolean call() throws Exception {
156                 return sendInputEventInternal(seq, event);
157             }
158         });
159         mHandler.post(task);
160         try {
161             return task.get();
162         } catch (InterruptedException exc) {
163             throw new IllegalStateException("Interrupted while sending " + event + ": " + exc);
164         } catch (ExecutionException exc) {
165             throw new IllegalStateException("Couldn't send " + event + ": " + exc);
166         }
167     }
168 
sendInputEventInternal(int seq, InputEvent event)169     private boolean sendInputEventInternal(int seq, InputEvent event) {
170         if (event instanceof KeyEvent) {
171             return nativeSendKeyEvent(mSenderPtr, seq, (KeyEvent)event);
172         } else {
173             return nativeSendMotionEvent(mSenderPtr, seq, (MotionEvent)event);
174         }
175     }
176 
177     // Called from native code.
178     @SuppressWarnings("unused")
179     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
dispatchInputEventFinished(int seq, boolean handled)180     private void dispatchInputEventFinished(int seq, boolean handled) {
181         onInputEventFinished(seq, handled);
182     }
183 
184     // Called from native code.
185     @SuppressWarnings("unused")
dispatchTimelineReported( int inputEventId, long gpuCompletedTime, long presentTime)186     private void dispatchTimelineReported(
187             int inputEventId, long gpuCompletedTime, long presentTime) {
188         onTimelineReported(inputEventId, gpuCompletedTime, presentTime);
189     }
190 }
191