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