Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 691e2ff5 authored by Linus Tufvesson's avatar Linus Tufvesson Committed by Android (Google) Code Review
Browse files

Merge "Block touches from passing through activities"

parents 3a20ce8e 1bc541e8
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -830,6 +830,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    // SystemUi sets the pinned mode on activity after transition is done.
    boolean mWaitForEnteringPinnedMode;

    private final ActivityRecordInputSink mActivityRecordInputSink;

    private final Runnable mPauseTimeoutRunnable = new Runnable() {
        @Override
        public void run() {
@@ -1785,6 +1787,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            createTime = _createTime;
        }
        mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);

        mActivityRecordInputSink = new ActivityRecordInputSink(this);
    }

    /**
@@ -6771,6 +6775,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            } else if (!show && mLastSurfaceShowing) {
                getSyncTransaction().hide(mSurfaceControl);
            }
            if (show) {
                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(
                        getSyncTransaction(), mSurfaceControl);
            }
        }
        if (mThumbnail != null) {
            mThumbnail.setShowing(getPendingTransaction(), show);
+171 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.server.wm;

import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;

import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.os.IBinder;
import android.os.InputConstants;
import android.os.Looper;
import android.util.Slog;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.widget.Toast;

/**
 * Creates a InputWindowHandle that catches all touches that would otherwise pass through an
 * Activity.
 */
class ActivityRecordInputSink {

    /**
     * Feature flag for making Activities consume all touches within their task bounds.
     */
    @ChangeId
    @Disabled
    static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;

    private static final String TAG = "ActivityRecordInputSink";
    private static final int NUMBER_OF_TOUCHES_TO_DISABLE = 3;
    private static final long TOAST_COOL_DOWN_MILLIS = 3000L;

    private final ActivityRecord mActivityRecord;
    private final boolean mIsCompatEnabled;

    // Hold on to InputEventReceiver to prevent it from getting GCd.
    private InputEventReceiver mInputEventReceiver;
    private InputWindowHandleWrapper mInputWindowHandleWrapper;
    private final String mName = Integer.toHexString(System.identityHashCode(this))
            + " ActivityRecordInputSink";
    private int mRapidTouchCount = 0;
    private IBinder mToken;
    private boolean mDisabled = false;

    ActivityRecordInputSink(ActivityRecord activityRecord) {
        mActivityRecord = activityRecord;
        mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
                mActivityRecord.getUid());
    }

    public void applyChangesToSurfaceIfChanged(
            SurfaceControl.Transaction transaction, SurfaceControl surfaceControl) {
        InputWindowHandleWrapper inputWindowHandleWrapper = getInputWindowHandleWrapper();
        if (inputWindowHandleWrapper.isChanged()) {
            inputWindowHandleWrapper.applyChangesToSurface(transaction, surfaceControl);
        }
    }

    private InputWindowHandleWrapper getInputWindowHandleWrapper() {
        if (mInputWindowHandleWrapper == null) {
            mInputWindowHandleWrapper = new InputWindowHandleWrapper(createInputWindowHandle());
            InputChannel inputChannel =
                    mActivityRecord.mWmService.mInputManager.createInputChannel(mName);
            mToken = inputChannel.getToken();
            mInputEventReceiver = createInputEventReceiver(inputChannel);
        }
        if (mDisabled || !mIsCompatEnabled || mActivityRecord.isAnimating(TRANSITION | PARENTS,
                ANIMATION_TYPE_APP_TRANSITION)) {
            // TODO(b/208662670): Investigate if we can have feature active during animations.
            mInputWindowHandleWrapper.setToken(null);
        } else if (mActivityRecord.mStartingData != null) {
            // TODO(b/208659130): Remove this special case
            // Don't block touches during splash screen. This is done to not show toasts for
            // touches passing through splash screens. b/171772640
            mInputWindowHandleWrapper.setToken(null);
        } else {
            mInputWindowHandleWrapper.setToken(mToken);
        }
        return mInputWindowHandleWrapper;
    }

    private InputWindowHandle createInputWindowHandle() {
        InputWindowHandle inputWindowHandle = new InputWindowHandle(
                mActivityRecord.getInputApplicationHandle(false),
                mActivityRecord.getDisplayId());
        inputWindowHandle.replaceTouchableRegionWithCrop(
                mActivityRecord.getParentSurfaceControl());
        inputWindowHandle.name = mName;
        inputWindowHandle.ownerUid = mActivityRecord.getUid();
        inputWindowHandle.ownerPid = mActivityRecord.getPid();
        inputWindowHandle.layoutParamsFlags =
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
        inputWindowHandle.dispatchingTimeoutMillis =
                InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
        return inputWindowHandle;
    }

    private InputEventReceiver createInputEventReceiver(InputChannel inputChannel) {
        return new SinkInputEventReceiver(inputChannel,
                mActivityRecord.mAtmService.mUiHandler.getLooper());
    }

    private void showAsToastAndLog(String message) {
        Toast.makeText(mActivityRecord.mAtmService.mUiContext, message,
                Toast.LENGTH_LONG).show();
        Slog.wtf(TAG, message + " " + mActivityRecord.mActivityComponent);
    }

    private class SinkInputEventReceiver extends InputEventReceiver {
        private long mLastToast = 0;

        SinkInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        public void onInputEvent(InputEvent event) {
            if (!(event instanceof MotionEvent)) {
                Slog.wtf(TAG,
                        "Received InputEvent that was not a MotionEvent");
                finishInputEvent(event, true);
                return;
            }
            MotionEvent motionEvent = (MotionEvent) event;
            if (motionEvent.getAction() != MotionEvent.ACTION_DOWN) {
                finishInputEvent(event, true);
                return;
            }

            if (event.getEventTime() - mLastToast > TOAST_COOL_DOWN_MILLIS) {
                String message = "go/activity-touch-opaque - "
                        + mActivityRecord.mActivityComponent.getPackageName()
                        + " blocked the touch!";
                showAsToastAndLog(message);
                mLastToast = event.getEventTime();
                mRapidTouchCount = 1;
            } else if (++mRapidTouchCount >= NUMBER_OF_TOUCHES_TO_DISABLE && !mDisabled) {
                // Disable touch blocking until Activity Record is recreated.
                String message = "Disabled go/activity-touch-opaque - "
                        + mActivityRecord.mActivityComponent.getPackageName();
                showAsToastAndLog(message);
                mDisabled = true;
            }
            finishInputEvent(event, true);
        }
    }

}