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