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