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 android.server.wm.other;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
21 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
22 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
24 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
25 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
26 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
27 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
28 
29 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertNotNull;
33 import static org.junit.Assert.assertTrue;
34 import static org.junit.Assume.assumeFalse;
35 
36 import android.app.Activity;
37 import android.content.Intent;
38 import android.graphics.Rect;
39 import android.os.Bundle;
40 import android.platform.test.annotations.Presubmit;
41 import android.server.wm.ActivityManagerTestBase;
42 import android.view.Gravity;
43 import android.view.View;
44 import android.view.Window;
45 import android.view.WindowInsets;
46 import android.view.WindowManager;
47 import android.view.WindowMetrics;
48 
49 import androidx.test.rule.ActivityTestRule;
50 
51 import com.android.compatibility.common.util.PollingCheck;
52 
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.junit.runners.Parameterized;
57 
58 @Presubmit
59 @RunWith(Parameterized.class)
60 public class PrivacyIndicatorBoundsTests extends ActivityManagerTestBase {
61 
62     private static final String TAG = PrivacyIndicatorBoundsTests.class.getSimpleName();
63     private static final String EXTRA_ORIENTATION = "extra.orientation";
64     private static final long TIMEOUT_MS = 1000;
65 
66     @Parameterized.Parameters(name= "{1}({0})")
data()67     public static Object[][] data() {
68         return new Object[][]{
69                 {SCREEN_ORIENTATION_PORTRAIT, "SCREEN_ORIENTATION_PORTRAIT"},
70                 {SCREEN_ORIENTATION_LANDSCAPE, "SCREEN_ORIENTATION_LANDSCAPE"},
71                 {SCREEN_ORIENTATION_REVERSE_LANDSCAPE, "SCREEN_ORIENTATION_REVERSE_LANDSCAPE"},
72                 {SCREEN_ORIENTATION_REVERSE_PORTRAIT, "SCREEN_ORIENTATION_REVERSE_PORTRAIT"},
73         };
74     }
75 
76     @Parameterized.Parameter(0)
77     public int orientation;
78 
79     @Parameterized.Parameter(1)
80     public String orientationName;
81 
82     @Rule
83     public final ActivityTestRule<TestActivity> mTestActivity =
84             new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
85                     false /* launchActivity */);
86 
87 
88     @Test
testStaticBoundsAreNotNull()89     public void testStaticBoundsAreNotNull() {
90         // TODO(b/187757919): Allow Automotive to skip this test until privacy chip is implemented
91         // in immersive mode
92         assumeFalse(isCar());
93         assumeFalse(isWatch());
94 
95         final PrivacyIndicatorBoundsTests.TestActivity activity = mTestActivity.launchActivity(
96                 new Intent().putExtra(EXTRA_ORIENTATION, orientation));
97         getInstrumentation().runOnMainSync(() -> {
98             activity.addChildWindow();
99         });
100 
101         final View childWindowRoot = activity.getChildWindowRoot();
102         PollingCheck.waitFor(TIMEOUT_MS, () -> childWindowRoot.getWidth() > 0);
103         PollingCheck.waitFor(TIMEOUT_MS, () -> activity.getDispatchedInsets() != null);
104         mWmState.waitForValidState(mTestActivity.getActivity().getComponentName());
105         WindowInsets insets = activity.getDispatchedInsets();
106         assertNotNull(insets);
107         Rect screenBounds = activity.getScreenBounds();
108         assertNotNull(screenBounds);
109         Rect bounds = insets.getPrivacyIndicatorBounds();
110         assertNotNull(bounds);
111         final int windowingMode = mWmState
112                 .getTaskDisplayArea(mTestActivity.getActivity().getComponentName())
113                 .getWindowingMode();
114         final boolean inMultiWindowMode = windowingMode != WINDOWING_MODE_FULLSCREEN
115                 && windowingMode != WINDOWING_MODE_UNDEFINED;
116         if (!inMultiWindowMode) {
117             // Multi-window environments may place the indicator bounds somewhere other than the
118             // top (e.g. desktops may decide that the bottom-right corner has the highest visual
119             // priority). Other windowing modes
120             assertEquals(bounds.top, 0);
121         }
122         // TODO 188788786: Figure out why the screen bounds are different in cuttlefish,
123         // causing failures
124         // assertTrue(bounds + " not contained in " + screenBounds, screenBounds.contains(bounds));
125         assertTrue(bounds.left >= 0);
126         assertTrue(bounds.right >= 0);
127     }
128 
129     public static class TestActivity extends Activity {
130         static final String EXTRA_ORIENTATION = "extra.orientation";
131 
132         private View mChildWindowRoot;
133         private WindowInsets mDispatchedInsets;
134         private Rect mScreenBounds = null;
135 
136         @Override
onCreate(Bundle savedInstanceState)137         protected void onCreate(Bundle savedInstanceState) {
138             super.onCreate(savedInstanceState);
139             getWindow().requestFeature(Window.FEATURE_NO_TITLE);
140             getWindow().getDecorView().getWindowInsetsController().hide(
141                     android.view.WindowInsets.Type.statusBars());
142             getWindow().getAttributes().layoutInDisplayCutoutMode =
143                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
144             if (getIntent() != null) {
145                 setRequestedOrientation(getIntent().getIntExtra(
146                         EXTRA_ORIENTATION, SCREEN_ORIENTATION_UNSPECIFIED));
147             }
148 
149         }
150 
addChildWindow()151         void addChildWindow() {
152             final WindowMetrics windowMetrics = getWindowManager().getMaximumWindowMetrics();
153             mScreenBounds = windowMetrics.getBounds();
154             final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
155             attrs.x = mScreenBounds.left;
156             attrs.y = mScreenBounds.top;
157             attrs.width = mScreenBounds.width();
158             attrs.height = mScreenBounds.height();
159             attrs.gravity = Gravity.LEFT | Gravity.TOP;
160             attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
161             attrs.flags = FLAG_NOT_FOCUSABLE;
162             attrs.setFitInsetsTypes(0);
163             mChildWindowRoot = new View(this);
164             mChildWindowRoot.setOnApplyWindowInsetsListener(
165                     (v, insets) -> {
166                         mDispatchedInsets = insets;
167                         return insets;
168                     });
169             getWindowManager().addView(mChildWindowRoot, attrs);
170         }
171 
getChildWindowRoot()172         View getChildWindowRoot() {
173             return mChildWindowRoot;
174         }
175 
getDispatchedInsets()176         WindowInsets getDispatchedInsets() {
177             return mDispatchedInsets;
178         }
179 
getScreenBounds()180         Rect getScreenBounds() {
181             return mScreenBounds;
182         }
183     }
184 }
185