1 /*
2  * Copyright (C) 2015 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.Handler;
21 import android.os.Looper;
22 
23 /**
24  * Similar to {@link InputEventReceiver}, but batches events to vsync boundaries when possible.
25  * @hide
26  */
27 public class BatchedInputEventReceiver extends InputEventReceiver {
28     private Choreographer mChoreographer;
29     private boolean mBatchingEnabled;
30     private boolean mBatchedInputScheduled;
31     private final Handler mHandler;
32     private final Runnable mConsumeBatchedInputEvents = new Runnable() {
33         @Override
34         public void run() {
35             consumeBatchedInputEvents(-1);
36         }
37     };
38 
39     @UnsupportedAppUsage
BatchedInputEventReceiver( InputChannel inputChannel, Looper looper, Choreographer choreographer)40     public BatchedInputEventReceiver(
41             InputChannel inputChannel, Looper looper, Choreographer choreographer) {
42         super(inputChannel, looper);
43         mChoreographer = choreographer;
44         mBatchingEnabled = true;
45         mHandler = new Handler(looper);
46     }
47 
48     @Override
onBatchedInputEventPending(int source)49     public void onBatchedInputEventPending(int source) {
50         if (mBatchingEnabled) {
51             scheduleBatchedInput();
52         } else {
53             consumeBatchedInputEvents(-1);
54         }
55     }
56 
57     @Override
dispose()58     public void dispose() {
59         unscheduleBatchedInput();
60         consumeBatchedInputEvents(-1);
61         super.dispose();
62     }
63 
64     /**
65      * Sets whether to enable batching on this input event receiver.
66      * @hide
67      */
setBatchingEnabled(boolean batchingEnabled)68     public void setBatchingEnabled(boolean batchingEnabled) {
69         if (mBatchingEnabled == batchingEnabled) {
70             return;
71         }
72 
73         mBatchingEnabled = batchingEnabled;
74         mHandler.removeCallbacks(mConsumeBatchedInputEvents);
75         if (!batchingEnabled) {
76             unscheduleBatchedInput();
77             mHandler.post(mConsumeBatchedInputEvents);
78         }
79     }
80 
doConsumeBatchedInput(long frameTimeNanos)81     protected void doConsumeBatchedInput(long frameTimeNanos) {
82         if (mBatchedInputScheduled) {
83             mBatchedInputScheduled = false;
84             if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) {
85                 // If we consumed a batch here, we want to go ahead and schedule the
86                 // consumption of batched input events on the next frame. Otherwise, we would
87                 // wait until we have more input events pending and might get starved by other
88                 // things occurring in the process. If the frame time is -1, however, then
89                 // we're in a non-batching mode, so there's no need to schedule this.
90                 scheduleBatchedInput();
91             }
92         }
93     }
94 
scheduleBatchedInput()95     private void scheduleBatchedInput() {
96         if (!mBatchedInputScheduled) {
97             mBatchedInputScheduled = true;
98             mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
99         }
100     }
101 
unscheduleBatchedInput()102     private void unscheduleBatchedInput() {
103         if (mBatchedInputScheduled) {
104             mBatchedInputScheduled = false;
105             mChoreographer.removeCallbacks(
106                     Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
107         }
108     }
109 
110     private final class BatchedInputRunnable implements Runnable {
111         @Override
run()112         public void run() {
113             doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
114         }
115     }
116     private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable();
117 
118     /**
119      * A {@link BatchedInputEventReceiver} that reports events to an {@link InputEventListener}.
120      * @hide
121      */
122     public static class SimpleBatchedInputEventReceiver extends BatchedInputEventReceiver {
123 
124         /** @hide */
125         public interface InputEventListener {
126             /**
127              * Process the input event.
128              * @return handled
129              */
onInputEvent(InputEvent event)130             boolean onInputEvent(InputEvent event);
131         }
132 
133         protected InputEventListener mListener;
134 
SimpleBatchedInputEventReceiver(InputChannel inputChannel, Looper looper, Choreographer choreographer, InputEventListener listener)135         public SimpleBatchedInputEventReceiver(InputChannel inputChannel, Looper looper,
136                 Choreographer choreographer, InputEventListener listener) {
137             super(inputChannel, looper, choreographer);
138             mListener = listener;
139         }
140 
141         @Override
onInputEvent(InputEvent event)142         public void onInputEvent(InputEvent event) {
143             boolean handled = false;
144             try {
145                 handled = mListener.onInputEvent(event);
146             } finally {
147                 finishInputEvent(event, handled);
148             }
149         }
150     }
151 }
152