• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 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 static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.app.Activity;
23 import android.app.ActivityManager;
24 import android.app.UiAutomation;
25 import android.graphics.Rect;
26 import android.os.Bundle;
27 import android.os.SystemClock;
28 import android.platform.test.annotations.RequiresFlagsDisabled;
29 import android.platform.test.annotations.RequiresFlagsEnabled;
30 import android.platform.test.flag.junit.CheckFlagsRule;
31 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
32 import android.view.MotionEvent;
33 import android.view.ViewGroup;
34 import android.widget.Button;
35 import android.window.WindowInfosListenerForTest;
36 import android.window.WindowInfosListenerForTest.DisplayInfo;
37 import android.window.WindowInfosListenerForTest.WindowInfo;
38 
39 import androidx.test.ext.junit.rules.ActivityScenarioRule;
40 import androidx.test.ext.junit.runners.AndroidJUnit4;
41 import androidx.test.filters.MediumTest;
42 import androidx.test.platform.app.InstrumentationRegistry;
43 
44 import com.android.window.flags.Flags;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Rule;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 
52 import java.util.List;
53 import java.util.concurrent.CountDownLatch;
54 import java.util.concurrent.TimeUnit;
55 import java.util.function.BiConsumer;
56 
57 /**
58  * Internal variant of {@link android.server.wm.window.ActivityRecordInputSinkTests}.
59  */
60 @MediumTest
61 @RunWith(AndroidJUnit4.class)
62 public class ActivityRecordInputSinkTests {
63     private static final String OVERLAY_APP_PKG = "com.android.server.wm.overlay_app";
64     private static final String OVERLAY_ACTIVITY = OVERLAY_APP_PKG + "/.OverlayApp";
65     private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink";
66 
67     @Rule
68     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
69 
70     @Rule
71     public final ActivityScenarioRule<TestActivity> mActivityRule =
72             new ActivityScenarioRule<>(TestActivity.class);
73 
74     private UiAutomation mUiAutomation;
75 
76     @Before
setUp()77     public void setUp() {
78         mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
79     }
80 
81     @After
tearDown()82     public void tearDown() {
83         ActivityManager am =
84                 InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
85                         ActivityManager.class);
86         mUiAutomation.adoptShellPermissionIdentity();
87         try {
88             am.forceStopPackage(OVERLAY_APP_PKG);
89         } finally {
90             mUiAutomation.dropShellPermissionIdentity();
91         }
92     }
93 
94     @Test
testSimpleButtonPress()95     public void testSimpleButtonPress() {
96         injectTapOnButton();
97 
98         mActivityRule.getScenario().onActivity(a -> {
99             assertEquals(1, a.mNumClicked);
100         });
101     }
102 
103     @Test
testSimpleButtonPress_withOverlay()104     public void testSimpleButtonPress_withOverlay() throws InterruptedException {
105         startOverlayApp(false);
106         waitForOverlayApp();
107 
108         injectTapOnButton();
109 
110         mActivityRule.getScenario().onActivity(a -> {
111             assertEquals(0, a.mNumClicked);
112         });
113     }
114 
115     @Test
116     @RequiresFlagsEnabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
testSimpleButtonPress_withOverlayDisableInputSink()117     public void testSimpleButtonPress_withOverlayDisableInputSink() throws InterruptedException {
118         startOverlayApp(true);
119         waitForOverlayApp();
120 
121         injectTapOnButton();
122 
123         mActivityRule.getScenario().onActivity(a -> {
124             assertEquals(1, a.mNumClicked);
125         });
126     }
127 
128     @Test
129     @RequiresFlagsDisabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
testSimpleButtonPress_withOverlayDisableInputSink_flagDisabled()130     public void testSimpleButtonPress_withOverlayDisableInputSink_flagDisabled()
131             throws InterruptedException {
132         startOverlayApp(true);
133         waitForOverlayApp();
134 
135         injectTapOnButton();
136 
137         mActivityRule.getScenario().onActivity(a -> {
138             assertEquals(0, a.mNumClicked);
139         });
140     }
141 
startOverlayApp(boolean disableInputSink)142     private void startOverlayApp(boolean disableInputSink) {
143         String launchCommand = "am start -n " + OVERLAY_ACTIVITY;
144         if (disableInputSink) {
145             launchCommand += " --ez " + KEY_DISABLE_INPUT_SINK + " true";
146         }
147 
148         mUiAutomation.adoptShellPermissionIdentity();
149         try {
150             mUiAutomation.executeShellCommand(launchCommand);
151         } finally {
152             mUiAutomation.dropShellPermissionIdentity();
153         }
154     }
155 
waitForOverlayApp()156     private void waitForOverlayApp() throws InterruptedException {
157         final var listenerHost = new WindowInfosListenerForTest();
158         final var latch = new CountDownLatch(1);
159         final BiConsumer<List<WindowInfo>, List<DisplayInfo>> listener =
160             (windowInfos, displayInfos) -> {
161                 final boolean inputSinkReady = windowInfos.stream().anyMatch(
162                     info -> info.isVisible
163                         && info.name.contains("ActivityRecordInputSink " + OVERLAY_ACTIVITY));
164                 if (inputSinkReady) {
165                     latch.countDown();
166                 }
167             };
168 
169         listenerHost.addWindowInfosListener(listener);
170         try {
171             assertTrue(latch.await(5, TimeUnit.SECONDS));
172         } finally {
173             listenerHost.removeWindowInfosListener(listener);
174         }
175     }
176 
injectTapOnButton()177     private void injectTapOnButton() {
178         Rect buttonBounds = new Rect();
179         mActivityRule.getScenario().onActivity(a -> {
180             a.mButton.getBoundsOnScreen(buttonBounds);
181         });
182         final int x = buttonBounds.centerX();
183         final int y = buttonBounds.centerY();
184 
185         MotionEvent down = MotionEvent.obtain(SystemClock.uptimeMillis(),
186                 SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0);
187         mUiAutomation.injectInputEvent(down, true);
188 
189         SystemClock.sleep(10);
190 
191         MotionEvent up = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
192                 MotionEvent.ACTION_UP, x, y, 0);
193         mUiAutomation.injectInputEvent(up, true);
194 
195         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
196     }
197 
198     public static class TestActivity extends Activity {
199         int mNumClicked = 0;
200         Button mButton;
201 
202         @Override
onCreate(Bundle savedInstanceState)203         public void onCreate(Bundle savedInstanceState) {
204             super.onCreate(savedInstanceState);
205             mButton = new Button(this);
206             mButton.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
207                     ViewGroup.LayoutParams.MATCH_PARENT));
208             setContentView(mButton);
209             mButton.setOnClickListener(v -> mNumClicked++);
210         }
211     }
212 }
213