1 /* 2 * Copyright (C) 2020 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 android.server.wm.window; 18 19 import static android.server.wm.app.Components.HIDE_OVERLAY_WINDOWS_ACTIVITY; 20 import static android.server.wm.app.Components.HideOverlayWindowsActivity.ACTION; 21 import static android.server.wm.app.Components.HideOverlayWindowsActivity.MOTION_EVENT_EXTRA; 22 import static android.server.wm.app.Components.HideOverlayWindowsActivity.PONG; 23 import static android.server.wm.app.Components.HideOverlayWindowsActivity.REPORT_TOUCH; 24 import static android.view.Gravity.LEFT; 25 import static android.view.Gravity.TOP; 26 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; 27 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 28 29 import static com.google.common.truth.Truth.assertThat; 30 31 import android.Manifest; 32 import android.app.Activity; 33 import android.content.BroadcastReceiver; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.graphics.Color; 39 import android.graphics.Point; 40 import android.graphics.Rect; 41 import android.os.Bundle; 42 import android.os.ConditionVariable; 43 import android.platform.test.annotations.Presubmit; 44 import android.server.wm.ActivityManagerTestBase; 45 import android.server.wm.CliIntentExtra; 46 import android.server.wm.app.Components; 47 import android.view.Display; 48 import android.view.MotionEvent; 49 import android.view.ViewTreeObserver; 50 import android.view.WindowManager; 51 import android.widget.TextView; 52 53 import androidx.annotation.Nullable; 54 55 import com.android.compatibility.common.util.SystemUtil; 56 57 import org.junit.After; 58 import org.junit.Before; 59 import org.junit.Test; 60 61 /** 62 * Build/Install/Run: 63 * atest CtsWindowManagerDeviceWindow:HideOverlayWindowsTest 64 */ 65 @Presubmit 66 public class HideOverlayWindowsTest extends ActivityManagerTestBase { 67 68 private static final String POP_UP_WINDOW = "POP_UP_WINDOW"; 69 private static final String WINDOW_NAME_EXTRA = "window_name"; 70 private static final String SYSTEM_APPLICATION_OVERLAY_EXTRA = "system_application_overlay"; 71 private PongReceiver mPongReceiver; 72 private TouchReceiver mTouchReceiver; 73 74 @Before 75 @Override setUp()76 public void setUp() throws Exception { 77 super.setUp(); 78 mPongReceiver = new PongReceiver(); 79 mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG), Context.RECEIVER_EXPORTED); 80 mTouchReceiver = new TouchReceiver(); 81 mContext.registerReceiver(mTouchReceiver, new IntentFilter(REPORT_TOUCH), 82 Context.RECEIVER_EXPORTED); 83 } 84 85 @After tearDown()86 public void tearDown() throws Exception { 87 mContext.unregisterReceiver(mPongReceiver); 88 mContext.unregisterReceiver(mTouchReceiver); 89 } 90 91 @Test testApplicationOverlayHiddenWhenRequested()92 public void testApplicationOverlayHiddenWhenRequested() { 93 String windowName = "SYSTEM_ALERT_WINDOW"; 94 ComponentName componentName = new ComponentName( 95 mContext, SystemWindowActivity.class); 96 97 SystemUtil.runWithShellPermissionIdentity(() -> { 98 launchActivity(componentName, 99 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName)); 100 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 101 }, Manifest.permission.SYSTEM_ALERT_WINDOW); 102 103 launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY); 104 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 105 106 setHideOverlayWindowsAndWaitForPong(true); 107 mWmState.waitAndAssertWindowSurfaceShown(windowName, false); 108 109 setHideOverlayWindowsAndWaitForPong(false); 110 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 111 } 112 113 @Test testSystemApplicationOverlayFlagNoEffectWithoutPermission()114 public void testSystemApplicationOverlayFlagNoEffectWithoutPermission() { 115 String windowName = "SYSTEM_ALERT_WINDOW"; 116 ComponentName componentName = new ComponentName( 117 mContext, SystemWindowActivity.class); 118 119 SystemUtil.runWithShellPermissionIdentity(() -> { 120 launchActivity(componentName, 121 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName), 122 CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true)); 123 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 124 }, Manifest.permission.SYSTEM_ALERT_WINDOW); 125 126 launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY); 127 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 128 129 setHideOverlayWindowsAndWaitForPong(true); 130 mWmState.waitAndAssertWindowSurfaceShown(windowName, false); 131 132 setHideOverlayWindowsAndWaitForPong(false); 133 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 134 } 135 136 @Test testInternalSystemApplicationOverlaysNotHidden()137 public void testInternalSystemApplicationOverlaysNotHidden() { 138 String windowName = "INTERNAL_SYSTEM_WINDOW"; 139 ComponentName componentName = new ComponentName( 140 mContext, InternalSystemWindowActivity.class); 141 142 SystemUtil.runWithShellPermissionIdentity(() -> { 143 launchActivity(componentName, 144 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName)); 145 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 146 }, Manifest.permission.INTERNAL_SYSTEM_WINDOW); 147 148 launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY); 149 setHideOverlayWindowsAndWaitForPong(true); 150 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 151 } 152 153 @Test testSystemApplicationOverlaysNotHidden()154 public void testSystemApplicationOverlaysNotHidden() { 155 String windowName = "SYSTEM_APPLICATION_OVERLAY"; 156 ComponentName componentName = new ComponentName( 157 mContext, SystemApplicationOverlayActivity.class); 158 SystemUtil.runWithShellPermissionIdentity(() -> { 159 launchActivity(componentName, 160 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName), 161 CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true)); 162 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 163 }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY); 164 165 launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY); 166 setHideOverlayWindowsAndWaitForPong(true); 167 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 168 } 169 170 @Test testSystemApplicationOverlayHiddenWithoutFlag()171 public void testSystemApplicationOverlayHiddenWithoutFlag() { 172 String windowName = "SYSTEM_APPLICATION_OVERLAY"; 173 ComponentName componentName = new ComponentName( 174 mContext, SystemApplicationOverlayActivity.class); 175 SystemUtil.runWithShellPermissionIdentity(() -> { 176 launchActivity(componentName, 177 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName)); 178 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 179 }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY); 180 181 launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY); 182 setHideOverlayWindowsAndWaitForPong(true); 183 mWmState.waitAndAssertWindowSurfaceShown(windowName, false); 184 } 185 186 @Test testSystemApplicationOverlayAllowsTouchWithoutObscured()187 public void testSystemApplicationOverlayAllowsTouchWithoutObscured() { 188 String windowName = "SYSTEM_APPLICATION_OVERLAY"; 189 ComponentName componentName = new ComponentName( 190 mContext, SystemApplicationOverlayActivity.class); 191 SystemUtil.runWithShellPermissionIdentity(() -> { 192 launchActivity(componentName, 193 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName), 194 CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true)); 195 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 196 }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY); 197 Rect appOverlayActivityFrame = mWmState.getWindowState(componentName).getFrame(); 198 199 launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY); 200 setHideOverlayWindowsAndWaitForPong(true); 201 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 202 203 MotionEvent motionEvent = touchCenterOfBoundsAndWaitForMotionEvent(appOverlayActivityFrame); 204 205 assertThat( 206 motionEvent.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED).isEqualTo( 207 0); 208 } 209 210 @Test testApplicationOverlay_touchIsObscuredWithoutCorrectPermission()211 public void testApplicationOverlay_touchIsObscuredWithoutCorrectPermission() { 212 String windowName = "SYSTEM_APPLICATION_OVERLAY"; 213 ComponentName componentName = new ComponentName( 214 mContext, SystemWindowActivity.class); 215 SystemUtil.runWithShellPermissionIdentity(() -> { 216 launchActivity(componentName, 217 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName), 218 CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true)); 219 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 220 }, Manifest.permission.SYSTEM_ALERT_WINDOW); 221 Rect appOverlayActivityFrame = mWmState.getWindowState(componentName).getFrame(); 222 223 launchActivityInFullscreen(HIDE_OVERLAY_WINDOWS_ACTIVITY); 224 setHideOverlayWindowsAndWaitForPong(false); 225 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 226 227 MotionEvent motionEvent = touchCenterOfBoundsAndWaitForMotionEvent(appOverlayActivityFrame); 228 assertThat( 229 motionEvent.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED).isEqualTo( 230 MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED); 231 } 232 233 @Test testApplicationOverlayWithPopUpHiddenWhenRequested()234 public void testApplicationOverlayWithPopUpHiddenWhenRequested() { 235 String windowName = "SYSTEM_ALERT_WINDOW"; 236 ComponentName componentName = new ComponentName( 237 mContext, SystemWindowActivity.class); 238 239 SystemUtil.runWithShellPermissionIdentity(() -> { 240 launchActivity(componentName, 241 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName)); 242 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 243 }, Manifest.permission.SYSTEM_ALERT_WINDOW); 244 245 SystemUtil.runWithShellPermissionIdentity(() -> { 246 launchActivity(componentName, 247 CliIntentExtra.extraString(WINDOW_NAME_EXTRA, POP_UP_WINDOW)); 248 mWmState.waitAndAssertWindowSurfaceShown(POP_UP_WINDOW, true); 249 }, Manifest.permission.SYSTEM_ALERT_WINDOW); 250 251 launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY); 252 mWmState.waitAndAssertWindowSurfaceShown(POP_UP_WINDOW, true); 253 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 254 255 setHideOverlayWindowsAndWaitForPong(true); 256 mWmState.waitAndAssertWindowSurfaceShown(windowName, false); 257 mWmState.waitAndAssertWindowSurfaceShown(POP_UP_WINDOW, false); 258 259 setHideOverlayWindowsAndWaitForPong(false); 260 mWmState.waitAndAssertWindowSurfaceShown(windowName, true); 261 mWmState.waitAndAssertWindowSurfaceShown(POP_UP_WINDOW, true); 262 } 263 touchCenterOfBoundsAndWaitForMotionEvent(Rect bounds)264 private MotionEvent touchCenterOfBoundsAndWaitForMotionEvent(Rect bounds) { 265 mTouchHelper.tapOnCenter(bounds, Display.DEFAULT_DISPLAY); 266 return mTouchReceiver.getMotionEvent(); 267 } 268 setHideOverlayWindowsAndWaitForPong(boolean hide)269 void setHideOverlayWindowsAndWaitForPong(boolean hide) { 270 Intent intent = new Intent(ACTION); 271 intent.putExtra(Components.HideOverlayWindowsActivity.SHOULD_HIDE, hide); 272 mContext.sendBroadcast(intent); 273 mPongReceiver.waitForPong(); 274 } 275 276 public static class BaseSystemWindowActivity extends Activity { 277 278 TextView mTextView; 279 TextView mSubWindow; 280 281 @Override onCreate(@ullable Bundle savedInstanceState)282 protected void onCreate(@Nullable Bundle savedInstanceState) { 283 super.onCreate(savedInstanceState); 284 String windowName = getIntent().getStringExtra(WINDOW_NAME_EXTRA); 285 286 Rect activityBounds = new Rect(); 287 getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener( 288 new ViewTreeObserver.OnGlobalLayoutListener() { 289 @Override 290 public void onGlobalLayout() { 291 // Remove the listener to avoid multiple calls 292 getWindow().getDecorView().getViewTreeObserver() 293 .removeOnGlobalLayoutListener(this); 294 getWindow().getDecorView().getBoundsOnScreen(activityBounds, true); 295 296 WindowManager.LayoutParams params = 297 new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY, 298 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); 299 params.x = activityBounds.left; 300 params.y = activityBounds.top; 301 params.width = (activityBounds.right - activityBounds.left) / 3; 302 params.height = (activityBounds.bottom - activityBounds.top) / 3; 303 params.gravity = TOP | LEFT; 304 params.setTitle(windowName); 305 params.setSystemApplicationOverlay( 306 getIntent().getBooleanExtra(SYSTEM_APPLICATION_OVERLAY_EXTRA, 307 false)); 308 309 mTextView = new TextView(BaseSystemWindowActivity.this); 310 mTextView.setText(windowName + " type=" + TYPE_APPLICATION_OVERLAY); 311 mTextView.setBackgroundColor(Color.GREEN); 312 313 getWindowManager().addView(mTextView, params); 314 } 315 }); 316 } 317 318 @Override onNewIntent(Intent intent)319 protected void onNewIntent(Intent intent) { 320 super.onNewIntent(intent); 321 if (POP_UP_WINDOW.equals(intent.getStringExtra(WINDOW_NAME_EXTRA))) { 322 final Point size = new Point(); 323 getDisplay().getRealSize(size); 324 325 WindowManager.LayoutParams params = 326 new WindowManager.LayoutParams(FIRST_SUB_WINDOW, 327 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); 328 params.width = size.x / 3; 329 params.height = size.y / 6; 330 params.gravity = TOP | LEFT; 331 params.setTitle(POP_UP_WINDOW); 332 params.token = mTextView.getWindowToken(); 333 334 mSubWindow = new TextView(this); 335 mSubWindow.setText(POP_UP_WINDOW + " type=" + FIRST_SUB_WINDOW); 336 mSubWindow.setBackgroundColor(Color.RED); 337 338 getWindowManager().addView(mSubWindow, params); 339 } 340 } 341 342 @Override onDestroy()343 protected void onDestroy() { 344 super.onDestroy(); 345 if (mSubWindow != null) { 346 getWindowManager().removeView(mSubWindow); 347 } 348 getWindowManager().removeView(mTextView); 349 } 350 } 351 352 // These activities are running the same code, but in different processes to ensure that they 353 // each create their own WindowSession, using the correct permissions. If they are run in the 354 // same process WindowSession is cached and might end up not matching the permissions set up 355 // with adoptShellPermissions 356 public static class InternalSystemWindowActivity extends BaseSystemWindowActivity {} 357 public static class SystemApplicationOverlayActivity extends BaseSystemWindowActivity {} 358 public static class SystemWindowActivity extends BaseSystemWindowActivity {} 359 360 private static class PongReceiver extends BroadcastReceiver { 361 362 volatile ConditionVariable mConditionVariable = new ConditionVariable(); 363 364 @Override onReceive(Context context, Intent intent)365 public void onReceive(Context context, Intent intent) { 366 mConditionVariable.open(); 367 } 368 waitForPong()369 public void waitForPong() { 370 assertThat(mConditionVariable.block(10000L)).isTrue(); 371 mConditionVariable = new ConditionVariable(); 372 } 373 } 374 375 private static class TouchReceiver extends BroadcastReceiver { 376 377 volatile ConditionVariable mConditionVariable = new ConditionVariable(); 378 MotionEvent mMotionEvent; 379 380 @Override onReceive(Context context, Intent intent)381 public void onReceive(Context context, Intent intent) { 382 mMotionEvent = intent.getParcelableExtra(MOTION_EVENT_EXTRA, MotionEvent.class); 383 mConditionVariable.open(); 384 } 385 getMotionEvent()386 public MotionEvent getMotionEvent() { 387 assertThat(mConditionVariable.block(10000L)).isTrue(); 388 mConditionVariable = new ConditionVariable(); 389 return mMotionEvent; 390 } 391 } 392 393 } 394