| /* |
| * Copyright 2020 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.example.android.autofillkeyboard; |
| |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.os.Build; |
| import android.util.AttributeSet; |
| import android.view.Choreographer; |
| import android.view.Surface; |
| import android.view.SurfaceHolder; |
| import android.view.SurfaceView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewTreeObserver; |
| import android.widget.inline.InlineContentView; |
| import android.widget.FrameLayout; |
| |
| import androidx.annotation.AttrRes; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RequiresApi; |
| import androidx.collection.ArraySet; |
| |
| /** |
| * This class is a container for showing {@link InlineContentView}s for cases |
| * where you want to ensure they appear only in a given area in your app. An |
| * example is having a scrollable list of items. Note that without this container |
| * the InlineContentViews' surfaces would cover parts of your app as these surfaces |
| * are owned by another process and always appearing on top of your app. |
| */ |
| @RequiresApi(api = Build.VERSION_CODES.R) |
| public class InlineContentClipView extends FrameLayout { |
| @NonNull |
| private final ArraySet<InlineContentView> mClippedDescendants = new ArraySet<>(); |
| |
| @NonNull |
| private final ViewTreeObserver.OnDrawListener mOnDrawListener = |
| this::clipDescendantInlineContentViews; |
| |
| @NonNull |
| private final Rect mParentBounds = new Rect(); |
| |
| @NonNull |
| private final Rect mContentBounds = new Rect(); |
| |
| @NonNull |
| private SurfaceView mBackgroundView; |
| |
| private int mBackgroundColor; |
| |
| public InlineContentClipView(@NonNull Context context) { |
| this(context, /*attrs*/ null); |
| } |
| |
| public InlineContentClipView(@NonNull Context context, @Nullable AttributeSet attrs) { |
| this(context, attrs, /*defStyleAttr*/ 0); |
| } |
| |
| public InlineContentClipView(@NonNull Context context, @Nullable AttributeSet attrs, |
| @AttrRes int defStyleAttr) { |
| super(context, attrs, defStyleAttr); |
| |
| mBackgroundView = new SurfaceView(context); |
| mBackgroundView.setZOrderOnTop(true); |
| mBackgroundView.getHolder().setFormat(PixelFormat.TRANSPARENT); |
| mBackgroundView.setLayoutParams(new ViewGroup.LayoutParams( |
| ViewGroup.LayoutParams.WRAP_CONTENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT)); |
| mBackgroundView.getHolder().addCallback(new SurfaceHolder.Callback() { |
| @Override |
| public void surfaceCreated(@NonNull SurfaceHolder holder) { |
| drawBackgroundColorIfReady(); |
| } |
| |
| @Override |
| public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, |
| int height) { /*do nothing*/ } |
| |
| @Override |
| public void surfaceDestroyed(@NonNull SurfaceHolder holder) { |
| /*do nothing*/ |
| } |
| }); |
| |
| addView(mBackgroundView); |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| getViewTreeObserver().addOnDrawListener(mOnDrawListener); |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| getViewTreeObserver().removeOnDrawListener(mOnDrawListener); |
| } |
| |
| @Override |
| public void setBackgroundColor(int color) { |
| mBackgroundColor = color; |
| Choreographer.getInstance().postFrameCallback((frameTimeNanos) -> |
| drawBackgroundColorIfReady()); |
| } |
| |
| private void drawBackgroundColorIfReady() { |
| final Surface surface = mBackgroundView.getHolder().getSurface(); |
| if (surface.isValid()) { |
| final Canvas canvas = surface.lockCanvas(null); |
| try { |
| canvas.drawColor(mBackgroundColor); |
| } finally { |
| surface.unlockCanvasAndPost(canvas); |
| } |
| } |
| } |
| |
| /** |
| * Sets whether the surfaces of the {@link InlineContentView}s wrapped by this view |
| * should appear on top or behind this view's window. Normally, they are placed on top |
| * of the window, to allow interaction ith the embedded UI. Via this method, you can |
| * place the surface below the window. This means that all of the contents of the window |
| * this view is in will be visible on top of the {@link InlineContentView}s' surfaces. |
| * |
| * @param onTop Whether to show the surface on top of this view's window. |
| * |
| * @see InlineContentView |
| * @see InlineContentView#setZOrderedOnTop(boolean) |
| */ |
| public void setZOrderedOnTop(boolean onTop) { |
| mBackgroundView.setZOrderOnTop(onTop); |
| for (InlineContentView inlineContentView : mClippedDescendants) { |
| inlineContentView.setZOrderedOnTop(onTop); |
| } |
| } |
| |
| private void clipDescendantInlineContentViews() { |
| mParentBounds.right = getWidth(); |
| mParentBounds.bottom = getHeight(); |
| mClippedDescendants.clear(); |
| clipDescendantInlineContentViews(this); |
| } |
| |
| private void clipDescendantInlineContentViews(@Nullable View root) { |
| if (root == null) { |
| return; |
| } |
| |
| if (root instanceof InlineContentView) { |
| final InlineContentView inlineContentView = (InlineContentView) root; |
| mContentBounds.set(mParentBounds); |
| offsetRectIntoDescendantCoords(inlineContentView, mContentBounds); |
| inlineContentView.setClipBounds(mContentBounds); |
| mClippedDescendants.add(inlineContentView); |
| return; |
| } |
| |
| if (root instanceof ViewGroup) { |
| final ViewGroup rootGroup = (ViewGroup) root; |
| final int childCount = rootGroup.getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View child = rootGroup.getChildAt(i); |
| clipDescendantInlineContentViews(child); |
| } |
| } |
| } |
| } |