1 /* 2 * Copyright (C) 2011 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.Looper; 21 import android.os.MessageQueue; 22 import android.util.Log; 23 import android.util.SparseIntArray; 24 25 import dalvik.system.CloseGuard; 26 27 import java.lang.ref.WeakReference; 28 29 /** 30 * Provides a low-level mechanism for an application to receive input events. 31 * @hide 32 */ 33 public abstract class InputEventReceiver { 34 private static final String TAG = "InputEventReceiver"; 35 36 private final CloseGuard mCloseGuard = CloseGuard.get(); 37 38 private long mReceiverPtr; 39 40 // We keep references to the input channel and message queue objects here so that 41 // they are not GC'd while the native peer of the receiver is using them. 42 private InputChannel mInputChannel; 43 private MessageQueue mMessageQueue; 44 45 // Map from InputEvent sequence numbers to dispatcher sequence numbers. 46 private final SparseIntArray mSeqMap = new SparseIntArray(); 47 nativeInit(WeakReference<InputEventReceiver> receiver, InputChannel inputChannel, MessageQueue messageQueue)48 private static native long nativeInit(WeakReference<InputEventReceiver> receiver, 49 InputChannel inputChannel, MessageQueue messageQueue); nativeDispose(long receiverPtr)50 private static native void nativeDispose(long receiverPtr); nativeFinishInputEvent(long receiverPtr, int seq, boolean handled)51 private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled); nativeConsumeBatchedInputEvents(long receiverPtr, long frameTimeNanos)52 private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr, 53 long frameTimeNanos); 54 55 /** 56 * Creates an input event receiver bound to the specified input channel. 57 * 58 * @param inputChannel The input channel. 59 * @param looper The looper to use when invoking callbacks. 60 */ InputEventReceiver(InputChannel inputChannel, Looper looper)61 public InputEventReceiver(InputChannel inputChannel, Looper looper) { 62 if (inputChannel == null) { 63 throw new IllegalArgumentException("inputChannel must not be null"); 64 } 65 if (looper == null) { 66 throw new IllegalArgumentException("looper must not be null"); 67 } 68 69 mInputChannel = inputChannel; 70 mMessageQueue = looper.getQueue(); 71 mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), 72 inputChannel, mMessageQueue); 73 74 mCloseGuard.open("dispose"); 75 } 76 77 @Override finalize()78 protected void finalize() throws Throwable { 79 try { 80 dispose(true); 81 } finally { 82 super.finalize(); 83 } 84 } 85 86 /** 87 * Disposes the receiver. 88 */ dispose()89 public void dispose() { 90 dispose(false); 91 } 92 dispose(boolean finalized)93 private void dispose(boolean finalized) { 94 if (mCloseGuard != null) { 95 if (finalized) { 96 mCloseGuard.warnIfOpen(); 97 } 98 mCloseGuard.close(); 99 } 100 101 if (mReceiverPtr != 0) { 102 nativeDispose(mReceiverPtr); 103 mReceiverPtr = 0; 104 } 105 mInputChannel = null; 106 mMessageQueue = null; 107 } 108 109 /** 110 * Called when an input event is received. 111 * The recipient should process the input event and then call {@link #finishInputEvent} 112 * to indicate whether the event was handled. No new input events will be received 113 * until {@link #finishInputEvent} is called. 114 * 115 * @param event The input event that was received. 116 */ 117 @UnsupportedAppUsage onInputEvent(InputEvent event)118 public void onInputEvent(InputEvent event) { 119 finishInputEvent(event, false); 120 } 121 122 /** 123 * Called when a batched input event is pending. 124 * 125 * The batched input event will continue to accumulate additional movement 126 * samples until the recipient calls {@link #consumeBatchedInputEvents} or 127 * an event is received that ends the batch and causes it to be consumed 128 * immediately (such as a pointer up event). 129 */ onBatchedInputEventPending()130 public void onBatchedInputEventPending() { 131 consumeBatchedInputEvents(-1); 132 } 133 134 /** 135 * Finishes an input event and indicates whether it was handled. 136 * Must be called on the same Looper thread to which the receiver is attached. 137 * 138 * @param event The input event that was finished. 139 * @param handled True if the event was handled. 140 */ finishInputEvent(InputEvent event, boolean handled)141 public final void finishInputEvent(InputEvent event, boolean handled) { 142 if (event == null) { 143 throw new IllegalArgumentException("event must not be null"); 144 } 145 if (mReceiverPtr == 0) { 146 Log.w(TAG, "Attempted to finish an input event but the input event " 147 + "receiver has already been disposed."); 148 } else { 149 int index = mSeqMap.indexOfKey(event.getSequenceNumber()); 150 if (index < 0) { 151 Log.w(TAG, "Attempted to finish an input event that is not in progress."); 152 } else { 153 int seq = mSeqMap.valueAt(index); 154 mSeqMap.removeAt(index); 155 nativeFinishInputEvent(mReceiverPtr, seq, handled); 156 } 157 } 158 event.recycleIfNeededAfterDispatch(); 159 } 160 161 /** 162 * Consumes all pending batched input events. 163 * Must be called on the same Looper thread to which the receiver is attached. 164 * 165 * This method forces all batched input events to be delivered immediately. 166 * Should be called just before animating or drawing a new frame in the UI. 167 * 168 * @param frameTimeNanos The time in the {@link System#nanoTime()} time base 169 * when the current display frame started rendering, or -1 if unknown. 170 * 171 * @return Whether a batch was consumed 172 */ consumeBatchedInputEvents(long frameTimeNanos)173 public final boolean consumeBatchedInputEvents(long frameTimeNanos) { 174 if (mReceiverPtr == 0) { 175 Log.w(TAG, "Attempted to consume batched input events but the input event " 176 + "receiver has already been disposed."); 177 } else { 178 return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos); 179 } 180 return false; 181 } 182 183 // Called from native code. 184 @SuppressWarnings("unused") 185 @UnsupportedAppUsage dispatchInputEvent(int seq, InputEvent event)186 private void dispatchInputEvent(int seq, InputEvent event) { 187 mSeqMap.put(event.getSequenceNumber(), seq); 188 onInputEvent(event); 189 } 190 191 // Called from native code. 192 @SuppressWarnings("unused") 193 @UnsupportedAppUsage dispatchBatchedInputEventPending()194 private void dispatchBatchedInputEventPending() { 195 onBatchedInputEventPending(); 196 } 197 198 public static interface Factory { createInputEventReceiver( InputChannel inputChannel, Looper looper)199 public InputEventReceiver createInputEventReceiver( 200 InputChannel inputChannel, Looper looper); 201 } 202 } 203