1 /* 2 * Copyright (C) 2016 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.systemui.qs; 18 19 import static junit.framework.Assert.assertEquals; 20 import static junit.framework.Assert.assertTrue; 21 22 import static org.mockito.Mockito.any; 23 import static org.mockito.Mockito.anyBoolean; 24 import static org.mockito.Mockito.anyInt; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.spy; 27 import static org.mockito.Mockito.times; 28 import static org.mockito.Mockito.verify; 29 import static org.mockito.Mockito.when; 30 31 import android.content.Context; 32 import android.content.res.Configuration; 33 import android.content.res.Resources; 34 import android.test.suitebuilder.annotation.SmallTest; 35 import android.testing.TestableLooper; 36 import android.view.ContextThemeWrapper; 37 import android.view.View; 38 import android.view.accessibility.AccessibilityNodeInfo; 39 40 import androidx.test.runner.AndroidJUnit4; 41 42 import com.android.systemui.R; 43 import com.android.systemui.SysuiTestCase; 44 import com.android.systemui.plugins.qs.QSTile; 45 import com.android.systemui.qs.tileimpl.QSIconViewImpl; 46 import com.android.systemui.qs.tileimpl.QSTileViewImpl; 47 48 import org.junit.Before; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 import org.mockito.ArgumentCaptor; 52 import org.mockito.Mockito; 53 54 @SmallTest 55 @RunWith(AndroidJUnit4.class) 56 @TestableLooper.RunWithLooper(setAsMainLooper = true) 57 public class TileLayoutTest extends SysuiTestCase { 58 private Resources mResources; 59 private int mLayoutSizeForOneTile; 60 private TileLayout mTileLayout; // under test 61 private Context mSpyContext; 62 63 64 @Before setUp()65 public void setUp() throws Exception { 66 mSpyContext = Mockito.spy( 67 new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings)); 68 mResources = Mockito.spy(mSpyContext.getResources()); 69 when(mSpyContext.getResources()).thenReturn(mResources); 70 71 mTileLayout = new TileLayout(mSpyContext); 72 // Layout needs to leave space for the tile margins. Three times the margin size is 73 // sufficient for any number of columns. 74 mLayoutSizeForOneTile = 75 mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) * 3; 76 } 77 createTileRecord()78 private QSPanelControllerBase.TileRecord createTileRecord() { 79 return new QSPanelControllerBase.TileRecord( 80 mock(QSTile.class), 81 spy(new QSTileViewImpl(mSpyContext, new QSIconViewImpl(mSpyContext)))); 82 } 83 84 @Test testAddTile_CallsSetListeningOnTile()85 public void testAddTile_CallsSetListeningOnTile() { 86 QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); 87 mTileLayout.addTile(tileRecord); 88 verify(tileRecord.tile, times(1)).setListening(mTileLayout, false); 89 } 90 91 @Test testSetListening_CallsSetListeningOnTile()92 public void testSetListening_CallsSetListeningOnTile() { 93 QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); 94 mTileLayout.addTile(tileRecord); 95 mTileLayout.setListening(true, null); 96 verify(tileRecord.tile, times(1)).setListening(mTileLayout, true); 97 } 98 99 @Test testSetListening_SameValueIsNoOp()100 public void testSetListening_SameValueIsNoOp() { 101 QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); 102 mTileLayout.addTile(tileRecord); 103 mTileLayout.setListening(false, null); 104 verify(tileRecord.tile, times(1)).setListening(any(), anyBoolean()); 105 } 106 107 @Test testSetListening_ChangesValueForAddingFutureTiles()108 public void testSetListening_ChangesValueForAddingFutureTiles() { 109 QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); 110 mTileLayout.setListening(true, null); 111 mTileLayout.addTile(tileRecord); 112 verify(tileRecord.tile, times(1)).setListening(mTileLayout, true); 113 } 114 115 @Test testRemoveTile_CallsSetListeningFalseOnTile()116 public void testRemoveTile_CallsSetListeningFalseOnTile() { 117 QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); 118 mTileLayout.setListening(true, null); 119 mTileLayout.addTile(tileRecord); 120 mTileLayout.removeTile(tileRecord); 121 verify(tileRecord.tile, times(1)).setListening(mTileLayout, false); 122 } 123 124 @Test testRemoveAllViews_CallsSetListeningFalseOnAllTiles()125 public void testRemoveAllViews_CallsSetListeningFalseOnAllTiles() { 126 QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord(); 127 QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord(); 128 mTileLayout.setListening(true, null); 129 mTileLayout.addTile(tileRecord1); 130 mTileLayout.addTile(tileRecord2); 131 mTileLayout.removeAllViews(); 132 verify(tileRecord1.tile, times(1)).setListening(mTileLayout, false); 133 verify(tileRecord2.tile, times(1)).setListening(mTileLayout, false); 134 } 135 136 @Test testMeasureLayout_CallsLayoutOnTile()137 public void testMeasureLayout_CallsLayoutOnTile() { 138 QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); 139 mTileLayout.addTile(tileRecord); 140 mTileLayout.measure(mLayoutSizeForOneTile, mLayoutSizeForOneTile); 141 mTileLayout.layout(0, 0, mLayoutSizeForOneTile, mLayoutSizeForOneTile); 142 verify(tileRecord.tileView, times(1)).layout(anyInt(), anyInt(), anyInt(), anyInt()); 143 } 144 145 @Test testMeasureLayout_CallsLayoutOnTilesWithNeighboredBounds()146 public void testMeasureLayout_CallsLayoutOnTilesWithNeighboredBounds() { 147 QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord(); 148 QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord(); 149 mTileLayout.addTile(tileRecord1); 150 mTileLayout.addTile(tileRecord2); 151 mTileLayout.measure(mLayoutSizeForOneTile * 2, mLayoutSizeForOneTile * 2); 152 mTileLayout.layout(0, 0, mLayoutSizeForOneTile * 2, mLayoutSizeForOneTile * 2); 153 154 // Capture the layout calls for both tiles. 155 ArgumentCaptor<Integer> left1 = ArgumentCaptor.forClass(Integer.class); 156 ArgumentCaptor<Integer> top1 = ArgumentCaptor.forClass(Integer.class); 157 ArgumentCaptor<Integer> right1 = ArgumentCaptor.forClass(Integer.class); 158 ArgumentCaptor<Integer> bottom1 = ArgumentCaptor.forClass(Integer.class); 159 verify(tileRecord1.tileView, times(1)) 160 .layout(left1.capture(), top1.capture(), right1.capture(), bottom1.capture()); 161 ArgumentCaptor<Integer> left2 = ArgumentCaptor.forClass(Integer.class); 162 ArgumentCaptor<Integer> top2 = ArgumentCaptor.forClass(Integer.class); 163 ArgumentCaptor<Integer> right2 = ArgumentCaptor.forClass(Integer.class); 164 ArgumentCaptor<Integer> bottom2 = ArgumentCaptor.forClass(Integer.class); 165 verify(tileRecord2.tileView, times(1)) 166 .layout(left2.capture(), top2.capture(), right2.capture(), bottom2.capture()); 167 168 // We assume two tiles will always fit side-by-side. 169 assertTrue(mSpyContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1); 170 171 // left <= right, top <= bottom 172 assertTrue(left1.getValue() <= right1.getValue()); 173 assertTrue(top1.getValue() <= bottom1.getValue()); 174 assertTrue(left2.getValue() <= right2.getValue()); 175 assertTrue(top2.getValue() <= bottom2.getValue()); 176 177 // The tiles' left and right should describe discrete ranges. 178 // Agnostic of Layout direction. 179 assertTrue(left1.getValue() > right2.getValue() || right1.getValue() < left2.getValue()); 180 181 // The tiles' Top and Bottom should be the same. 182 assertEquals(top1.getValue().intValue(), top2.getValue().intValue()); 183 assertEquals(bottom1.getValue().intValue(), bottom2.getValue().intValue()); 184 } 185 186 @Test 187 public void testEmptyHeight() { 188 mTileLayout.measure(mLayoutSizeForOneTile, mLayoutSizeForOneTile); 189 assertEquals(0, mTileLayout.getMeasuredHeight()); 190 } 191 192 @Test 193 public void testCollectionInfo() { 194 QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord(); 195 QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord(); 196 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(mTileLayout); 197 mTileLayout.addTile(tileRecord1); 198 199 mTileLayout.onInitializeAccessibilityNodeInfo(info); 200 AccessibilityNodeInfo.CollectionInfo collectionInfo = info.getCollectionInfo(); 201 assertEquals(1, collectionInfo.getRowCount()); 202 assertEquals(1, collectionInfo.getColumnCount()); // always use one column 203 204 mTileLayout.addTile(tileRecord2); 205 mTileLayout.onInitializeAccessibilityNodeInfo(info); 206 collectionInfo = info.getCollectionInfo(); 207 assertEquals(2, collectionInfo.getRowCount()); 208 assertEquals(1, collectionInfo.getColumnCount()); // always use one column 209 } 210 211 @Test 212 public void testSetPositionOnTiles() { 213 QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord(); 214 QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord(); 215 mTileLayout.addTile(tileRecord1); 216 mTileLayout.addTile(tileRecord2); 217 mTileLayout.measure(mLayoutSizeForOneTile * 2, mLayoutSizeForOneTile * 2); 218 mTileLayout.layout(0, 0, mLayoutSizeForOneTile * 2, mLayoutSizeForOneTile * 2); 219 220 verify(tileRecord1.tileView).setPosition(0); 221 verify(tileRecord2.tileView).setPosition(1); 222 } 223 224 @Test 225 public void resourcesChanged_updateResources_returnsTrue() { 226 when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); 227 mTileLayout.updateResources(); // setup with 1 228 when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2); 229 230 assertEquals(true, mTileLayout.updateResources()); 231 } 232 233 @Test 234 public void resourcesSame_updateResources_returnsFalse() { 235 when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); 236 mTileLayout.updateResources(); // setup with 1 237 238 assertEquals(false, mTileLayout.updateResources()); 239 } 240 241 @Test 242 public void fontScalingChanged_updateResources_cellHeightEnoughForTileContent() { 243 final float originalFontScale = mContext.getResources().getConfiguration().fontScale; 244 float[] testScales = {0.8f, 1.0f, 1.4f, 1.6f, 2.0f}; 245 for (float scale: testScales) { 246 changeFontScaling_updateResources_cellHeightEnoughForTileContent(scale); 247 } 248 249 changeFontScaling(originalFontScale); 250 } 251 252 private void changeFontScaling_updateResources_cellHeightEnoughForTileContent(float scale) { 253 changeFontScaling(scale); 254 255 QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); 256 mTileLayout.addTile(tileRecord); 257 258 FakeTileView tileView = new FakeTileView(mSpyContext); 259 QSTile.State state = new QSTile.State(); 260 state.label = "TEST LABEL"; 261 state.secondaryLabel = "TEST SECONDARY LABEL"; 262 tileView.changeState(state); 263 264 mTileLayout.updateResources(); 265 266 int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 267 tileView.measure(spec, spec); 268 assertTrue(mTileLayout.getCellHeight() >= tileView.getMeasuredHeight()); 269 270 mTileLayout.removeTile(tileRecord); 271 } 272 273 private static class FakeTileView extends QSTileViewImpl { FakeTileView(Context context)274 FakeTileView(Context context) { 275 super(context, new QSIconViewImpl(context), /* collapsed= */ false); 276 } 277 changeState(QSTile.State state)278 void changeState(QSTile.State state) { 279 handleStateChanged(state); 280 } 281 } 282 changeFontScaling(float scale)283 private void changeFontScaling(float scale) { 284 Configuration configuration = 285 new Configuration(mSpyContext.getResources().getConfiguration()); 286 configuration.fontScale = scale; 287 // updateConfiguration could help update on both resource configuration and displayMetrics 288 mSpyContext.getResources().updateConfiguration(configuration, null, null); 289 } 290 } 291