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 View view = new View(this) { 92 @Override 93 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 94 super.onLayout(changed, left, top, right, bottom); 95 mLayoutHappened = true; 96 } 97 98 @Override 99 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 100 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 101 mMeasureHappened = true; 102 103 } 104 }; 105 view.setOnApplyWindowInsetsListener((v, wi) -> { 106 mLastContentInsets = wi; 107 if (wi.getInsets(systemBars()).equals(Insets.NONE)) { 108 109 // Make sure full traversal happens before counting down. 110 v.post(mZeroInsets::countDown); 111 } 112 return WindowInsets.CONSUMED; 113 }); 114 setContentView(view); 115 116 getWindow().setDecorFitsSystemWindows(false); 117 getWindow().getAttributes().layoutInDisplayCutoutMode = 118 LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 119 } 120 } 121 remoteInsetsControllerControlsSystemBars()122 private static boolean remoteInsetsControllerControlsSystemBars() { 123 return InstrumentationRegistry.getInstrumentation().getTargetContext().getResources() 124 .getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars); 125 } 126 isCar()127 private boolean isCar() { 128 PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext() 129 .getPackageManager(); 130 return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 131 } 132 } 133