Merge "Block touches from passing through activities"

This commit is contained in:
Linus Tufvesson
2021-12-02 09:17:03 +00:00
committed by Android (Google) Code Review
2 changed files with 179 additions and 0 deletions

View File

@@ -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);

View File

@@ -0,0 +1,171 @@
/*
* 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);
}
}
}