• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.systemui.car.input;
18 
19 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
20 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
21 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.os.Binder;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.RemoteException;
29 import android.view.Display;
30 import android.view.IWindowSession;
31 import android.view.InputChannel;
32 import android.view.InputEvent;
33 import android.view.InputEventReceiver;
34 import android.view.SurfaceControl;
35 import android.view.WindowManagerGlobal;
36 
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.internal.view.BaseIWindow;
40 
41 /**
42  * Creates a {@link InputWindowHandle} that catches all input events. Shows
43  * a Toast when input events are received while
44  * {@link DisplayInputSink} is activated.
45  */
46 public final class DisplayInputSink {
47     private static final int INPUT_LOCK_LAYER = Integer.MAX_VALUE;
48 
49     private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
50     private final SurfaceControl mSurfaceControl;
51     private final int mDisplayId;
52     private final OnInputEventListener mCallback;
53 
54     private BaseIWindow mFakeWindow;
55     private IBinder mFocusGrantToken;
56     private InputChannel mInputChannel;
57     @VisibleForTesting
58     InputEventReceiver mInputEventReceiver;
59 
60     /**
61      * Construct a new {@link DisplayInputSink}.
62      *
63      * @param display The display to add the {@link DisplayInputSink} on, must be non-null.
64      * @param callback The callback to invoke when an input event is received on
65      * {@link DisplayInputSink}, null for no callback.
66      */
DisplayInputSink(@onNull Display display, @Nullable OnInputEventListener callback)67     public DisplayInputSink(@NonNull Display display,
68             @Nullable OnInputEventListener callback) {
69         mDisplayId = display.getDisplayId();
70         mCallback = callback;
71         mSurfaceControl = createSurface(display.getLayerStack());
72         if (mCallback != null) {
73             createDisplayInputListener();
74         }
75     }
76 
createSurface(int layerStack)77     private SurfaceControl createSurface(int layerStack) {
78         SurfaceControl.Builder sb = new SurfaceControl.Builder();
79         SurfaceControl surfaceControl =
80                 sb.setName("DisplayInputSink-" + mDisplayId)
81                         .setHidden(false)
82                         .setCallsite("DisplayInputSink.createSurface")
83                         .build();
84         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
85         tx.setLayer(surfaceControl, INPUT_LOCK_LAYER)
86                 .setLayerStack(surfaceControl, layerStack)
87                 .apply();
88         return surfaceControl;
89     }
90 
91     /**
92      * Removes surface and display input listener for the display input sink.
93      */
release()94     public void release() {
95         if (mCallback != null) {
96             removeDisplayInputListener();
97         }
98 
99         if (mSurfaceControl != null) {
100             SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
101             tx.remove(mSurfaceControl).apply();
102         }
103     }
104 
createDisplayInputListener()105     private void createDisplayInputListener() {
106         // Use a fake window as the backing surface is a container layer and we don't want
107         // to create a buffer layer for it so we can't use ViewRootImpl.
108         mFakeWindow = new BaseIWindow();
109         mFakeWindow.setSession(mWindowSession);
110         mFocusGrantToken = new Binder();
111         mInputChannel = new InputChannel();
112         try {
113             mWindowSession.grantInputChannel(
114                     mDisplayId,
115                     mSurfaceControl,
116                     mFakeWindow,
117                     /* hostInputToken= */ null,
118                     FLAG_NOT_FOCUSABLE,
119                     PRIVATE_FLAG_TRUSTED_OVERLAY,
120                     /* inputFeatures= */ 0,
121                     TYPE_INPUT_CONSUMER,
122                     /* windowToken= */ null,
123                     mFocusGrantToken,
124                     "InputListener of " + mSurfaceControl.toString(),
125                     mInputChannel);
126         } catch (RemoteException e) {
127             e.rethrowFromSystemServer();
128         }
129 
130         mInputEventReceiver = new InputEventReceiver(mInputChannel, Looper.getMainLooper()) {
131             @Override
132             public void onInputEvent(InputEvent event) {
133                 mCallback.onInputEvent(event);
134                 finishInputEvent(event, /* handled= */ true);
135             }
136         };
137     }
138 
removeDisplayInputListener()139     private void removeDisplayInputListener() {
140         if (mInputEventReceiver != null) {
141             mInputEventReceiver.dispose();
142             mInputEventReceiver = null;
143         }
144         if (mInputChannel != null) {
145             mInputChannel.dispose();
146             mInputChannel = null;
147         }
148         try {
149             if (mFakeWindow != null) {
150                 mWindowSession.remove(mFakeWindow);
151             }
152         } catch (RemoteException e) {
153             e.rethrowFromSystemServer();
154         }
155     }
156 
157     @Override
toString()158     public String toString() {
159         StringBuilder sb = new StringBuilder("name='DisplayInputSink-");
160         sb.append(mDisplayId)
161                 .append("', inputChannelToken=")
162                 .append(mInputChannel != null ? mInputChannel.getToken() : "null");
163         return sb.toString();
164     }
165 
166     /**
167      * Interface definition for a callback to be invoked when an input event is received
168      * on {@link DisplayInputSink}.
169      */
170     public interface OnInputEventListener {
171         /**
172          * Called when an input event is received on {@link DisplayInputSink}. This can be
173          * used to notify the user that display input lock is currently enabled when the user
174          * touches the screen.
175          */
onInputEvent(InputEvent event)176         void onInputEvent(InputEvent event);
177     }
178 }
179