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