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