1 /* 2 * Copyright (C) 2018 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 com.android.internal.widget; 18 19 import static android.view.DisplayCutout.NO_CUTOUT; 20 import static android.view.View.MeasureSpec.EXACTLY; 21 import static android.view.View.MeasureSpec.makeMeasureSpec; 22 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 23 24 import static org.hamcrest.CoreMatchers.nullValue; 25 import static org.hamcrest.Matchers.is; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertThat; 28 29 import android.content.Context; 30 import android.graphics.Insets; 31 import android.graphics.Rect; 32 import android.view.DisplayCutout; 33 import android.view.View; 34 import android.view.View.OnApplyWindowInsetsListener; 35 import android.view.ViewGroup; 36 import android.view.WindowInsets; 37 import android.widget.FrameLayout; 38 import android.widget.Toolbar; 39 40 import androidx.test.InstrumentationRegistry; 41 import androidx.test.filters.SmallTest; 42 import androidx.test.runner.AndroidJUnit4; 43 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 48 import java.lang.reflect.Field; 49 50 @RunWith(AndroidJUnit4.class) 51 @SmallTest 52 public class ActionBarOverlayLayoutTest { 53 54 private static final Insets TOP_INSET_5 = Insets.of(0, 5, 0, 0); 55 private static final Insets TOP_INSET_25 = Insets.of(0, 25, 0, 0); 56 private static final DisplayCutout CONSUMED_CUTOUT = null; 57 private static final DisplayCutout CUTOUT_5 = new DisplayCutout( 58 TOP_INSET_5, 59 null /* boundLeft */, 60 new Rect(100, 0, 200, 5), 61 null /* boundRight */, 62 null /* boundBottom*/); 63 private static final int EXACTLY_1000 = makeMeasureSpec(1000, EXACTLY); 64 65 private Context mContext; 66 private TestActionBarOverlayLayout mLayout; 67 68 private ViewGroup mContent; 69 private ViewGroup mActionBarTop; 70 private Toolbar mToolbar; 71 private FakeOnApplyWindowListener mContentInsetsListener; 72 73 74 @Before setUp()75 public void setUp() throws Exception { 76 mContext = InstrumentationRegistry.getContext(); 77 mLayout = new TestActionBarOverlayLayout(mContext); 78 mLayout.makeOptionalFitsSystemWindows(); 79 80 mContent = createViewGroupWithId(com.android.internal.R.id.content); 81 mContent.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 82 mLayout.addView(mContent); 83 84 mContentInsetsListener = new FakeOnApplyWindowListener(); 85 mContent.setOnApplyWindowInsetsListener(mContentInsetsListener); 86 87 mActionBarTop = new ActionBarContainer(mContext); 88 mActionBarTop.setId(com.android.internal.R.id.action_bar_container); 89 mActionBarTop.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, 20)); 90 mLayout.addView(mActionBarTop); 91 mLayout.setActionBarHeight(20); 92 93 mToolbar = new Toolbar(mContext); 94 mToolbar.setId(com.android.internal.R.id.action_bar); 95 mActionBarTop.addView(mToolbar); 96 } 97 98 @Test topInset_consumedCutout_stable()99 public void topInset_consumedCutout_stable() { 100 mLayout.setStable(true); 101 mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CONSUMED_CUTOUT)); 102 103 assertThat(mContentInsetsListener.captured, nullValue()); 104 105 mLayout.measure(EXACTLY_1000, EXACTLY_1000); 106 107 // Action bar height is added to the top inset 108 assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CONSUMED_CUTOUT))); 109 } 110 111 @Test topInset_consumedCutout_notStable()112 public void topInset_consumedCutout_notStable() { 113 mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CONSUMED_CUTOUT)); 114 115 assertThat(mContentInsetsListener.captured, nullValue()); 116 117 mLayout.measure(EXACTLY_1000, EXACTLY_1000); 118 119 assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, CONSUMED_CUTOUT))); 120 } 121 122 @Test topInset_noCutout_stable()123 public void topInset_noCutout_stable() { 124 mLayout.setStable(true); 125 mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT)); 126 127 assertThat(mContentInsetsListener.captured, nullValue()); 128 129 mLayout.measure(EXACTLY_1000, EXACTLY_1000); 130 131 // Action bar height is added to the top inset 132 assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, NO_CUTOUT))); 133 } 134 135 @Test topInset_noCutout_notStable()136 public void topInset_noCutout_notStable() { 137 mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT)); 138 139 assertThat(mContentInsetsListener.captured, nullValue()); 140 141 mLayout.measure(EXACTLY_1000, EXACTLY_1000); 142 143 assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT))); 144 } 145 146 @Test topInset_cutout_stable()147 public void topInset_cutout_stable() { 148 mLayout.setStable(true); 149 mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5)); 150 151 assertThat(mContentInsetsListener.captured, nullValue()); 152 153 mLayout.measure(EXACTLY_1000, EXACTLY_1000); 154 155 // Action bar height is added to the top inset 156 assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CUTOUT_5))); 157 } 158 159 @Test topInset_cutout_notStable()160 public void topInset_cutout_notStable() { 161 mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5)); 162 163 assertThat(mContentInsetsListener.captured, nullValue()); 164 165 mLayout.measure(EXACTLY_1000, EXACTLY_1000); 166 167 assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT))); 168 } 169 insetsWith(Insets content, DisplayCutout cutout)170 private WindowInsets insetsWith(Insets content, DisplayCutout cutout) { 171 return new WindowInsets(content.toRect(), null, false, false, cutout); 172 } 173 createViewGroupWithId(int id)174 private ViewGroup createViewGroupWithId(int id) { 175 final FrameLayout v = new FrameLayout(mContext); 176 v.setId(id); 177 return v; 178 } 179 180 static class TestActionBarOverlayLayout extends ActionBarOverlayLayout { 181 private boolean mStable; 182 TestActionBarOverlayLayout(Context context)183 public TestActionBarOverlayLayout(Context context) { 184 super(context); 185 } 186 187 @Override computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets)188 public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) { 189 if (mStable) { 190 // Emulate the effect of makeOptionalFitsSystemWindows, because we can't do that 191 // without being attached to a window. 192 outLocalInsets.setEmpty(); 193 return in; 194 } 195 return super.computeSystemWindowInsets(in, outLocalInsets); 196 } 197 setStable(boolean stable)198 void setStable(boolean stable) { 199 mStable = stable; 200 setSystemUiVisibility(stable ? SYSTEM_UI_FLAG_LAYOUT_STABLE : 0); 201 } 202 203 @Override getWindowSystemUiVisibility()204 public int getWindowSystemUiVisibility() { 205 return getSystemUiVisibility(); 206 } 207 setActionBarHeight(int height)208 void setActionBarHeight(int height) { 209 try { 210 final Field field = ActionBarOverlayLayout.class.getDeclaredField( 211 "mActionBarHeight"); 212 field.setAccessible(true); 213 field.setInt(this, height); 214 } catch (ReflectiveOperationException e) { 215 throw new RuntimeException(e); 216 } 217 } 218 } 219 220 static class FakeOnApplyWindowListener implements OnApplyWindowInsetsListener { 221 WindowInsets captured; 222 223 @Override onApplyWindowInsets(View v, WindowInsets insets)224 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 225 assertNotNull(insets); 226 captured = insets; 227 return v.onApplyWindowInsets(insets); 228 } 229 } 230 } 231