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