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