/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.car.input; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Binder; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.view.Display; import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.SurfaceControl; import android.view.WindowManagerGlobal; import androidx.annotation.VisibleForTesting; import com.android.internal.view.BaseIWindow; /** * Creates a {@link InputWindowHandle} that catches all input events. Shows * a Toast when input events are received while * {@link DisplayInputSink} is activated. */ public final class DisplayInputSink { private static final int INPUT_LOCK_LAYER = Integer.MAX_VALUE; private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); private final SurfaceControl mSurfaceControl; private final int mDisplayId; private final OnInputEventListener mCallback; private BaseIWindow mFakeWindow; private IBinder mFocusGrantToken; private InputChannel mInputChannel; @VisibleForTesting InputEventReceiver mInputEventReceiver; /** * Construct a new {@link DisplayInputSink}. * * @param display The display to add the {@link DisplayInputSink} on, must be non-null. * @param callback The callback to invoke when an input event is received on * {@link DisplayInputSink}, null for no callback. */ public DisplayInputSink(@NonNull Display display, @Nullable OnInputEventListener callback) { mDisplayId = display.getDisplayId(); mCallback = callback; mSurfaceControl = createSurface(display.getLayerStack()); if (mCallback != null) { createDisplayInputListener(); } } private SurfaceControl createSurface(int layerStack) { SurfaceControl.Builder sb = new SurfaceControl.Builder(); SurfaceControl surfaceControl = sb.setName("DisplayInputSink-" + mDisplayId) .setHidden(false) .setCallsite("DisplayInputSink.createSurface") .build(); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.setLayer(surfaceControl, INPUT_LOCK_LAYER) .setLayerStack(surfaceControl, layerStack) .apply(); return surfaceControl; } /** * Removes surface and display input listener for the display input sink. */ public void release() { if (mCallback != null) { removeDisplayInputListener(); } if (mSurfaceControl != null) { SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.remove(mSurfaceControl).apply(); } } private void createDisplayInputListener() { // Use a fake window as the backing surface is a container layer and we don't want // to create a buffer layer for it so we can't use ViewRootImpl. mFakeWindow = new BaseIWindow(); mFakeWindow.setSession(mWindowSession); mFocusGrantToken = new Binder(); mInputChannel = new InputChannel(); try { mWindowSession.grantInputChannel( mDisplayId, mSurfaceControl, mFakeWindow, /* hostInputToken= */ null, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, /* inputFeatures= */ 0, TYPE_INPUT_CONSUMER, /* windowToken= */ null, mFocusGrantToken, "InputListener of " + mSurfaceControl.toString(), mInputChannel); } catch (RemoteException e) { e.rethrowFromSystemServer(); } mInputEventReceiver = new InputEventReceiver(mInputChannel, Looper.getMainLooper()) { @Override public void onInputEvent(InputEvent event) { mCallback.onInputEvent(event); finishInputEvent(event, /* handled= */ true); } }; } private void removeDisplayInputListener() { if (mInputEventReceiver != null) { mInputEventReceiver.dispose(); mInputEventReceiver = null; } if (mInputChannel != null) { mInputChannel.dispose(); mInputChannel = null; } try { if (mFakeWindow != null) { mWindowSession.remove(mFakeWindow); } } catch (RemoteException e) { e.rethrowFromSystemServer(); } } @Override public String toString() { StringBuilder sb = new StringBuilder("name='DisplayInputSink-"); sb.append(mDisplayId) .append("', inputChannelToken=") .append(mInputChannel != null ? mInputChannel.getToken() : "null"); return sb.toString(); } /** * Interface definition for a callback to be invoked when an input event is received * on {@link DisplayInputSink}. */ public interface OnInputEventListener { /** * Called when an input event is received on {@link DisplayInputSink}. This can be * used to notify the user that display input lock is currently enabled when the user * touches the screen. */ void onInputEvent(InputEvent event); } }