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