1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import android.app.compat.CompatChanges;
20 import android.compat.annotation.ChangeId;
21 import android.os.InputConfig;
22 import android.view.InputWindowHandle;
23 import android.view.SurfaceControl;
24 import android.view.WindowManager;
25 
26 /**
27  * Creates a InputWindowHandle that catches all touches that would otherwise pass through an
28  * Activity.
29  */
30 class ActivityRecordInputSink {
31 
32     /**
33      * Feature flag for making Activities consume all touches within their task bounds.
34      */
35     @ChangeId
36     static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
37 
38     private final ActivityRecord mActivityRecord;
39     private final boolean mIsCompatEnabled;
40     private final String mName;
41 
42     private InputWindowHandleWrapper mInputWindowHandleWrapper;
43     private SurfaceControl mSurfaceControl;
44 
ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord)45     ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord) {
46         mActivityRecord = activityRecord;
47         mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
48                 mActivityRecord.getUid());
49         mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink "
50                 + mActivityRecord.mActivityComponent.flattenToShortString();
51         if (sourceRecord != null) {
52             sourceRecord.mAllowedTouchUid = mActivityRecord.getUid();
53         }
54     }
55 
applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction)56     public void applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction) {
57         InputWindowHandleWrapper inputWindowHandleWrapper = getInputWindowHandleWrapper();
58         if (mSurfaceControl == null) {
59             mSurfaceControl = createSurface(transaction);
60         }
61         if (inputWindowHandleWrapper.isChanged()) {
62             inputWindowHandleWrapper.applyChangesToSurface(transaction, mSurfaceControl);
63         }
64     }
65 
createSurface(SurfaceControl.Transaction t)66     private SurfaceControl createSurface(SurfaceControl.Transaction t) {
67         SurfaceControl surfaceControl = mActivityRecord.makeChildSurface(null)
68                 .setName(mName)
69                 .setHidden(false)
70                 .setCallsite("ActivityRecordInputSink.createSurface")
71                 .build();
72         // Put layer below all siblings (and the parent surface too)
73         t.setLayer(surfaceControl, Integer.MIN_VALUE);
74         return surfaceControl;
75     }
76 
getInputWindowHandleWrapper()77     private InputWindowHandleWrapper getInputWindowHandleWrapper() {
78         if (mInputWindowHandleWrapper == null) {
79             mInputWindowHandleWrapper = new InputWindowHandleWrapper(createInputWindowHandle());
80         }
81         // Don't block touches from passing through to an activity below us in the same task, if
82         // that activity is either from the same uid or if that activity has launched an activity
83         // in our uid.
84         final ActivityRecord activityBelowInTask = mActivityRecord.getTask() != null
85                 ? mActivityRecord.getTask().getActivityBelow(mActivityRecord) : null;
86         final boolean allowPassthrough = activityBelowInTask != null && (
87                 activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid()
88                         || activityBelowInTask.isUid(mActivityRecord.getUid()));
89         if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()
90                 || !mActivityRecord.mActivityRecordInputSinkEnabled) {
91             // Set to non-touchable, so the touch events can pass through.
92             mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE,
93                     InputConfig.NOT_TOUCHABLE);
94         } else {
95             // Set to touchable, so it can block by intercepting the touch events.
96             mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE);
97         }
98         mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId());
99         return mInputWindowHandleWrapper;
100     }
101 
createInputWindowHandle()102     private InputWindowHandle createInputWindowHandle() {
103         InputWindowHandle inputWindowHandle = new InputWindowHandle(null,
104                 mActivityRecord.getDisplayId());
105         inputWindowHandle.replaceTouchableRegionWithCrop = true;
106         inputWindowHandle.name = mName;
107         inputWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
108         inputWindowHandle.ownerPid = WindowManagerService.MY_PID;
109         inputWindowHandle.ownerUid = WindowManagerService.MY_UID;
110         inputWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.NO_INPUT_CHANNEL;
111         return inputWindowHandle;
112     }
113 
releaseSurfaceControl()114     void releaseSurfaceControl() {
115         if (mSurfaceControl != null) {
116             mSurfaceControl.release();
117             mSurfaceControl = null;
118         }
119     }
120 
121 }
122