/* * Copyright (C) 2023 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 android.car.builtin.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UiThread; import android.graphics.Rect; import android.graphics.Region; import android.view.View; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; /** * Calculates {@link InternalInsetsInfo#TOUCHABLE_INSETS_REGION} for the given {@link View}. *

The touch events on the View will pass through the host and be delivered to the window * below it. * *

It also provides the api {@link #setObscuredTouchRegion(Region)} to specify the region which * the view host can accept the touch events on it. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @UiThread public final class TouchableInsetsProvider { private static final String TAG = TouchableInsetsProvider.class.getSimpleName(); private final View mView; private final OnComputeInternalInsetsListener mListener = this::onComputeInternalInsets; private final int[] mLocation = new int[2]; private final Rect mRect = new Rect(); @Nullable private Region mObscuredTouchRegion; public TouchableInsetsProvider(@NonNull View view) { mView = view; } /** * Specifies the region of the view which the view host can accept the touch events. * * @param obscuredRegion the obscured region of the view. */ public void setObscuredTouchRegion(@Nullable Region obscuredRegion) { mObscuredTouchRegion = obscuredRegion; } private void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { if (!mView.isVisibleToUser()) { return; } if (inoutInfo.touchableRegion.isEmpty()) { // This is the first View to set touchableRegion, then set the entire Window as // touchableRegion first, then subtract each View's region from it. inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); View root = mView.getRootView(); root.getLocationInWindow(mLocation); mRect.set(mLocation[0], mLocation[1], mLocation[0] + root.getWidth(), mLocation[1] + root.getHeight()); inoutInfo.touchableRegion.set(mRect); } mView.getLocationInWindow(mLocation); mRect.set(mLocation[0], mLocation[1], mLocation[0] + mView.getWidth(), mLocation[1] + mView.getHeight()); inoutInfo.touchableRegion.op(mRect, Region.Op.DIFFERENCE); if (mObscuredTouchRegion != null) { inoutInfo.touchableRegion.op(mObscuredTouchRegion, Region.Op.UNION); } }; /** Registers this to the internal insets computation callback. */ public void addToViewTreeObserver() { mView.getViewTreeObserver().addOnComputeInternalInsetsListener(mListener); } /** Removes this from the internal insets computation callback. */ public void removeFromViewTreeObserver() { mView.getViewTreeObserver().removeOnComputeInternalInsetsListener(mListener); } @Override public String toString() { return TAG + "(rect=" + mRect + ", obscuredTouch=" + mObscuredTouchRegion + ")"; } }