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