1 /*
2  * Copyright (C) 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 android.server.wm.insets;
18 
19 import static android.view.WindowInsets.Type.navigationBars;
20 import static android.view.WindowInsets.Type.statusBars;
21 import static android.view.WindowInsets.Type.systemBars;
22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
23 
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assume.assumeFalse;
28 
29 import android.app.Activity;
30 import android.content.pm.PackageManager;
31 import android.graphics.Insets;
32 import android.os.Bundle;
33 import android.view.View;
34 import android.view.Window;
35 import android.view.WindowInsets;
36 
37 import androidx.annotation.Nullable;
38 import androidx.test.platform.app.InstrumentationRegistry;
39 import androidx.test.rule.ActivityTestRule;
40 
41 import org.junit.Rule;
42 
43 import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.TimeUnit;
45 
46 public class ForceRelayoutTestBase {
47 
48     @Rule
49     public ActivityTestRule<TestActivity> mDecorActivity =
50             new ActivityTestRule<>(TestActivity.class);
51 
testRelayoutWhenInsetsChange(boolean expectRelayoutWhenInsetsChange, int softInputMode)52     void testRelayoutWhenInsetsChange(boolean expectRelayoutWhenInsetsChange, int softInputMode)
53             throws Throwable {
54         TestActivity activity = mDecorActivity.getActivity();
55         assertNotNull("test setup failed", activity.mLastContentInsets);
56         assumeFalse(
57                 Insets.NONE.equals(
58                         activity.mLastContentInsets.getInsetsIgnoringVisibility(
59                                 statusBars() | navigationBars())));
60         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
61 
62         InstrumentationRegistry.getInstrumentation()
63                 .runOnMainSync(
64                         () -> {
65                             activity.mLayoutHappened = false;
66                             activity.mMeasureHappened = false;
67                             activity.getWindow().setSoftInputMode(softInputMode);
68                             activity.getWindow().getInsetsController().hide(systemBars());
69                         });
70         activity.mZeroInsets.await(180, TimeUnit.SECONDS);
71         InstrumentationRegistry.getInstrumentation()
72                 .runOnMainSync(
73                         () -> {
74                             if (expectRelayoutWhenInsetsChange) {
75                                 assertTrue(activity.mLayoutHappened);
76                                 assertTrue(activity.mMeasureHappened);
77                             } else {
78                                 assertFalse(activity.mLayoutHappened);
79                                 assertFalse(activity.mMeasureHappened);
80                             }
81                         });
82     }
83 
84     public static class TestActivity extends Activity {
85         WindowInsets mLastContentInsets;
86         final CountDownLatch mZeroInsets = new CountDownLatch(1);
87 
88         volatile boolean mLayoutHappened;
89         volatile boolean mMeasureHappened;
90 
91         @Override
onCreate(@ullable Bundle savedInstanceState)92         protected void onCreate(@Nullable Bundle savedInstanceState) {
93             super.onCreate(savedInstanceState);
94             getWindow().requestFeature(Window.FEATURE_NO_TITLE);
95             View view =
96                     new View(this) {
97                         @Override
98                         protected void onLayout(
99                                 boolean changed, int left, int top, int right, int bottom) {
100                             super.onLayout(changed, left, top, right, bottom);
101                             mLayoutHappened = true;
102                         }
103 
104                         @Override
105                         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
106                             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
107                             mMeasureHappened = true;
108                         }
109                     };
110             view.setOnApplyWindowInsetsListener(
111                     (v, wi) -> {
112                         mLastContentInsets = wi;
113                         if (wi.getInsets(systemBars()).equals(Insets.NONE)) {
114 
115                             // Make sure full traversal happens before counting down.
116                             v.post(mZeroInsets::countDown);
117                         }
118                         return WindowInsets.CONSUMED;
119                     });
120             setContentView(view);
121 
122             getWindow().setDecorFitsSystemWindows(false);
123             getWindow().getAttributes().layoutInDisplayCutoutMode =
124                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
125         }
126     }
127 
remoteInsetsControllerControlsSystemBars()128     private static boolean remoteInsetsControllerControlsSystemBars() {
129         return InstrumentationRegistry.getInstrumentation()
130                 .getTargetContext()
131                 .getResources()
132                 .getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars);
133     }
134 
isCar()135     private boolean isCar() {
136         PackageManager pm =
137                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
138         return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
139     }
140 }
141