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; 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 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 24 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assume.assumeFalse; 29 30 import android.app.Activity; 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 = new ActivityTestRule<>( 50 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(Insets.NONE.equals(activity.mLastContentInsets.getInsetsIgnoringVisibility( 57 statusBars() | navigationBars()))); 58 59 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 60 activity.mLayoutHappened = false; 61 activity.mMeasureHappened = false; 62 activity.getWindow().setSoftInputMode(softInputMode); 63 activity.getWindow().getInsetsController().hide(systemBars()); 64 }); 65 activity.mZeroInsets.await(180, TimeUnit.SECONDS); 66 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 67 if (expectRelayoutWhenInsetsChange) { 68 assertTrue(activity.mLayoutHappened); 69 assertTrue(activity.mMeasureHappened); 70 } else { 71 assertFalse(activity.mLayoutHappened); 72 assertFalse(activity.mMeasureHappened); 73 } 74 }); 75 } 76 77 public static class TestActivity extends Activity { 78 WindowInsets mLastContentInsets; 79 final CountDownLatch mZeroInsets = new CountDownLatch(1); 80 81 volatile boolean mLayoutHappened; 82 volatile boolean mMeasureHappened; 83 84 @Override onCreate(@ullable Bundle savedInstanceState)85 protected void onCreate(@Nullable Bundle savedInstanceState) { 86 super.onCreate(savedInstanceState); 87 getWindow().requestFeature(Window.FEATURE_NO_TITLE); 88 getWindow().setDecorFitsSystemWindows(false); 89 getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 90 View view = new View(this) { 91 @Override 92 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 93 super.onLayout(changed, left, top, right, bottom); 94 mLayoutHappened = true; 95 } 96 97 @Override 98 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 99 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 100 mMeasureHappened = true; 101 102 } 103 }; 104 view.setOnApplyWindowInsetsListener((v, wi) -> { 105 mLastContentInsets = wi; 106 if (wi.getInsets(systemBars()).equals(Insets.NONE)) { 107 108 // Make sure full traversal happens before counting down. 109 v.post(mZeroInsets::countDown); 110 } 111 return WindowInsets.CONSUMED; 112 }); 113 setContentView(view); 114 } 115 } 116 } 117